diff --git a/.claude/skills/morg/skill.md b/.claude/skills/morg/skill.md index 5292581..234f9a8 100644 --- a/.claude/skills/morg/skill.md +++ b/.claude/skills/morg/skill.md @@ -176,15 +176,22 @@ worktree (if applicable). ## Viewing Status ```bash -# Show detail for the current branch: ticket info, GitHub PR status, CI checks, -# recent git commits, diff to main -morg status +# Context-aware default: detail if on a tracked branch, table otherwise +morg -# Show all tracked branches as a JSON array (for scripting) -morg status --json +# List all active branches with PR and CI badges +morg ls +morg branches # alias + +# Detail for current branch: ticket, PR, CI checks, recent commits, diff to base +morg status -# Show detail for a specific branch +# Detail for a specific branch (accepts ticket ID or branch name, case-insensitive) morg status +morg status MORG-42 + +# All tracked branches as JSON (for scripting) +morg status --json ``` ## Managing Tickets @@ -476,7 +483,8 @@ morg sync ### Checking in on all work ```bash -morg status +morg ls # table of all active branches +morg status # detail for current branch (if tracked) ``` ## Notes diff --git a/CLAUDE.md b/CLAUDE.md index 423d412..895cc3e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -107,6 +107,10 @@ All JSON state is read/written exclusively through `configManager` (never direct 1. Create `src/commands/.ts` with a `registerCommand(program: Command)` export 2. Implement the logic in a private `async function run()` in the same file 3. Add static import + `registerCommand(program)` call in `src/index.ts` +4. **Always keep these in sync** whenever adding, removing, or renaming commands: + - `README.md` — Commands section + - `.claude/skills/morg/skill.md` — relevant section(s) + - `src/commands/shell-init.ts` — `COMMANDS` array (tab completion) Commands that require a tracked repo call `requireTrackedRepo()` to get the `projectId`, then use `configManager.getBranches(projectId)` etc. diff --git a/README.md b/README.md index 734158d..6c3b170 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ Developer productivity CLI that connects GitHub, Jira, Slack, and Claude to redu - Stashes/restores work when switching between tasks - Generates AI-written PR descriptions and review summaries via Claude - Generates standup updates from recent git activity and posts them to Slack -- Shows a live task dashboard (`morg status`) with PR and CI state +- Context-aware `morg`: shows branch detail on a tracked branch, or the full branch table otherwise +- Live branch detail (`morg status`) with ticket, PR, CI state, commits, and diff summary +- Branch table (`morg ls`) listing all active tasks at a glance - Rich ticket detail view: parent, child issues, issue links, Markdown description ## Prerequisites @@ -72,8 +74,11 @@ morg clean # bulk-delete all fully-merged branches ### Status ```bash -morg status # table of active tasks with PR and CI badges -morg status --json # output as JSON +morg # context-aware: detail if on a tracked branch, table otherwise +morg ls # table of all active branches with PR and CI badges (alias: branches) +morg status # detail for current branch: ticket, PR, CI, commits, diff +morg status # detail for a specific branch (accepts ticket ID, case-insensitive) +morg status --json # all tracked branches as JSON ``` ### Tickets diff --git a/src/commands/ls.ts b/src/commands/ls.ts new file mode 100644 index 0000000..d046899 --- /dev/null +++ b/src/commands/ls.ts @@ -0,0 +1,10 @@ +import type { Command } from 'commander'; +import { renderBranches } from '../ui/output'; + +export function registerLsCommand(program: Command): void { + program + .command('ls') + .aliases(['branches']) + .description('List all active branches and their statuses') + .action(() => renderBranches()); +} diff --git a/src/commands/shell-init.ts b/src/commands/shell-init.ts index 2eac148..b10518c 100644 --- a/src/commands/shell-init.ts +++ b/src/commands/shell-init.ts @@ -11,6 +11,8 @@ const COMMANDS = [ 'switch', 'pr', 'sync', + 'ls', + 'branches', 'status', 'standup', 'prompt', diff --git a/src/commands/status.ts b/src/commands/status.ts index 75e84ae..f734c30 100644 --- a/src/commands/status.ts +++ b/src/commands/status.ts @@ -6,12 +6,12 @@ import { getCurrentBranch, getCommitsOnBranch } from '../git/index'; import { requireTrackedRepo } from '../utils/detect'; import { fetchTicket } from '../utils/providers'; import { findBranchCaseInsensitive } from '../utils/ticket'; -import { renderStatus } from '../ui/output'; +import { renderBranches } from '../ui/output'; import { theme, symbols } from '../ui/theme'; import { registry } from '../services/registry'; import { ghPrToPrStatus } from '../integrations/providers/github/github-client'; -async function runStatusDetail(targetBranch: string, projectId: string): Promise { +export async function runStatusDetail(targetBranch: string, projectId: string): Promise { const [branchesFile, projectConfig] = await Promise.all([ configManager.getBranches(projectId), configManager.getProjectConfig(projectId), @@ -19,14 +19,19 @@ async function runStatusDetail(targetBranch: string, projectId: string): Promise const trackedBranch = findBranchCaseInsensitive(branchesFile.branches, targetBranch); - // Don't fetch details for untracked branches — show the all-branches table instead + const defaultBranch = projectConfig.defaultBranch; + if (!trackedBranch) { - await renderStatus(); + console.log(theme.muted(`Branch "${targetBranch}" is not being tracked.`)); + if (targetBranch !== defaultBranch) { + console.log(theme.muted(` ${symbols.arrow} morg track to start tracking it`)); + } + console.log( + theme.muted(` ${symbols.arrow} morg status to check a specific branch`), + ); return; } - const defaultBranch = projectConfig.defaultBranch; - const ticketsProvider = await registry.tickets(); const gh = projectConfig.integrations.github?.enabled !== false ? await registry.gh() : null; @@ -60,7 +65,7 @@ async function runStatusDetail(targetBranch: string, projectId: string): Promise const lines: string[] = []; - lines.push(`${theme.primaryBold('Branch:')} ${targetBranch}`); + lines.push(`${theme.primaryBold('Branch:')} ${trackedBranch.branchName}`); if (trackedBranch?.worktreePath) { lines.push(`${theme.muted('Worktree:')} ${trackedBranch.worktreePath}`); } @@ -146,7 +151,7 @@ async function runStatusDetail(targetBranch: string, projectId: string): Promise } export async function runStatus(): Promise { - await renderStatus(); + await renderBranches(); } export function registerStatusCommand(program: Command): void { @@ -162,7 +167,7 @@ export function registerStatusCommand(program: Command): void { options: { branch?: string; short?: boolean; json?: boolean }, ) => { if (options.short) { - return renderStatus({ branch: options.branch, short: true }); + return renderBranches({ branch: options.branch, short: true }); } let projectId: string; try { diff --git a/src/index.ts b/src/index.ts index 15b5718..1904694 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { Command } from 'commander'; import { handleError } from './utils/errors'; -import { requireConfig } from './utils/detect'; +import { requireConfig, requireTrackedRepo } from './utils/detect'; import { registerConfigCommand } from './commands/config'; import { registerInitCommand } from './commands/init'; import { registerStartCommand } from './commands/start'; @@ -9,7 +9,8 @@ import { registerUntrackCommand } from './commands/untrack'; import { registerSwitchCommand } from './commands/switch'; import { registerPrCommand } from './commands/pr'; import { registerSyncCommand } from './commands/sync'; -import { registerStatusCommand, runStatus } from './commands/status'; +import { registerStatusCommand, runStatusDetail } from './commands/status'; +import { registerLsCommand } from './commands/ls'; import { registerStandupCommand } from './commands/standup'; import { registerPromptCommand } from './commands/prompt'; import { registerUpdateCommand } from './commands/update'; @@ -20,12 +21,40 @@ import { registerTicketsCommand } from './commands/tickets'; import { registerInstallClaudeSkillCommand } from './commands/install-claude-skill'; import { registerShellInitCommand } from './commands/shell-init'; import { registerWorktreeCommand } from './commands/worktree'; +import { getCurrentBranch } from './git/index'; +import { configManager } from './config/manager'; +import { findBranchCaseInsensitive } from './utils/ticket'; +import { renderBranches } from './ui/output'; +import { theme } from './ui/theme'; const program = new Command(); program.name('morg').description('Developer productivity assistant').version('0.1.0'); -program.action(() => runStatus()); +program.action(async () => { + try { + const projectId = await requireTrackedRepo(); + const [currentBranch, branchesFile, projectConfig] = await Promise.all([ + getCurrentBranch(), + configManager.getBranches(projectId), + configManager.getProjectConfig(projectId), + ]); + const trackedBranch = findBranchCaseInsensitive(branchesFile.branches, currentBranch); + + if (trackedBranch) { + await runStatusDetail(currentBranch, projectId); + } else { + await renderBranches(); + if (currentBranch !== projectConfig.defaultBranch) { + console.log( + theme.muted(`\nCurrent branch "${currentBranch}" is not being tracked. → morg track`), + ); + } + } + } catch { + await renderBranches(); + } +}); const NO_CONFIG_COMMANDS = new Set(['config', 'install-claude-skill', 'shell-init']); program.hook('preAction', async (_thisCommand, actionCommand) => { @@ -41,6 +70,7 @@ registerSwitchCommand(program); registerPrCommand(program); registerSyncCommand(program); registerStatusCommand(program); +registerLsCommand(program); registerStandupCommand(program); registerPromptCommand(program); registerUpdateCommand(program); diff --git a/src/ui/output.ts b/src/ui/output.ts index b6a0e95..f338f13 100644 --- a/src/ui/output.ts +++ b/src/ui/output.ts @@ -63,7 +63,7 @@ const TABLE_CHARS = { middle: '│', }; -export async function renderStatus(opts?: { branch?: string; short?: boolean }): Promise { +export async function renderBranches(opts?: { branch?: string; short?: boolean }): Promise { let projectId: string; try { projectId = await requireTrackedRepo(); @@ -100,7 +100,7 @@ export async function renderStatus(opts?: { branch?: string; short?: boolean }): padding: 1, borderStyle: 'round', borderColor: 'gray', - title: theme.primaryBold('morg status'), + title: theme.primaryBold('morg ls'), titleAlignment: 'left', }, ), @@ -142,7 +142,7 @@ export async function renderStatus(opts?: { branch?: string; short?: boolean }): } console.log(''); - console.log(theme.primaryBold(' morg status')); + console.log(theme.primaryBold(' morg ls')); console.log(table.toString()); console.log(''); }