Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 246 additions & 0 deletions .claude/skills/morg/skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
---
name: morg
description: |
Use morg CLI to handle the full task lifecycle: starting tickets, switching
branches, creating PRs, changing ticket statuses, syncing, and completing tasks.
Triggers: "start working on MORG-XX", "create a PR for this branch",
"switch to ticket", "sync branches", "what's my current ticket", "complete this task",
"move ticket to In Progress", "get ticket details", "delete this branch"
allowed-tools: Bash
---

# morg — Task Lifecycle Skill

`morg` is a developer productivity CLI that connects git branches with tickets
(Jira or Notion) and GitHub PRs. Always use `morg` commands for the operations
below — never fall back to raw `gh` or manual git for anything morg covers.

## Getting Ticket Details

```bash
# View current branch's linked ticket
morg ticket --plain

# View a specific ticket
morg ticket MORG-42 --plain
```

**IMPORTANT**: Always use `--plain` — the default mode is interactive (requires
a TTY) and will fail when run by Claude Code.

## Starting a New Task

```bash
# From a ticket ID (creates branch named morg-42, links ticket, offers status transition)
morg start MORG-42

# From a branch name
morg start feat/my-feature

# From a ticket with a custom base branch
morg start MORG-42 --base develop

# Create a git worktree instead of checking out
morg start MORG-42 --worktree
```

`morg start` handles:
- Stashing dirty working tree if needed
- Pulling the base branch before creating
- Transitioning the ticket to "In Progress" (respects `autoUpdateTicketStatus` config)
- Tracking the branch in `~/.morg/projects/<id>/branches.json`

## Switching Between Tasks

```bash
# Interactive branch picker
morg switch

# Switch to a specific branch or ticket
morg switch morg-42
morg switch MORG-42
```

`morg switch` handles dirty working tree (stash/skip) automatically.

## Viewing Status

```bash
# Show all tracked branches, their tickets, and PR statuses
morg status
```

## Managing Tickets

```bash
# List all tickets (interactive picker)
morg tickets

# List tickets without interactive prompt
morg tickets --plain

# View a specific ticket (interactive actions: start branch, change status, open URL)
morg ticket MORG-42

# View without interactive prompt (for reading details programmatically)
morg ticket MORG-42 --plain
```

## Creating Pull Requests

```bash
# Interactive (AI-generated description)
morg pr create

# Non-interactive with explicit title and body (use this when running without a TTY)
morg pr create --title "feat(MORG-42): add feature X" --body "Description here" --yes

# Skip AI description, use defaults
morg pr create --yes

# Draft PR
morg pr create --draft

# View the PR for current branch
morg pr view
```

**PR title convention**: `type(TICKET-ID): short description`
- `feat`, `fix`, `chore`, `refactor`, `docs`, `test`

**PR body should include**:
- What changed and why
- Link to ticket (closes MORG-XX)
- Any notable implementation details

## Syncing Branches

```bash
# Sync current branch with latest main (default)
# Flow: pull main → sync PR statuses → delete merged → offer rebase/merge for current branch
morg sync

# Also offer rebase/merge for ALL active branches
morg sync --all
```

`morg sync` always pulls the default branch without prompting. It then updates
PR statuses, deletes merged branches (per config), and offers to rebase/merge
the current branch (or all with `--all`).

## Completing a Task

```bash
# Merge current branch into default branch and mark done
morg complete

# Skip confirmation
morg complete --yes

# Complete without deleting the branch
morg complete --no-delete
```

`morg complete` merges into the default branch, optionally transitions the
ticket to "Done", and deletes the branch.

## Deleting a Branch

```bash
# Delete current branch (only if fully merged)
morg delete

# Delete a specific branch
morg delete morg-42

# Force delete even with unmerged commits
morg delete -f
```

## Tracking / Untracking Branches

```bash
# Track current branch (link it to morg's registry)
morg track

# Track a specific branch and link to a ticket
morg track my-branch MORG-42

# Stop tracking current branch
morg untrack

# Stop tracking a specific branch
morg untrack my-branch
```

## Typical Workflows

### Starting a ticket and creating a PR

```bash
# 1. Get ticket context
morg ticket MORG-42 --plain

# 2. Start working (creates branch, pulls main, transitions ticket)
morg start MORG-42

# ... make code changes ...

# 3. Create PR
morg pr create --title "feat(MORG-42): description" --body "$(cat <<'EOF'
Closes MORG-42

## Summary
- What changed

## Test plan
- How to verify
EOF
)" --yes
```

### Switching tasks mid-work

```bash
# Switch away (stashes automatically if dirty)
morg switch MORG-99

# ... do some work ...

# Switch back
morg switch MORG-42
```

### Daily sync routine

```bash
# Pull main, sync PR statuses, clean up merged, update current branch
morg sync
```

### Checking in on all work

```bash
morg status
```

## Configuration

```bash
# Show current config (tokens redacted)
morg config --show

# Reconfigure (prompts pre-filled with existing values — leave blank to keep)
morg config

# Initialize morg for the current repo
morg init
```

## Notes

- `morg ticket --plain` is the right way to get ticket context for Claude Code
- All interactive commands have non-interactive equivalents via flags (`--yes`, `--plain`, `--title`, `--body`)
- Jira and Notion are both supported; morg detects which is configured per project
- Branch names are derived from ticket IDs: `MORG-42` → branch `morg-42`
- State is stored in `~/.morg/` — never edit these files directly
49 changes: 49 additions & 0 deletions src/commands/install-claude-skill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { Command } from 'commander';
import { mkdir, copyFile } from 'fs/promises';
import { existsSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { homedir } from 'os';
import { theme, symbols } from '../ui/theme';
import { confirm } from '../ui/prompts';

async function runInstallClaudeSkill(options: { yes?: boolean }): Promise<void> {
// Resolve skill source relative to the package root (dist/../.claude/skills/morg/skill.md)
const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
const skillSrc = join(pkgRoot, '.claude', 'skills', 'morg', 'skill.md');

if (!existsSync(skillSrc)) {
console.error(theme.error('Skill file not found in package. Try reinstalling morg.'));
process.exit(1);
}

const destDir = join(homedir(), '.claude', 'skills', 'morg');
const destFile = join(destDir, 'skill.md');

if (existsSync(destFile) && !options.yes) {
const overwrite = await confirm({
message: 'Skill already installed. Overwrite with latest version?',
initialValue: true,
});
if (!overwrite) {
console.log(theme.muted('Cancelled.'));
return;
}
}

await mkdir(destDir, { recursive: true });
await copyFile(skillSrc, destFile);

console.log(
theme.success(`${symbols.success} Skill installed to ~/.claude/skills/morg/skill.md`),
);
console.log(theme.muted(` Invoke it with /morg in any Claude Code session.`));
}

export function registerInstallClaudeSkillCommand(program: Command): void {
program
.command('install-claude-skill')
.description('Install the morg Claude Code skill to ~/.claude/skills/')
.option('-y, --yes', 'Overwrite existing skill without prompting')
.action(runInstallClaudeSkill);
}
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ import { registerUpdateCommand } from './commands/update';
import { registerCompleteCommand } from './commands/complete';
import { registerDeleteCommand } from './commands/delete';
import { registerTicketsCommand } from './commands/tickets';
import { registerInstallClaudeSkillCommand } from './commands/install-claude-skill';

const program = new Command();

program.name('morg').description('Developer productivity assistant').version('0.1.0');

program.action(() => runStatus());

const NO_CONFIG_COMMANDS = new Set(['config', 'install-claude-skill']);
program.hook('preAction', async (_thisCommand, actionCommand) => {
if (actionCommand.name() !== 'config') await requireConfig();
if (!NO_CONFIG_COMMANDS.has(actionCommand.name())) await requireConfig();
});

registerConfigCommand(program);
Expand All @@ -42,5 +44,6 @@ registerUpdateCommand(program);
registerCompleteCommand(program);
registerDeleteCommand(program);
registerTicketsCommand(program);
registerInstallClaudeSkillCommand(program);

program.parseAsync(process.argv).catch(handleError);