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
177 changes: 40 additions & 137 deletions .ai-team/agents/linus/history.md
Original file line number Diff line number Diff line change
@@ -1,137 +1,40 @@
# 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

### v0.1v0.2: Core Data Pipeline Foundation
- OrchestrationLogService, TeamMdService, SquadDataProvider, FileWatcherService, GitHubIssuesService with test fixtures
- Two-tier member resolution: team.md + log overlay
- Dual-directory log discovery (orchestration-log/ + log/)
- Multi-format participant extraction (inline, table, agent-routed fields)
- Prose + issue-based task extraction with deterministic task IDs ({date}-{agent-slug})
- Flexible GitHub issue matching: labels, assignees, any-label strategies

### GitHubIssuesService Architecture
- IGitHubIssuesService interface enables graceful degradation and late binding
- Issues use $(issues) codicon with theme color tinting (green open, purple closed)
- Squad labels filtered from display to avoid redundancy
- Default matching strategy: ['labels', 'assignees'] when no config present
- Member Aliases table parsed from team.md Issue Source section
- Separate closedCache for closed issues; fetch max 50, sorted by updated_at descending

### SkillCatalogService & Log Parsing
- Fetches from awesome-copilot + skills.sh using Node's https module
- All methods swallow network errors and return empty arrays (graceful degradation)
- Deduplicates toward awesome-copilot version
- No npm dependencies

### Log Summary Extraction Priority Chain
1. ## Summary section
2. | **Outcome** | value | table field
3. Heading title after em dash
4. First prose paragraph (prevents table markdown leakage)

### OrchestrationLogService Features
- Filename regex handles both YYYY-MM-DD-topic.md and YYYY-MM-DDThhmm-topic.md formats
- Agent Routed table field fallback: | **Agent routed** | Fury (Lead) | extracts agent name, strips role suffix and pipes
- Who Worked table format parsing: xtractTableFirstColumn() helper
- Prose task extraction: "What Was Done" section highest priority, synthetic fallback per entry

### 2026-02-15 Team Updates
Issues Service Interface Contract IGitHubIssuesService decouples tree view from implementation decided by Rusty
Issue Icons & Display Filtering uses codicon with theme tinting, Squad labels filtered decided by Rusty
Release Pipeline Workflow tag-based trigger, version verification gate, VSCE_PAT secret decided by Livingston
Closed Issues Architecture separate closedCache, max 50, sorted by updated_at descending decided by Linus
SkillCatalogService Graceful Degradation swallow network errors, return empty arrays decided by Linus
Table-Format Log Summary Extraction priority chain to prevent markdown leakage decided by Linus
Default Issue Matching & Member Aliases defaults to labels+assignees, aliases in team.md decided by Linus
E2E Validation Test Strategy TestableWebviewRenderer pattern, acceptance criteria traceability decided by Basher

### H1 Decision Format Support
- Added support for `# Decision: {title}` (H1) format in `parseDecisionsMd()` — some projects (e.g. aspire-minecraft) use this instead of H2/H3
- H1 decisions use `**Date:**`, `**Author:**`, `**Issue:**` metadata lines below the heading
- Section boundary: an H1 decision runs until the next H1 heading (or EOF)
- Inner `## Context`, `## Decision`, `## Rationale` subsections are NOT treated as separate decisions — they're consumed as content of the parent H1 block
- The parser skips `i` forward to `sectionEnd` after consuming an H1 decision to prevent subsection re-parsing
- Non-decision H1 headings (e.g. `# Decisions`, `# Team Log`) are skipped — only `# Decision: ` with the prefix triggers parsing
- Existing H2/H3 parsing is completely untouched — the H1 block uses `continue` before reaching H2/H3 logic

📌 Team update (2026-02-16): Test hardening conventions established — command registration tests use triple-guard pattern (extension/isActive/workspace); tree provider tests must await getChildren(); temp directories use test-fixtures/temp-{name}-${Date.now()} with teardown; private methods accessed via (instance as any).method.bind(instance) — decided by Basher

📌 Team update (2026-02-17): Orchestration Log vs Session Log Scope — OrchestrationLogService now uses separate discoverOrchestrationLogFiles() and parseOrchestrationLogs() methods for task status derivation; session logs in log/ remain for display only (Recent Activity, log cards). Prevents false "working" indicators from old session logs. — decided by Rusty

### Agents Folder Scanning Fallback
- Added `discoverMembersFromAgentsFolder()` to SquadDataProvider as a second-level fallback in the member detection chain
- Detection order is now: team.md Members/Roster table → agents folder scan → orchestration log participants
- Scans `.ai-team/agents/` subdirectories, skipping `_alumni` and `scribe`
- Reads `charter.md` from each agent folder to extract role via `- **Role:** {role}` regex
- Falls back to "Squad Member" default role if no charter or no Role line found
- Folder names are capitalized for display (e.g., `danny` → `Danny`)
- Method is self-contained and handles missing/unreadable agents directory gracefully (returns empty array)
- Pre-existing test suite at `agentsFolderDiscovery.test.ts` validates all edge cases
📌 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 (2026-02-18)
- Extended `TeamMdService.parseMembers()` to parse the `## Coding Agent` section in addition to `## Members`/`## Roster`
- Bug fix: @copilot was missing from member list because it lives in its own table under `## Coding Agent`
- The `extractSection()` and `parseMarkdownTable()` methods already handle this format — just needed to add the second extraction pass
- Both sections now contribute to the unified member array returned by `parseMembers()`
- No changes needed to `parseTableRow()` — it already handles the Name/Role/Charter/Status columns correctly regardless of which section they come from

### Active-Work Marker Detection (2026-02-18)
- Implemented `detectActiveMarkers()` in SquadDataProvider — scans `{squadFolder}/active-work/` for `.md` marker files
- Detection is mtime-based with `STALENESS_THRESHOLD_MS = 300_000` (5 minutes); stale markers are ignored
- Integrated into `getSquadMembers()` after existing status resolution (roster + log + task demotion) but before caching
- Slug matching uses `member.name.toLowerCase()` since agent folder names are already lowercase
- Handles missing directory gracefully via try/catch (returns empty set)
- SquadUI is read-only — it never creates or deletes marker files, only reads presence + mtime
- No model changes needed — `MemberStatus` already includes `'working'`
- No watcher changes needed — existing `**/{.squad,.ai-team}/**/*.md` glob covers `active-work/*.md`

### Velocity Chart: All Closed Issues (2026-02-18)
- `buildVelocityTimeline()` previously only counted closed issues from `MemberIssueMap` (member-matched subset)
- Issues without `squad:*` labels or matching assignee aliases were silently dropped from velocity
- Fix: added `allClosedIssues?: GitHubIssue[]` parameter to both `buildDashboardData()` and `buildVelocityTimeline()`
- `allClosedIssues` is the unfiltered array from `getClosedIssues()` — every closed issue in the repo
- Deduplication via `Set<number>` on issue number prevents double-counting
- Fallback: if `allClosedIssues` not provided, falls back to iterating `closedIssues` MemberIssueMap (backward compat)
- `closedIssues` MemberIssueMap still used for Team Overview per-member breakdown — that data is correct per-member
- `IGitHubIssuesService` interface extended with `getClosedIssues()` — already existed on GitHubIssuesService, just not in the contract
- Key files: `DashboardDataBuilder.ts` (velocity logic), `SquadDashboardWebview.ts` (data fetch), `models/index.ts` (interface)
Team update (2026-02-18): Active-work marker protocol for detecting agent status during subagent turns decided by Danny

### Velocity Chart: Session Log Inclusion (2026-02-18)
- Velocity chart was undercounting — only orchestration-log tasks and closed GitHub issues were counted
- Session logs in `log/` contain real completed work (issue refs, outcomes, participants) but were excluded by `getTasks()` which deliberately uses orchestration-only entries
- Added `getVelocityTasks()` to `SquadDataProvider` — uses `getLogEntries()` (all logs) instead of `getOrchestrationLogEntries()`
- `getTasks()` unchanged — orchestration-only for member status isolation and tree view correctness
- `DashboardDataBuilder.buildDashboardData()` now accepts optional 9th param `velocityTasks?: Task[]`; velocity timeline uses `velocityTasks ?? tasks`
- Activity swimlanes still use orchestration-only `tasks` — only velocity benefits from session logs
- Architectural principle: velocity = all work signals; status = orchestration-only (prevents false "working" indicators from old session logs)

### Dashboard & Decisions Pipeline Deep Dive (2026-02-18)
- **Bug: Hardcoded `.ai-team` in `SquadDataProvider.discoverMembersFromAgentsFolder()`** — line 271 used `'.ai-team'` literal instead of `this.squadFolder`. Agent folder fallback broken for `.squad` users. Fixed.
- **Bug: Hardcoded `.ai-team` in `SquadDashboardWebview.handleOpenLogEntry()`** — line 167 used `'.ai-team'` literal. Clicking "Recent Sessions" log cards failed for `.squad` users. Fixed by adding `getSquadFolder()` to `SquadDataProvider`.
- **Bug: `DecisionsTreeProvider` created `DecisionService()` without `squadFolder`** — line 434 used default `.ai-team`. Decisions tree was empty for `.squad` workspaces. Fixed: constructor now accepts and passes `squadFolder`.
- **Bug: `TeamTreeProvider` created `OrchestrationLogService()` without `squadFolder`** — line 42. Member log entries in tree view broken for `.squad` users. Fixed: constructor now accepts and passes `squadFolder`.
- Key files: `src/services/SquadDataProvider.ts` (data aggregation, caching, member resolution chain), `src/views/SquadDashboardWebview.ts` (webview panel, data fetch orchestration), `src/views/dashboard/DashboardDataBuilder.ts` (data transformation for charts), `src/views/dashboard/htmlTemplate.ts` (HTML + JS rendering, ~1226 lines)
- HTML template has good null guards: all `render*()` functions check for empty/undefined arrays before iterating. Canvas charts check `offsetWidth === 0` to skip hidden tabs. Empty states shown for missing data.
- `DashboardDataBuilder.buildDashboardData()` always returns fully-populated `DashboardData` — no null fields in the structure.
- `DecisionService` handles missing file (returns early), empty file (no headings found), and inbox subdirectories (recursive `scanDirectory`). Graceful: never throws.


### Team Update: 2026-02-23 - Fork-Aware Issue Fetching
**Team update (2026-02-23):** Fork-aware issue fetching shipped: when repo is a fork, SquadUI auto-detects upstream via GitHub API (GET /repos/{owner}/{repo} parent), with manual override via team.md **Upstream** | owner/repo. All issue queries (open, closed, milestones) use upstream. Fallback to configured repo if not a fork. No breaking changes repos without forks behave identically. decided by @copilot

### Feature Roadmap & Assignments (2026-02-24)
📌 Team update (2026-02-23): Feature roadmap defined — 10 features across v1.0/v1.1/v1.2. See decisions.md.
- P1 features assigned: Decision Search & Filter (#69), Health Check (#70), Milestone Burndown Template (#75)
- P2 features assigned: Skill Usage Metrics (#74)
- v1.0 ship target with focus on decision search service, diagnostic tooling
- v1.1 enables observability (skills usage, burndown metrics)
- Key implementation: DecisionService.search() and HealthCheck diagnostic command
- Roadmap session logged to .ai-team/log/2026-02-23-feature-roadmap.md
# Linus Search Service & Health Check

**Role:** Infrastructure & Observability
**Current Focus:** v1.0 features Decision Search (#69), Health Check (#70)

## v1.0 Batch 1 (2026-02-23)

**#69 Decision Search Service**
- Full-text search with relevance ranking and date/author filtering
- 37 new tests added
- PR #79 merged to squad/v1.0-features
- Status: Complete

**v1.0 Roadmap Assignments**
- P1: Decision Search & Filter (#69)
- P1: Health Check (#70) — in progress (Batch 2)
- P1: Milestone Burndown Template (#75)
- P2: Skill Usage Metrics (#74)

**#70 Health Check Diagnostic Command**
- `HealthCheckService` — pure TypeScript, no VS Code dependency, 4 checks: team.md, agent charters, log parse health, GitHub token
- Each check returns `HealthCheckResult { name, status: 'pass'|'fail'|'warn', message, fix? }`
- `runAll()` parallel execution, `formatResults()` human-readable output with icons
- `squadui.healthCheck` command wired to VS Code output channel (minimal extension.ts touch)
- Squad folder passed as parameter everywhere — never hardcoded
- 17 tests in `healthCheckService.test.ts`
- PR #81 → closes #70
- Status: Complete

## Historical Summaries

**Earlier work (v0.1v0.2, 2026-02-13 to 2026-02-18)** archived to history-archive-v1.md.

Key milestones:
- Core data pipeline: OrchestrationLogService, TeamMdService, SquadDataProvider
- GitHub issues integration with graceful degradation
- H1 decision format support
- Test hardening patterns
- Dashboard bugfixes (squad folder awareness, velocity chart, session log inclusion)
- Fork-aware issue fetching (2026-02-23)
17 changes: 17 additions & 0 deletions .ai-team/decisions/inbox/linus-health-check-service-pattern.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Decision: HealthCheckService is a pure-TypeScript service

**Date:** 2026-02-23
**Author:** Linus
**Issue:** #70

## Context
The health check command needs to validate team configuration (team.md, agent charters, orchestration logs, GitHub token). This could be implemented directly in the command handler or as a standalone service.

## Decision
Created `HealthCheckService` as a pure TypeScript service with no VS Code API dependencies. Each check method accepts `squadFolder` and `workspaceRoot` as parameters. The command handler in `extension.ts` is minimal — just wires the service to an output channel.

## Rationale
- Testable in isolation (Mocha tests without VS Code test runner complexity)
- Follows existing service patterns (TeamMdService, OrchestrationLogService)
- Keeps service layer decoupled from VS Code UI (Linus/Rusty boundary)
- `HealthCheckResult` interface enables structured consumption by future UI (tree view, dashboard tab)
21 changes: 21 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@
"category": "Squad",
"icon": "$(open-preview)"
},
{
"command": "squadui.editCharter",
"title": "Edit Charter",
"category": "Squad",
"icon": "$(edit)"
},
{
"command": "squadui.removeMember",
"title": "Remove Team Member",
Expand Down Expand Up @@ -134,6 +140,12 @@
"title": "Generate Standup Report",
"category": "Squad",
"icon": "$(report)"
},
{
"command": "squadui.healthCheck",
"title": "Health Check",
"category": "Squad",
"icon": "$(checklist)"
}
],
"viewsWelcome": [
Expand Down Expand Up @@ -172,6 +184,11 @@
}
],
"view/item/context": [
{
"command": "squadui.editCharter",
"when": "view == squadTeam && viewItem == member",
"group": "inline"
},
{
"command": "squadui.removeMember",
"when": "view == squadTeam && viewItem == member",
Expand All @@ -194,6 +211,10 @@
}
],
"commandPalette": [
{
"command": "squadui.editCharter",
"when": "false"
},
{
"command": "squadui.showWorkDetails",
"when": "false"
Expand Down
47 changes: 46 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import * as path from 'path';
import * as fs from 'fs';
import { GitHubIssue } from './models';
import { SquadDataProvider, FileWatcherService, GitHubIssuesService, SquadVersionService } from './services';
import { SquadDataProvider, FileWatcherService, GitHubIssuesService, SquadVersionService, HealthCheckService } from './services';
import { TeamTreeProvider, SkillsTreeProvider, DecisionsTreeProvider, WorkDetailsWebview, IssueDetailWebview, SquadStatusBar, SquadDashboardWebview, StandupReportWebview } from './views';
import { registerInitSquadCommand, registerUpgradeSquadCommand, registerAddMemberCommand, registerRemoveMemberCommand, registerAddSkillCommand } from './commands';
import { detectSquadFolder, hasSquadTeam } from './utils/squadFolderDetection';
Expand Down Expand Up @@ -201,7 +201,7 @@
if (typeof rawName === 'string') {
memberName = rawName;
} else if (typeof rawName === 'object' && rawName !== null) {
memberName = String((rawName as any).label || (rawName as any).name || '');

Check warning on line 204 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Unexpected any. Specify a different type

Check warning on line 204 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Unexpected any. Specify a different type

Check warning on line 204 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (macos-latest)

Unexpected any. Specify a different type

Check warning on line 204 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (macos-latest)

Unexpected any. Specify a different type

Check warning on line 204 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Unexpected any. Specify a different type

Check warning on line 204 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Unexpected any. Specify a different type
}
const teamRoot = typeof rawRoot === 'string' ? rawRoot : undefined;
if (!memberName) {
Expand All @@ -221,6 +221,32 @@
})
);

// Register edit charter command — opens charter in text editor + markdown preview side-by-side
context.subscriptions.push(
vscode.commands.registerCommand('squadui.editCharter', async (rawName?: unknown) => {
let memberName: string = '';
if (typeof rawName === 'string') {
memberName = rawName;
} else if (typeof rawName === 'object' && rawName !== null) {
memberName = String((rawName as any).label || (rawName as any).memberId || (rawName as any).name || '');

Check warning on line 231 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Unexpected any. Specify a different type

Check warning on line 231 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Unexpected any. Specify a different type

Check warning on line 231 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Unexpected any. Specify a different type

Check warning on line 231 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (macos-latest)

Unexpected any. Specify a different type

Check warning on line 231 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (macos-latest)

Unexpected any. Specify a different type

Check warning on line 231 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (macos-latest)

Unexpected any. Specify a different type

Check warning on line 231 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Unexpected any. Specify a different type

Check warning on line 231 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Unexpected any. Specify a different type

Check warning on line 231 in src/extension.ts

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Unexpected any. Specify a different type
}
if (!memberName) {
vscode.window.showWarningMessage('No member selected');
return;
}
const slug = memberName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
const charterPath = path.join(currentRoot, squadFolderName, 'agents', slug, 'charter.md');
if (!fs.existsSync(charterPath)) {
vscode.window.showWarningMessage(`Charter not found for ${memberName}`);
return;
}
const uri = vscode.Uri.file(charterPath);
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc, { preview: false });
await vscode.commands.executeCommand('markdown.showPreviewToSide', uri);
})
);

// Register squad init command
let allocationPollInterval: ReturnType<typeof setInterval> | undefined;
let initInProgress = false;
Expand Down Expand Up @@ -493,6 +519,25 @@
})
);

// Register health check command — runs diagnostics and shows results in output channel
context.subscriptions.push(
vscode.commands.registerCommand('squadui.healthCheck', async () => {
const healthService = new HealthCheckService();
const results = await healthService.runAll(squadFolderName, currentRoot);
const output = vscode.window.createOutputChannel('Squad Health Check');
output.clear();
output.appendLine(healthService.formatResults(results));
output.show(true);

const failed = results.filter(r => r.status === 'fail').length;
if (failed > 0) {
vscode.window.showWarningMessage(`Squad Health Check: ${failed} issue(s) found. See output for details.`);
} else {
vscode.window.showInformationMessage('Squad Health Check: All checks passed.');
}
})
);

// Connect file watcher to tree refresh and context key update
fileWatcher.onFileChange(() => {
teamProvider.refresh();
Expand Down
Loading
Loading