Skip to content
Draft
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
1 change: 1 addition & 0 deletions docs/iloom-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ Each entry in `swarmIssues` has:
| `url` | `string` | Issue URL |
| `state` | `string \| null` | Current lifecycle state (`pending`, `in_progress`, `code_review`, `done`, `failed`) or `null` |
| `worktreePath` | `string \| null` | Path to the child's worktree, or `null` if not yet created |
| `complexity` | `object \| null` | Complexity assessment (`{ level, reason }`) from recap, or `null` if not available |

**Examples:**

Expand Down
12 changes: 6 additions & 6 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1214,14 +1214,14 @@ program
: Array.from(metadata.values()).filter((m): m is LoomMetadata => m != null)

// Format active looms
let activeJson: ReturnType<typeof formatLoomsForJson> extends (infer T)[] ? (T & { status: 'active'; finishedAt: null })[] : never = []
let activeJson: Awaited<ReturnType<typeof formatLoomsForJson>> extends (infer T)[] ? (T & { status: 'active'; finishedAt: null })[] : never = []
if (showActive) {
if (options.global) {
// Format global active looms from metadata (similar to finished looms format)
activeJson = globalActiveLooms.map(loom => {
activeJson = await Promise.all(globalActiveLooms.map(async loom => {
const isEpic = (loom.issueType ?? 'branch') === 'epic'
const swarmIssues = isEpic && loom.childIssues && loom.childIssues.length > 0
? enrichSwarmIssues(loom.childIssues, globalActiveLooms, finishedLooms, loom.projectPath)
? await enrichSwarmIssues(loom.childIssues, globalActiveLooms, finishedLooms, loom.projectPath)
: isEpic ? [] : undefined
const depMap = isEpic
? (loom.dependencyMap && Object.keys(loom.dependencyMap).length > 0
Expand Down Expand Up @@ -1251,10 +1251,10 @@ program
...(swarmIssues !== undefined && { swarmIssues }),
...(depMap !== undefined && { dependencyMap: depMap }),
}
})
}))
} else {
// Format worktrees from current repo
activeJson = formatLoomsForJson(worktrees, mainWorktreePath, metadata, allActiveMetadata, finishedLooms).map(loom => ({
activeJson = (await formatLoomsForJson(worktrees, mainWorktreePath, metadata, allActiveMetadata, finishedLooms)).map(loom => ({
...loom,
status: 'active' as const,
finishedAt: null,
Expand All @@ -1272,7 +1272,7 @@ program

// Format finished looms (only when --finished or --all is set)
let finishedJson = showFinished
? finishedLooms.map(loom => formatFinishedLoomForJson(loom, allActiveMetadata, finishedLooms))
? await Promise.all(finishedLooms.map(loom => formatFinishedLoomForJson(loom, allActiveMetadata, finishedLooms)))
: []

// Filter finished looms by project (include looms with null/undefined projectPath for legacy support)
Expand Down
14 changes: 7 additions & 7 deletions src/commands/list.regression.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ describe('il list --json: finished looms gating regression', () => {
expect(allLooms).toEqual([])
})

it('should include finished looms when --finished flag is set', () => {
it('should include finished looms when --finished flag is set', async () => {
// Simulate: il list --json --finished
const options = { json: true, finished: true }
const showFinished = Boolean(options.finished) || Boolean((options as { all?: boolean }).all)

const finishedJson = showFinished
? finishedLooms.map(loom => formatFinishedLoomForJson(loom, allActiveMetadata, finishedLooms))
? await Promise.all(finishedLooms.map(loom => formatFinishedLoomForJson(loom, allActiveMetadata, finishedLooms)))
: []

const allLooms = [...activeJson, ...finishedJson]
Expand All @@ -90,13 +90,13 @@ describe('il list --json: finished looms gating regression', () => {
})
})

it('should include finished looms when --all flag is set', () => {
it('should include finished looms when --all flag is set', async () => {
// Simulate: il list --json --all
const options = { json: true, all: true }
const showFinished = Boolean((options as { finished?: boolean }).finished) || Boolean(options.all)

const finishedJson = showFinished
? finishedLooms.map(loom => formatFinishedLoomForJson(loom, allActiveMetadata, finishedLooms))
? await Promise.all(finishedLooms.map(loom => formatFinishedLoomForJson(loom, allActiveMetadata, finishedLooms)))
: []

const allLooms = [...activeJson, ...finishedJson]
Expand All @@ -110,19 +110,19 @@ describe('il list --json: finished looms gating regression', () => {
})
})

it('should demonstrate the bug: without gating, finished looms leak into output', () => {
it('should demonstrate the bug: without gating, finished looms leak into output', async () => {
// This test shows what USED TO happen before the fix:
// finishedJson was always populated from finishedLooms.map(...)
// regardless of showFinished
const options = { json: true } // no --finished, no --all
const showFinished = Boolean((options as { finished?: boolean }).finished) || Boolean((options as { all?: boolean }).all)

// BUG BEHAVIOR (old code): always map finishedLooms
const buggyFinishedJson = finishedLooms.map(loom => formatFinishedLoomForJson(loom, allActiveMetadata, finishedLooms))
const buggyFinishedJson = await Promise.all(finishedLooms.map(loom => formatFinishedLoomForJson(loom, allActiveMetadata, finishedLooms)))

// FIX BEHAVIOR (new code): gate with showFinished
const fixedFinishedJson = showFinished
? finishedLooms.map(loom => formatFinishedLoomForJson(loom, allActiveMetadata, finishedLooms))
? await Promise.all(finishedLooms.map(loom => formatFinishedLoomForJson(loom, allActiveMetadata, finishedLooms)))
: []

// The buggy version incorrectly includes finished looms
Expand Down
Loading