From fef605dc22cf24bd261632f0b7c1df65570e193f Mon Sep 17 00:00:00 2001 From: "Jeffrey T. Fritz csharpfritz" Date: Sat, 21 Feb 2026 20:02:56 -0500 Subject: [PATCH 1/7] fix: correct Idle status display in VS Code Copilot Chat (#63) When Squad runs in Copilot Chat, subagents often show Idle because the Squad orchestrator doesn't create active-work marker files. Added fallback detection: if no active-work markers exist but ANY orchestration log file was modified in the last 10 minutes, members who appear in logs are marked as working. This covers the Copilot Chat scenario where logs update but markers aren't created. The active-work marker system remains the primary status indicator when present (5-min staleness threshold). This fallback only activates when no markers exist (10-min activity window). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/services/SquadDataProvider.ts | 47 +++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/services/SquadDataProvider.ts b/src/services/SquadDataProvider.ts index c11d6bc..6179e09 100644 --- a/src/services/SquadDataProvider.ts +++ b/src/services/SquadDataProvider.ts @@ -20,6 +20,9 @@ import { normalizeEol } from '../utils/eol'; /** Markers older than this are considered stale and ignored. */ const STALENESS_THRESHOLD_MS = 300_000; // 5 minutes +/** Orchestration activity within this window indicates active work (Copilot Chat fallback). */ +const ORCHESTRATION_ACTIVITY_WINDOW_MS = 600_000; // 10 minutes + /** * Provides squad data to the UI layer. * Aggregates data from TeamMdService (primary) and OrchestrationLogService (overlay). @@ -160,6 +163,17 @@ export class SquadDataProvider { } } + // Fallback: If no active markers but recent orchestration activity detected, + // mark all members who appear in logs as working (Copilot Chat scenario) + if (activeMarkers.size === 0 && await this.hasRecentOrchestrationActivity()) { + for (const member of members) { + const logStatus = memberStates.get(member.name); + if (logStatus === 'working') { + member.status = 'working'; + } + } + } + this.cachedMembers = members; return members; } @@ -278,6 +292,39 @@ export class SquadDataProvider { return activeAgents; } + /** + * Checks if ANY orchestration log file has been modified recently. + * Fallback indicator for active work when Squad orchestrator doesn't create markers. + * Used in Copilot Chat scenarios where orchestration logs may update before markers. + * @returns true if any orchestration log file was modified in the last 10 minutes + */ + private async hasRecentOrchestrationActivity(): Promise { + const logDirs = [ + path.join(this.teamRoot, this.squadFolder, 'orchestration-log'), + path.join(this.teamRoot, this.squadFolder, 'log') + ]; + + for (const logDir of logDirs) { + try { + const files = await fs.promises.readdir(logDir); + for (const file of files) { + if (!file.endsWith('.md')) { continue; } + const filePath = path.join(logDir, file); + const stat = await fs.promises.stat(filePath); + const ageMs = Date.now() - stat.mtimeMs; + if (ageMs < ORCHESTRATION_ACTIVITY_WINDOW_MS) { + return true; + } + } + } catch { + // Directory doesn't exist — try next + continue; + } + } + + return false; + } + // ─── Public Data Access Methods ─────────────────────────────────────── /** From 11ee92d756f043d0058a44a6f687fac7478da552 Mon Sep 17 00:00:00 2001 From: "Jeffrey T. Fritz csharpfritz" Date: Sat, 21 Feb 2026 20:08:16 -0500 Subject: [PATCH 2/7] docs: update Rusty history and decision inbox for issue #63 --- .ai-team/agents/rusty/history.md | 8 ++++++++ .ai-team/decisions/inbox/rusty-idle-status-fix.md | 4 ++++ 2 files changed, 12 insertions(+) create mode 100644 .ai-team/decisions/inbox/rusty-idle-status-fix.md diff --git a/.ai-team/agents/rusty/history.md b/.ai-team/agents/rusty/history.md index 616cc10..e53cfaf 100644 --- a/.ai-team/agents/rusty/history.md +++ b/.ai-team/agents/rusty/history.md @@ -142,4 +142,12 @@ Team update (2026-02-18): Active-work marker protocol adopted; tree view reacts to marker files via cache invalidation decided by Danny 📌 Team update (2026-02-18): Velocity chart uses all logs; status stays orchestration-only — DashboardDataBuilder now accepts optional `velocityTasks` parameter (9th arg). Velocity timeline uses `velocityTasks ?? tasks` (all logs or fallback). Activity swimlanes still use orchestration-only `tasks`. Architectural principle: velocity = all work signals; status = orchestration-only to prevent false "working" indicators from old session logs. — decided by Linus + +### Copilot Chat Idle Status Fallback (2026-02-18) +- **Issue #63:** Squad subagents showed "Idle" in VS Code Copilot Chat even when actively working because Squad orchestrator doesn't create active-work marker files during chat sessions +- **Root cause:** SquadUI reads active-work markers (primary) and orchestration logs (secondary) for status. When Squad runs in Copilot Chat, marker files aren't created, and orchestration logs may not exist or update immediately +- **Solution:** Added `hasRecentOrchestrationActivity()` fallback detection in `SquadDataProvider` — scans both `orchestration-log/` and `log/` directories for ANY `.md` file modified in the last 10 minutes (controlled by `ORCHESTRATION_ACTIVITY_WINDOW_MS` constant) +- **Status precedence:** Active-work markers remain highest priority (5-min staleness). Fallback only activates when `activeMarkers.size === 0` AND recent orchestration activity detected. Members with log status 'working' are then marked as working (bypassing the task demotion rule) +- **Key insight:** This handles the Copilot Chat scenario where Squad is actively orchestrating work (evidenced by recent log writes) but hasn't created the formal marker protocol files. The 10-min window is wider than marker staleness to catch delayed log writes +- **Files changed:** `src/services/SquadDataProvider.ts` — added `ORCHESTRATION_ACTIVITY_WINDOW_MS` constant, `hasRecentOrchestrationActivity()` method, fallback logic in `getSquadMembers()` after marker detection diff --git a/.ai-team/decisions/inbox/rusty-idle-status-fix.md b/.ai-team/decisions/inbox/rusty-idle-status-fix.md new file mode 100644 index 0000000..97aa277 --- /dev/null +++ b/.ai-team/decisions/inbox/rusty-idle-status-fix.md @@ -0,0 +1,4 @@ +### 2026-02-18: Copilot Chat Idle Status Fallback +**By:** Rusty +**What:** Added fallback status detection when Squad runs in VS Code Copilot Chat without creating active-work marker files. SquadUI now checks if ANY orchestration log file was modified in the last 10 minutes — if so, members who appear in logs are marked as working. +**Why:** Issue #63 reported that subagents show "Idle" during Copilot Chat sessions because the Squad orchestrator doesn't write active-work marker files. This fallback detects orchestration activity via log file timestamps, providing a signal that work is happening even when the formal marker protocol isn't used. The 10-minute window (vs. 5-minute marker staleness) accounts for delayed log writes. Active-work markers remain the primary status indicator when present. From b3ce72382d734c569e31dd7b4f9e65eb87822e2f Mon Sep 17 00:00:00 2001 From: "Jeffrey T. Fritz csharpfritz" Date: Sat, 21 Feb 2026 20:22:02 -0500 Subject: [PATCH 3/7] fix: correct Idle status display in VS Code Copilot Chat (#63) Root cause: When orchestration logs had #NNN issue references in both relatedIssues AND outcomes, the task was created from relatedIssues first (default to in_progress), missing the completion signal in outcomes. Also: The working-to-idle override was too aggressive - it marked members idle even when they had NO tasks at all (common in Copilot Chat where logs may not have parseable task markers). Changes: - OrchestrationLogService: Check outcomes for completion signals when extracting tasks from relatedIssues - SquadDataProvider: Only apply working-to-idle override when member has tasks but none are in-progress (not when they have zero tasks) - Update test fixture to properly test completion detection Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/services/OrchestrationLogService.ts | 14 +++- src/services/SquadDataProvider.ts | 66 ++++--------------- .../suite/squadDataProviderExtended.test.ts | 5 +- 3 files changed, 31 insertions(+), 54 deletions(-) diff --git a/src/services/OrchestrationLogService.ts b/src/services/OrchestrationLogService.ts index 30f26e4..0c0f085 100644 --- a/src/services/OrchestrationLogService.ts +++ b/src/services/OrchestrationLogService.ts @@ -267,13 +267,25 @@ export class OrchestrationLogService { // Assign to first participant from the log entry const assignee = entry.participants[0] ?? 'unknown'; + // Check if any outcome mentions this issue with a completion signal + let isCompleted = false; + if (entry.outcomes) { + for (const outcome of entry.outcomes) { + if (outcome.includes(`#${taskId}`) && this.isCompletionSignal(outcome)) { + isCompleted = true; + break; + } + } + } + tasks.push({ id: taskId, title: `Issue #${taskId}`, description: entry.summary ?? undefined, - status: 'in_progress', + status: isCompleted ? 'completed' : 'in_progress', assignee, startedAt: parseDateAsLocal(entry.date), + completedAt: isCompleted ? parseDateAsLocal(entry.date) : undefined, }); } } diff --git a/src/services/SquadDataProvider.ts b/src/services/SquadDataProvider.ts index 6179e09..0039678 100644 --- a/src/services/SquadDataProvider.ts +++ b/src/services/SquadDataProvider.ts @@ -20,9 +20,6 @@ import { normalizeEol } from '../utils/eol'; /** Markers older than this are considered stale and ignored. */ const STALENESS_THRESHOLD_MS = 300_000; // 5 minutes -/** Orchestration activity within this window indicates active work (Copilot Chat fallback). */ -const ORCHESTRATION_ACTIVITY_WINDOW_MS = 600_000; // 10 minutes - /** * Provides squad data to the UI layer. * Aggregates data from TeamMdService (primary) and OrchestrationLogService (overlay). @@ -104,9 +101,12 @@ export class SquadDataProvider { members = roster.members.map(member => { const logStatus = memberStates.get(member.name) ?? 'idle'; const currentTask = tasks.find(t => t.assignee === member.name && t.status === 'in_progress'); - // Override 'working' to 'idle' if the member has no in-progress tasks - // (all their work is completed — they shouldn't show as spinning) - const status = (logStatus === 'working' && !currentTask) ? 'idle' : logStatus; + const memberTasks = tasks.filter(t => t.assignee === member.name); + // Override 'working' to 'idle' ONLY if the member has tasks but none are in-progress + // (all their work is completed — they shouldn't show as spinning). + // If they have NO tasks at all, trust the log status (Copilot Chat scenario). + const hasTasksButNoneActive = memberTasks.length > 0 && !currentTask; + const status = (logStatus === 'working' && hasTasksButNoneActive) ? 'idle' : logStatus; return { name: member.name, role: member.role, @@ -122,7 +122,10 @@ export class SquadDataProvider { members = agentMembers.map(member => { const logStatus = memberStates.get(member.name) ?? 'idle'; const currentTask = tasks.find(t => t.assignee === member.name && t.status === 'in_progress'); - const status = (logStatus === 'working' && !currentTask) ? 'idle' : logStatus; + const memberTasks = tasks.filter(t => t.assignee === member.name); + // Override 'working' to 'idle' ONLY if the member has tasks but none are in-progress + const hasTasksButNoneActive = memberTasks.length > 0 && !currentTask; + const status = (logStatus === 'working' && hasTasksButNoneActive) ? 'idle' : logStatus; return { name: member.name, role: member.role, @@ -143,7 +146,10 @@ export class SquadDataProvider { for (const name of memberNames) { const logStatus = memberStates.get(name) ?? 'idle'; const currentTask = tasks.find(t => t.assignee === name && t.status === 'in_progress'); - const status = (logStatus === 'working' && !currentTask) ? 'idle' : logStatus; + const memberTasks = tasks.filter(t => t.assignee === name); + // Override 'working' to 'idle' ONLY if the member has tasks but none are in-progress + const hasTasksButNoneActive = memberTasks.length > 0 && !currentTask; + const status = (logStatus === 'working' && hasTasksButNoneActive) ? 'idle' : logStatus; members.push({ name, role: 'Squad Member', @@ -163,17 +169,6 @@ export class SquadDataProvider { } } - // Fallback: If no active markers but recent orchestration activity detected, - // mark all members who appear in logs as working (Copilot Chat scenario) - if (activeMarkers.size === 0 && await this.hasRecentOrchestrationActivity()) { - for (const member of members) { - const logStatus = memberStates.get(member.name); - if (logStatus === 'working') { - member.status = 'working'; - } - } - } - this.cachedMembers = members; return members; } @@ -292,39 +287,6 @@ export class SquadDataProvider { return activeAgents; } - /** - * Checks if ANY orchestration log file has been modified recently. - * Fallback indicator for active work when Squad orchestrator doesn't create markers. - * Used in Copilot Chat scenarios where orchestration logs may update before markers. - * @returns true if any orchestration log file was modified in the last 10 minutes - */ - private async hasRecentOrchestrationActivity(): Promise { - const logDirs = [ - path.join(this.teamRoot, this.squadFolder, 'orchestration-log'), - path.join(this.teamRoot, this.squadFolder, 'log') - ]; - - for (const logDir of logDirs) { - try { - const files = await fs.promises.readdir(logDir); - for (const file of files) { - if (!file.endsWith('.md')) { continue; } - const filePath = path.join(logDir, file); - const stat = await fs.promises.stat(filePath); - const ageMs = Date.now() - stat.mtimeMs; - if (ageMs < ORCHESTRATION_ACTIVITY_WINDOW_MS) { - return true; - } - } - } catch { - // Directory doesn't exist — try next - continue; - } - } - - return false; - } - // ─── Public Data Access Methods ─────────────────────────────────────── /** diff --git a/src/test/suite/squadDataProviderExtended.test.ts b/src/test/suite/squadDataProviderExtended.test.ts index ce5d081..9174171 100644 --- a/src/test/suite/squadDataProviderExtended.test.ts +++ b/src/test/suite/squadDataProviderExtended.test.ts @@ -167,7 +167,7 @@ suite('SquadDataProvider — Extended Coverage', () => { ].join('\n')); // Create log that marks Alice as participant (most recent = working) - // but with completed tasks only + // but with completed tasks only (issue reference with completion signal) const logDir = path.join(tempDir, '.ai-team', 'orchestration-log'); fs.mkdirSync(logDir, { recursive: true }); fs.writeFileSync(path.join(logDir, '2026-03-01-completed.md'), [ @@ -175,6 +175,9 @@ suite('SquadDataProvider — Extended Coverage', () => { '', '**Participants:** Alice', '', + '## Outcomes', + '- Closed #42 — fixed the bug', + '', '## Summary', 'All tasks completed.', ].join('\n')); From 3d340bbe88280c3abb028d659424faf3cf5cd451 Mon Sep 17 00:00:00 2001 From: "Jeffrey T. Fritz csharpfritz" Date: Sat, 21 Feb 2026 20:25:49 -0500 Subject: [PATCH 4/7] test: add comprehensive tests for issue #63 fix - Add 4 tests for SquadDataProvider working-to-idle override scenarios - Add 4 tests for OrchestrationLogService completion signal detection - Cover edge cases: no tasks, completed tasks, in-progress tasks - Test completion signals: Closed, Resolved, Working on, no outcome Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .ai-team/agents/basher/history.md | 25 +++++ .../suite/orchestrationLogService.test.ts | 85 +++++++++++++++++ .../suite/squadDataProviderExtended.test.ts | 93 ++++++++++++++++++- 3 files changed, 202 insertions(+), 1 deletion(-) diff --git a/.ai-team/agents/basher/history.md b/.ai-team/agents/basher/history.md index cc91091..fe3f8ea 100644 --- a/.ai-team/agents/basher/history.md +++ b/.ai-team/agents/basher/history.md @@ -268,3 +268,28 @@ - Temp dir cleanup in `finally` block for non-suite-scoped tests - Compilation clean (`npx tsc --noEmit`); all 959 tests passing (9 new + 950 existing) +### Issue #63 Working-to-Idle and Completion Signal Tests (2026-02-22) +- Added 8 new tests across 2 test suites to validate issue #63 fix +- **SquadDataProvider working-to-idle tests (4 tests in `squadDataProviderExtended.test.ts`):** + 1. Member with completed tasks only shows as idle (not working) — validates new `hasTasksButNoneActive` logic + 2. Member with NO tasks at all stays working (Copilot Chat scenario) — ensures log status preserved when tasks array is empty + 3. Member with in-progress tasks stays working — baseline behavior preserved + 4. Member not in logs shows as idle — default state when absent from orchestration logs +- **OrchestrationLogService completion signal tests (4 tests in `orchestrationLogService.test.ts`):** + 1. relatedIssues with "Closed #NN" outcome → task status = 'completed' with completedAt timestamp + 2. relatedIssues with "Resolved #NN" outcome → task status = 'completed' (validates isCompletionSignal patterns) + 3. relatedIssues with "Working on #NN" outcome → task status = 'in_progress' without completedAt + 4. relatedIssues without matching outcomes → task status = 'in_progress' by default +- Test patterns used: + - Synthetic `OrchestrationLogEntry` objects for unit testing (avoids temp files/disk I/O) + - Import `OrchestrationLogEntry` type from `../../models` for type safety + - Temp dirs with `fs.mkdirSync` + `fs.writeFileSync` for integration tests + - SquadDataProvider tests verify status calculation at member-level (lines 108-109 of SquadDataProvider.ts) + - OrchestrationLogService tests verify completion detection at task extraction level (lines 270-279 of OrchestrationLogService.ts) +- Key file locations: + - `src/test/suite/squadDataProviderExtended.test.ts` — getSquadMembers() status override suite + - `src/test/suite/orchestrationLogService.test.ts` — getActiveTasks() completion signal suite + - `src/services/SquadDataProvider.ts:108` — hasTasksButNoneActive logic + - `src/services/OrchestrationLogService.ts:270-279` — isCompletionSignal() integration +- Compilation clean (`npx tsc --noEmit`); all 1003 tests passing (8 new + 995 existing) + diff --git a/src/test/suite/orchestrationLogService.test.ts b/src/test/suite/orchestrationLogService.test.ts index 018e717..99e8c9b 100644 --- a/src/test/suite/orchestrationLogService.test.ts +++ b/src/test/suite/orchestrationLogService.test.ts @@ -8,6 +8,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as fs from 'fs'; import { OrchestrationLogService } from '../../services/OrchestrationLogService'; +import { OrchestrationLogEntry } from '../../models'; const TEST_FIXTURES_ROOT = path.resolve(__dirname, '../../../test-fixtures'); @@ -131,6 +132,90 @@ suite('OrchestrationLogService — Table Format Extraction', () => { }); }); + suite('getActiveTasks() — relatedIssues completion signal detection (issue #63)', () => { + test('relatedIssues with "Closed #NN" outcome → task status = completed', () => { + const entries: OrchestrationLogEntry[] = [ + { + timestamp: '2026-03-01T00:00:00Z', + date: '2026-03-01', + topic: 'fixed-bug', + participants: ['Alice'], + summary: 'Fixed authentication bug', + relatedIssues: ['#42'], + outcomes: ['Closed #42 — fixed the authentication bug'], + }, + ]; + + const tasks = service.getActiveTasks(entries); + + const task42 = tasks.find(t => t.id === '42'); + assert.ok(task42, 'Should find task #42'); + assert.strictEqual(task42!.status, 'completed', 'Task should be completed due to "Closed #42" signal'); + assert.ok(task42!.completedAt, 'Task should have completedAt timestamp'); + }); + + test('relatedIssues with "Fixed #NN" outcome → task status = completed', () => { + const entries: OrchestrationLogEntry[] = [ + { + timestamp: '2026-03-02T00:00:00Z', + date: '2026-03-02', + topic: 'bug-fix', + participants: ['Bob'], + summary: 'Bug fix', + relatedIssues: ['#99'], + outcomes: ['Resolved #99 in latest commit'], + }, + ]; + + const tasks = service.getActiveTasks(entries); + + const task99 = tasks.find(t => t.id === '99'); + assert.ok(task99, 'Should find task #99'); + assert.strictEqual(task99!.status, 'completed', 'Task should be completed due to "Resolved" signal'); + }); + + test('relatedIssues with "Working on #NN" outcome → task status = in_progress', () => { + const entries: OrchestrationLogEntry[] = [ + { + timestamp: '2026-03-03T00:00:00Z', + date: '2026-03-03', + topic: 'active-work', + participants: ['Carol'], + summary: 'Active work', + relatedIssues: ['#77'], + outcomes: ['Working on #77 — implementing new feature'], + }, + ]; + + const tasks = service.getActiveTasks(entries); + + const task77 = tasks.find(t => t.id === '77'); + assert.ok(task77, 'Should find task #77'); + assert.strictEqual(task77!.status, 'in_progress', 'Task should be in_progress without completion signal'); + assert.strictEqual(task77!.completedAt, undefined, 'Task should not have completedAt timestamp'); + }); + + test('relatedIssues without matching outcomes → task status = in_progress', () => { + const entries: OrchestrationLogEntry[] = [ + { + timestamp: '2026-03-04T00:00:00Z', + date: '2026-03-04', + topic: 'start-feature', + participants: ['Dave'], + summary: 'Started investigating the issue', + relatedIssues: ['#55'], + }, + ]; + + const tasks = service.getActiveTasks(entries); + + const task55 = tasks.find(t => t.id === '55'); + assert.ok(task55, 'Should find task #55'); + assert.strictEqual(task55!.status, 'in_progress', 'Task should be in_progress by default'); + assert.strictEqual(task55!.completedAt, undefined, 'Task should not have completedAt timestamp'); + }); + }); + suite('parseLogFile() — table-format integration', () => { let tempDir: string; diff --git a/src/test/suite/squadDataProviderExtended.test.ts b/src/test/suite/squadDataProviderExtended.test.ts index 9174171..c2eade5 100644 --- a/src/test/suite/squadDataProviderExtended.test.ts +++ b/src/test/suite/squadDataProviderExtended.test.ts @@ -153,7 +153,7 @@ suite('SquadDataProvider — Extended Coverage', () => { // ─── getSquadMembers() — Status Override Logic ────────────────────── - suite('getSquadMembers() — working-to-idle override', () => { + suite('getSquadMembers() — working-to-idle override (issue #63)', () => { test('member shown as idle when log says working but no in-progress tasks', async () => { // Create team.md fs.writeFileSync(path.join(tempDir, '.ai-team', 'team.md'), [ @@ -190,5 +190,96 @@ suite('SquadDataProvider — Extended Coverage', () => { // Alice should be idle because no in-progress tasks exist assert.strictEqual(alice!.status, 'idle', 'Should be idle when no in-progress tasks'); }); + + test('member stays working when log says working and has no tasks at all (Copilot Chat)', async () => { + fs.writeFileSync(path.join(tempDir, '.ai-team', 'team.md'), [ + '# Team', + '', + '## Members', + '', + '| Name | Role | Charter | Status |', + '|------|------|---------|--------|', + '| Bob | Dev | `.ai-team/agents/bob/charter.md` | ✅ Active |', + ].join('\n')); + + // Create log that marks Bob as participant but NO tasks or issue refs at all + const logDir = path.join(tempDir, '.ai-team', 'orchestration-log'); + fs.mkdirSync(logDir, { recursive: true }); + fs.writeFileSync(path.join(logDir, '2026-03-01-chat.md'), [ + '# Copilot Chat Session', + '', + '**Participants:** Bob', + '', + '## Summary', + 'Answered questions about the codebase.', + ].join('\n')); + + const provider = new SquadDataProvider(tempDir, '.ai-team'); + const members = await provider.getSquadMembers(); + + const bob = members.find(m => m.name === 'Bob'); + assert.ok(bob, 'Should find Bob'); + // Bob has no tasks at all, so should stay "working" (Copilot Chat scenario) + assert.strictEqual(bob!.status, 'working', 'Should stay working when no tasks exist'); + }); + + test('member stays working when log says working and has in-progress tasks', async () => { + fs.writeFileSync(path.join(tempDir, '.ai-team', 'team.md'), [ + '# Team', + '', + '## Members', + '', + '| Name | Role | Charter | Status |', + '|------|------|---------|--------|', + '| Carol | Dev | `.ai-team/agents/carol/charter.md` | ✅ Active |', + ].join('\n')); + + // Create log that marks Carol as participant with in-progress task + const logDir = path.join(tempDir, '.ai-team', 'orchestration-log'); + fs.mkdirSync(logDir, { recursive: true }); + fs.writeFileSync(path.join(logDir, '2026-03-01-in-progress.md'), [ + '# Active Work', + '', + '**Participants:** Carol', + '', + '## Related Issues', + '- #99', + '', + '## Summary', + 'Working on new feature.', + ].join('\n')); + + const provider = new SquadDataProvider(tempDir, '.ai-team'); + const members = await provider.getSquadMembers(); + + const carol = members.find(m => m.name === 'Carol'); + assert.ok(carol, 'Should find Carol'); + // Carol has an in-progress task, should stay "working" + assert.strictEqual(carol!.status, 'working', 'Should stay working with in-progress tasks'); + }); + + test('member not in logs shows as idle', async () => { + fs.writeFileSync(path.join(tempDir, '.ai-team', 'team.md'), [ + '# Team', + '', + '## Members', + '', + '| Name | Role | Charter | Status |', + '|------|------|---------|--------|', + '| Dave | Dev | `.ai-team/agents/dave/charter.md` | ✅ Active |', + ].join('\n')); + + // Create log directory but no entries for Dave + const logDir = path.join(tempDir, '.ai-team', 'orchestration-log'); + fs.mkdirSync(logDir, { recursive: true }); + + const provider = new SquadDataProvider(tempDir, '.ai-team'); + const members = await provider.getSquadMembers(); + + const dave = members.find(m => m.name === 'Dave'); + assert.ok(dave, 'Should find Dave'); + // Dave not in any logs, should be "idle" + assert.strictEqual(dave!.status, 'idle', 'Should be idle when not in logs'); + }); }); }); From cb9cfdd233c8990fd1b271258a4b73020764d772 Mon Sep 17 00:00:00 2001 From: "Jeffrey T. Fritz csharpfritz" Date: Sat, 21 Feb 2026 20:26:24 -0500 Subject: [PATCH 5/7] docs: add decision inbox entry for issue #63 tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../decisions/inbox/basher-issue-63-tests.md | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .ai-team/decisions/inbox/basher-issue-63-tests.md diff --git a/.ai-team/decisions/inbox/basher-issue-63-tests.md b/.ai-team/decisions/inbox/basher-issue-63-tests.md new file mode 100644 index 0000000..6cdc330 --- /dev/null +++ b/.ai-team/decisions/inbox/basher-issue-63-tests.md @@ -0,0 +1,36 @@ +# Test Strategy for Status Override Logic + +**Date:** 2026-02-22 +**Author:** Basher +**Issue:** #63 + +## Decision + +For testing status override logic and completion signal detection, use synthetic `OrchestrationLogEntry` objects instead of temp files on disk. + +## Context + +Issue #63 involved two behavior changes: +1. SquadDataProvider working-to-idle override now distinguishes between "no tasks at all" (Copilot Chat, stay working) vs "tasks but none active" (show idle) +2. OrchestrationLogService now checks outcomes for completion signals when extracting tasks from relatedIssues + +Both behaviors required comprehensive test coverage. + +## Rationale + +- **Synthetic entries are faster** — No disk I/O, temp directory cleanup, or async file operations +- **Synthetic entries are clearer** — Test data is inline with assertions, easier to read and maintain +- **Follows existing patterns** — `orchestrationTaskPipeline.test.ts` already uses synthetic entries for unit testing getActiveTasks() +- **Integration tests still use disk** — Where file parsing is the behavior under test (e.g., parseLogFile), we still use temp fixtures + +## Implementation + +- Import `OrchestrationLogEntry` type from `../../models` +- Construct minimal entry objects with required fields: `timestamp`, `date`, `topic`, `participants`, `summary` +- Add optional fields as needed: `relatedIssues`, `outcomes` +- SquadDataProvider tests still use temp directories because they test the full member resolution flow including team.md parsing + +## When to Use Each Approach + +- **Synthetic entries:** Unit testing task extraction, member states, completion signals +- **Temp files:** Integration testing file parsing, directory scanning, multi-file workflows From ac122c86eb358d36ec01201a4e97bb38c79fe5f5 Mon Sep 17 00:00:00 2001 From: "Jeffrey T. Fritz csharpfritz" Date: Sat, 21 Feb 2026 20:27:02 -0500 Subject: [PATCH 6/7] chore: remove .ai-team files from PR (protected branch policy) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .ai-team/agents/basher/history.md | 295 ------------------ .ai-team/agents/rusty/history.md | 153 --------- .../decisions/inbox/basher-issue-63-tests.md | 36 --- .../decisions/inbox/rusty-idle-status-fix.md | 4 - 4 files changed, 488 deletions(-) delete mode 100644 .ai-team/agents/basher/history.md delete mode 100644 .ai-team/agents/rusty/history.md delete mode 100644 .ai-team/decisions/inbox/basher-issue-63-tests.md delete mode 100644 .ai-team/decisions/inbox/rusty-idle-status-fix.md diff --git a/.ai-team/agents/basher/history.md b/.ai-team/agents/basher/history.md deleted file mode 100644 index fe3f8ea..0000000 --- a/.ai-team/agents/basher/history.md +++ /dev/null @@ -1,295 +0,0 @@ -# Project Context - -- **Owner:** Jeffrey T. Fritz (csharpfritz@users.noreply.github.com) -- **Project:** VS Code extension for visualizing Squad team members and their tasks -- **Stack:** TypeScript, VS Code Extension API, potentially GitHub Copilot integration -- **Created:** 2026-02-13 - -## Learnings Summary - -### Test Infrastructure & Patterns (v0.1v0.2) -- Mocha TDD style with temp directory cleanup in est-fixtures/temp-* -- VS Code API stubs use s any casts; private method tests access via (service as any).methodName() -- Acceptance criteria traceability (AC-1 through AC-6) for E2E validation -- TestableWebviewRenderer pattern for HTML validation without live webview panels - -### Skill Import Tests (2026-02-15) -- Comprehensive test suite for skill import: 55+ test cases -- parseInstalledSkill() 20+ tests (YAML frontmatter, heading extraction, fallbacks) -- getInstalledSkills() 15+ tests (directory reading, malformed handling, skipping) -- Deduplication logic 6 tests (awesome-copilot preferred, case-insensitive matching) -- Edge cases: CRLF line endings, unicode characters, empty SKILL.md files - -### DecisionService Tests (2026-02-15) -- Complex parsing with mixed heading levels (## vs ###), dual date extraction -- 35+ tests for parseDecisionsMd() covering: - - ## headings as always-potential decisions - - ### headings with date prefixes (YYYY-MM-DD:) - - Date range extraction, subsection filtering - - Multiple date formats and fallback logic - -### Key File Locations -- Test suites: src/test/suite/skillImport.test.ts, src/test/suite/decisionService.test.ts -- Skills service: src/services/SkillCatalogService.ts -- Tree providers: src/views/SquadTreeProvider.ts - -### 2026-02-15 Team Updates - User directive releases require explicit human approval before tagging/publishing decided by Jeffrey T. Fritz - v0.6.0 Sprint Plan (QA skill flow, enable Add Skill button, dashboard polish, backlog audit) decided by Danny - Dashboard decisions sort order decisions list on dashboard should be sorted most-recent first decided by Jeffrey T. Fritz - Add Skill Error Handling network failures now throw exceptions for better UX instead of silent empty arrays decided by Rusty - Backlog Audit and Issue Cleanup issues #27, #37, #38 closed; backlog triaged for v0.6.0 sprint decided by Danny - -### H1 Decision Format Tests (2026-02-16) -- Added 7 tests for new `# Decision: Title` (H1) format in `parseDecisionsMd()`: - 1. Basic H1 decision parsing — title, date, author extraction - 2. Multiple H1 decisions in one file - 3. H1 with `## Context`/`## Decision`/`## Rationale` subsections — only parent H1 counts - 4. Mixed H1 + H2 decisions — both formats parsed correctly - 5. Plain H1 without "Decision:" prefix — must NOT be treated as a decision (avoids false positives) - 6. `**Issue:** #78` metadata — silently ignored, no crash or pollution - 7. Content capture — `content` field includes full section with subsections -- Tests are written test-first for Linus's upcoming parser change; they will FAIL until the H1 handling is added to `parseDecisionsMd()` - -### upgradeSquadCommand & hasTeam Context Key Tests (2026-02-16) -- New test file: `src/test/suite/upgradeSquadCommand.test.ts` — 5 tests total -- **Registration tests (2):** - 1. `registerUpgradeSquadCommand` returns a `Disposable` — mock context `{ subscriptions: [] }` pattern from addMemberCommand tests - 2. `upgradeSquad` command is registered — `this.skip()` guard pattern (extension/isActive/workspace check) from viewCharterCommand tests -- **hasTeam context key detection (3):** - 3. `hasTeam` true when `.ai-team/team.md` exists — temp dir with file, verifies `fs.existsSync` logic - 4. `hasTeam` false when `.ai-team/` absent — empty temp dir - 5. `hasTeam` false when `.ai-team/` exists but `team.md` missing — edge case, directory without file -- Uses `setup()`/`teardown()` with `test-fixtures/temp-upgrade-squad` cleanup -- Rusty's `upgradeSquadCommand.ts` was already in place — tests compile clean - -### Test Hardening Sprint (2026-02-16, Issue #54) -- Added **125 new tests** across 11 new test files (658 → 783 passing) -- **New test files created:** - - `fileWatcherService.test.ts` — 17 tests: constructor, isWatching, onFileChange callbacks, registerCacheInvalidator, start/stop, dispose idempotency, internal queueEvent/flush guards - - `decisionServiceFiles.test.ts` — 21 tests: parseDecisionFile() with H1/H2/H3 headings, date extraction, title prefix stripping, author metadata, fallback dates; scanDirectory() recursive traversal, non-.md filtering; getDecisions() integration combining decisions.md + decisions/ directory - - `squadDataProviderExtended.test.ts` — 10 tests: getWorkspaceRoot(), getDecisions() with caching, refresh invalidation, getLogEntries/getTasks caching, placeholder member in getWorkDetails(), working-to-idle override logic - - `initSquadCommand.test.ts` — 2 tests: Disposable return, command registration with this.skip() guard - - `addSkillCommand.test.ts` — 2 tests: Disposable return, command registration with this.skip() guard - - `skillsTreeProvider.test.ts` — 9 tests: getChildren() root/leaf/empty states, skill item rendering (book icon, viewSkill command, contextValue, tooltip), refresh event - - `decisionsTreeProvider.test.ts` — 10 tests: getChildren() root/leaf/empty, decision item rendering (notebook icon, openDecision command, description with date+author, tooltip), getTreeItem, refresh event - - `removeMemberEdgeCases.test.ts` — 13 tests: slug generation for simple names, mixed case, spaces, special chars, @prefix, hyphens, underscores, leading/trailing spaces, empty string, numbers - - `markdownUtilsEdgeCases.test.ts` — 12 tests: adjacent links, empty display text, parentheses in text/URL, multiline, image syntax preservation, query parameters, hash fragments - - `workDetailsEdgeCases.test.ts` — 17 tests: getInitials() with hyphenated/single-char/uppercase/lowercase names, renderInline() with multiple bold/code/unclosed, renderTable() empty/single-row/sparse/alignment, renderMarkdown() empty/newlines/entities, status badges, dispose idempotency - - `treeProviderSpecialMembers.test.ts` — 12 tests: sort order (regular→@copilot→scribe/ralph), special icons (edit/eye/robot), collapsibility (infra=None, @copilot=Collapsed), viewCharter command exclusion for @copilot, status badges (⚡/💤), markdown link stripping in names -- **Patterns established:** - - Command registration tests use `this.skip()` guard with `extension/isActive/workspace` triple-check (from viewCharterCommand pattern) - - DecisionsTreeProvider.getChildren() is async — must be awaited (unlike the sync getDecisionItems() it calls internally) - - Private method tests use `(service as any).methodName.bind(service)` pattern - - Temp directories use `test-fixtures/temp-{name}-${Date.now()}` with teardown cleanup - -### Init Wizard Tests (2026-02-16) -- New test file: `src/test/suite/initSquadWizard.test.ts` — 7 test cases -- Written test-first for Rusty's upcoming native wizard rewrite of initSquadCommand -- **Test cases:** - 1. Welcome view configuration — verifies viewsWelcome entries for all three panels (squadTeam, squadSkills, squadDecisions) with "Form your Squad" button and `!squadui.hasTeam` condition - 2. Command registration (package.json) — verifies `squadui.initSquad` declared with category "Squad" - 3. Command registration (live) — `this.skip()` guard pattern - 4. Universe list completeness — checks for exported `UNIVERSE_OPTIONS`/`universeOptions`/`UNIVERSES` array; graceful no-op if not yet exported - 5. Cancellation at universe step — stubs `showQuickPick` → undefined, asserts no terminal and no InputBox - 6. Cancellation at mission step — stubs `showQuickPick` → selection, `showInputBox` → undefined, asserts no terminal - 7. Empty mission validation — captures `validateInput` function from `showInputBox` options, asserts empty string returns error message -- Tests 3, 5, 6, 7 use `this.skip()` guard (extension/isActive/workspace) — pending until live workspace available -- Tests 1, 2 pass now (package.json already configured); test 4 passes with graceful fallback -- Compilation clean (`npx tsc --noEmit`), full suite 788 passing - -### SquadVersionService Tests (2026-02-16) -- New test file: `src/test/suite/squadVersionService.test.ts` — 32 tests total -- **isNewer() semver comparison (11 tests):** - - Identical versions → false; newer major/minor/patch → true; current ahead → false - - Different segment counts (1.0 vs 1.0.0), single-segment, four-segment, 0.x versions -- **normalizeVersion() v-prefix stripping (5 tests):** - - Lowercase/uppercase v stripped; no-op without prefix; middle-of-string v preserved - - Leading whitespace prevents `^v` match (trim happens after replace) -- **Caching behavior (3 tests):** - - Second `checkForUpgrade()` returns cached result without re-fetching - - Caches `available: false` results too - - `resetCache()` forces next `checkForUpgrade()` to re-fetch -- **forceCheck() bypass (2 tests):** - - Always re-fetches regardless of cache state - - Updates cached result for subsequent `checkForUpgrade()` calls -- **Error handling (7 tests):** - - GitHub API fails → `{ available: false }`; CLI not installed → same - - Both fail → same; getLatestVersion throws → same; getInstalledVersion throws → same - - Both throw → same; partial failure includes available version info -- **UpgradeCheckResult shape (2 tests):** - - Includes currentVersion/latestVersion when upgrade available and when not -- **package.json validation (2 tests):** - - `squadui.checkForUpdates` command declared with category "Squad" - - Upgrade button when-clause includes `squadui.upgradeAvailable` -- Test approach: stub private methods via `(service as any).methodName` for network/exec isolation; test pure functions (isNewer, normalizeVersion) directly -- Compilation clean, full suite 820 passing (32 new + 788 existing) - -### Team Display Resilience Tests (2026-02-16) -- New test file: `src/test/suite/teamDisplayResilience.test.ts` — 12 tests across 8 suites -- **Scenarios covered:** - 1. Happy path: getSquadMembers() with valid Members table — 3 members, correct roles - 2. Partial write: team.md exists but no Members section — returns empty (race condition during init) - 3. Retry on empty roster: mock TeamMdService returns empty first, populated second — validates retry mechanism Rusty is adding - 4. Log-participant fallback: no team.md, derives members from orchestration log participants with generic "Squad Member" role - 5. No retry when team.md missing: spy confirms parseTeamMd called exactly once when file doesn't exist (null return) - 6. FileWatcherService glob pattern: verifies WATCH_PATTERN includes `.ai-team` and `*.md` - 7. TeamTreeProvider empty members: getChildren() returns empty array without crash when no members - 8. Cache invalidation: refresh() clears all 4 cache fields, re-read from disk returns updated data -- Uses `(provider as any).teamMdService.parseTeamMd` override pattern to simulate race condition without flaky timing -- Retry delay set to 0ms compatibility — tests are deterministic, no timing assertions -- Temp dirs use `test-fixtures/temp-resilience-${Date.now()}` with teardown cleanup -- Compilation clean (`npx tsc --noEmit`); test execution deferred until Rusty's retry changes land - Team update (2026-02-16): Native Init Wizard squad init replaced with native VS Code wizard: QuickPick (15 universes) InputBox (mission description) Terminal with --universe and --mission flags. viewsWelcome now covers all three panels (squadTeam, squadSkills, squadDecisions). Upgrade button only in Team toolbar. API signatures unchanged existing tests pass. decided by Rusty - Team update (2026-02-16): Conditional Upgrade Button via Version Check new context key squadui.upgradeAvailable set when SquadVersionService confirms newer release. Upgrade button gated on squadui.hasTeam && squadui.upgradeAvailable. Manual re-check available via squadui.checkForUpdates command. Post-upgrade flow resets context and re-checks. decided by Rusty - Team update (2026-02-16): Team Display Resilience Race Condition Handling SquadDataProvider.getSquadMembers() retries once (after configurable delay) when team.md exists but roster is empty. Delayed re-refresh 2s after init. Tree view shows "Loading team..." when hasTeam is true but members empty. Retry delay is constructor-configurable for testability. decided by Rusty - -### Agents Folder Discovery Tests (2026-02-17) -- New test file: `src/test/suite/agentsFolderDiscovery.test.ts` — 9 test cases -- Written test-first for Linus's `discoverMembersFromAgentsFolder()` method in SquadDataProvider -- **Test cases:** - 1. Agents folder with charter files — discovers members with roles extracted from `## Identity` → `- **Role:**` line - 2. Agents folder without charter files — discovers members with default "Squad Member" role, capitalized folder names - 3. Skips `_alumni` and `scribe` directories — only non-excluded dirs produce members - 4. Empty agents folder — returns empty array (falls through to log participant fallback) - 5. No agents folder — `.ai-team/` exists but no `agents/` subdirectory, returns empty array gracefully - 6. team.md takes priority — when team.md has valid Members table, agents folder is not consulted - 7. Role extraction: `- **Role:** Backend Dev` format → "Backend Dev" - 8. Role extraction: charter with no Identity section → default role - 9. Role extraction: charter with Identity but no Role line → default role -- All tests use empty team.md (no Members table) to force the agents folder fallback path, except test 6 which validates priority -- Temp dirs use `test-fixtures/temp-agents-${Date.now()}` with teardown cleanup -- Tests compile clean (`npx tsc --noEmit`); execution deferred until Linus's implementation lands -- Follows established patterns: Mocha TDD, `SquadDataProvider(dir, 0)` for zero retry delay, `fs.mkdirSync/writeFileSync` for fixtures - -### Session Log Isolation Tests (2026-02-17) -- New test file: `src/test/suite/sessionLogIsolation.test.ts` — 13 tests total -- Validates that session logs in `log/` do not pollute task status or member working state -- **Two test fixtures created:** - 1. `test-fixtures/sensei-scenario/` — session logs with participant names (bold-name fallback parsing) - 2. `test-fixtures/session-log-issues/` — session logs with issue references (#21, #22, #28) -- **Key learnings:** - - Test fixtures must include both `log/` and `orchestration-log/` directories to validate isolation - - `parseOrchestrationLogs()` reads ONLY from `orchestration-log/` - - `parseAllLogs()` reads from BOTH directories (still used for display) - - Member "working" status requires BOTH: (1) appearing in most recent orchestration log, AND (2) having in_progress tasks - - Completed prose tasks don't trigger "working" status (by design — lines 94-96 of SquadDataProvider) -- **Test suites:** - 1. Sensei scenario (5 tests) — members from session logs only are idle; orchestration log parsing is isolated - 2. Issue reference scenario (4 tests) — issues from session log don't create tasks; Rusty working, Livingston idle - 3. Parser isolation (3 tests) — `discoverOrchestrationLogFiles()` vs `discoverLogFiles()` behavior -- Tests compile clean (`npx tsc --noEmit`); all 881 tests passing -- Validates the fix for the bug where session logs were being read for task/member status derivation - - -📌 Team update (2026-02-17): Always use normalizeEol() for markdown parsing to ensure cross-platform compatibility — decided by Copilot (Jeffrey T. Fritz) -### Coding Agent Section Parsing Tests (2026-02-17) -- New test suite: `parseContent() — Coding Agent section` with 5 tests for Linus's `## Coding Agent` section parsing -- **Test scenarios:** - 1. `@copilot` parsed from Coding Agent table with 🤖 status → idle - 2. Member count includes entries from both `## Members` AND `## Coding Agent` sections - 3. Coding Agent section works standalone (no Members section needed) - 4. Empty Coding Agent table (header only) doesn't add phantom entries - 5. No deduplication — if member appears in both sections, both entries returned -- Added 6th test in edge cases suite: Ralph with 🔄 Monitor status maps to idle (validates status badge logic) -- **Why these tests matter:** The `## Coding Agent` section lets @copilot appear as a squad member for routing/display purposes. Without these tests, regressions could break @copilot visibility in the team roster or cause duplicate/missing entries when sections overlap. -- Linus's implementation: `parseMembers()` now calls `extractSection('Coding Agent')` after parsing Members/Roster, uses same `parseMarkdownTable()` and `parseTableRow()` logic, no special handling needed. -- All 6 tests passing (872 total passing); compilation clean with `npx tsc --noEmit` - -### Active-Work Marker Detection Tests (2026-02-18) -- New test file: `src/test/suite/activeWorkMarkers.test.ts` — 13 test cases -- Written test-first for Linus's `detectActiveMarkers()` method and `getSquadMembers()` integration -- **Test cases:** - 1. No active-work directory — backward compatible, members stay idle - 2. Empty active-work directory — no status overrides - 3. Active marker for known member — status overridden to 'working' - 4. Marker overrides log-based idle — member idle from logs but marker makes them 'working' - 5. Stale marker (mtime > 5 min) — ignored, member stays idle - 6. Fresh marker (mtime < 5 min) — respected, member becomes 'working' - 7. Non-.md files (.gitkeep, .txt, .yaml) — ignored by marker detection - 8. Multiple markers — multiple members set to 'working' simultaneously - 9. Marker for unknown member — doesn't crash, doesn't affect roster members - 10. Case-insensitive slug matching — lowercase 'linus.md' matches 'Linus' - 11. Boundary: marker just past 5-min threshold — treated as stale - 12. Boundary: marker just under 5-min threshold — treated as fresh - 13. Marker + log-based working — no conflict, member stays 'working' -- Uses `fs.utimesSync()` to simulate stale markers without waiting -- Temp dirs use `test-fixtures/temp-active-markers-${Date.now()}` with teardown cleanup -- Tests compile clean (`npx tsc --noEmit`); execution deferred until Linus's implementation lands -- Key pattern: `SquadDataProvider(dir, '.ai-team', 0)` with zero retry delay for test speed - -### Velocity allClosedIssues Tests (2026-02-18) -- Added 5 tests to `src/test/suite/dashboardDataBuilder.test.ts` inside existing `'Velocity Timeline (via buildDashboardData)'` suite -- Tests cover new 8th `allClosedIssues?: GitHubIssue[]` parameter on `buildDashboardData()` -- **Test cases:** - 1. Unmatched closed issues in allClosedIssues appear in velocity timeline - 2. No double-counting when same issue in both closedIssues map and allClosedIssues array - 3. allClosedIssues undefined falls back to member map (backward compat) - 4. Empty allClosedIssues array produces all-zero closed-issue counts - 5. Old issues (45 days ago) outside 30-day window excluded from allClosedIssues -- Implementation note: `buildVelocityTimeline` uses `if (allClosedIssues) / else if (closedIssues)` pattern — when allClosedIssues is provided, closedIssues member map is entirely skipped (not merged). Dedup is via `seenIssues` Set on `issue.number`. -- Uses existing `makeIssue()` helper, `MemberIssueMap` import added to test file -- Compilation clean (`npx tsc --noEmit`); tests align with Linus's implementation already on disk - - Team update (2026-02-18): Velocity chart now counts ALL closed GitHub issues, not just member-matched decided by Linus - -### Velocity Session Log Counting Tests (2026-02-18) -- Added 5 new tests across 2 new suite blocks appended to `src/test/suite/dashboardDataBuilder.test.ts` -- **Suite 1: `Velocity Tasks — Session Log Counting` (4 unit tests):** - 1. `velocityTasks` (9th arg) routes all-log tasks to velocity timeline — 2 completed today via velocityTasks, orchestration task not counted - 2. `velocityTasks` undefined falls back to `tasks` parameter — backward compat, existing behavior preserved - 3. `velocityTasks` accepts session-log-derived task IDs (e.g., `2026-02-18-rocket` prose-derived format) not present in `tasks` - 4. Swimlanes still use orchestration-only `tasks` even when `velocityTasks` is provided — Danny gets orch-1, Linus gets nothing -- **Suite 2: `getVelocityTasks() — SquadDataProvider integration` (1 integration test):** - 5. Uses `test-fixtures/session-log-issues` fixture. `getTasks()` returns only #8 (orchestration-log). `getVelocityTasks()` returns superset including session log issues (#21, #22, #28). -- Key pattern: `buildDashboardData(logEntries, members, tasks, decisions, openIssues, closedIssues, milestoneBurndowns, allClosedIssues, velocityTasks)` — 9 params total, velocityTasks is the 9th -- Line 37 in DashboardDataBuilder.ts: `velocityTasks ?? tasks` feeds velocity; line 41: `tasks` feeds swimlanes (no velocityTasks involvement) -- Compilation clean (`npx tsc --noEmit`); Linus's `velocityTasks` param and `getVelocityTasks()` already on disk - -### Error Handling Hardening Tests (2026-02-18) -- New test file: `src/test/suite/decisionServiceEdgeCases.test.ts` — 9 tests total -- **DecisionService — scanDirectory() error handling (1 test):** - 1. Non-existent directory returns empty array, no throw — validates try/catch around `readdirSync` -- **DecisionService — parseDecisionFile() error handling (2 tests):** - 2. Non-existent file path returns null — validates outer try/catch - 3. Directory path instead of file returns null — `readFileSync` on directory triggers catch -- **DecisionService — getDecisions() missing resources (5 tests):** - 4. decisions.md exists but decisions/ directory does not — only markdown decisions returned - 5. Neither decisions.md nor decisions/ directory exists — returns empty array - 6. `.ai-team/` does not exist at all — returns empty array - 7. Empty decisions.md (file exists, no content) — returns empty array - 8. Whitespace-only decisions.md — returns empty array -- **DecisionsTreeProvider — error handling (1 test):** - 9. `getDecisionItems()` returns empty array when `decisionService.getDecisions()` throws — stubs service with throwing mock, validates try/catch guard -- Test patterns for error handling: - - Replace service internals via `(provider as any).decisionService = { getDecisions: () => { throw ... } }` for throw simulation - - Use `assert.doesNotThrow()` to verify graceful error handling in `scanDirectory()` - - Private method tests via `(service as any).methodName()` pattern (established convention) - - Temp dir cleanup in `finally` block for non-suite-scoped tests -- Compilation clean (`npx tsc --noEmit`); all 959 tests passing (9 new + 950 existing) - -### Issue #63 Working-to-Idle and Completion Signal Tests (2026-02-22) -- Added 8 new tests across 2 test suites to validate issue #63 fix -- **SquadDataProvider working-to-idle tests (4 tests in `squadDataProviderExtended.test.ts`):** - 1. Member with completed tasks only shows as idle (not working) — validates new `hasTasksButNoneActive` logic - 2. Member with NO tasks at all stays working (Copilot Chat scenario) — ensures log status preserved when tasks array is empty - 3. Member with in-progress tasks stays working — baseline behavior preserved - 4. Member not in logs shows as idle — default state when absent from orchestration logs -- **OrchestrationLogService completion signal tests (4 tests in `orchestrationLogService.test.ts`):** - 1. relatedIssues with "Closed #NN" outcome → task status = 'completed' with completedAt timestamp - 2. relatedIssues with "Resolved #NN" outcome → task status = 'completed' (validates isCompletionSignal patterns) - 3. relatedIssues with "Working on #NN" outcome → task status = 'in_progress' without completedAt - 4. relatedIssues without matching outcomes → task status = 'in_progress' by default -- Test patterns used: - - Synthetic `OrchestrationLogEntry` objects for unit testing (avoids temp files/disk I/O) - - Import `OrchestrationLogEntry` type from `../../models` for type safety - - Temp dirs with `fs.mkdirSync` + `fs.writeFileSync` for integration tests - - SquadDataProvider tests verify status calculation at member-level (lines 108-109 of SquadDataProvider.ts) - - OrchestrationLogService tests verify completion detection at task extraction level (lines 270-279 of OrchestrationLogService.ts) -- Key file locations: - - `src/test/suite/squadDataProviderExtended.test.ts` — getSquadMembers() status override suite - - `src/test/suite/orchestrationLogService.test.ts` — getActiveTasks() completion signal suite - - `src/services/SquadDataProvider.ts:108` — hasTasksButNoneActive logic - - `src/services/OrchestrationLogService.ts:270-279` — isCompletionSignal() integration -- Compilation clean (`npx tsc --noEmit`); all 1003 tests passing (8 new + 995 existing) - diff --git a/.ai-team/agents/rusty/history.md b/.ai-team/agents/rusty/history.md deleted file mode 100644 index e53cfaf..0000000 --- a/.ai-team/agents/rusty/history.md +++ /dev/null @@ -1,153 +0,0 @@ -# Project Context - -- **Owner:** Jeffrey T. Fritz (csharpfritz@users.noreply.github.com) -- **Project:** VS Code extension for visualizing Squad team members and their tasks -- **Stack:** TypeScript, VS Code Extension API, potentially GitHub Copilot integration -- **Created:** 2026-02-13 - -## Learnings Summary - -### Dashboard Enhancements (2026-02-15) -- **Decisions null-safety fix:** Added null-coalescing (d.content || '').toLowerCase() and (d.author || '').toLowerCase() to prevent TypeError crashes -- **Recent Activity in sidebar:** Collapsible section showing 10 most recent orchestration log entries; topic truncated to 60 chars, date as description, notebook icon; command squadui.openLogEntry hidden from palette -- **Recent Sessions in Activity tab:** Panel below swimlanes, last 10 log entries as clickable cards; DashboardData.activity extended with ecentLogs field -- **Activity card click handler:** Searches both .ai-team/log/ and .ai-team/orchestration-log/ directories for matching file - -### Add Skill Feature QA & Re-enable (2026-02-15) -- **Error handling improved:** Changed service layer to throw exceptions on network failures instead of silent empty arrays -- **Feature re-enabled:** Removed when: false from commandPalette, added Add Skill button to Skills panel toolbar -- **Implementation quality:** Multi-step QuickPick flow solid, cancellation handling works at every step, loading indicators use withProgress, deduplication logic works - -### Skill Catalog Bug Fixes (2026-02-15) -1. **awesome-copilot URL 404:** Repo moved from radygaster/awesome-copilot to github/awesome-copilot; updated in etchAwesomeCopilot() -2. **skills.sh parser garbage:** Rewrote parseSkillsShHtml() to match actual leaderboard pattern ( with

and

) to prevent nav/logo/tab label noise -3. **Search crash on empty descriptions:** Added null-coalescing in searchSkills() filter - -### Sidebar Label Fixes (2026-02-15) -- **Skill prefix stripping:** parseInstalledSkill() strips "Skill: " prefix (case-insensitive) from heading names -- **Skill click error fix:** Changed SkillsTreeProvider.getSkillItems() arguments from [skill.name] [skill.slug] to pass directory name instead of display name - -### Init Redesign (2026-02-15) - Init redesign now absorbs issue #26 (universe selector) into native VS Code init flow. Universe selection becomes step 1 of init wizard instead of standalone command decided by Danny - -### 2026-02-15 Team Updates - User directive releases require explicit human approval before tagging/publishing decided by Jeffrey T. Fritz - Dashboard Chart & Decisions Rendering Fixes (canvas colors, axis labels, empty state) decided by Rusty - Dashboard decisions sort order decisions list on dashboard should be sorted most-recent first decided by Jeffrey T. Fritz - Add Skill Error Handling network failures now throw exceptions for better UX instead of silent empty arrays decided by Rusty - Backlog Audit and Issue Cleanup issues #27, #37, #38 closed; backlog triaged for v0.6.0 sprint decided by Danny - Markdown link handling utility separates display text extraction (for tree view) from HTML rendering (for dashboard webviews) decided by Rusty - -### File Watcher Broadening & Agent Mode Chat (2026-02-15) -- **FileWatcherService WATCH_PATTERN:** Changed from `**/.ai-team/orchestration-log/**/*.md` to `**/.ai-team/**/*.md` — covers team.md, charters, decisions, skills, and orchestration logs. Debounce already in place prevents thrashing. -- **addMemberCommand chat API:** `workbench.action.chat.open` accepts `agentId` and `agentMode` fields in addition to `query` and `isPartialQuery`. Using `@squad` prefix in query text provides belt-and-suspenders targeting of the Squad chat participant. -- **Key paths:** `src/services/FileWatcherService.ts` (line 34 WATCH_PATTERN), `src/commands/addMemberCommand.ts` (lines 49–56 chat open call) - -### Init & Upgrade Features (2026-02-16) -- **Upgrade command:** Created `src/commands/upgradeSquadCommand.ts` following exact same factory pattern as `initSquadCommand.ts` — exports `registerUpgradeSquadCommand(context, onUpgradeComplete)`, opens terminal named "Squad Upgrade", runs `npx github:bradygaster/squad upgrade` -- **viewsWelcome contribution:** Added `viewsWelcome` section to `package.json` for `squadTeam` view — shows Initialize and Upgrade buttons when `!squadui.hasTeam` context key is false. VS Code renders these as clickable command links in the empty tree view. -- **Context key `squadui.hasTeam`:** Set on activation by checking `fs.existsSync(.ai-team/team.md)`. Updated to `true` in both init and upgrade `onComplete` callbacks. FileWatcher `onFileChange` handler also re-checks existence so context stays in sync with filesystem. -- **Package.json updates:** Added `squadui.upgradeSquad` command (title: "Upgrade Team", category: "Squad", icon: `$(arrow-up)`), upgrade button in Team view title bar (gated on `squadui.hasTeam`), no commandPalette restriction so it's always accessible. -- **Key pattern:** VS Code `viewsWelcome` uses `\n` for line breaks in the contents string, `[Button Text](command:id)` for command links. The `when` clause uses context keys set via `setContext` command. - -### Dashboard Canvas Chart Rendering Fix (2026-02-16) -- **Hidden tab canvas bug:** Canvas elements inside `display: none` tabs return `offsetWidth === 0`. Setting `canvas.width = canvas.offsetWidth` on hidden tabs produces a zero-width canvas — nothing renders. Charts must only be drawn when their tab is visible. -- **Fix pattern:** Removed `renderBurndownChart()` and `renderVelocityChart()` from initial page load. Both are now rendered on-demand when the user clicks their tab. Added `offsetWidth === 0` guard in both chart functions as belt-and-suspenders. -- **Duplicate listener bug:** `renderBurndownChart()` was adding a new `change` listener to the milestone `