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
22 changes: 15 additions & 7 deletions .claude/skills/morg/skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <branch>
morg status MORG-42

# All tracked branches as JSON (for scripting)
morg status --json
```

## Managing Tickets
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ All JSON state is read/written exclusively through `configManager` (never direct
1. Create `src/commands/<name>.ts` with a `register<Name>Command(program: Command)` export
2. Implement the logic in a private `async function run<Name>()` in the same file
3. Add static import + `register<Name>Command(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.

Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 <branch> # detail for a specific branch (accepts ticket ID, case-insensitive)
morg status --json # all tracked branches as JSON
```

### Tickets
Expand Down
10 changes: 10 additions & 0 deletions src/commands/ls.ts
Original file line number Diff line number Diff line change
@@ -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());
}
2 changes: 2 additions & 0 deletions src/commands/shell-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const COMMANDS = [
'switch',
'pr',
'sync',
'ls',
'branches',
'status',
'standup',
'prompt',
Expand Down
23 changes: 14 additions & 9 deletions src/commands/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,32 @@ 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<void> {
export async function runStatusDetail(targetBranch: string, projectId: string): Promise<void> {
const [branchesFile, projectConfig] = await Promise.all([
configManager.getBranches(projectId),
configManager.getProjectConfig(projectId),
]);

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 <branch> 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;

Expand Down Expand Up @@ -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}`);
}
Expand Down Expand Up @@ -146,7 +151,7 @@ async function runStatusDetail(targetBranch: string, projectId: string): Promise
}

export async function runStatus(): Promise<void> {
await renderStatus();
await renderBranches();
}

export function registerStatusCommand(program: Command): void {
Expand All @@ -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 {
Expand Down
36 changes: 33 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand All @@ -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) => {
Expand All @@ -41,6 +70,7 @@ registerSwitchCommand(program);
registerPrCommand(program);
registerSyncCommand(program);
registerStatusCommand(program);
registerLsCommand(program);
registerStandupCommand(program);
registerPromptCommand(program);
registerUpdateCommand(program);
Expand Down
6 changes: 3 additions & 3 deletions src/ui/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const TABLE_CHARS = {
middle: '│',
};

export async function renderStatus(opts?: { branch?: string; short?: boolean }): Promise<void> {
export async function renderBranches(opts?: { branch?: string; short?: boolean }): Promise<void> {
let projectId: string;
try {
projectId = await requireTrackedRepo();
Expand Down Expand Up @@ -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',
},
),
Expand Down Expand Up @@ -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('');
}