Skip to content

Include complexity data in swarm issue output (il list --json)#848

Draft
acreeger wants to merge 2 commits intomainfrom
feat/issue-847__swarm-complexity-data
Draft

Include complexity data in swarm issue output (il list --json)#848
acreeger wants to merge 2 commits intomainfrom
feat/issue-847__swarm-complexity-data

Conversation

@acreeger
Copy link
Collaborator

@acreeger acreeger commented Mar 1, 2026

Fixes #847

Include complexity data in swarm issue output (il list --json)

Context

The VS Code extension's kanban board (epic board) needs to display complexity indicators on swarm issue cards. Currently, complexity data is only available in recap files (il recap --json), but the kanban board sources its data from il list --json via the swarmIssues array.

Request

When il list --json returns swarm issues, include the complexity data from each issue's recap file (if available) in the swarm issue object.

Current SwarmIssue shape:

{
  "number": "#123",
  "title": "Fix login bug",
  "url": "https://github.com/...",
  "state": "in_progress",
  "worktreePath": "/path/to/worktree"
}

Desired SwarmIssue shape:

{
  "number": "#123",
  "title": "Fix login bug",
  "url": "https://github.com/...",
  "state": "in_progress",
  "worktreePath": "/path/to/worktree",
  "complexity": { "level": "simple", "reason": "Single file change" }
}

The complexity field should be optional/nullable — only present when the issue has a recap file with complexity set.

Related

  • iloom-ai/iloom-vscode#246 — VS Code extension issue to display complexity on kanban cards

This PR was created automatically by iloom.

@acreeger
Copy link
Collaborator Author

acreeger commented Mar 1, 2026

Combined Analysis & Plan - Issue #847

Executive Summary

The VS Code extension's kanban board needs complexity indicators on swarm issue cards. Currently, complexity data lives only in recap files (~/.config/iloom-ai/recaps/), but the kanban board sources data from il list --json via the swarmIssues array. This plan adds an optional complexity field ({ level, reason }) to each SwarmIssue by reading recap files for child looms during the existing enrichment step.

Implementation Overview

High-Level Execution Phases

  1. Update types: Add complexity field to SwarmIssue interface and create a SwarmComplexity type
  2. Add helper: Create loadComplexityMap() to bulk-read recap files and return a Map<worktreePath, SwarmComplexity>
  3. Update enrichment: Add complexityMap parameter to enrichSwarmIssues() and populate complexity from it
  4. Update callers: Pass complexity map through formatLoomForJson, formatFinishedLoomForJson, and the cli.ts global path
  5. Tests & docs: Add tests for complexity enrichment; update docs/iloom-commands.md

Quick Stats

  • 4 files to modify (src/utils/loom-formatter.ts, src/utils/loom-formatter.test.ts, src/cli.ts, docs/iloom-commands.md)
  • 0 new files to create
  • 0 files to delete
  • Dependencies: None

Complete Analysis & Implementation Details (click to expand)

Research Findings

Problem Space

  • Problem: VS Code kanban board needs complexity data on swarm issue cards but il list --json doesn't include it
  • Architectural context: Recap files store complexity per-loom at ~/.config/iloom-ai/recaps/{slugified-worktree-path}.json; enrichSwarmIssues already enriches child issues with state and worktreePath from loom metadata
  • Edge cases: Child issues without a recap file or without complexity set should return complexity: null; child issues without an active/finished loom (no worktreePath) should also return complexity: null

Codebase Research

  • SwarmIssue interface: src/utils/loom-formatter.ts:74-80 - has number, title, url, state, worktreePath
  • enrichSwarmIssues function: src/utils/loom-formatter.ts:187-238 - enriches child issues with state/worktreePath from metadata; already resolves childMeta which has worktreePath
  • RecapComplexity type: src/mcp/recap-types.ts:37-41 - { level: RecapComplexityLevel, reason?: string, timestamp: string }
  • RecapComplexityLevel: src/mcp/recap-types.ts:16 - 'trivial' | 'simple' | 'complex'
  • resolveRecapFilePath: src/utils/mcp.ts:171-173 - converts worktreePath to recap file path
  • readRecapFile: src/utils/mcp.ts:179-189 - reads recap file with graceful degradation (returns {} on error)
  • Call sites for enrichSwarmIssues: src/utils/loom-formatter.ts:286 (formatLoomForJson), src/utils/loom-formatter.ts:357 (formatFinishedLoomForJson), src/cli.ts:1224 (global active looms)
  • formatLoomsForJson: src/utils/loom-formatter.ts:327-339 - passes parameters through to formatLoomForJson
  • Similar pattern: slugifyPath and recap file reading in src/lib/SessionSummaryService.ts:37-79
  • Documentation: docs/iloom-commands.md:523-531 - swarmIssues field reference table needs complexity row

Affected Files

  • src/utils/loom-formatter.ts:74-80,187-238,251-316,327-339,350-389 - SwarmIssue type + enrichment + formatters
  • src/utils/loom-formatter.test.ts:1580+ - enrichSwarmIssues tests
  • src/cli.ts:1202-1275 - JSON output path that calls enrichSwarmIssues and formatters
  • docs/iloom-commands.md:525-531 - swarmIssues field documentation

Integration Points

  • RecapComplexity type from src/mcp/recap-types.ts (type reference only for loadComplexityMap internal use)
  • resolveRecapFilePath and readRecapFile from src/utils/mcp.ts (for loading recap files)

Medium Severity Risks

  • Performance: Loading recap files for all child looms adds filesystem I/O; mitigated by using Promise.allSettled for parallel reads and only loading for epic looms with children

Implementation Plan

Automated Test Cases to Create

Test File: src/utils/loom-formatter.test.ts (MODIFY)

Click to expand complete test structure (20 lines)
// Add to existing 'enrichSwarmIssues' describe block

it('should include complexity from complexityMap when worktreePath matches', () => {
  // childIssues with #101, #102
  // allMetadata with worktreePaths for both
  // complexityMap with entry for #101's worktreePath -> { level: 'simple', reason: 'Single file' }
  // Expect result[0].complexity = { level: 'simple', reason: 'Single file' }
  // Expect result[1].complexity = null (no complexity in map)
})

it('should set complexity to null when no complexityMap is provided', () => {
  // Same as existing test but verify complexity: null is present
})

it('should set complexity to null when child has no worktreePath', () => {
  // childIssues with #101, no matching metadata
  // complexityMap with some entries
  // Expect result[0].complexity = null
})

Files to Modify

1. src/utils/loom-formatter.ts:72-80

Change: Add SwarmComplexity type and complexity field to SwarmIssue interface

// Add new type before SwarmIssue interface (~line 72)
export interface SwarmComplexity {
  level: string
  reason?: string
}

// Add to SwarmIssue interface (after worktreePath field):
complexity: SwarmComplexity | null

2. src/utils/loom-formatter.ts:1-5 (imports)

Change: Add import for resolveRecapFilePath and readRecapFile from ../utils/mcp.js

3. src/utils/loom-formatter.ts (new helper, after SwarmIssue interface ~line 82)

Change: Add loadComplexityMap async helper function

Click to expand helper function pseudocode (20 lines)
/**
 * Load complexity data from recap files for a set of worktree paths.
 * Returns a Map<worktreePath, SwarmComplexity> for efficient lookup.
 * Reads recap files in parallel with graceful degradation (missing/invalid files are skipped).
 */
export async function loadComplexityMap(
  worktreePaths: string[],
): Promise<Map<string, SwarmComplexity>> {
  const map = new Map<string, SwarmComplexity>()
  const uniquePaths = [...new Set(worktreePaths.filter(Boolean))]
  
  const results = await Promise.allSettled(
    uniquePaths.map(async (wp) => {
      const filePath = resolveRecapFilePath(wp)
      const recap = await readRecapFile(filePath)
      const complexity = recap.complexity as { level?: string; reason?: string } | undefined
      if (complexity?.level) {
        map.set(wp, { level: complexity.level, ...(complexity.reason && { reason: complexity.reason }) })
      }
    })
  )
  // Ignore rejected promises (graceful degradation)
  return map
}

4. src/utils/loom-formatter.ts:187-238

Change: Add optional complexityMap parameter to enrichSwarmIssues and include complexity in return value

// Add parameter after projectPath:
complexityMap?: Map<string, SwarmComplexity>,

// In the return object (inside .map), after worktreePath line:
complexity: (childMeta?.worktreePath && complexityMap?.get(childMeta.worktreePath)) ?? null,

5. src/utils/loom-formatter.ts:251-316 (formatLoomForJson)

Change: Add optional complexityMap parameter, pass through to enrichSwarmIssues at line 286

6. src/utils/loom-formatter.ts:327-339 (formatLoomsForJson)

Change: Add optional complexityMap parameter, pass through to formatLoomForJson

7. src/utils/loom-formatter.ts:350-389 (formatFinishedLoomForJson)

Change: Add optional complexityMap parameter, pass through to enrichSwarmIssues at line 357

8. src/cli.ts:23 (imports)

Change: Add loadComplexityMap to the import from ./utils/loom-formatter.js

9. src/cli.ts:1214 (after allActiveMetadata computation)

Change: Build complexity map by collecting worktree paths from allActiveMetadata + finishedLooms, then calling loadComplexityMap

Click to expand complexity map loading pseudocode (10 lines)
// After line 1214 (allActiveMetadata computation):
// Collect worktree paths for complexity map loading
const childWorktreePaths: string[] = []
for (const meta of [...allActiveMetadata, ...finishedLooms]) {
  if (meta.worktreePath) childWorktreePaths.push(meta.worktreePath)
}
const complexityMap = await loadComplexityMap(childWorktreePaths)

10. src/cli.ts:1224 (inline enrichSwarmIssues call)

Change: Pass complexityMap as the 5th argument

11. src/cli.ts:1257 (formatLoomsForJson call)

Change: Pass complexityMap as additional (6th) argument

12. src/cli.ts:1275 (formatFinishedLoomForJson call in map)

Change: Pass complexityMap as additional (4th) argument

13. docs/iloom-commands.md:525-531

Change: Add complexity row to the swarmIssues field reference table

| `complexity` | `object \| null` | Complexity assessment (`{ level, reason }`) from recap, or `null` if not available |

Detailed Execution Order

NOTE: These steps are executed in a SINGLE implementation run.

  1. Define types and helper

    • Files: src/utils/loom-formatter.ts
    • Add SwarmComplexity interface, add complexity to SwarmIssue, add loadComplexityMap helper, add imports for resolveRecapFilePath/readRecapFile -> Verify: pnpm compile
  2. Update enrichSwarmIssues

    • Files: src/utils/loom-formatter.ts
    • Add complexityMap? parameter, add complexity to return object -> Verify: existing tests show complexity: null failures (expected, fix in step 5)
  3. Update formatter functions

    • Files: src/utils/loom-formatter.ts
    • Add complexityMap? parameter to formatLoomForJson, formatLoomsForJson, formatFinishedLoomForJson; pass through to enrichSwarmIssues -> Verify: pnpm compile
  4. Update cli.ts callers

    • Files: src/cli.ts
    • Import loadComplexityMap, build map before formatting, pass to all three call sites -> Verify: pnpm build
  5. Update existing tests and add new tests

    • Files: src/utils/loom-formatter.test.ts
    • Add complexity: null to all existing enrichSwarmIssues test expectations; add new test cases for complexity enrichment -> Verify: pnpm test:single src/utils/loom-formatter.test.ts
  6. Update documentation

    • Files: docs/iloom-commands.md
    • Add complexity row to swarmIssues field table -> Verify: visual check
  7. Final build

    • Run pnpm build to ensure full compilation succeeds

Dependencies and Configuration

None

@acreeger
Copy link
Collaborator Author

acreeger commented Mar 1, 2026

Implementation Complete

Summary

Added an optional complexity field ({ level, reason }) to the SwarmIssue interface returned by il list --json. The complexity data is loaded from recap files for each child loom's worktree path, enabling the VS Code extension's kanban board to display complexity indicators on swarm issue cards.

Changes Made

  • src/utils/loom-formatter.ts: Added SwarmComplexity type, complexity field to SwarmIssue, loadComplexityMap() helper, and threaded complexityMap parameter through enrichSwarmIssues, formatLoomForJson, formatLoomsForJson, and formatFinishedLoomForJson
  • src/cli.ts: Imported loadComplexityMap, built complexity map once before JSON formatting, passed to all three call sites
  • src/utils/loom-formatter.test.ts: Updated 8 existing test expectations with complexity: null, added 4 complexity enrichment tests and 7 loadComplexityMap tests
  • docs/iloom-commands.md: Added complexity row to swarmIssues field reference table

Validation Results

  • ✅ Tests: 4560 passed / 1 skipped (all 135 test files pass)
  • ✅ Typecheck: Passed
  • ✅ Lint: Passed (0 warnings)
  • ✅ Build: Passed

Detailed Changes by File (click to expand)

src/utils/loom-formatter.ts

Changes: Added types, helper function, and threaded complexity through formatting pipeline

  • Added SwarmComplexity interface ({ level: string, reason?: string })
  • Added complexity: SwarmComplexity | null to SwarmIssue interface
  • Added loadComplexityMap() - bulk-reads recap files via Promise.allSettled for parallel I/O with graceful degradation
  • Added optional complexityMap parameter to enrichSwarmIssues(), formatLoomForJson(), formatLoomsForJson(), formatFinishedLoomForJson()

src/cli.ts

Changes: Built and passed complexity map to all JSON formatting call sites

  • Imported loadComplexityMap from loom-formatter
  • Collected worktree paths from allActiveMetadata and finishedLooms
  • Built complexity map once before JSON formatting block
  • Passed complexityMap to inline enrichSwarmIssues(), formatLoomsForJson(), and formatFinishedLoomForJson()

src/utils/loom-formatter.test.ts

Changes: Updated existing tests and added comprehensive new test coverage

  • Added complexity: null to all 8 existing enrichSwarmIssues test expectations
  • Added 4 new complexity enrichment tests (map match, no map, no worktreePath, level-only)
  • Added 7 new loadComplexityMap tests (empty paths, no complexity, extraction, timestamp exclusion, deduplication, falsy filtering, error handling)

docs/iloom-commands.md

Changes: Added complexity field documentation

  • Added complexity row to swarmIssues field reference table

Add optional `complexity` field ({ level, reason }) to SwarmIssue objects
returned by `il list --json`. Complexity data is loaded from recap files
for each child loom, enabling the VS Code kanban board to display
complexity indicators on swarm issue cards.

Fixes #847
@acreeger acreeger force-pushed the feat/issue-847__swarm-complexity-data branch from a68ec8b to c6e10ed Compare March 1, 2026 02:59
Remove loadComplexityMap helper and complexityMap parameter threading.
Instead, read recap files inline in enrichSwarmIssues for each child
with a worktreePath. Removes unnecessary pre-loading, deduplication,
and parameter plumbing across 4 function signatures.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Include complexity data in swarm issue output (il list --json)

1 participant