diff --git a/.squad/agents/booster/history.md b/.squad/agents/booster/history.md index 9465240c..2451584c 100644 --- a/.squad/agents/booster/history.md +++ b/.squad/agents/booster/history.md @@ -84,6 +84,15 @@ Analyzed 20 CI runs from March 15. Identified 3 distinct failure categories: 4. Better failure grouping/attribution in CI UI (distinguish "new gate" vs "regression") 5. Spell check dictionary maintenance workflow (easier to add known-good usernames/terms) +### whatsnew.md Version Sync — March 22, 2026 +**What was built:** scripts/sync-whatsnew-version.mjs — strips -build.N suffix from package.json version, finds the ## v{X} — Current Release heading in docs/src/content/docs/whatsnew.md, and replaces it with the current clean semver. Idempotent; writes only when changed. + +**Test added:** est/whatsnew-version-sync.test.ts — Vitest test that asserts the Current Release heading in whatsnew.md matches the stripped package.json version. Fails CI when versions diverge. + +**Hook:** Appended +ode scripts/sync-whatsnew-version.mjs to the prebuild npm script (runs after bump-build.mjs, so it always sees the bumped version). Also set SKIP_BUILD_BUMP=1 guard pattern documented for CI validate runs. + +**Immediate fix:** Updated the stale ## v0.8.2 — Current Release heading to ## v0.8.25 — Current Release to match the actual package.json version at time of work. ### CI Workflow Audit — March 23, 2026 **Status:** Conducted full audit of 15 workflow files. Brady's perception ("complete nightmare, 12,000 workflows") is not accurate — the codebase is lean, well-organized, and 99% authored by Brady (bradygaster + Copilot). diff --git a/.squad/agents/pao/history.md b/.squad/agents/pao/history.md index abbb323e..3d87a857 100644 --- a/.squad/agents/pao/history.md +++ b/.squad/agents/pao/history.md @@ -94,12 +94,45 @@ Evaluated four docs pages from PR #331 (Tamir's blog analysis) against Squad-spe ### Boundary Review Execution (v0.8.26) Executed boundary review findings from PR #331: (1) Deleted ralph-operations.md (infrastructure around Squad, not Squad itself — moved to IRL); (2) Deleted proactive-communication.md (external tools/webhooks — moved to IRL); (3) Reframed issue-templates.md intro to clarify "GitHub feature configured for Squad" not "Squad feature"; (4) Updated EXPECTED_SCENARIOS in docs-build.test.ts to match remaining files. Pattern reinforced: boundary review = remove external infrastructure docs, reframe platform integration docs to clarify whose feature it is, keep Squad behavior/config docs. Changes staged for commit. -### Cross-Org Authentication Docs (v0.8.26) +### Docs Catalog Audit (2026-03-22) +Full audit of the Astro docs site identified critical quality and navigation gaps. **Findings:** 0 dead nav links (healthy); 15 orphaned pages not discoverable via sidebar (FAQ, guides, reference pages, 6 legacy root files); 3 stale/broken pages using deprecated install syntax; 5 duplicate content conflicts. **Top 5 Actions:** (1) Add CI test to enforce nav coverage — catch orphaned pages automatically; (2) Delete 6 root-level legacy files (guide.md, sample-prompts.md, tips-and-tricks.md, tour-*.md) — deprecated syntax and not in nav; (3) Make whatsnew.md a release checklist artifact — current report (v0.8.2) vs actual (v0.8.26+) erodes trust; (4) Update insider-program.md to current install method — replace deprecated `npx github:` syntax; (5) Resolve choose-your-interface vs choosing-your-path duplication — one canonical page rule. **Skill Created:** docs-catalog-audit (low confidence; audit framework needs iteration). **Decision:** Merged into decisions.md for team adoption. + +### Docs Fire Fixes (post-audit, 2026-03-22) +Fixed four fires from the catalog audit: (1) Updated `insider-program.md` — replaced all deprecated `npx github:bradygaster/squad#insider` commands with `npm install -g @bradygaster/squad-cli@insider`, and all `.ai-team/` references with `.squad/`; (2) Added six orphaned pages to `navigation.ts` — `guide/faq`, `guide/build-autonomous-agent`, `features/built-in-roles`, `features/context-hygiene`, `features/issue-templates`, `reference/vscode-troubleshooting`; (3) Deleted five stale root-level files via `git rm` (`guide.md`, `sample-prompts.md`, `tips-and-tricks.md`, `tour-first-session.md`, `tour-github-issues.md`); (4) Added `vscode-troubleshooting` to EXPECTED_REFERENCE in docs-build.test.ts — all 23 tests pass. New nav entries use sentence-case and "and" over ampersands per team decision. Created docs/src/content/docs/scenarios/cross-org-auth.md covering GitHub personal + Enterprise Managed Users (EMU) multi-account auth. Three solutions documented: (1) gh auth switch for manual account toggling; (2) Copilot instructions (.github/copilot-instructions.md) for account mapping documentation; (3) Squad skill pattern for auth error detection and recovery. Covered git credential helpers (per-host and per-org), EMU hostname variations (github.com vs dedicated instances), and common error messages (HTTP 401, authentication required). Added cross-references in troubleshooting.md (new section), enterprise-platforms.md (authentication section), and navigation.ts. Updated test/docs-build.test.ts with 'cross-org-auth' in EXPECTED_SCENARIOS. Pattern: Microsoft Style Guide (sentence-case), "Try this" prompts at top, problem/solution structure, practical examples over abstractions, links to related pages at bottom. ### Scannability Framework (v0.8.25) Format selection is a scannability decision, not style preference. Paragraphs for narrative/concepts (3-4 sentences max). Bullets for scannable items (features, options, non-sequential steps). Tables for comparisons or structured reference data (config, API params). Quotes/indents for callouts/warnings. Decision test: if reader hunts for one item in a paragraph, convert to bullets/table. This framework is now a hard rule in charter under SCANNABILITY REVIEW. +### Docs Catalog Audit (2026) +Full audit of the Astro-based docs site. Key patterns and findings: + +**Orphaned pages (exist but not in navigation.ts):** 15 total — `get-started/choose-your-interface.md`, `guide/faq.md`, `guide/build-autonomous-agent.md`, `guide/github-auth-setup.md`, `features/built-in-roles.md`, `features/context-hygiene.md`, `features/cost-tracking.md`, `features/issue-templates.md`, `reference/vscode-troubleshooting.md`, and 6 root-level legacy files (`guide.md`, `sample-prompts.md`, `tips-and-tricks.md`, `tour-first-session.md`, `tour-github-issues.md`, `tour-gitlab-issues.md`). + +**Stale content:** `whatsnew.md` reports v0.8.2 as current; actual is v0.8.26+. `insider-program.md` uses deprecated `npx github:` install format and references old `.ai-team/` directory name throughout. + +**Duplicate/overlap pairs:** `choosing-your-path.md` (in nav) vs `choose-your-interface.md` (orphan, more complete); root-level `sample-prompts.md` vs `guide/sample-prompts.md`; root-level `tips-and-tricks.md` vs `guide/tips-and-tricks.md`; root-level `tour-first-session.md` vs `get-started/first-session.md`. + +**Content quality:** All actively-navved pages are well-written, follow Microsoft Style Guide, and use correct install commands. Format standards (H1, experimental callout, "Try this" block, HR, H2 sections) are inconsistently applied — some orphaned pages like `built-in-roles.md` and `cost-tracking.md` lack the standard header/callout pattern. + +**Structural issues:** `features/team-setup.md` has a duplicate `## How Init Works` heading (merge artifact). `features/streams.md` nav title is "Streams" but H1 is "Squad SubSquads" (mismatch). `guide/faq.md` is a high-value page completely invisible from the sidebar. `features/built-in-roles.md` is a comprehensive roles reference also invisible from nav. + +**Gap:** No dedicated FAQ entry point, no changelog page, cookbook section is thin (one page), no user-facing explanation of the NASA Mission Control naming scheme for agents. + +**Navigation:** Zero dead nav links (every nav slug has a matching file). All orphan pages are linked internally from other pages so they are reachable — but not browseable via sidebar. + +📌 **Team update (2026-03-22T12:46:00Z):** Booster implemented automated version sync for `whatsnew.md` (finding #1). Script reads `package.json` version, updates "Current Release" heading on every prebuild, with Vitest test gate. Heading now correct (v0.8.25+), will stay in sync automatically on all future builds. Finding #1 resolved. + +### CI Mermaid Rendering Proposal (2026-03-25) + +Researched and proposed CI pipeline for pre-rendering Mermaid diagrams to PNG before docs build. Evaluated three approaches: +- **Option A:** GitHub Actions pre-build step (CI-only; poor local DX) +- **Option B:** npm `prebuild` script hook (local + CI; excellent DX) +- **Option C:** Astro integration plugin (seamless but complex/maintenance burden) + +**Recommendation:** Option B (npm prebuild) + Option A (CI safety net). Tool: `@mermaid-js/mermaid-cli` (mmdc). Source files: `.mmd` in `docs/src/content/docs/*/diagrams/`, output PNGs in `*/images/` (git-ignored). Enables live diagram authoring locally, fresh renders in CI. Proposal doc: `docs/research/ci-mermaid-rendering-proposal.md`. Decision: `.squad/decisions/inbox/pao-ci-mermaid.md`. +Researched and proposed CI pipeline for pre-rendering Mermaid diagrams to PNG before docs build. Evaluated three approaches: Option A (GitHub Actions pre-build, CI-only, poor local DX), Option B (npm prebuild script, local + CI, excellent DX), Option C (Astro integration plugin, seamless but complex/maintenance burden). **Recommendation:** Option B (npm prebuild) + Option A (CI safety net). Tool: `@mermaid-js/mermaid-cli` (mmdc). Source files: `.mmd` in `docs/src/content/docs/*/diagrams/`, output PNGs in `*/images/` (git-ignored, generated). Benefits: excellent local DX (live diagram authoring), reliable CI (always fresh PNGs), simple implementation (npm lifecycle hook). Proposal: `docs/research/ci-mermaid-rendering-proposal.md`. Decision: `.squad/decisions/inbox/pao-ci-mermaid.md`. Enables authors to iterate on diagrams in real-time, reduces client-side rendering load. + ### Issue Triage (2026-03-22T06:44:01Z) **Flight triaged 6 unlabeled issues and filed 1 new issue.** @@ -192,3 +225,46 @@ Teams MCP critical update: Office 365 Connectors retired Dec 2024 → Power Auto **Commit:** `docs: rewrite PUBLISH-README.md as release playbook (#564)` on squad/release-hardening branch. 📌 **Team update (2026-03-24T06-release-hardening):** Release playbook rewrite (#564) completed. PUBLISH-README.md transformed from v0.8.22 stub to living 232-line playbook with 11 sections: Overview, Pre-Flight Checklist, Publish via CI (recommended), Publish via workflow_dispatch, Insider Channel, Workspace Publish Policy, Manual Local Publish (emergency fallback), 422 Race Condition & npm Errors, Post-Publish Verification, Version Bump After Publish, Legacy Publish Scripts. Absorbed issues #558, #559, #560 into unified decision tree. Microsoft Style Guide enforced; version-agnostic; all commands runnable. Scannability: checklist format, bash code blocks, error reference table. Committed to squad/release-hardening. +### JSDoc API Reference PRD (2026-03-24) + +Completed full PRD based on research findings. **Document:** `docs/research/jsdoc-api-reference-prd.md`. + +**Structure (8 major sections):** +1. Problem Statement — 5 concrete gaps (no dedicated API ref, uneven JSDoc coverage, discoverability, StorageProvider docs lag, Pagefind misses API symbols) +2. Goals & Success Metrics — 4 primary goals, 8 measurable targets (100% JSDoc coverage, 50+ auto-documented symbols, searchable API) +3. Key User Scenarios — 4 personas (SDK consumer, contributor, agent author, evaluator) with today vs future workflows +4. Scope — clear in/out boundaries (TypeDoc + JSDoc improvements in; CLI ref gen, Starlight migration, multi-version docs out) +5. Approach — architecture (TypeDoc in Astro hook), config template (typedoc.json), output/URL structure, build integration code, JSDoc improvement plan with effort table +6. Implementation Phases — 4 phases: Phase 0 (setup/PoC, 1–2 days), Phase 1 (JSDoc audit, 5–6 hrs), Phase 2 (integration/nav, 3–4 hrs), Phase 3 (CI/CD optional, 2–4 hrs) +7. Risks & Mitigations — 7 risks (TypeDoc breaks on changes, stale markdown, link validation strictness, Pagefind misses, config maintenance, build perf, breaking changes) with specific mitigations +8. Architecture Review section — 4 items for CONTROL to review (TypeScript export strategy, TypeDoc config, JSDoc standards, stability commitments) + +**Key decisions baked into PRD:** +- TypeDoc + typedoc-plugin-markdown (not Starlight, not api-extractor) — zero migration, Markdown-first, Pagefind-compatible +- Astro integration hook auto-runs TypeDoc on build (single step: `npm run build`) +- Generated output goes to docs/src/content/docs/reference/api/ (one file per symbol) +- JSDoc improvement priority: config/schema.ts (8% → 100%), state/io/ functions (@param/@return tags), StorageProvider interface audit +- Total effort: 13–18 hours (8–12 JSDoc + 5–6 setup) + +**Style & Tone:** +- Written for Flight-level review/approval (actionable, opinionated, specific) +- Includes code examples (typedoc.json, Astro hook, JSDoc template) +- References research doc for detailed findings +- PRD as decision/commitment document — not advisory, but directive + +**Learnings:** +- PRD structure differs from research (research = exploratory findings/options; PRD = chosen path + tactical roadmap) +- Recommendation section in PRD serves as binding decision (TypeDoc chosen, rationale locked in) +- Architecture Review section ensures TypeScript team reviews export strategy and JSDoc standards early — prevents rework later +- Four-phase approach breaks large effort into digestible increments (Phase 0 validation before JSDoc audit helps mitigate risk of TypeDoc setup failing) + +**Decision:** PRD approved for handoff to implementation team. Ready for execution on next sprint. +### Architecture Diagrams PNG Conversion (2026-03-24) + +Converted all 7 Mermaid diagrams from the architecture page to optimized PNG images. **Process:** (1) extracted Mermaid source from squad/pao-arch-diagram branch; (2) ran `mmdc` (mermaid-cli v11.12.0) with `-b transparent -t neutral` flags to generate 7 PNG files (36–124 KB each); (3) created new `squad/pao-arch-docs` branch from dev; (4) updated `docs/src/content/docs/concepts/architecture.md` with PNG references and collapsed Mermaid source blocks; (5) added 2-3 paragraph intro explaining Squad's orchestration model at a glance. + +**Diagrams rendered:** User Interaction Flow (sequence), Component Architecture (layered graph), State Management (drop-box pattern), Parallel Execution Model (3-mode routing), Casting & Persistent Naming (knowledge compounds), Decision & Knowledge Flow (full cycle), Git Worktree Lifecycle (worktree-local strategy). + +**Key pattern:** PNG images render in docs for clarity and performance; collapsed `
` blocks preserve Mermaid source for readers who want to copy/adapt. Structure follows Microsoft Style Guide: sentence-case headings, active voice, second person ("you"), present tense, practical descriptions. Intro contextualizes: "Squad is a programmable multi-agent orchestration runtime" + isolation/persistence/portability principles + preview of layer-by-layer explanation. + +**Learning:** Mermaid-cli succeeds with direct call (`mmdc` binary) after global npm install; avoid `npx` wrapper on this machine. Transparent PNG background + neutral theme keeps diagrams legible in both light/dark doc modes. Backtick-escaping in Mermaid `subgraph` labels is brittle; use simpler node labels and rely on Mermaid's rendering. All PNG files committed; docs site Astro will serve from content directory via relative paths (`./images/architecture-*.png`). diff --git a/.squad/decisions.md b/.squad/decisions.md index ded85571..bfd859c5 100644 --- a/.squad/decisions.md +++ b/.squad/decisions.md @@ -6526,6 +6526,77 @@ ESM module resolution uses dual-layer postinstall strategy: --- +## Docs Catalog Audit Findings — PAO Decision + +**Author:** PAO (DevRel) +**Date:** 2026-03-22 + +Comprehensive audit of the Astro-based Squad docs site identified critical gaps in navigation coverage, stale content, and structural inconsistencies. + +### 1. Navigation gap is a CI failure condition + +Every content file under docs/src/content/docs/ that is not in +avigation.ts (or STANDALONE_PAGES) must be treated as a defect. Pattern of 15 orphaned pages (FAQ, built-in roles reference, context hygiene guide, VS Code troubleshooting, autonomous agent guide, GitHub auth setup) shows no automated check preventing nav gaps. + +**Action:** Add test assertion in est/docs-build.test.ts to verify every .md file in docs content tree appears in either NAV_SECTIONS or STANDALONE_PAGES. + +### 2. Root-level legacy files must be removed + +Six root-level files ( our-first-session.md, our-github-issues.md, our-gitlab-issues.md, guide.md, sample-prompts.md, ips-and-tricks.md) are stale legacy artifacts using deprecated install commands ( +px github:bradygaster/squad, .ai-team/), not in nav, creating confusion. Delete or archive — do not keep indefinitely. + +### 3. whatsnew.md must be updated on every release + +What's New page is the trust signal for active maintenance. Currently reports v0.8.2 when actual is v0.8.26+. This erodes user trust. **Update policy:** whatsnew.md is a required artifact in every release checklist. + +### 4. insider-program.md must use current distribution + +Insider Program page uses deprecated +px github:bradygaster/squad#insider syntax and references old .ai-team/ directory. Must be updated to use current npm insider channel or removed if insider program format changed. + +### 5. choose-your-interface.md supersedes choosing-your-path.md + +Orphaned get-started/choose-your-interface.md is significantly more complete than navved get-started/choosing-your-path.md. Options: (a) add choose-your-interface to nav and point from installation.md, or (b) merge into single canonical page. Do not keep both — enforce "one canonical page per concept" rule. + +### Observations (No Action Required) + +- **Zero dead nav links** — every nav reference has backing file (healthy signal) +- **All actively-navved pages** follow Microsoft Style Guide, use correct install commands +- **Blog section healthy** — 28 posts, consistent format +- **Concepts section clean** — well-structured + +--- + +## whatsnew.md "Current Release" Version Sync + +**By:** Booster (CI/CD Engineer) +**Status:** Implemented +**Date:** 2026-03-22 + +### Problem + +`docs/src/content/docs/whatsnew.md` contains a `## v{X} — Current Release` heading that drifts from `package.json` version during build cycles. Manually updating it during releases is error-prone and easy to skip, eroding team trust in release docs. + +### Decision + +Implement automated version sync via prebuild script: + +1. **scripts/sync-whatsnew-version.mjs** — Reads `package.json` version, strips pre-release suffixes (e.g., `-build.N`), finds `## v{X} — Current Release` heading in whatsnew.md, replaces it if needed (idempotent, no-ops if already correct). +2. **Prebuild hook** — Wire into `package.json` `"prebuild"` script to run after `bump-build.mjs`, so it always sees freshly bumped version. +3. **Test gate** — Add Vitest test (`test/whatsnew-version-sync.test.ts`) that fails CI if heading and `package.json` are out of sync. + +### Rationale + +- Root cause: No automated gate. Version bumps fire via `bump-build.mjs` but `whatsnew.md` update was manual and skipped. +- **Prebuild** (not build) ensures it runs on every local `npm run build` + CI, keeping the file always current. +- Idempotent design allows safe use with `SKIP_BUILD_BUMP=1` (validate-only builds still sync). +- Test is the safety net: even manual edits to wrong version are caught. + +### Alternatives Rejected + +- **Git hook (pre-commit):** Not portable across all contributors and Copilot agents. +- **Test-only, no script:** Would fail CI but give no remediation path. +- **Modify bump-build.mjs:** Out of scope per Booster charter (don't modify internal bump logic). # Economy Mode Design — #500 **Date:** 2026-03-20 @@ -7582,3 +7653,255 @@ Meta-references to "npm publish" in echo, grep, and YAML `name:` lines are exclu - `init --global` now suppresses GitHub workflows (they're meaningless in the global config dir). - `RunInitOptions` has a new `isGlobal` field. +## Phase 2 State Facade Decisions (2026-03-24) + +### CI Workflow Audit — March 2026 +**By:** Booster (CI/CD Engineer) +**Date:** 2026-03-23 + +**Summary:** CI is NOT a disaster. 15 workflow files total — 7 load-bearing (essential), 7 administrative, 1 ghost (to delete). + +**Key findings:** +- ✅ Workflow set is lean, well-organized, non-overlapping +- 🚨 Ghost workflow: `publish-npm.yml` deleted but GitHub index still lists it (returns 422) +- ⚠️ Implicit ordering: `squad-release` and `squad-npm-publish` both trigger on `release: published`, no explicit dependency +- ✅ Type safety strong, Zero hidden tech debt + +**Action items:** +1. Delete ghost `publish-npm.yml` via GitHub API +2. Decide: keep or delete `ci-rerun.yml` (optional, useful for debugging) +3. Optional: add explicit job dependency from `squad-release` to `squad-npm-publish` + +**Authorship:** bradygaster (65%), Copilot (10%), others (24%) + +--- + +### Pre-publish Preflight Gate +**By:** Booster (CI/CD Engineer) +**Date:** 2026-03-23 + +**Decision:** Added `preflight` job to `squad-npm-publish.yml` that runs BEFORE all publish jobs. + +**What it does:** +1. Scans all `packages/*/package.json` for `file:` references in any dependency section +2. Validates all versions are valid semver +3. Blocks entire publish pipeline if any violation found + +**Rationale:** v0.9.1 release shipped with `file:` references (local monorepo paths), breaking global installs. Preflight catches this at zero cost (no npm ci, no build — just JSON reads). + +**Impact:** All squad members — publish pipeline will now reject PRs with `file:` references in dependencies. + +--- + +### State Module Type Naming +**By:** CONTROL (TypeScript Engineer) +**Date:** 2026-03-24 + +**Decisions:** +1. **`StateError` not `StorageError`** — New error base class avoids collision with existing `StorageError` +2. **Aliased barrel exports** — Main SDK barrel re-exports state types with aliases (`StateTeamConfig`, etc.) to avoid namespace conflicts +3. **`ReadonlyMap` for ownership** — `RoutingConfig.moduleOwnership` uses `ReadonlyMap` to prevent mutation + +**Impact:** No breaking changes; future SquadState implementation will be generic over `CollectionName` + +--- + +### User Directive: Release Governance +**By:** Brady (via Copilot) +**Date:** 2026-03-23T10:08:00Z + +**Directives:** +1. Coordinator should NOT release. Releases are Brady's responsibility. +2. Strict adherence to same release process every time. No improvisation. +3. Document problems thoroughly to avoid repeating them. +4. CI/CD and release quality is TOP priority. +5. Release scramble logs should be scrubbed — file issues instead. +6. Every release must follow written, step-by-step playbook. + +**Why:** v0.9.0→v0.9.1 incident burned ~8 hours and excessive Actions minutes. + +--- + +### User Directive: No npx in Docs +**By:** Brady (via Copilot) +**Date:** 2026-03-23T00-17-57Z + +**Directive:** Stop mentioning npx in README, docs, all user-facing content. Distribution is `npm install -g` only. + +**Why:** npx path is deprecated, causes confusion. + +--- + +### History IO Section-Split Strategy +**By:** EECOM (Core Dev) +**Date:** 2026-07-24 + +**Decision:** `parseHistory()` in `state/io/history-io.ts` uses header-position-based section splitter instead of regex. + +**Why:** JavaScript doesn't support `\Z` (Perl end-of-string anchor). Existing regex `(?=^##\s|\Z)` silently fails on last section. IO layer uses `RegExp.exec()` with `g` flag to find headers, then `substring()` between positions — more robust. + +**Impact:** Existing `readHistory()` in `history-shadow.ts` NOT modified (per task rules). Bug is latent there but doesn't cause practical issues (appendToHistory always creates sections above existing content). + +--- + +### SquadState Case-Insensitive Lookup +**By:** EECOM (Core Dev) +**Date:** 2026-07-25 + +**Decision:** `AgentHandle.update()` uses case-insensitive comparison (`toLowerCase()`) for agent lookup. + +**Why:** Team.md parser normalizes agent names to kebab-case. Direct comparison fails when calling with display names like `EECOM`. Case-insensitive matching solves this without coupling to parser internals. + +--- + +### `version` Subcommand Handled Inline +**By:** EECOM (Core Dev) +**Date:** 2026-07-15 + +**Decision:** Handle `squad version` inline in `cli-entry.ts` alongside `--version`/`-v`, not as separate command file. + +**Rationale:** Trivial handlers (just print a value) don't warrant their own module. Same output, same code path as `--version`. Follows precedent: `help` is also inline. + +--- + +### JSDoc API Reference PRD Complete +**By:** PAO (DevRel) +**Date:** 2026-03-24 + +**Decision:** TypeDoc + typedoc-plugin-markdown for API reference generation (not Starlight, not api-extractor). + +**Why TypeDoc:** +- ✅ Zero Astro migration +- ✅ Markdown-first output +- ✅ Pagefind-ready +- ✅ Simple config +- ✅ Industry standard + +**Four-phase roadmap:** Phase 0 (setup), Phase 1 (JSDoc improvements), Phase 2 (Astro integration), Phase 3 (CI automation). + +**100% JSDoc coverage required** for all exported symbols before generation. + +--- + +### JSDoc API Reference Research +**By:** PAO (DevRel) +**Date:** 2026-03-24 + +**Recommendation:** TypeDoc + typedoc-plugin-markdown (no Starlight migration needed). + +**JSDoc Coverage Audit:** 60–80% across major modules (state: 81%, config: 8%) + +**Setup Strategy:** +1. Install TypeDoc + markdown plugin +2. Create `typedoc.json` at project root +3. Add Astro integration hook to `docs/astro.config.mjs` +4. `npm run build` auto-generates API docs + +**Effort:** 5–6 hours setup only; 13–18 hours with JSDoc improvements + +--- + +### npm-Only Distribution (No npx) +**By:** PAO (DevRel) +**Date:** 2026 + +**Decision:** All user-facing docs use `npm install -g @bradygaster/squad-cli` only. No npx references. + +**What changed:** +- Removed all `npx @bradygaster/squad-cli` alternatives +- Removed all `npx github:bradygaster/squad` (deprecated) +- Replaced with `npm install -g` for installs, `squad` for usage +- Insider builds: `npm install -g @bradygaster/squad-cli@insider` + `squad upgrade` + +**What was NOT changed:** +- `npx` for dev tools (changeset, vitest, astro) — those aren't Squad CLI +- Blog posts, historical docs — reflect what was true at the time +- `agency` attribution strings — legally required (MIT license) + +--- + +### README is Orientation, Not SDK Reference +**By:** PAO (DevRel) +**Date:** 2025-07-24 + +**Decision:** README's role is discovery + quick-start. SDK internals belong in docs site. + +**What changed:** +- Removed ~212 lines of SDK deep-dive (duplicates what's in `reference/`) +- Added compact SDK pointer section with links +- Added dedicated "Upgrading" section +- README: 512 → 331 lines + +**Going forward:** SDK API surface, hook pipeline, event-driven code → `docs/`. README links out. + +--- + +### v0.9.0 Release Blog Post +**By:** PAO (DevRel) +**Date:** 2026-03-23 + +**Decision:** Created comprehensive v0.9.0 blog post (`docs/src/content/blog/028-v090-whats-new.md`). + +**Messaging:** "Personal Squad, Worktrees, and Cooperative Rate Limiting make multi-agent work safe and scalable at last." + +**10 shipped features documented:** +- Personal Squad + ambient discovery +- Worktree spawning +- Machine capability discovery +- Cooperative rate limiting +- Economy mode +- Auto-wire telemetry +- Issue lifecycle template +- KEDA external scaler +- GAP analysis verification +- Session recovery skill + +**Tone:** Factual, not hype. Demos over descriptions. No npx. + +--- + +### v0.9.0 CHANGELOG Organization +**By:** Surgeon (Release Manager) +**Date:** 2026-03-23 + +**Decision:** v0.9.0 is MAJOR minor version bump (0.8.25 → 0.9.0) justified by 40+ commits, 6+ major features, new governance layer, breaking behavioral changes. + +**CHANGELOG organized by capability cluster:** +- Personal Squad governance (4 entries) +- Orchestration (worktree + cross-squad) +- Capability discovery +- Rate limiting & cost (3 entries) +- Skills & governance (3 entries) +- Docs improvements + +**Results:** 40+ commits organized, 6+ major features highlighted, 15+ stability fixes categorized. + +--- + +### v0.9.0 → v0.9.1 Release Retrospective +**By:** Surgeon (Release Manager) +**Date:** 2026-03-23 + +**Incident:** v0.9.0 shipped with broken dependency reference (`file:../squad-sdk` in CLI package.json), hotfixed to v0.9.1. Total elapsed: 8 hours (should have been 10 minutes). + +**Root causes:** +1. **Dependency validation gap** — No pre-publish scan for `file:` references +2. **GitHub Actions cache race** — workflow_dispatch returned 422 after file deletion +3. **npm workspace publish hang** — `-w` flag doesn't work with interactive 2FA +4. **Coordinator decision-making** — Retried failed automation instead of escalating to fallback +5. **No pre-publish checklist** — Missing dependency/version/CI validation + +**Action items (URGENT):** +1. Add dependency validation to publish workflow (scan for `file:`, validate semver) +2. Establish npm workspace publish policy (never use `-w` for publish; always `cd` into package) +3. Mitigate GitHub Actions cache race condition +4. Define escalation protocol (workflow_dispatch fails 2x → fallback to local publish immediately) +5. Coordinate release readiness review (pre-flight checklist) +6. Add smoke test post-publish + +**Process changes for next release:** +1. Pre-publish validation (mandatory) +2. Simplified publish flow (remove manual workflow_dispatch step) +3. Explicit publish runbook (human-readable PUBLISH-README.md) +4. Escalation to fallback (failfast) +5. Package validation in CI (linter rule for `file:` references) diff --git a/docs/research/ci-mermaid-rendering-proposal.md b/docs/research/ci-mermaid-rendering-proposal.md new file mode 100644 index 00000000..9711ea8e --- /dev/null +++ b/docs/research/ci-mermaid-rendering-proposal.md @@ -0,0 +1,698 @@ +# CI: Mermaid → PNG Rendering Proposal + +**Requested by:** Dina +**Date:** 2026-03-25 +**Status:** Research & Proposal + +--- + +## Executive Summary + +Squad docs already contain inline Mermaid diagrams (e.g., `architecture.md`, `parallel-work.md`) embedded as code blocks in markdown. Currently, these render **dynamically in the browser** via JavaScript. This proposal evaluates three approaches to **pre-render Mermaid diagrams to PNG** at build time, reducing rendering load and improving site performance. + +**Recommendation:** **Option A + B Hybrid** — npm script hook (`prebuild`) with GitHub Actions fallback. + +--- + +## Current State + +### Existing Mermaid Usage +- **Location:** Inline mermaid code blocks in `.md` files +- **Examples:** + - `docs/src/content/docs/concepts/architecture.md` — sequenceDiagram (user flow), graph (component stack) + - `docs/src/content/docs/concepts/parallel-work.md` — graph (fan-out execution) + - `docs/src/content/docs/concepts/memory-and-knowledge.md` — mermaid blocks + - `docs/src/content/docs/concepts/your-team.md` — mermaid blocks +- **Current rendering:** Astro outputs mermaid code blocks as-is; client-side JavaScript (mermaid.js library) renders them in the browser +- **Build process:** `docs/package.json` runs `"build": "astro build && npx pagefind --site dist"` +- **Astro config:** `docs/astro.config.mjs` currently has no mermaid integration; uses remark/rehype plugins for links and pagefind attributes + +### Observations +- No `.mmd` source files exist — all diagrams are inline +- No Mermaid-specific tooling in the docs pipeline yet +- Astro has no built-in Mermaid renderer; integration requires custom plugin or pre-build script +- GitHub Actions workflow (`.github/workflows/squad-docs.yml`) is simple: checkout → install → build → deploy + +--- + +## Tool Analysis: `@mermaid-js/mermaid-cli` (mmdc) + +### What is mmdc? +Official CLI for Mermaid rendering. Converts `.mmd` files or inline Mermaid syntax to PNG/SVG. + +### Installation & Usage +```bash +# Install +npm install --save-dev @mermaid-js/mermaid-cli + +# Render a .mmd file to PNG +mmdc -i diagram.mmd -o diagram.png + +# Render with theme +mmdc -i diagram.mmd -o diagram.png --theme dark +``` + +### Pros +- Official, well-maintained tool +- No custom code needed for rendering +- Supports themes (light/dark), output formats (PNG/SVG), scaling +- Cross-platform (Windows, macOS, Linux) + +### Cons +- Requires Puppeteer (headless Chrome) — adds ~100MB to CI, slows initial build +- Heavy for lightweight diagrams +- No real-time preview without running locally + +### Alternatives Considered +- **mermaid.js direct (Node.js API):** Lighter, but less stable; limited CLI support +- **Kroki:** Cloud-based diagram rendering (generic, not mermaid-specific; adds external dependency) +- **Hand-drawn SVGs:** No automation; doesn't scale + +**Verdict:** mmdc is the standard for Mermaid pre-rendering. Trade-offs are acceptable. + +--- + +## Three Approaches Evaluated + +### Option A: Pre-build Step in GitHub Actions (CI-Only) + +**Approach:** +1. Add a GitHub Actions step before `npm run build` in `.github/workflows/squad-docs.yml` +2. Use mmdc to scan for mermaid blocks or `.mmd` files +3. Generate PNGs to `docs/src/content/docs/*/images/` +4. Markdown references diagrams as `![alt](./images/diagram.png)` instead of code blocks + +**Implementation (YAML snippet):** +```yaml +- name: Install Mermaid CLI + working-directory: docs + run: npm install --save-dev @mermaid-js/mermaid-cli puppeteer + +- name: Render Mermaid diagrams + working-directory: docs + run: | + node scripts/render-mermaid.js + # Scans src/content/docs/**/*.md, extracts mermaid blocks, + # renders to src/content/docs/*/images/*.png +``` + +**Pros:** +- ✅ PNGs generated fresh every CI build, never stale +- ✅ No local tooling burden on developers +- ✅ Minimal docs/package.json changes +- ✅ Reduces client-side rendering work + +**Cons:** +- ❌ Developers cannot preview diagrams locally (`npm run dev` sees raw code blocks) +- ❌ Requires ~1-2min extra CI time for Puppeteer + rendering +- ❌ Script must extract mermaid from markdown (requires regex/AST parsing) +- ❌ No real-time feedback during authoring + +**Timeline:** ~3 min (Puppeteer download + rendering) +**Local DX:** Poor — authors can't preview locally + +--- + +### Option B: npm Script Hook (Local + CI) + +**Approach:** +1. Add `"prebuild": "node scripts/render-mermaid.js"` to `docs/package.json` +2. Script discovers `.mmd` files in `docs/src/content/docs/diagrams/` +3. Renders to `docs/src/content/docs/*/images/` (sibling or nearby directory) +4. Markdown references: `![alt](./images/diagram.png)` +5. Run `npm ci && npm run build` locally or in CI → prebuild runs automatically + +**Implementation (package.json):** +```json +{ + "scripts": { + "prebuild": "node scripts/render-mermaid.js", + "build": "astro build && npx pagefind --site dist" + } +} +``` + +**Pros:** +- ✅ Works locally AND in CI (single script, reused everywhere) +- ✅ Developers can run `npm run build` locally and see generated PNGs +- ✅ Real-time feedback during authoring +- ✅ Faster CI (no Puppeteer re-download on every run — cached via npm) +- ✅ Simpler workflow: edit `.mmd` → run build → preview + +**Cons:** +- ❌ Requires mmdc + Puppeteer locally (developers must install) +- ❌ Adds ~100MB to `node_modules` +- ❌ First build slower if Puppeteer isn't cached +- ❌ `.mmd` files are source — must be committed to repo (PNGs generated or .gitignored) + +**Timeline:** ~1-2 min locally (first time); ~30s cached +**Local DX:** Excellent — live preview, iterate on diagrams + +--- + +### Option C: Astro Integration Plugin + +**Approach:** +1. Create custom Astro integration in `docs/src/plugins/astro-mermaid.mjs` +2. Plugin processes mermaid code blocks during Astro's build phase +3. Outputs inline SVG or references PNG +4. No separate npm script; happens transparently during `astro build` + +**Implementation sketch:** +```javascript +// docs/src/plugins/astro-mermaid.mjs +export default function astroBuildMermaid() { + return { + name: 'astro-mermaid', + hooks: { + 'astro:build:done': async ({ dir }) => { + // Scan generated HTML, find mermaid blocks, + // replace with SVG or img tags + } + } + }; +} +``` + +**Pros:** +- ✅ Cleanest integration — no separate step +- ✅ Transparent to build process +- ✅ Diagrams remain inline or optimized based on strategy + +**Cons:** +- ❌ Requires custom JavaScript code (not trivial) +- ❌ Post-processing approach (slower than pre-rendering) +- ❌ Tight coupling to Astro internals (maintenance burden) +- ❌ Debugging is harder if rendering fails mid-build +- ❌ No proven library; custom implementation risk + +**Timeline:** ~2-3 min (custom code + rendering) +**Local DX:** Good but opaque — magic happens inside build + +--- + +## Decision Matrix + +| Factor | Option A | Option B | Option C | +|--------|----------|----------|----------| +| **Local preview** | ❌ None | ✅ Excellent | ✅ Good | +| **CI performance** | ⚠️ Slow (DL Puppeteer) | ✅ Fast | ⚠️ Moderate | +| **Developer friction** | ✅ Low (no local install) | ⚠️ Moderate (install mmdc) | ✅ Low | +| **Implementation** | ⚠️ Medium (script + YAML) | ✅ Simple (npm prebuild) | ❌ Complex (Astro plugin) | +| **Maintainability** | ✅ High | ✅ High | ❌ Low | +| **Debuggability** | ✅ Explicit step | ✅ Explicit step | ❌ Hidden in build | +| **Cache support** | ⚠️ No | ✅ Yes | ⚠️ Limited | + +--- + +## Recommendation: **Option B (npm prebuild) + Option A (CI fallback)** + +### Why Option B Primary? +- **DX wins:** Developers can author and preview diagrams locally in real-time +- **Reliable:** Same script runs everywhere (local, CI, CI/CD) +- **Speed:** npm cache hits after first run +- **Simplicity:** One script, one command + +### Why Add Option A Fallback? +- **Fresh CI builds:** Even if a developer doesn't run prebuild locally, CI always regenerates PNGs +- **Safety net:** Diagrams never stale in deployed site +- **Scalability:** If local develop workflow changes, CI is still safe + +### Hybrid Approach (Recommended) + +**Step 1: Create prebuild script** +```bash +docs/scripts/render-mermaid.js +``` +- Discovers `.mmd` files in `docs/src/content/docs/diagrams/` +- Renders to `docs/src/content/docs/*/images/` +- Handles errors gracefully (warn, don't fail build) + +**Step 2: Update docs/package.json** +```json +{ + "scripts": { + "prebuild": "node scripts/render-mermaid.js", + "build": "astro build && npx pagefind --site dist" + }, + "devDependencies": { + "@mermaid-js/mermaid-cli": "^10.9.0" + } +} +``` + +**Step 3: Update .github/workflows/squad-docs.yml** +```yaml +- name: Install docs dependencies + working-directory: docs + run: npm ci + # npx prebuild runs automatically via npm's "pre" hook + +- name: Build docs site + working-directory: docs + run: npm run build +``` + +**Step 4: Source file organization** +``` +docs/ +├── src/ +│ └── content/ +│ └── docs/ +│ ├── concepts/ +│ │ ├── architecture.md +│ │ ├── images/ +│ │ │ ├── architecture-flow.png (generated) +│ │ │ └── architecture-components.png (generated) +│ │ └── diagrams/ +│ │ ├── architecture-flow.mmd (source) +│ │ └── architecture-components.mmd (source) +│ ├── features/ +│ └── ... +└── scripts/ + └── render-mermaid.js +``` + +**Step 5: .gitignore** +``` +docs/src/content/docs/**/images/*.png +``` + +--- + +## Implementation Details + +### Source Format: `.mmd` Files vs Inline + +**Recommendation:** `.mmd` source files + markdown references + +**Rationale:** +- **Separation of concerns:** Diagram logic separate from narrative +- **Reusability:** One diagram referenced from multiple pages +- **Easier authoring:** No escaping backticks in markdown +- **Version history:** Git diff on `.mmd` files is cleaner than on markdown code blocks + +**Markdown usage:** +```markdown +## System Architecture + +The system is organized into three layers: + +![Architecture flow diagram](./images/architecture-flow.png) + +### Components + +![Component stack diagram](./images/architecture-components.png) +``` + +**Alternative (if preferred):** Keep inline mermaid blocks; parsing script extracts + renders them. More work in the script, but no `.mmd` files. + +--- + +## File Organization + +### Proposed Structure +``` +docs/src/content/docs/ +├── concepts/ +│ ├── architecture.md +│ ├── diagrams/ +│ │ ├── user-flow.mmd +│ │ └── component-stack.mmd +│ └── images/ +│ ├── user-flow.png (git-ignored, generated) +│ └── component-stack.png (git-ignored, generated) +├── features/ +│ ├── parallel-execution.md +│ ├── diagrams/ +│ │ └── fan-out.mmd +│ └── images/ +│ └── fan-out.png (generated) +└── ... +``` + +**Benefits:** +- Source and output live near the content +- Clear separation of input (`.mmd`) and output (`.png`) +- Easy to maintain per-page diagrams +- Generated PNGs are `.gitignore`d (not bloat repo) + +--- + +## Local Development Experience + +### For Authors + +**Workflow:** +1. Edit or create `.mmd` file in `docs/src/content/docs/{section}/diagrams/` +2. Update markdown to reference the PNG: `![alt](./images/diagram-name.png)` +3. Run `npm run build` from `docs/` directory +4. PNGs are generated to `docs/src/content/docs/{section}/images/` +5. Run `npm run dev` to see site with PNGs rendered +6. Iterate on `.mmd` until satisfied + +**Dependencies:** Must run `npm install` once in `docs/` to get mmdc + Puppeteer + +**First-time setup (one-time):** +```bash +cd docs +npm install +``` + +**Then, authoring is seamless:** +```bash +npm run build # Renders diagrams + builds site +npm run dev # Preview live +``` + +--- + +## GitHub Actions Snippet (Recommended Hybrid) + +**File: `.github/workflows/squad-docs.yml`** + +```yaml +name: Squad Docs — Build & Deploy + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - 'docs/**' + - '.github/workflows/squad-docs.yml' + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + cache-dependency-path: docs/package-lock.json + + - name: Install docs dependencies + working-directory: docs + run: npm ci + # prebuild hook runs automatically: npm triggers "prebuild" before "build" + + - name: Build docs site + working-directory: docs + run: npm run build + # "build" script runs "prebuild" automatically (npm lifecycle hook), + # then runs astro build + pagefind + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/dist + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 +``` + +**Key points:** +- ✅ No new GitHub Actions step required +- ✅ npm's built-in `prebuild` hook runs automatically when `npm run build` is called +- ✅ Puppeteer downloads on first CI run, then cached +- ✅ Fresh PNGs on every push to main + +--- + +## Render Script Outline: `docs/scripts/render-mermaid.js` + +```javascript +#!/usr/bin/env node + +import fs from 'fs/promises'; +import path from 'path'; +import { execSync } from 'child_process'; + +const DOCS_ROOT = path.join(process.cwd(), 'src/content/docs'); +const MMDC = 'npx @mermaid-js/mermaid-cli'; + +/** + * Recursively find all .mmd files in docs/src/content/docs/ + */ +async function findMermaidFiles(dir) { + const files = []; + const entries = await fs.readdir(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + files.push(...await findMermaidFiles(fullPath)); + } else if (entry.name.endsWith('.mmd')) { + files.push(fullPath); + } + } + + return files; +} + +/** + * Render a single .mmd file to PNG + */ +async function renderDiagram(mmdPath) { + try { + // Output PNG to sibling images/ directory + const dir = path.dirname(mmdPath); + const filename = path.basename(mmdPath, '.mmd'); + const imageDir = path.join(dir, '..', 'images'); + const pngPath = path.join(imageDir, `${filename}.png`); + + // Ensure images/ directory exists + await fs.mkdir(imageDir, { recursive: true }); + + // Run mmdc + const cmd = `${MMDC} -i "${mmdPath}" -o "${pngPath}"`; + execSync(cmd, { stdio: 'inherit' }); + + console.log(`✅ Rendered: ${path.relative(DOCS_ROOT, pngPath)}`); + } catch (err) { + console.error(`❌ Failed to render ${mmdPath}:`, err.message); + // Don't fail build; warn and continue + } +} + +/** + * Main + */ +async function main() { + console.log('🎨 Rendering Mermaid diagrams...'); + + try { + const mmdFiles = await findMermaidFiles(DOCS_ROOT); + + if (mmdFiles.length === 0) { + console.log('ℹ️ No .mmd files found.'); + return; + } + + console.log(`Found ${mmdFiles.length} diagram(s).`); + + for (const mmdPath of mmdFiles) { + await renderDiagram(mmdPath); + } + + console.log('✨ Done.'); + } catch (err) { + console.error('Error:', err); + process.exit(1); + } +} + +main(); +``` + +--- + +## PNGs in Git: Commit or .gitignore? + +### Recommendation: `.gitignore` PNGs, commit `.mmd` source files + +**Rationale:** +- PNGs are generated artifacts, not source +- Source of truth is `.mmd` files +- Reduces repo size and history bloat +- If someone changes a `.mmd` file, CI regenerates fresh PNG +- Team members who clone can rebuild locally (`npm run build`) + +**.gitignore entry:** +``` +docs/src/content/docs/**/images/*.png +``` + +**Alternative:** Commit PNGs if: +- You want exact visual parity across all CI/local builds +- PNGs are large and regeneration is slow +- Team doesn't have Puppeteer installed locally + +Not recommended for this use case (diagrams are small, regeneration is fast, DX is better with source-only). + +--- + +## Testing & Validation + +### What to test in CI/CD + +**1. Prebuild script errors don't block build** +```javascript +// render-mermaid.js should warn, not fail +if (mmdFiles.length === 0) { + console.log('ℹ️ No .mmd files found.'); + return; // exit 0 +} +``` + +**2. All generated PNGs exist** +```bash +# Add to docs/test/ if desired +if [ ! -f "src/content/docs/concepts/images/user-flow.png" ]; then + echo "ERROR: user-flow.png not generated" + exit 1 +fi +``` + +**3. Astro build still works after prebuild** +- Existing `npm run test:build` validates Astro output +- No new tests needed if script doesn't change markdown + +### Local validation + +```bash +# Author runs locally +cd docs +npm install # First time only +npm run build # Renders + builds +npm run dev # Preview with PNGs + +# Verify PNGs exist +ls src/content/docs/concepts/images/ +``` + +--- + +## Rollout Plan + +### Phase 1: Implementation (Week 1) +- [ ] Create `docs/scripts/render-mermaid.js` +- [ ] Update `docs/package.json` with `@mermaid-js/mermaid-cli` dependency and `prebuild` script +- [ ] Create `.mmd` source files for existing diagrams (e.g., `architecture.md` → `diagrams/user-flow.mmd`, `diagrams/components.mmd`) +- [ ] Update markdown to reference PNGs +- [ ] Test locally: `npm run build` → PNGs generated ✅ +- [ ] Commit `.mmd` files + script; `.gitignore` PNGs + +### Phase 2: CI Integration (Week 1) +- [ ] Update `.github/workflows/squad-docs.yml` (no changes needed if prebuild is in package.json) +- [ ] Merge to dev, test CI run +- [ ] Verify PNGs generated in CI artifact +- [ ] Deploy to main + +### Phase 3: Documentation (Week 2) +- [ ] Add to docs handbook: "How to create diagrams" (`.mmd` files, prebuild script, preview locally) +- [ ] Link from contribution guidelines + +--- + +## Risks & Mitigations + +| Risk | Mitigation | +|------|-----------| +| **Puppeteer fails in CI** | Pre-cache Puppeteer binary in `node_modules` cache; use GitHub Actions service container as fallback | +| **Large diagrams slow build** | Render in parallel; add timeout to mmdc (default ~30s/diagram is fine) | +| **Developers forget to run prebuild locally** | Document in contribution guide; CI always regenerates, so site is safe | +| **PNG filenames don't match references** | Script uses consistent naming: `.mmd` basename → `.png` basename | +| **First CI run is slow** | Expected (~2-3 min); subsequent runs cached. Document in PR/release notes | + +--- + +## Conclusion + +**Recommended approach: Option B (npm prebuild) + Option A (CI safety net)** + +**Benefits:** +- ✅ Excellent local DX (live preview, iterate on diagrams) +- ✅ Reliable CI (always fresh PNGs) +- ✅ Simple implementation (one script, npm lifecycle hook) +- ✅ Maintainable (explicit, debuggable steps) +- ✅ No GitHub Actions pipeline changes needed + +**Next steps:** +1. Approve proposal +2. Implement render script +3. Create `.mmd` source files for existing diagrams +4. Test locally + CI +5. Document for team +6. Deploy to main + +--- + +## Appendix: Example Diagram Files + +### `docs/src/content/docs/concepts/diagrams/architecture-user-flow.mmd` +```mermaid +sequenceDiagram + participant User + participant CLI as Copilot CLI + participant Coordinator + participant Agents + participant State as .squad/ Files + + User->>CLI: Message (Copilot Chat) + CLI->>Coordinator: Parse input, resolve team + Coordinator->>State: Load team.md, routing.md + Coordinator->>Agents: Spawn agents (parallel) +``` + +### `docs/src/content/docs/concepts/diagrams/architecture-components.mmd` +```mermaid +graph TB + UI["🖥 User Interfaces
Copilot CLI / VS Code / Shell"] + Coordinator["🎯 Coordinator
Routes work, spawns agents"] + Agents["🤖 Agents
Autonomous work units"] + State["💾 State
.squad/ files (team, decisions)"] + Git["🔗 Git
Persist & version control"] + + UI --> Coordinator + Coordinator --> Agents + Agents --> State + State --> Git +``` + +### `docs/src/content/docs/concepts/architecture.md` (updated reference) +```markdown +# Architecture + +## User Interaction Flow + +![User flow diagram](./images/architecture-user-flow.png) + +## Component Architecture + +![Component stack diagram](./images/architecture-components.png) +``` + +--- + +**Prepared by PAO | Squad DevRel** diff --git a/docs/src/content/docs/sample-prompts.md b/docs/src/content/docs/sample-prompts.md deleted file mode 100644 index b3a73fee..00000000 --- a/docs/src/content/docs/sample-prompts.md +++ /dev/null @@ -1,412 +0,0 @@ -# Sample Prompts - -Ready-to-use prompts for Squad. Copy any prompt, open Copilot, select **Squad**, and paste it in. - ---- - -## Quick Builds - -Small projects that ship in a single session. Good for parallel fan-out and fast iteration. - ---- - -### 1. CLI Pomodoro Timer - -``` -I'm building a cross-platform CLI pomodoro timer in Python: -- Configurable work/break intervals (25/5/15 defaults) -- Persistent stats tracker (local JSON) -- Desktop notifications (macOS, Windows, Linux) -- Focus mode: blocks domains via /etc/hosts (with undo) -- --report flag for weekly stats table - -Set up the team. I want this done fast — everyone works at once. -``` - -**What it demonstrates:** -- Parallel fan-out on a small, well-scoped project -- Backend handles timer logic while systems agent tackles cross-platform notifications -- Tester writes test cases from spec while implementation is in flight - ---- - -### 2. Markdown Static Site Generator - -``` -Zero-dependency static site generator in Node.js: markdown→HTML with built-in template, generates index page, outputs to dist/. Support front matter (title, date, tags), tag index pages, RSS feed. No frameworks — just fs, path, and a custom markdown parser. - -Set up the team and start building. -``` - -**What it demonstrates:** -- Agents own distinct pipeline components (parser, template engine, RSS, file I/O) -- Tester writes test cases from spec while others build in parallel -- Front matter format decisions propagate via decisions.md - ---- - -### 3. Retro Snake Game - -``` -Browser Snake game (vanilla HTML/CSS/JS, no frameworks): -- Canvas rendering at 60fps -- Arrow keys and WASD controls -- Score tracking with localStorage high scores -- Progressive speed increase every 5 points -- Retro CRT-style CSS filters -- Mobile: touch swipe controls -- Sound effects via Web Audio API - -Start building — I want to play in 20 minutes. -``` - -**What it demonstrates:** -- Frontend, audio, and input handling built in parallel -- Tester writes Playwright tests while game is under construction -- Fast iteration with visible progress across agents - ---- - -### 4. Turn-by-Turn Text Adventure Engine - -``` -Text-based adventure engine in TypeScript: -- Load worlds from JSON (rooms, items, NPCs, transitions) -- Command parser: go [dir], look, take [item], use [item] on [target], talk to [npc], inventory -- Sample adventure: 10 rooms, 5 items, 3 NPCs, 2 puzzles -- Save/load game state to JSON -- Terminal via Node.js with colored output (chalk) -- Narrator voice: descriptions vary by inventory/actions - -Build engine and sample adventure simultaneously. Content writer and engine builder work in parallel. -``` - -**What it demonstrates:** -- Natural split between engine logic and content creation -- Both streams run fully in parallel with shared data format decisions -- Tester writes test cases from spec before implementation completes - ---- - -### 5. Arcane Duel — A Card Battle Game - -``` -Strategic card duel game (browser, inspired by MTG): -- 30+ cards across 4 types: Attack, Defense, Spell, Trap (with mana cost, power, toughness, effects) -- Turn phases: Draw → Main → Combat → End -- Mana system: +1 per turn (max 10), some cards generate bonus mana -- Stack-based spell resolution -- HP: 20 each, win at 0 -- AI opponent with basic strategy -- HTML/CSS grid battlefield showing fields, hands, graveyards -- Card hover preview - -One agent designs cards/balance, another builds engine/rules, another builds UI, tester validates combat math. Go. -``` - -**What it demonstrates:** -- Deep parallelism requiring early data format alignment via decisions.md -- UI scaffolding proceeds while card design is underway -- Scribe's decision propagation becomes critical (mana curve affects engine and AI) - ---- - -### Squad Blog Engine (Meta Demo) - -``` -Static blog engine rendering markdown posts to HTML (no frameworks): - -Input: docs/blog/ markdown with YAML frontmatter (title, date, author, wave, tags, status, hero). - -Output: -- Index page: posts sorted by date, with title/hero/author/tags -- Post pages: clean typography, syntax-highlighted code, responsive tables -- Tag index grouping posts by tag -- Wave navigation: ← Previous | Next → links -- Dark mode toggle (CSS custom properties, localStorage) -- RSS feed (feed.xml) - -Design: Clean, modern, developer-focused. Monospace headings, proportional body. Dark code blocks with copy button. Mobile responsive. Fast — no JS for reading (JS only for dark mode and copy). - -Build parser, template engine, RSS generator, static output (dist/). Include `node build.js` script. Set up team and build in one session. -``` - -**What it demonstrates:** -- Meta-demo where Squad builds its own publishing tool -- All components (parser, templating, RSS, CSS) build in parallel -- Finished product is visual, functional, and self-documenting - ---- - -## Mid-Size Projects - -Real coordination needed. Agents make architectural decisions, share them, and build across multiple rounds. - ---- - -### 6. Cloud-Native E-Commerce Store - -``` -Build an event-driven e-commerce store: -- Product Catalog API (Node.js/Express, PostgreSQL) — CRUD + search -- Order Service (Node.js) — async processing via message queue, payment stubs, events -- Notification Service — listens for order events, emails confirmations -- API Gateway — auth (JWT), rate limiting -- RabbitMQ or in-memory stub for local dev -- React SPA: product grid, cart, checkout - -Each service with its own Dockerfile. Include docker-compose.yml. Orders return 202 Accepted, status polled/pushed via WebSocket. - -Set up a team. One agent per service. Coordinate on API contracts and event schemas early, then build in parallel. -``` - -**What it demonstrates:** -- True microservice parallelism with contract-first coordination -- Event schema decisions must propagate early via Scribe -- API gateway scaffolds while downstream services build independently - ---- - -### 7. Playwright-Tested Dashboard App - -``` -Build a project management dashboard (React + TypeScript, Node.js/Express): -- Kanban board with drag-and-drop (Backlog, In Progress, Review, Done) -- Task creation: title, description, assignee, priority, due date -- Filtering by assignee, priority, status -- Real-time updates via WebSocket -- User auth: login/signup (JWT, bcrypt) -- SQLite + Drizzle ORM - -Full Playwright test suite covering login, CRUD, drag-and-drop, filtering, real-time sync (two browser contexts). Write Gherkin feature files FIRST, then implement Playwright step definitions. Runnable with `npx playwright test`. - -Set up the team. Write Gherkin specs and test skeletons before implementation starts, update as UI takes shape. -``` - -**What it demonstrates:** -- Test-first development with Gherkin specs written before implementation -- Frontend and backend build in parallel while tests scaffold -- Anticipatory work pattern: tests and implementation converge without blocking - ---- - -### 8. GitHub Copilot Extension - -``` -Build a GitHub Copilot Chat extension (Copilot Extensions SDK): -- Act as @code-reviewer agent -- Accept GitHub repo URL or PR number -- Fetch diff via GitHub API, analyze for security (SQL injection, XSS, secrets), performance (N+1 queries), style violations (configurable .code-reviewer.yml) -- Return structured feedback with file-level annotations -- Blackbeard-style SSE streaming response -- Deploy as Vercel serverless function -- Include GitHub App manifest - -Read SDK docs carefully. One agent owns SDK integration/streaming, another owns analysis engine, another owns GitHub API. Set up the team. -``` - -**What it demonstrates:** -- Agents read external SDK docs and build to prescribed patterns -- SDK integration and analysis engine work in parallel with shared interface contract -- Real-world API integration with deployment considerations - ---- - -### 9. .NET Aspire Cloud-Native App - -``` -Build a cloud-native app with .NET Aspire (read https://learn.microsoft.com/en-us/dotnet/aspire/): -- AppHost orchestrating all services -- Blazor Server dashboard: current conditions + 5-day forecast for saved cities -- Weather API service: wraps OpenWeatherMap with Redis caching -- User Preferences service: stores cities (PostgreSQL) -- Background Worker: refreshes cache every 15 minutes -- Service discovery via Aspire (no hardcoded URLs) -- Health checks and OpenTelemetry tracing - -Team organized by Aspire integration: AppHost/discovery, Redis caching, PostgreSQL, Blazor frontend, background worker. Tester validates service discovery and end-to-end data flow. Set up the team. -``` - -**What it demonstrates:** -- Agents specialized by infrastructure component rather than traditional roles -- AppHost coordinates wiring while service agents build independently -- Infrastructure decisions (service names, connection strings) propagate via decisions.md - ---- - -## Large Projects - -Complex coordination, memory, and team size. Multiple rounds, cross-cutting decisions, agents remember earlier work. - ---- - -### 10. Legacy .NET-to-Azure Migration - -``` -Migrate legacy .NET Framework to Azure. Clone: -1. https://github.com/bradygaster/ProductCatalogApp — ASP.NET MVC with WCF SOAP, in-memory repo, MSMQ orders -2. https://github.com/bradygaster/IncomingOrderProcessor — Windows Service monitoring MSMQ - -Target: -- ProductCatalogApp → ASP.NET Core/.NET 10 or Blazor on App Service. WCF→REST API, MSMQ→Service Bus -- IncomingOrderProcessor → Azure Functions with Service Bus trigger -- Shared models → .NET 10 class library -- Infrastructure: Bicep for App Service, Function App, Service Bus -- CI/CD: GitHub Actions -- Local dev: docker-compose or Aspire - -Preserve all business logic. SOAP→REST with same data structures, MSMQ→Service Bus compatible format. - -Team: web app migration, WCF-to-API, Windows Service-to-Functions, shared models, Azure infrastructure, CI/CD, tester. Start with migration plan. -``` - -**What it demonstrates:** -- Realistic enterprise migration from legacy .NET Framework to modern Azure -- Agents analyze unfamiliar code and translate to Azure-native patterns -- Business logic preservation while modernizing infrastructure (WCF→REST, MSMQ→Service Bus) - ---- - -### 11. Multiplayer Space Trading Game - -``` -Build multiplayer space trading game (browser-based): -- Galaxy: 50+ procedural star systems with stations, trade routes -- Economy: dynamic commodity prices (fuel, ore, food, tech, luxuries) driven by supply/demand -- Ships: 3 tiers with cargo capacity, fuel range, hull strength -- Trading: buy low, sell high. Prices shift with player activity and events -- Combat: turn-based encounters with pirates/players -- Multiplayer: WebSocket real-time. Players see each other, chat, PvP opt-in -- Persistence: PostgreSQL (credits, cargo, location, ship) -- Frontend: Canvas galaxy map, HTML/CSS panels for station/trading/inventory - -Tech: Node.js, PostgreSQL, WebSocket, vanilla HTML/CSS/Canvas. - -One agent per system: economy/trading, galaxy generator/map, combat, multiplayer/networking, frontend UI, tester. Economy and galaxy work simultaneously — agree on star system data format early. Go. -``` - -**What it demonstrates:** -- Complex game with 6+ agents owning distinct but interoperating systems -- Data format decisions shared early and respected across all agents -- Economy and galaxy agents work in parallel from turn 1 - ---- - -### 12. AI Recipe App with Image Recognition - -``` -Build recipe app with image recognition (React Native Expo, Python FastAPI, SQLite): -- Camera: photograph ingredients -- Image analysis: GPT-4 Vision to identify ingredients -- Recipe matching: match against database (50+ recipes) -- Recipe display: ingredients (have vs. need), instructions, time -- Favorites: save, rate, notes -- Shopping list: auto-generate missing ingredients -- Dietary filters: vegetarian, vegan, gluten-free, dairy-free - -One agent: React Native frontend. One: FastAPI backend + DB. One: vision/AI integration. One: recipe curation/seed data. Tester: API tests with mocked vision responses. Set up team. -``` - -**What it demonstrates:** -- Cross-platform mobile + backend + AI integration in one project -- Recipe curator and AI integration agent work simultaneously with shared taxonomy -- Tester mocks vision API responses for deterministic testing before real integration - ---- - -### 13. DevOps Pipeline Builder - -``` -Build self-service DevOps platform (React, Go, PostgreSQL, Docker): -- Pipeline designer: drag-and-drop UI composing stages (build, test, deploy, notify) -- Stage templates: npm build, Docker build, Helm deploy, Slack notify -- Pipeline execution: stages run as Docker containers (Go orchestration) -- Live logs: stream to browser via SSE -- Pipeline-as-code: export/import YAML (GitHub Actions compatible) -- Secrets management: encrypted storage -- Execution history: searchable logs with status, duration, artifacts - -Team: frontend (drag-and-drop), backend (execution engine), Docker/infrastructure, security (secrets), tester. Set up team. -``` - -**What it demonstrates:** -- Agents with diverse expertise (UI, containers, cryptography) on one product -- Execution engine and pipeline designer build in parallel with shared data model -- Security agent works independently on secrets encryption - ---- - -### 14. Roguelike Dungeon Crawler - -``` -Build browser-based roguelike dungeon crawler: -- Dungeons: procedural rooms/corridors (BSP or cellular automata), 10 floors, scaling difficulty -- Character: warrior/mage/rogue with unique abilities (3 each), health/mana/stamina -- Combat: turn-based, grid-positioned. Enemy AI flanks, retreats at low HP -- Items: weapons, armor, potions, scrolls. Random loot tables. Unidentified items until used -- Fog of war: tile-based visibility with raycasting -- Rendering: Canvas with tilemap (16x16 or 32x32 colored squares) -- Permadeath: high score table with name, class, floor, cause of death -- Save: save-on-exit only (LocalStorage) - -One agent per: dungeon gen, combat + AI, items + loot, rendering + fog of war, tester. All build simultaneously with shared tile/entity data model. Start building. -``` - -**What it demonstrates:** -- Four independently buildable systems converging on shared data model -- Early data model decision via decisions.md enables full parallelism -- Tester validates game math from specs while systems are under construction - ---- - -### 15. Real-Time Collaborative Whiteboard - -``` -Build real-time collaborative whiteboard using React Flow (React + TypeScript, Node.js, WebSocket): -- Built on React Flow (https://reactflow.dev/) -- Shapes: rectangles, circles, text, sticky notes, arrows/edges -- Drag-and-drop from palette, reposition, resize (handles) -- Color picker, stroke width, fill/background per shape -- Multi-select (bounding box), group operations -- Real-time sync: cursor + edits via WebSocket -- Rooms: shareable URL -- Undo/redo per user -- Export: PNG and SVG -- Persistence: PostgreSQL (nodes, edges, viewport), auto-save every 30s - -Frontend agent: React Flow + drag-and-drop. Networking: WebSocket sync + conflict resolution. Backend: rooms + persistence. Tester: Playwright multi-user drag-and-drop tests. Set up team. -``` - -**What it demonstrates:** -- Networking and frontend agents coordinate closely on React Flow data model -- Frontend leverages React Flow's built-in features while networking syncs across users -- Tester writes multi-context Playwright tests for real-time sync validation - ---- - -### 16. Multiplayer Dice Roller — Bar Games PWA - -``` -Build mobile-first PWA dice roller (React + TypeScript, Three.js/React Three Fiber, Node.js + WebSocket, PostgreSQL): -- Mobile-first responsive, PWA installable, works offline -- Double-tap to roll: realistic 3D dice with physics (Three.js) -- Customizable: 1-10 dice, die types (d6, d10, d12, d20), colors -- Multiplayer: rooms with 6-digit code or QR, real-time roll sync, chat -- Game modes: Freeroll, Yahtzee (auto-scoring), Liar's Dice, custom rules -- Score history: roll log, replay animations, export JSON -- Sound effects, haptic feedback, night mode - -One agent: 3D dice/physics. One: PWA/gesture handling. One: multiplayer backend (rooms, WebSocket, scores). One: game logic. Tester: mobile Playwright for touch + multiplayer. Set up team. -``` - -**What it demonstrates:** -- Mobile-first project with agents specialized by concern (3D, touch, networking, logic) -- 3D and gesture agents coordinate on tap-to-roll triggers and animation states -- PWA requirements and mobile testing showcase production mobile app concerns - ---- - -## Advanced Features - -For detailed guidance on advanced features like export/import, GitHub Issues integration, ceremonies, PRD mode, human team members, and skills, see [Tips and Tricks](tips-and-tricks.md). - diff --git a/docs/src/content/docs/tour-github-issues.md b/docs/src/content/docs/tour-github-issues.md deleted file mode 100644 index aff55a70..00000000 --- a/docs/src/content/docs/tour-github-issues.md +++ /dev/null @@ -1,199 +0,0 @@ -# GitHub Issues Walkthrough - -A step-by-step tour of Squad's GitHub Issues workflow. This connects your team to a real repository's issue tracker so agents can pick up issues, create branches, open PRs, and handle review feedback. - -**Prerequisite:** The `gh` CLI must be installed and authenticated (`gh auth login`). - ---- - -## 1. Connect to a Repository - -Tell Squad which repo to track: - -``` -> Connect to myorg/recipe-app -``` - -Squad stores the issue source in team state: - -``` -✅ Issue source stored: myorg/recipe-app - Using gh CLI for issue tracking. -``` - -From now on, Squad can read issues from that repo and create branches and PRs against it. - ---- - -## 2. View the Backlog - -Ask to see open issues: - -``` -> Show the backlog -``` - -Squad pulls open issues via `gh` and displays them: - -``` -┌─────┬──────────────────────────────────────────┬───────────┬────────────┐ -│ # │ Title │ Labels │ Assignee │ -├─────┼──────────────────────────────────────────┼───────────┼────────────┤ -│ 12 │ Add ingredient search │ feature │ — │ -│ 15 │ Fix pagination on recipe list │ bug │ — │ -│ 18 │ Add user profile page │ feature │ — │ -│ 21 │ Rate limiting on API endpoints │ security │ — │ -│ 23 │ Mobile responsive layout │ ui │ — │ -└─────┴──────────────────────────────────────────┴───────────┴────────────┘ -5 open issues -``` - ---- - -## 3. Work on an Issue - -Pick an issue for an agent to work on: - -``` -> Work on #12 -``` - -Squad reads the issue details, routes it to the right agent, and kicks off the workflow: - -``` -🔧 Dallas — picking up #12 (Add ingredient search) - -Dallas is reading the issue and starting work. -``` - -Behind the scenes, the agent: - -1. **Creates a branch** — named descriptively based on the issue (e.g., `12-add-ingredient-search`) -2. **Does the implementation work** — writes code, tests, whatever the issue requires -3. **Opens a PR** — linked back to issue #12, with a description of what was done - -When the agent finishes: - -``` -🔧 Dallas — Completed #12 (Add ingredient search) - Branch: 12-add-ingredient-search - PR: #24 opened — "Add ingredient search (#12)" - Files changed: - - src/routes/recipes.ts (added search endpoint) - - src/models/recipe.ts (added text index) - - test/search.test.ts (6 test cases) -``` - ---- - -## 4. Multiple Issues in Parallel - -You can assign multiple issues at once: - -``` -> Work on #15 and #23 -``` - -``` -🔧 Dallas — picking up #15 (Fix pagination on recipe list) -⚛️ Ripley — picking up #23 (Mobile responsive layout) -📋 Scribe — logging session -``` - -Each agent creates its own branch and works independently. If your repo supports worktrees, Squad can work on multiple branches simultaneously. - ---- - -## 5. Handle Review Feedback - -After a PR is open, reviewers may leave comments. When you see feedback: - -``` -> There's review feedback on PR #24 -``` - -Squad routes the review to the agent who opened the PR: - -``` -🔧 Dallas — reading review comments on PR #24 - -Dallas is addressing the feedback now. -``` - -The agent reads the review comments, makes the requested changes, and pushes new commits to the same branch: - -``` -🔧 Dallas — Addressed review feedback on PR #24 - - Added input sanitization for search query (reviewer concern) - - Added test case for SQL injection attempt - - Pushed 2 new commits to 12-add-ingredient-search -``` - ---- - -## 6. Merge Completed Work - -When the PR is approved and ready: - -``` -> Merge PR #24 -``` - -``` -✅ PR #24 merged — "Add ingredient search (#12)" - Issue #12 closed. - Branch 12-add-ingredient-search deleted. -``` - -The issue is closed automatically when the PR merges (if the PR body includes `Closes #12`). - ---- - -## 7. Check Remaining Work - -After merging, see what's left: - -``` -> What's left? -``` - -Squad refreshes the backlog: - -``` -┌─────┬──────────────────────────────────────────┬───────────┬────────────┐ -│ # │ Title │ Labels │ Assignee │ -├─────┼──────────────────────────────────────────┼───────────┼────────────┤ -│ 15 │ Fix pagination on recipe list │ bug │ Dallas │ -│ 18 │ Add user profile page │ feature │ — │ -│ 21 │ Rate limiting on API endpoints │ security │ — │ -│ 23 │ Mobile responsive layout │ ui │ Ripley │ -└─────┴──────────────────────────────────────────┴───────────┴────────────┘ -4 open issues (2 in progress) -``` - ---- - -## Full Workflow at a Glance - -``` -Connect → "connect to myorg/recipe-app" -Browse → "show the backlog" -Assign → "work on #12" - └─ Agent creates branch, implements, opens PR -Review → "there's review feedback on PR #24" - └─ Agent reads comments, pushes fixes -Merge → "merge PR #24" - └─ PR merged, issue closed -Status → "what's left?" - └─ Updated backlog -``` - ---- - -## Tips - -- **You don't pick the agent.** Squad routes the issue to the agent whose expertise matches the issue's domain. A bug in the API goes to the backend agent. A UI issue goes to the frontend agent. -- **Agents name branches sensibly.** Branch names include the issue number and a slugified title, so they're easy to find in `git branch`. -- **PRs link to issues.** The PR description includes a `Closes #N` reference so merging automatically closes the issue. -- **Review feedback is incremental.** When you tell Squad about review feedback, the agent pushes new commits to the existing branch — no force-pushes, no new PRs. -- **Check `decisions.md` after issue work.** Agents often record decisions while working on issues (e.g., "chose cursor pagination" or "added text index for search"). These decisions carry forward to future issues. diff --git a/packages/squad-sdk/src/state/collections.ts b/packages/squad-sdk/src/state/collections.ts new file mode 100644 index 00000000..b41e2a12 --- /dev/null +++ b/packages/squad-sdk/src/state/collections.ts @@ -0,0 +1,310 @@ +/** + * State Module — Typed Collection Facades + * + * Each class provides a typed, ergonomic interface over a specific + * `.squad/` collection, backed by StorageProvider + IO layer. + * + * @module state/collections + */ + +import type { StorageProvider } from '../storage/storage-provider.js'; +import type { AgentHandle } from './collection-map.js'; +import type { + Decision, + RoutingConfig, + RoutingConfigRule, + TeamConfig, + TeamMember, +} from './domain-types.js'; +import type { SkillDefinition } from '../skills/skill-loader.js'; +import { NotFoundError } from './domain-types.js'; +import { resolveCollectionPath } from './schema.js'; +import { createAgentHandle } from './handles.js'; +import { parseDecisions, serializeDecision, serializeDecisions } from './io/decisions-io.js'; +import { parseRouting, serializeRouting } from './io/routing-io.js'; +import { parseTeam, serializeTeam } from './io/team-io.js'; +import { parseSkillFile } from '../skills/skill-loader.js'; +import type { ParsedDecision } from './io/decisions-io.js'; +import type { ParsedRoutingRule } from './io/routing-io.js'; +import type { ParsedAgent } from './io/team-io.js'; + +// ── AgentsCollection ─────────────────────────────────────────────────────── + +export class AgentsCollection { + constructor( + private readonly storage: StorageProvider, + private readonly rootDir: string, + ) {} + + /** Return a handle for interacting with a specific agent's state. */ + get(name: string): AgentHandle { + return createAgentHandle(name, this.storage, this.rootDir); + } + + /** List agent names from `.squad/agents/`. */ + async list(): Promise { + const agentsDir = `${this.rootDir}/.squad/agents`; + return this.storage.list(agentsDir); + } + + /** Create a new agent with charter and empty history. */ + async create(name: string, charter: string): Promise { + const agentDir = `${this.rootDir}/.squad/agents/${name}`; + await this.storage.write(`${agentDir}/charter.md`, charter); + await this.storage.write(`${agentDir}/history.md`, `# ${name}\n\n## Learnings\n\n## Context\n`); + } + + /** Soft-delete an agent by removing its directory. */ + async delete(name: string): Promise { + const agentDir = `${this.rootDir}/.squad/agents/${name}`; + const exists = await this.storage.exists(agentDir); + if (!exists) { + throw new NotFoundError('agents', name); + } + await this.storage.deleteDir(agentDir); + } +} + +// ── DecisionsCollection ──────────────────────────────────────────────────── + +/** Map a ParsedDecision to the domain Decision type. */ +function toDomainDecision(pd: ParsedDecision): Decision { + return { + date: pd.date ?? '', + title: pd.title, + author: pd.author ?? '', + body: pd.body, + configRelevant: pd.configRelevant, + }; +} + +/** Map a domain Decision to ParsedDecision for serialization. */ +function toParsedDecision(d: Decision): ParsedDecision { + return { + title: d.title, + body: d.body, + configRelevant: d.configRelevant, + date: d.date || undefined, + author: d.author || undefined, + }; +} + +export class DecisionsCollection { + constructor( + private readonly storage: StorageProvider, + private readonly rootDir: string, + ) {} + + /** Parse and return all decisions from `decisions.md`. */ + async list(): Promise { + const filePath = `${this.rootDir}/${resolveCollectionPath('decisions')}`; + const content = await this.storage.read(filePath); + if (content === undefined) { + return []; + } + return parseDecisions(content).map(toDomainDecision); + } + + /** Append a new decision. Date is auto-generated if not provided. */ + async add(decision: Omit): Promise { + const filePath = `${this.rootDir}/${resolveCollectionPath('decisions')}`; + const date = new Date().toISOString().split('T')[0]!; + const full: Decision = { ...decision, date }; + const parsed = toParsedDecision(full); + + const existing = await this.storage.read(filePath); + if (existing === undefined || existing.trim().length === 0) { + await this.storage.write(filePath, serializeDecisions([parsed])); + } else { + const fragment = '\n\n' + serializeDecision(parsed) + '\n'; + await this.storage.append(filePath, fragment); + } + } +} + +// ── RoutingCollection ────────────────────────────────────────────────────── + +/** Map ParsedRoutingRule[] to RoutingConfig. */ +function toRoutingConfig(rules: ParsedRoutingRule[]): RoutingConfig { + return { + rules: rules.map( + (r): RoutingConfigRule => ({ + workType: r.workType, + agents: r.agents, + examples: r.examples ?? [], + }), + ), + moduleOwnership: new Map(), + principles: [], + }; +} + +/** Map RoutingConfig rules back to ParsedRoutingRule[]. */ +function toParsedRoutingRules(config: RoutingConfig): ParsedRoutingRule[] { + return config.rules.map( + (r): ParsedRoutingRule => ({ + workType: r.workType, + agents: [...r.agents], + examples: [...r.examples], + }), + ); +} + +export class RoutingCollection { + constructor( + private readonly storage: StorageProvider, + private readonly rootDir: string, + ) {} + + /** Parse and return the routing configuration. */ + async get(): Promise { + const filePath = `${this.rootDir}/${resolveCollectionPath('routing')}`; + const content = await this.storage.read(filePath); + if (content === undefined) { + throw new NotFoundError('routing'); + } + return toRoutingConfig(parseRouting(content)); + } + + /** Write back a full routing configuration. */ + async update(config: RoutingConfig): Promise { + const filePath = `${this.rootDir}/${resolveCollectionPath('routing')}`; + const rules = toParsedRoutingRules(config); + await this.storage.write(filePath, serializeRouting(rules)); + } +} + +// ── TeamCollection ───────────────────────────────────────────────────────── + +/** Map ParsedAgent[] to TeamConfig. */ +function toTeamConfig(agents: ParsedAgent[]): TeamConfig { + return { + projectContext: '', + members: agents.map( + (a): TeamMember => ({ + name: a.name, + role: a.role, + status: a.status, + }), + ), + }; +} + +/** Map TeamConfig members back to ParsedAgent[]. */ +function toParsedAgents(config: TeamConfig): ParsedAgent[] { + return config.members.map( + (m): ParsedAgent => ({ + name: m.name, + role: m.role, + skills: [], + status: m.status, + }), + ); +} + +export class TeamCollection { + constructor( + private readonly storage: StorageProvider, + private readonly rootDir: string, + ) {} + + /** Parse and return the team configuration. */ + async get(): Promise { + const filePath = `${this.rootDir}/${resolveCollectionPath('team')}`; + const content = await this.storage.read(filePath); + if (content === undefined) { + throw new NotFoundError('team'); + } + return toTeamConfig(parseTeam(content)); + } + + /** Write back a full team configuration. */ + async update(config: TeamConfig): Promise { + const filePath = `${this.rootDir}/${resolveCollectionPath('team')}`; + const agents = toParsedAgents(config); + await this.storage.write(filePath, serializeTeam(agents)); + } +} + +// ── SkillsCollection ────────────────────────────────────────────────────── + +export class SkillsCollection { + constructor( + private readonly storage: StorageProvider, + private readonly rootDir: string, + ) {} + + /** List all skill IDs (directory names under .squad/skills/). */ + async list(): Promise { + const skillsDir = `${this.rootDir}/.squad/skills`; + return this.storage.list(skillsDir); + } + + /** Get a skill definition by ID. Returns undefined if not found or unparseable. */ + async get(id: string): Promise { + const skillFile = `${this.rootDir}/${resolveCollectionPath('skills', id)}/SKILL.md`; + const content = await this.storage.read(skillFile); + if (content === undefined) return undefined; + return parseSkillFile(id, content); + } + + /** Check if a skill exists. */ + async exists(id: string): Promise { + const skillFile = `${this.rootDir}/${resolveCollectionPath('skills', id)}/SKILL.md`; + return this.storage.exists(skillFile); + } +} + +// ── TemplatesCollection ─────────────────────────────────────────────────── + +export class TemplatesCollection { + constructor( + private readonly storage: StorageProvider, + private readonly rootDir: string, + ) {} + + /** List template filenames under .squad/templates/. */ + async list(): Promise { + const templatesDir = `${this.rootDir}/.squad/templates`; + return this.storage.list(templatesDir); + } + + /** Get raw template content by ID. Returns undefined if not found. */ + async get(id: string): Promise { + const filePath = `${this.rootDir}/${resolveCollectionPath('templates', id)}`; + return this.storage.read(filePath); + } + + /** Check if a template exists. */ + async exists(id: string): Promise { + const filePath = `${this.rootDir}/${resolveCollectionPath('templates', id)}`; + return this.storage.exists(filePath); + } +} + +// ── LogCollection ───────────────────────────────────────────────────────── + +export class LogCollection { + constructor( + private readonly storage: StorageProvider, + private readonly rootDir: string, + ) {} + + /** List log entry filenames under .squad/log/. */ + async list(): Promise { + const logDir = `${this.rootDir}/${resolveCollectionPath('log')}`; + return this.storage.list(logDir); + } + + /** Read a specific log entry. Returns undefined if not found. */ + async get(id: string): Promise { + const filePath = `${this.rootDir}/${resolveCollectionPath('log')}/${id}`; + return this.storage.read(filePath); + } + + /** Write a new log entry. */ + async write(id: string, content: string): Promise { + const filePath = `${this.rootDir}/${resolveCollectionPath('log')}/${id}`; + await this.storage.write(filePath, content); + } +} diff --git a/packages/squad-sdk/src/state/index.ts b/packages/squad-sdk/src/state/index.ts new file mode 100644 index 00000000..fab4df06 --- /dev/null +++ b/packages/squad-sdk/src/state/index.ts @@ -0,0 +1,65 @@ +/** + * State Module — Barrel Export + * + * Phase 2 of StorageProvider PRD (#481). + * Typed facade layer over `.squad/` file-based state. + */ + +// Domain types (new + re-exported canonical types) +export type { + Agent, + Decision, + HistoryEntry, + HistorySection, + LogEntry, + ModelTier, + RoutingConfig, + RoutingConfigRule, + RoutingRule, + SkillDefinition, + SquadStateConfig, + StateErrorKind, + StorageProvider, + TeamConfig, + TeamMember, + Template, + WorkType, +} from './domain-types.js'; + +export type { AgentStatus } from './domain-types.js'; + +export { + StateError, + NotFoundError, + ParseError, + WriteConflictError, + ProviderError, +} from './domain-types.js'; + +// Collection map +export type { + CollectionEntityMap, + CollectionName, + AgentHandle, +} from './collection-map.js'; + +// Schema / path resolution +export type { CollectionPathResolver } from './schema.js'; +export { COLLECTION_PATHS, resolveCollectionPath } from './schema.js'; + +// Agent handle factory +export { createAgentHandle } from './handles.js'; + +// Collection facades +export { + AgentsCollection, + DecisionsCollection, + LogCollection, + RoutingCollection, + SkillsCollection, + TeamCollection, + TemplatesCollection, +} from './collections.js'; + +// SquadState facade +export { SquadState } from './squad-state.js'; diff --git a/packages/squad-sdk/src/state/squad-state.ts b/packages/squad-sdk/src/state/squad-state.ts new file mode 100644 index 00000000..ec4b3c34 --- /dev/null +++ b/packages/squad-sdk/src/state/squad-state.ts @@ -0,0 +1,65 @@ +/** + * State Module — SquadState Facade + * + * Top-level typed API for reading and writing `.squad/` state. + * Composes the collection facades (agents, decisions, routing, team) + * over a pluggable StorageProvider. + * + * @module state/squad-state + */ + +import type { StorageProvider } from '../storage/storage-provider.js'; +import { + AgentsCollection, + DecisionsCollection, + LogCollection, + RoutingCollection, + SkillsCollection, + TeamCollection, + TemplatesCollection, +} from './collections.js'; +import { NotFoundError } from './domain-types.js'; + +export class SquadState { + readonly agents: AgentsCollection; + readonly decisions: DecisionsCollection; + readonly routing: RoutingCollection; + readonly team: TeamCollection; + readonly skills: SkillsCollection; + readonly templates: TemplatesCollection; + readonly log: LogCollection; + + private constructor( + private readonly storage: StorageProvider, + private readonly rootDir: string, + ) { + this.agents = new AgentsCollection(storage, rootDir); + this.decisions = new DecisionsCollection(storage, rootDir); + this.routing = new RoutingCollection(storage, rootDir); + this.team = new TeamCollection(storage, rootDir); + this.skills = new SkillsCollection(storage, rootDir); + this.templates = new TemplatesCollection(storage, rootDir); + this.log = new LogCollection(storage, rootDir); + } + + /** + * Factory method — validates that `rootDir/.squad/` exists before + * returning a new SquadState instance. + */ + static async create( + storage: StorageProvider, + rootDir: string, + ): Promise { + const squadDir = `${rootDir}/.squad`; + const exists = await storage.exists(squadDir); + if (!exists) { + throw new NotFoundError('squad', rootDir); + } + return new SquadState(storage, rootDir); + } + + /** Check if a `.squad/` directory exists at the root. */ + async isInitialized(): Promise { + return this.storage.exists(`${this.rootDir}/.squad`); + } +} diff --git a/test/state/squad-state.test.ts b/test/state/squad-state.test.ts new file mode 100644 index 00000000..c8d6a6ee --- /dev/null +++ b/test/state/squad-state.test.ts @@ -0,0 +1,565 @@ +/** + * SquadState integration tests. + * + * Uses InMemoryStorageProvider seeded with sample .squad/ files + * to verify the full facade: SquadState → Collections → Handles → IO. + */ + +import { describe, it, expect, beforeEach } from 'vitest'; +import { InMemoryStorageProvider } from '../../packages/squad-sdk/src/storage/in-memory-storage-provider.js'; +import { SquadState } from '../../packages/squad-sdk/src/state/squad-state.js'; +import { NotFoundError } from '../../packages/squad-sdk/src/state/domain-types.js'; + +// ── Sample data ──────────────────────────────────────────────────────────── + +const ROOT = '/project'; + +const CHARTER_EECOM = `# EECOM — Core Dev + +> Practical, thorough, makes it work then makes it right. + +## Identity + +- **Name:** EECOM +- **Role:** Core Dev +- **Expertise:** Runtime implementation, spawning +- **Style:** Practical, thorough, makes it work then makes it right. +`; + +const CHARTER_RETRO = `# RETRO — Docs Lead + +> Precise, structured, docs-first. + +## Identity + +- **Name:** RETRO +- **Role:** Docs Lead +- **Expertise:** Documentation, API references +- **Style:** Precise, structured, docs-first. +`; + +const HISTORY_EECOM = `# EECOM + +## Context + +Project uses TypeScript with vitest for testing. + +## Learnings + +### 2026-07-24 + +Built the IO layer for state module. + +### 2026-07-15 + +Fixed squad version subcommand. + +## Decisions + +### 2026-07-20 + +Use InMemoryStorageProvider for tests. +`; + +const TEAM_MD = `# Project Squad + +## Members + +| Name | Role | Charter | Status | +|------|------|---------|--------| +| EECOM | Core Dev | \`.squad/agents/EECOM/charter.md\` | ✅ Active | +| RETRO | Docs Lead | \`.squad/agents/RETRO/charter.md\` | ✅ Active | +`; + +const DECISIONS_MD = `# Decisions + +### 2026-07-20: Use StorageProvider abstraction +**By:** EECOM +All file I/O goes through StorageProvider for testability. + +### 2026-07-18: Markdown-first state +**By:** Dina +State files stay as markdown for human readability. +`; + +const ROUTING_MD = `# Routing Rules + +## Routing Table + +| Work Type | Agent | Examples | +|-----------|-------|----------| +| feature-dev | EECOM | New features, refactors | +| docs | RETRO | Documentation updates | +`; + +const SKILL_TYPESCRIPT_TESTING = `--- +name: TypeScript Testing +domain: testing +triggers: [vitest, jest, test, spec] +roles: [tester, developer] +--- +Guidelines for writing TypeScript tests with vitest. +`; + +const SKILL_CODE_REVIEW = `--- +name: Code Review +domain: quality +triggers: [review, pr, pull-request] +roles: [reviewer] +--- +Best practices for code review. +`; + +const TEMPLATE_CHARTER = `# {{name}} — {{role}} + +> {{tagline}} + +## Identity + +- **Name:** {{name}} +- **Role:** {{role}} +`; + +const TEMPLATE_DECISION = `### {{date}}: {{title}} +**By:** {{author}} +{{body}} +`; + +// ── Helpers ──────────────────────────────────────────────────────────────── + +function seedStorage(storage: InMemoryStorageProvider): void { + storage.writeSync(`${ROOT}/.squad/agents/EECOM/charter.md`, CHARTER_EECOM); + storage.writeSync(`${ROOT}/.squad/agents/EECOM/history.md`, HISTORY_EECOM); + storage.writeSync(`${ROOT}/.squad/agents/RETRO/charter.md`, CHARTER_RETRO); + storage.writeSync(`${ROOT}/.squad/agents/RETRO/history.md`, `# RETRO\n\n## Learnings\n`); + storage.writeSync(`${ROOT}/.squad/team.md`, TEAM_MD); + storage.writeSync(`${ROOT}/.squad/decisions.md`, DECISIONS_MD); + storage.writeSync(`${ROOT}/.squad/routing.md`, ROUTING_MD); + // Skills + storage.writeSync(`${ROOT}/.squad/skills/typescript-testing/SKILL.md`, SKILL_TYPESCRIPT_TESTING); + storage.writeSync(`${ROOT}/.squad/skills/code-review/SKILL.md`, SKILL_CODE_REVIEW); + // Templates + storage.writeSync(`${ROOT}/.squad/templates/charter.md`, TEMPLATE_CHARTER); + storage.writeSync(`${ROOT}/.squad/templates/decision.md`, TEMPLATE_DECISION); + // Log entries + storage.writeSync(`${ROOT}/.squad/log/2026-07-24-session.md`, '# Session Log\nSpawned EECOM for feature work.'); + storage.writeSync(`${ROOT}/.squad/log/2026-07-25-review.md`, '# Review Log\nCode review completed.'); +} + +// ── Tests ────────────────────────────────────────────────────────────────── + +describe('SquadState', () => { + let storage: InMemoryStorageProvider; + let state: SquadState; + + beforeEach(async () => { + storage = new InMemoryStorageProvider(); + seedStorage(storage); + state = await SquadState.create(storage, ROOT); + }); + + // ── Factory ──────────────────────────────────────────────────────────── + + describe('create()', () => { + it('succeeds when .squad/ exists', async () => { + expect(state).toBeInstanceOf(SquadState); + }); + + it('throws NotFoundError when .squad/ is missing', async () => { + const empty = new InMemoryStorageProvider(); + await expect(SquadState.create(empty, '/empty')).rejects.toThrow( + NotFoundError, + ); + }); + }); + + describe('isInitialized()', () => { + it('returns true when .squad/ exists', async () => { + expect(await state.isInitialized()).toBe(true); + }); + + it('returns false when .squad/ is missing', async () => { + const empty = new InMemoryStorageProvider(); + // Bypass create() validation with a direct instantiation trick + // by testing on a state that was created then had squad/ removed + await storage.deleteDir(`${ROOT}/.squad`); + expect(await state.isInitialized()).toBe(false); + }); + }); + + // ── AgentsCollection ─────────────────────────────────────────────────── + + describe('agents', () => { + describe('list()', () => { + it('returns agent names', async () => { + const names = await state.agents.list(); + expect(names).toContain('EECOM'); + expect(names).toContain('RETRO'); + expect(names).toHaveLength(2); + }); + }); + + describe('get().charter()', () => { + it('reads charter content', async () => { + const handle = state.agents.get('EECOM'); + const charter = await handle.charter(); + expect(charter).toContain('EECOM — Core Dev'); + expect(charter).toContain('Practical, thorough'); + }); + + it('throws NotFoundError for missing agent', async () => { + const handle = state.agents.get('GHOST'); + await expect(handle.charter()).rejects.toThrow(NotFoundError); + }); + }); + + describe('get().history()', () => { + it('returns all parsed history entries', async () => { + const entries = await state.agents.get('EECOM').history(); + expect(entries.length).toBeGreaterThan(0); + // Should have entries from Context, Learnings, and Decisions sections + const sections = new Set(entries.map((e) => e.section)); + expect(sections.has('Learnings')).toBe(true); + expect(sections.has('Decisions')).toBe(true); + }); + + it('filters by section', async () => { + const entries = await state.agents.get('EECOM').history('Learnings'); + expect(entries.length).toBe(2); + expect(entries.every((e) => e.section === 'Learnings')).toBe(true); + }); + + it('returns empty array for agent with no history file', async () => { + await storage.delete(`${ROOT}/.squad/agents/RETRO/history.md`); + const entries = await state.agents.get('RETRO').history(); + expect(entries).toEqual([]); + }); + + it('extracts timestamps from sub-headers', async () => { + const entries = await state.agents.get('EECOM').history('Learnings'); + expect(entries[0]!.timestamp).toBe('2026-07-24'); + expect(entries[1]!.timestamp).toBe('2026-07-15'); + }); + }); + + describe('get().appendHistory()', () => { + it('appends a new entry to an existing section', async () => { + const handle = state.agents.get('EECOM'); + await handle.appendHistory('Learnings', { + section: 'Learnings', + content: 'New learning about testing.', + timestamp: '2026-07-26', + }); + + const entries = await handle.history('Learnings'); + expect(entries.length).toBe(3); + expect(entries.some((e) => e.content.includes('New learning about testing.'))).toBe(true); + }); + + it('creates section if it does not exist', async () => { + const handle = state.agents.get('EECOM'); + await handle.appendHistory('Issues', { + section: 'Issues', + content: 'Found a bug in parsing.', + timestamp: '2026-07-26', + }); + + const entries = await handle.history('Issues'); + expect(entries.length).toBe(1); + expect(entries[0]!.content).toContain('Found a bug in parsing.'); + }); + + it('creates history file if missing', async () => { + await storage.delete(`${ROOT}/.squad/agents/RETRO/history.md`); + const handle = state.agents.get('RETRO'); + await handle.appendHistory('Learnings', { + section: 'Learnings', + content: 'First learning.', + timestamp: '2026-07-26', + }); + + const entries = await handle.history('Learnings'); + expect(entries.length).toBe(1); + }); + }); + + describe('get().update()', () => { + it('updates agent role in team.md', async () => { + const handle = state.agents.get('EECOM'); + await handle.update({ role: 'Lead Dev' } as Partial); + + const teamConfig = await state.team.get(); + const member = teamConfig.members.find((m) => m.name === 'eecom'); + expect(member?.role).toBe('Lead Dev'); + }); + + it('throws NotFoundError when agent not in team.md', async () => { + const handle = state.agents.get('GHOST'); + await expect( + handle.update({ role: 'Nobody' } as Partial), + ).rejects.toThrow(NotFoundError); + }); + }); + + describe('create()', () => { + it('creates agent directory with charter and history', async () => { + await state.agents.create('NEWBIE', '# NEWBIE — Intern\n'); + const handle = state.agents.get('NEWBIE'); + const charter = await handle.charter(); + expect(charter).toContain('NEWBIE — Intern'); + + const names = await state.agents.list(); + expect(names).toContain('NEWBIE'); + }); + }); + + describe('delete()', () => { + it('removes the agent directory', async () => { + await state.agents.delete('RETRO'); + const names = await state.agents.list(); + expect(names).not.toContain('RETRO'); + }); + + it('throws NotFoundError for missing agent', async () => { + await expect(state.agents.delete('GHOST')).rejects.toThrow( + NotFoundError, + ); + }); + }); + }); + + // ── DecisionsCollection ──────────────────────────────────────────────── + + describe('decisions', () => { + describe('list()', () => { + it('returns parsed decisions', async () => { + const decisions = await state.decisions.list(); + expect(decisions.length).toBe(2); + expect(decisions[0]!.title).toContain('Use StorageProvider abstraction'); + expect(decisions[1]!.title).toContain('Markdown-first state'); + }); + + it('returns empty array when file missing', async () => { + await storage.delete(`${ROOT}/.squad/decisions.md`); + const decisions = await state.decisions.list(); + expect(decisions).toEqual([]); + }); + }); + + describe('add()', () => { + it('appends a new decision', async () => { + await state.decisions.add({ + title: 'Use typed facades', + author: 'EECOM', + body: 'SquadState provides typed access to all collections.', + configRelevant: false, + }); + + const decisions = await state.decisions.list(); + expect(decisions.length).toBe(3); + expect(decisions.some((d) => d.title.includes('Use typed facades'))).toBe(true); + }); + }); + }); + + // ── RoutingCollection ────────────────────────────────────────────────── + + describe('routing', () => { + describe('get()', () => { + it('returns parsed routing config', async () => { + const config = await state.routing.get(); + expect(config.rules.length).toBe(2); + expect(config.rules[0]!.workType).toBe('feature-dev'); + expect(config.rules[0]!.agents).toContain('EECOM'); + expect(config.rules[1]!.workType).toBe('docs'); + }); + + it('throws NotFoundError when file missing', async () => { + await storage.delete(`${ROOT}/.squad/routing.md`); + await expect(state.routing.get()).rejects.toThrow(NotFoundError); + }); + }); + + describe('update()', () => { + it('writes updated routing config', async () => { + const config = await state.routing.get(); + const updated = { + ...config, + rules: [ + ...config.rules, + { workType: 'testing', agents: ['EECOM'], examples: ['Unit tests'] }, + ], + }; + await state.routing.update(updated); + + const reloaded = await state.routing.get(); + expect(reloaded.rules.length).toBe(3); + expect(reloaded.rules[2]!.workType).toBe('testing'); + }); + }); + }); + + // ── TeamCollection ───────────────────────────────────────────────────── + + describe('team', () => { + describe('get()', () => { + it('returns parsed team config', async () => { + const config = await state.team.get(); + expect(config.members.length).toBe(2); + // parseTeam kebab-cases names, so EECOM → eecom + expect(config.members[0]!.name).toBe('eecom'); + expect(config.members[0]!.role).toBe('Core Dev'); + expect(config.members[1]!.name).toBe('retro'); + }); + + it('throws NotFoundError when file missing', async () => { + await storage.delete(`${ROOT}/.squad/team.md`); + await expect(state.team.get()).rejects.toThrow(NotFoundError); + }); + }); + + describe('update()', () => { + it('writes updated team config', async () => { + const config = await state.team.get(); + const updated = { + ...config, + members: [ + ...config.members, + { name: 'NEWBIE', role: 'Intern' }, + ], + }; + await state.team.update(updated); + + const reloaded = await state.team.get(); + expect(reloaded.members.length).toBe(3); + // Names survive round-trip as-is since we don't go through parseTeam on write + expect(reloaded.members[2]!.name).toBe('newbie'); + }); + }); + }); + + // ── SkillsCollection ────────────────────────────────────────────────── + + describe('skills', () => { + describe('list()', () => { + it('returns skill IDs', async () => { + const ids = await state.skills.list(); + expect(ids).toContain('typescript-testing'); + expect(ids).toContain('code-review'); + expect(ids).toHaveLength(2); + }); + + it('returns empty array when skills directory is missing', async () => { + const empty = new InMemoryStorageProvider(); + empty.writeSync(`${ROOT}/.squad/team.md`, TEAM_MD); + const s = await SquadState.create(empty, ROOT); + const ids = await s.skills.list(); + expect(ids).toEqual([]); + }); + }); + + describe('get()', () => { + it('returns SkillDefinition for existing skill', async () => { + const skill = await state.skills.get('typescript-testing'); + expect(skill).toBeDefined(); + expect(skill!.id).toBe('typescript-testing'); + expect(skill!.name).toBe('TypeScript Testing'); + expect(skill!.domain).toBe('testing'); + expect(skill!.triggers).toEqual(['vitest', 'jest', 'test', 'spec']); + expect(skill!.agentRoles).toEqual(['tester', 'developer']); + expect(skill!.content).toContain('Guidelines for writing TypeScript tests'); + }); + + it('returns undefined for missing skill', async () => { + const skill = await state.skills.get('nonexistent'); + expect(skill).toBeUndefined(); + }); + }); + + describe('exists()', () => { + it('returns true for existing skill', async () => { + expect(await state.skills.exists('code-review')).toBe(true); + }); + + it('returns false for missing skill', async () => { + expect(await state.skills.exists('nonexistent')).toBe(false); + }); + }); + }); + + // ── TemplatesCollection ─────────────────────────────────────────────── + + describe('templates', () => { + describe('list()', () => { + it('returns template filenames', async () => { + const names = await state.templates.list(); + expect(names).toContain('charter.md'); + expect(names).toContain('decision.md'); + expect(names).toHaveLength(2); + }); + }); + + describe('get()', () => { + it('returns raw template content', async () => { + const content = await state.templates.get('charter.md'); + expect(content).toBeDefined(); + expect(content).toContain('{{name}}'); + expect(content).toContain('{{role}}'); + }); + + it('returns undefined for missing template', async () => { + const content = await state.templates.get('nonexistent.md'); + expect(content).toBeUndefined(); + }); + }); + + describe('exists()', () => { + it('returns true for existing template', async () => { + expect(await state.templates.exists('charter.md')).toBe(true); + }); + + it('returns false for missing template', async () => { + expect(await state.templates.exists('nonexistent.md')).toBe(false); + }); + }); + }); + + // ── LogCollection ───────────────────────────────────────────────────── + + describe('log', () => { + describe('list()', () => { + it('returns log entry filenames', async () => { + const names = await state.log.list(); + expect(names).toContain('2026-07-24-session.md'); + expect(names).toContain('2026-07-25-review.md'); + expect(names).toHaveLength(2); + }); + }); + + describe('get()', () => { + it('reads a specific log entry', async () => { + const content = await state.log.get('2026-07-24-session.md'); + expect(content).toContain('Session Log'); + expect(content).toContain('Spawned EECOM'); + }); + + it('returns undefined for missing log entry', async () => { + const content = await state.log.get('nonexistent.md'); + expect(content).toBeUndefined(); + }); + }); + + describe('write()', () => { + it('persists a new log entry and is readable', async () => { + await state.log.write('2026-07-26-deploy.md', '# Deploy Log\nDeployed v1.2.0.'); + + const content = await state.log.get('2026-07-26-deploy.md'); + expect(content).toBe('# Deploy Log\nDeployed v1.2.0.'); + + const names = await state.log.list(); + expect(names).toContain('2026-07-26-deploy.md'); + expect(names).toHaveLength(3); + }); + }); + }); +});