From 1fc4fe1131925f668af9bbfa8e6803e43d62d553 Mon Sep 17 00:00:00 2001 From: Davi Rodrigues Date: Tue, 10 Mar 2026 12:27:49 -0300 Subject: [PATCH 1/6] feat(MORG-54): add `morg ls` and make default `morg` context-aware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename `renderStatus()` → `renderBranches()` in `ui/output.ts` to better reflect what it renders - Export `runStatusDetail()` from `status.ts` - Add `src/commands/ls.ts`: `morg ls` / `morg branches` shows the multi-branch table (old default behavior) - Replace default `morg` action with context-aware logic: - On a tracked branch → `runStatusDetail()` (rich single-branch view) - Otherwise → `renderBranches()` + "not being tracked" hint --- src/commands/ls.ts | 10 ++++++++++ src/commands/status.ts | 10 +++++----- src/index.ts | 31 ++++++++++++++++++++++++++++--- src/ui/output.ts | 2 +- 4 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 src/commands/ls.ts 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/status.ts b/src/commands/status.ts index 75e84ae..c9f16b0 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), @@ -21,7 +21,7 @@ async function runStatusDetail(targetBranch: string, projectId: string): Promise // Don't fetch details for untracked branches — show the all-branches table instead if (!trackedBranch) { - await renderStatus(); + await renderBranches(); return; } @@ -146,7 +146,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 +162,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..88d2abb 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,35 @@ 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 = await getCurrentBranch(); + const branchesFile = await configManager.getBranches(projectId); + const trackedBranch = findBranchCaseInsensitive(branchesFile.branches, currentBranch); + + if (trackedBranch) { + await runStatusDetail(currentBranch, projectId); + } else { + await renderBranches(); + 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 +65,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..8807b50 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(); From dd2942b86167a4d12e71d4275811fab7fd9b8bc7 Mon Sep 17 00:00:00 2001 From: Davi Rodrigues Date: Tue, 10 Mar 2026 12:32:56 -0300 Subject: [PATCH 2/6] fix(MORG-54): don't show 'morg track' hint on the default branch --- src/index.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 88d2abb..1904694 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,17 +34,22 @@ program.name('morg').description('Developer productivity assistant').version('0. program.action(async () => { try { const projectId = await requireTrackedRepo(); - const currentBranch = await getCurrentBranch(); - const branchesFile = await configManager.getBranches(projectId); + 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(); - console.log( - theme.muted(`\nCurrent branch "${currentBranch}" is not being tracked. → morg track`), - ); + if (currentBranch !== projectConfig.defaultBranch) { + console.log( + theme.muted(`\nCurrent branch "${currentBranch}" is not being tracked. → morg track`), + ); + } } } catch { await renderBranches(); From 8bbb3b15989c33a6645198ea11b92e4c59685214 Mon Sep 17 00:00:00 2001 From: Davi Rodrigues Date: Tue, 10 Mar 2026 12:34:50 -0300 Subject: [PATCH 3/6] fix(MORG-54): update branch table title from 'morg status' to 'morg ls' --- src/ui/output.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/output.ts b/src/ui/output.ts index 8807b50..f338f13 100644 --- a/src/ui/output.ts +++ b/src/ui/output.ts @@ -100,7 +100,7 @@ export async function renderBranches(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 renderBranches(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(''); } From a469feda00669a3e8f2ce46e15a21c43725704fc Mon Sep 17 00:00:00 2001 From: Davi Rodrigues Date: Tue, 10 Mar 2026 12:37:34 -0300 Subject: [PATCH 4/6] fix(MORG-54): morg status on untracked branch shows error instead of branch table --- src/commands/status.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/commands/status.ts b/src/commands/status.ts index c9f16b0..e57b360 100644 --- a/src/commands/status.ts +++ b/src/commands/status.ts @@ -19,14 +19,19 @@ export async function runStatusDetail(targetBranch: string, projectId: string): 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 renderBranches(); + 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; From 211288a07f198ad81b0b3481516e972b56b2a3a6 Mon Sep 17 00:00:00 2001 From: Davi Rodrigues Date: Tue, 10 Mar 2026 12:41:37 -0300 Subject: [PATCH 5/6] fix(MORG-54): show canonical branch name in status output, not user input --- src/commands/status.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/status.ts b/src/commands/status.ts index e57b360..f734c30 100644 --- a/src/commands/status.ts +++ b/src/commands/status.ts @@ -65,7 +65,7 @@ export async function runStatusDetail(targetBranch: string, projectId: string): 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}`); } From 3180c06ec039a498ee8da6ec44470b22e3759b0d Mon Sep 17 00:00:00 2001 From: Davi Rodrigues Date: Tue, 10 Mar 2026 12:49:08 -0300 Subject: [PATCH 6/6] docs(MORG-54): sync README, skill, completions and CLAUDE.md with new command structure - Add 'ls' to shell-init COMMANDS array for tab completion - README: update 'What it does' blurb and Status section to document morg/morg ls/morg status - skill.md: update Viewing Status and workflow examples for new commands - CLAUDE.md: add rule to always keep README, skill, and shell-init in sync when changing commands --- .claude/skills/morg/skill.md | 22 +++++++++++++++------- CLAUDE.md | 4 ++++ README.md | 11 ++++++++--- src/commands/shell-init.ts | 2 ++ 4 files changed, 29 insertions(+), 10 deletions(-) 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/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',