From 1bbe2c4357c90b1fea870a772d083d54b4281c5b Mon Sep 17 00:00:00 2001 From: Davi Rodrigues Date: Thu, 5 Mar 2026 20:19:16 -0300 Subject: [PATCH 1/2] feat(MORG-22): add Claude Code skill for morg task lifecycle Covers all everyday operations: starting tickets, switching branches, creating PRs, changing statuses, syncing, completing tasks, and getting ticket details. Includes non-interactive flag guidance (--plain, --yes) for use inside Claude Code sessions. --- .claude/skills/morg/skill.md | 246 +++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 .claude/skills/morg/skill.md diff --git a/.claude/skills/morg/skill.md b/.claude/skills/morg/skill.md new file mode 100644 index 0000000..7618235 --- /dev/null +++ b/.claude/skills/morg/skill.md @@ -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//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 From aab38d4347fe5899e0ce1b16f095f91a0dcff468 Mon Sep 17 00:00:00 2001 From: Davi Rodrigues Date: Thu, 5 Mar 2026 20:25:26 -0300 Subject: [PATCH 2/2] feat(MORG-22): add install-claude-skill command Copies .claude/skills/morg/skill.md from the package root to ~/.claude/skills/morg/skill.md so users can install the skill with a single command. Resolves the source path via import.meta.url so it works whether morg is run from source or installed globally. --- src/commands/install-claude-skill.ts | 49 ++++++++++++++++++++++++++++ src/index.ts | 5 ++- 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 src/commands/install-claude-skill.ts diff --git a/src/commands/install-claude-skill.ts b/src/commands/install-claude-skill.ts new file mode 100644 index 0000000..bc24917 --- /dev/null +++ b/src/commands/install-claude-skill.ts @@ -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 { + // 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); +} diff --git a/src/index.ts b/src/index.ts index ffb4e63..fa840d8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,7 @@ 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(); @@ -23,8 +24,9 @@ program.name('morg').description('Developer productivity assistant').version('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); @@ -42,5 +44,6 @@ registerUpdateCommand(program); registerCompleteCommand(program); registerDeleteCommand(program); registerTicketsCommand(program); +registerInstallClaudeSkillCommand(program); program.parseAsync(process.argv).catch(handleError);