diff --git a/.changeset/audit-onboarding-skill-guards.md b/.changeset/audit-onboarding-skill-guards.md new file mode 100644 index 000000000..a9bff2a99 --- /dev/null +++ b/.changeset/audit-onboarding-skill-guards.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Add defensive guards from architecture audit: (1) verify roster is populated after team creation before dispatching to coordinator, preventing empty-roster dispatch loops; (2) warn when `squad upgrade` overwrites customized built-in skills diff --git a/.changeset/builtin-skills.md b/.changeset/builtin-skills.md new file mode 100644 index 000000000..30962cc2e --- /dev/null +++ b/.changeset/builtin-skills.md @@ -0,0 +1,17 @@ +--- +"@bradygaster/squad-cli": minor +--- + +Ship 8 built-in skills with squad init/upgrade (#788) + +New skills distributed automatically on `squad init` and `squad upgrade`: +- **error-recovery** — graceful failure handling patterns +- **secret-handling** — credential safety and secrets management +- **git-workflow** — branch management and commit conventions +- **session-recovery** — checkpoint and recovery patterns +- **reviewer-protocol** — code review gate patterns +- **test-discipline** — test-first discipline and coverage +- **agent-collaboration** — multi-agent handoff patterns +- **squad-conventions** — (already shipped, now part of curated set) + +All skills are squad-owned (`overwriteOnUpgrade: true`) and update on upgrade. diff --git a/.changeset/cleanup-ceremony.md b/.changeset/cleanup-ceremony.md new file mode 100644 index 000000000..4a9a2631e --- /dev/null +++ b/.changeset/cleanup-ceremony.md @@ -0,0 +1,12 @@ +--- +"@bradygaster/squad-cli": minor +--- + +Add cleanup watch capability for stale file housekeeping (#791) + +- New `cleanup` capability in the `housekeeping` phase +- Clears `.squad/.scratch/` (all ephemeral temp files) +- Archives orchestration-log and session-log entries older than 30 days +- Warns about stale decision inbox files (>7 days) +- Configurable: `everyNRounds` (default: 10), `maxAgeDays` (default: 30) +- 12 new tests diff --git a/.changeset/contamination-guard.md b/.changeset/contamination-guard.md new file mode 100644 index 000000000..29ed0ae95 --- /dev/null +++ b/.changeset/contamination-guard.md @@ -0,0 +1,8 @@ +--- +--- + +ci: add PR contamination prevention guard + +New diff-guard CI check warns when single-commit PRs touch 30+ files +(likely branch contamination). Updated copilot-instructions with branch +creation and staging safety rules. diff --git a/.changeset/dynamic-state-discovery.md b/.changeset/dynamic-state-discovery.md new file mode 100644 index 000000000..0a54892c6 --- /dev/null +++ b/.changeset/dynamic-state-discovery.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Replace hardcoded state artifact lists with dynamic directory scanning in externalize/internalize, preventing silent orphaning when new `.squad/` artifacts are added. diff --git a/.changeset/external-capability-loading.md b/.changeset/external-capability-loading.md new file mode 100644 index 000000000..8564320b6 --- /dev/null +++ b/.changeset/external-capability-loading.md @@ -0,0 +1,9 @@ +--- +'@bradygaster/squad-cli': minor +--- + +feat(watch): load external WatchCapabilities from .squad/capabilities/ + +Users can now define custom watch capabilities as .js files in `.squad/capabilities/`. +Each file default-exports a WatchCapability object (name, phase, preflight, execute). +Capabilities are loaded at watch startup and participate in the normal phase-based round cycle. diff --git a/.changeset/external-state.md b/.changeset/external-state.md new file mode 100644 index 000000000..9585a11d6 --- /dev/null +++ b/.changeset/external-state.md @@ -0,0 +1,16 @@ +--- +"@bradygaster/squad-sdk": minor +"@bradygaster/squad-cli": minor +--- + +Add external state storage — move .squad/ out of the working tree (#792) + +- New `stateLocation: 'external'` option in `.squad/config.json` +- `resolveExternalStateDir()` resolves state under the platform-specific global Squad directory (`resolveGlobalSquadPath()`) +- `deriveProjectKey()` generates a stable key from the repo path (cross-platform) +- `resolveSquadPaths()` honors external state location +- `squad externalize` moves state out, `squad internalize` moves it back +- State survives branch switches, invisible to `git status`, never pollutes PRs +- Thin `.squad/config.json` marker stays in repo (gitignored) +- Path traversal protection on projectKey +- 12 new tests diff --git a/.changeset/fact-checker-agent.md b/.changeset/fact-checker-agent.md new file mode 100644 index 000000000..7f20635d3 --- /dev/null +++ b/.changeset/fact-checker-agent.md @@ -0,0 +1,12 @@ +--- +"@bradygaster/squad-sdk": minor +"@bradygaster/squad-cli": minor +--- + +Add fact-checker as a built-in agent role (#789) + +- New `fact-checker` role in the engineering role catalog (emoji: 🔍, category: quality) +- Charter template at `templates/fact-checker-charter.md` with verification methodology +- Added to `AGENT_TEMPLATES` for init scaffolding +- Template manifest entry for init/upgrade distribution +- Routing patterns: fact-check, verify, validate, audit, double-check, hallucination, devil's advocate diff --git a/.changeset/fix-cast-base-path.md b/.changeset/fix-cast-base-path.md new file mode 100644 index 000000000..60c850b6a --- /dev/null +++ b/.changeset/fix-cast-base-path.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Passes repo root to LocalAgentSource instead of .squad/ dir, preventing a double-nested .squad/.squad/agents/ lookup. In remote mode, passes paths.teamDir (team repo root) so agents are discovered from the correct location. diff --git a/.changeset/fix-changelog-gate.md b/.changeset/fix-changelog-gate.md new file mode 100644 index 000000000..1d84acec2 --- /dev/null +++ b/.changeset/fix-changelog-gate.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Fix changelog-gate CI to accept .changeset/ files as alternative to direct CHANGELOG.md edits diff --git a/.changeset/fix-copilot-message-flag.md b/.changeset/fix-copilot-message-flag.md new file mode 100644 index 000000000..3d2cff7b3 --- /dev/null +++ b/.changeset/fix-copilot-message-flag.md @@ -0,0 +1,11 @@ +--- +'@bradygaster/squad-cli': patch +'@bradygaster/squad-sdk': patch +--- + +Fix obsolete --message flag in watch and loop commands + +Replace `--message` with `-p` across all watch capabilities and loop command. +Use `copilot` directly on Windows to avoid .cmd console window issues, +fall back to `gh copilot` on other platforms. Fix TOKEN_PATH typo in +comms-teams.ts (pre-existing bug from #906). diff --git a/.changeset/fix-detect-squad-dir-zero-deps.md b/.changeset/fix-detect-squad-dir-zero-deps.md new file mode 100644 index 000000000..ca9fb1396 --- /dev/null +++ b/.changeset/fix-detect-squad-dir-zero-deps.md @@ -0,0 +1,7 @@ +--- +"@bradygaster/squad-cli": patch +--- + +fix(cli): revert detect-squad-dir to zero-dependency bootstrap + +The StorageProvider refactor (26047dc5) accidentally converted this bootstrap utility from raw node:fs to FSStorageProvider. This file runs before the SDK is loaded and must not depend on @bradygaster/squad-sdk. Adds regression guard test. diff --git a/.changeset/fix-doctor-casting-upgrade.md b/.changeset/fix-doctor-casting-upgrade.md new file mode 100644 index 000000000..636aa7682 --- /dev/null +++ b/.changeset/fix-doctor-casting-upgrade.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Fix squad doctor and upgrade for insiders installs: add .squad/casting/ to ENSURE_DIRECTORIES, scaffold casting defaults from templates with permission-safe fallback, and clarify ESM warnings for global installs. diff --git a/.changeset/fix-nap-archival-budget.md b/.changeset/fix-nap-archival-budget.md new file mode 100644 index 000000000..95461886a --- /dev/null +++ b/.changeset/fix-nap-archival-budget.md @@ -0,0 +1,12 @@ +--- +"@bradygaster/squad-cli": patch +--- + +fix(nap): account for separator newlines in decision archival budget + +The budget calculation in archiveDecisions() did not account for the newline +separators added during content reassembly. This caused the final recentContent +to exceed DECISION_THRESHOLD even after archival. Fix adds reassemblyOverhead +and per-entry separator bytes to the budget calculation. + +Closes #123 \ No newline at end of file diff --git a/.changeset/fix-nap-subprocess-cwd.md b/.changeset/fix-nap-subprocess-cwd.md new file mode 100644 index 000000000..11edb2947 --- /dev/null +++ b/.changeset/fix-nap-subprocess-cwd.md @@ -0,0 +1,9 @@ +--- +"@bradygaster/squad-cli": patch +--- + +fix(cli): wire --team-root / SQUAD_TEAM_ROOT into squad resolution for nap, status, and cost commands (#734) + +Commands that resolve .squad/ now respect the SQUAD_TEAM_ROOT env var (set by --team-root flag), +fixing subprocess invocations (e.g. Copilot CLI bang commands) where process.cwd() differs from +the interactive shell. Also improves the nap error message to show the searched directory. diff --git a/.changeset/fix-onboarding-mode-bail.md b/.changeset/fix-onboarding-mode-bail.md new file mode 100644 index 000000000..49f63f71c --- /dev/null +++ b/.changeset/fix-onboarding-mode-bail.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-cli": patch +--- + +fix: CLI no longer bails when configured is false; starts onboarding session instead (#843) diff --git a/.changeset/fix-roster-sync.md b/.changeset/fix-roster-sync.md new file mode 100644 index 000000000..2fd31a766 --- /dev/null +++ b/.changeset/fix-roster-sync.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-sdk': patch +--- + +Sync squad.agent.md roster when agents added to team.md diff --git a/.changeset/fix-silent-agent-md-skip.md b/.changeset/fix-silent-agent-md-skip.md new file mode 100644 index 000000000..349139fcb --- /dev/null +++ b/.changeset/fix-silent-agent-md-skip.md @@ -0,0 +1,6 @@ +--- +'@bradygaster/squad-cli': patch +'@bradygaster/squad-sdk': patch +--- + +Warn when squad.agent.md template is missing during upgrade or init instead of silently skipping file creation. Adds `warnings` field to `InitResult` for structured error reporting. diff --git a/.changeset/fix-team-root-all-commands.md b/.changeset/fix-team-root-all-commands.md new file mode 100644 index 000000000..dfffaea34 --- /dev/null +++ b/.changeset/fix-team-root-all-commands.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Extend SQUAD_TEAM_ROOT to all resolveSquad() call sites for subprocess compatibility diff --git a/.changeset/fix-triage-label-slug.md b/.changeset/fix-triage-label-slug.md new file mode 100644 index 000000000..60fb4ec81 --- /dev/null +++ b/.changeset/fix-triage-label-slug.md @@ -0,0 +1,8 @@ +--- +'@bradygaster/squad-sdk': patch +'@bradygaster/squad-cli': patch +--- + +Fix triage label slug derivation: use `slugify()` instead of `toLowerCase()` so multi-word agent names produce valid GitHub labels (e.g. "Steve Rogers" → `squad:steve-rogers` instead of `squad:steve rogers`). + +Pre-create all `squad:{member}` labels at watch startup via `ensureTag()` so `gh issue edit --add-label` never fails on missing labels. diff --git a/.changeset/fleet-dispatch-hybrid.md b/.changeset/fleet-dispatch-hybrid.md new file mode 100644 index 000000000..70f5d8b7a --- /dev/null +++ b/.changeset/fleet-dispatch-hybrid.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-cli": minor +--- + +Add /fleet hybrid dispatch mode for squad watch --execute (#775) — enables parallel issue analysis via Copilot CLI /fleet, 2.9x faster than sequential dispatch for read-heavy work diff --git a/.changeset/git-state-backends.md b/.changeset/git-state-backends.md new file mode 100644 index 000000000..994e03465 --- /dev/null +++ b/.changeset/git-state-backends.md @@ -0,0 +1,17 @@ +--- +"@bradygaster/squad-sdk": minor +"@bradygaster/squad-cli": minor +--- + +feat(sdk): git-notes + orphan-branch state backends for .squad/ + +Adds two git-native state storage backends as alternatives to the worktree +and external directory approaches: + +- **git-notes** (`refs/notes/squad`): State stored in git notes ref. Survives + branch switches, invisible in diffs and PRs. +- **orphan-branch** (`squad-state`): Dedicated orphan branch with no common + ancestor. State files never appear in main. + +Configure via `.squad/config.json`: `{ "stateBackend": "git-notes" }` or +the `--state-backend` CLI flag. \ No newline at end of file diff --git a/.changeset/init-upgrade-parity-tests.md b/.changeset/init-upgrade-parity-tests.md new file mode 100644 index 000000000..8d97181d0 --- /dev/null +++ b/.changeset/init-upgrade-parity-tests.md @@ -0,0 +1,8 @@ +--- +--- + +test: add init/upgrade/doctor parity test suite + +7 tests verifying that init and upgrade produce equivalent scaffolding. +Guards against drift where new init features (like casting) are not +mirrored in upgrade. diff --git a/.changeset/loop-command.md b/.changeset/loop-command.md new file mode 100644 index 000000000..80097834d --- /dev/null +++ b/.changeset/loop-command.md @@ -0,0 +1,9 @@ +--- +"@bradygaster/squad-cli": minor +--- + +feat(cli): Add `squad loop` command — prompt-driven continuous work loop + +New `squad loop` command reads a `loop.md` file and runs it as a continuous work loop. +No GitHub issues required — the prompt is the work driver. Includes `--init` to scaffold +a boilerplate loop file, frontmatter validation, and composable capability flags. diff --git a/.changeset/manifest-driven-skills.md b/.changeset/manifest-driven-skills.md new file mode 100644 index 000000000..9caad36df --- /dev/null +++ b/.changeset/manifest-driven-skills.md @@ -0,0 +1,17 @@ +--- +"@bradygaster/squad-sdk": patch +"@bradygaster/squad-cli": patch +--- + +fix: use TEMPLATE_MANIFEST to drive skill installation instead of wholesale directory copy + +Both sdkInitSquad() and syncAllSkills() previously copied the entire templates/skills/ +directory, ignoring the curated 8-skill subset declared in TEMPLATE_MANIFEST. +This meant all 37+ template skills shipped on every init/upgrade, and +overwriteOnUpgrade was never consulted. + +Now both code paths iterate TEMPLATE_MANIFEST entries: +- init (SDK): only the 8 manifest skills are copied on first init +- upgrade (CLI): syncAllSkills() reads manifest entries, respects overwriteOnUpgrade + +Fixes #833 diff --git a/.changeset/monorepo-subfolder-support.md b/.changeset/monorepo-subfolder-support.md new file mode 100644 index 000000000..83eefed28 --- /dev/null +++ b/.changeset/monorepo-subfolder-support.md @@ -0,0 +1,8 @@ +--- +"@bradygaster/squad-cli": patch +"@bradygaster/squad-sdk": patch +--- + +fix: monorepo subfolder support — never run git init (#939) + +When `squad init` runs in a subfolder of a monorepo, place `.github/agents/squad.agent.md` at the git root and `.squad/` in the subfolder. Never run `git init`. diff --git a/.changeset/pid-tracker-cleanup.md b/.changeset/pid-tracker-cleanup.md new file mode 100644 index 000000000..eaf5c8dad --- /dev/null +++ b/.changeset/pid-tracker-cleanup.md @@ -0,0 +1,9 @@ +--- +'@bradygaster/squad-cli': patch +--- + +fix(watch): track child process PIDs and cleanup orphans on exit + +Prevents MCP server process leaks during long-running watch sessions. +Tracks PIDs of spawned copilot sessions, kills them on exit/crash, +and cleans up stale orphans from previous crashed runs on startup. diff --git a/.changeset/quiet-notifications.md b/.changeset/quiet-notifications.md new file mode 100644 index 000000000..5a8880512 --- /dev/null +++ b/.changeset/quiet-notifications.md @@ -0,0 +1,13 @@ +--- +"@bradygaster/squad-cli": minor +--- + +Add `--notify-level` to control watch round reporting noise (#803) + +- `--notify-level important` (default): only print rounds with actual work items +- `--notify-level all`: print every round including empty (old behavior) +- `--notify-level none`: suppress all round output +- Add machine name (`os.hostname()`) and repo name to round headers for attribution + (shown in round headers when the board has items, and in "Board is clear" message) +- Configurable via `.squad/config.json` watch section: `"notifyLevel": "important"` +- Empty rounds silenced by default — no more "Round 160, Round 161" spam diff --git a/.changeset/readiness-file-list.md b/.changeset/readiness-file-list.md new file mode 100644 index 000000000..5cc36c951 --- /dev/null +++ b/.changeset/readiness-file-list.md @@ -0,0 +1,7 @@ +--- +--- + +ci: add file list with line stats to PR readiness comment + +The PR readiness bot now shows changed files with per-file addition/deletion +counts, scope classification (Product/Infrastructure/Mixed), and totals. diff --git a/.changeset/readiness-rerun.md b/.changeset/readiness-rerun.md new file mode 100644 index 000000000..f1f2ee952 --- /dev/null +++ b/.changeset/readiness-rerun.md @@ -0,0 +1,8 @@ +--- +--- + +ci: re-run PR readiness check after Squad CI completes + +Added workflow_run trigger to the readiness check so it refreshes after +CI finishes. Uses PR API for draft status instead of env var (more reliable +for workflow_run events). Includes 3 new draft-override tests. diff --git a/.changeset/review-findings-fix.md b/.changeset/review-findings-fix.md new file mode 100644 index 000000000..45b641b97 --- /dev/null +++ b/.changeset/review-findings-fix.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +fix: address post-merge review findings — catch unknown narrowing and deprecation warnings for start/rc commands diff --git a/.changeset/scope-boundary-check.md b/.changeset/scope-boundary-check.md new file mode 100644 index 000000000..6f98a8bd3 --- /dev/null +++ b/.changeset/scope-boundary-check.md @@ -0,0 +1,8 @@ +--- +--- + +ci: scope boundary enforcement for repo-health PRs + +New CI check that fails repo-health PRs if they modify product source +code under packages/*/src/. Enforces separation between infrastructure +and product changes. diff --git a/.changeset/scratch-dir-utility.md b/.changeset/scratch-dir-utility.md new file mode 100644 index 000000000..9fd5d5769 --- /dev/null +++ b/.changeset/scratch-dir-utility.md @@ -0,0 +1,10 @@ +--- +"@bradygaster/squad-sdk": minor +--- + +Add `.squad/.scratch/` directory for organized temp file management (#790) + +- `scratchDir()` — resolve and optionally create the scratch directory +- `scratchFile()` — create named temp files inside `.scratch/` +- Init scaffolds `.squad/.scratch/` and adds it to `.gitignore` +- Agents and CLI should use these utilities instead of writing temp files to repo root diff --git a/.changeset/self-upgrade.md b/.changeset/self-upgrade.md new file mode 100644 index 000000000..0567983d1 --- /dev/null +++ b/.changeset/self-upgrade.md @@ -0,0 +1,12 @@ +--- +"@bradygaster/squad-cli": minor +--- + +Add `squad upgrade --self` to upgrade the CLI package itself (#798) + +- `squad upgrade --self` → installs `@bradygaster/squad-cli@latest` (stable) +- `squad upgrade --self --insider` → installs `@bradygaster/squad-cli@insider` (prerelease) +- After self-upgrade, automatically continues with repo upgrade to apply new templates +- Detects package manager (npm/pnpm/yarn) from npm_config_user_agent +- Clear error on permission denied (suggests sudo or npx) +- Help text updated with new flags diff --git a/.changeset/shell-injection-fixes.md b/.changeset/shell-injection-fixes.md new file mode 100644 index 000000000..52a95ff33 --- /dev/null +++ b/.changeset/shell-injection-fixes.md @@ -0,0 +1,12 @@ +--- +'@bradygaster/squad-sdk': patch +--- + +Eliminate shell injection vectors in scheduler and state backend + +- Replace all `execSync` calls with `execFileSync` using explicit argv arrays (no shell interpretation) +- Refactor git helper functions to accept `string[]` instead of space-delimited strings +- Add `validateTaskRef()` for scheduler script refs (rejects null bytes, newlines) +- Add `validateStateKey()` for state backend keys (rejects null bytes, newlines, tabs, path traversal) +- Validate script task refs at manifest parse time (defense-in-depth) +- Add security-focused tests for both scheduler and state backend diff --git a/.changeset/skill-security-scanner.md b/.changeset/skill-security-scanner.md new file mode 100644 index 000000000..283f2e497 --- /dev/null +++ b/.changeset/skill-security-scanner.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Add markdown-aware skill security scanner (Phase 1) to security-review.mjs. Scans .copilot/skills and .squad/skills markdown files for embedded credentials, download-and-execute patterns, and privilege escalation commands. Includes fenced code block suppression, inline code span suppression, and placeholder token detection. Zero false positives on existing 35 skill files. diff --git a/.changeset/smart-pr-nudge.md b/.changeset/smart-pr-nudge.md new file mode 100644 index 000000000..2144528df --- /dev/null +++ b/.changeset/smart-pr-nudge.md @@ -0,0 +1,8 @@ +--- +--- + +ci: add smart PR nudge for stale PRs + +New workflow that runs on weekdays and posts actionable diagnoses on PRs +stale for 7+ days. Checks CI status, unresolved threads, missing reviews, +outdated branches, and draft status. Won't nudge the same PR twice per week. diff --git a/.changeset/start-tunnel-node-pty.md b/.changeset/start-tunnel-node-pty.md new file mode 100644 index 000000000..b0d27302c --- /dev/null +++ b/.changeset/start-tunnel-node-pty.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Validate node-pty availability before side effects in squad start --tunnel diff --git a/.changeset/subprocess-integration-tests.md b/.changeset/subprocess-integration-tests.md new file mode 100644 index 000000000..9535f967c --- /dev/null +++ b/.changeset/subprocess-integration-tests.md @@ -0,0 +1,8 @@ +--- +--- + +test: add Copilot CLI subprocess integration tests for SQUAD_TEAM_ROOT + +8 tests covering SQUAD_TEAM_ROOT env-var resolution, fallback to cwd, +and resolveSquad() integration. Guards against subprocess working directory +bugs in Copilot CLI bang commands. diff --git a/.changeset/teams-adapter-fixes.md b/.changeset/teams-adapter-fixes.md new file mode 100644 index 000000000..6c35388d5 --- /dev/null +++ b/.changeset/teams-adapter-fixes.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-sdk': patch +--- + +Teams adapter: encode channelId in deep-link URL, log warning on icacls failure, add integration tests diff --git a/.changeset/teams-adapter-security.md b/.changeset/teams-adapter-security.md new file mode 100644 index 000000000..f6e34b3d6 --- /dev/null +++ b/.changeset/teams-adapter-security.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-sdk': patch +--- + +Teams adapter token security: tenant-scoped token cache (keyed by tenant ID hash), explicit logout() for session teardown, 15-minute device-code timeout guard, stale token cleanup on permanent auth errors, per-instance user ID cache. Migration guide for async createCommunicationAdapter change. diff --git a/.changeset/teams-comms-adapter.md b/.changeset/teams-comms-adapter.md new file mode 100644 index 000000000..c676b9dd9 --- /dev/null +++ b/.changeset/teams-comms-adapter.md @@ -0,0 +1,12 @@ +--- +"@bradygaster/squad-sdk": minor +--- + +feat(sdk): Add Microsoft Teams communication adapter + +BREAKING: `createCommunicationAdapter` is now async (returns `Promise`). +Callers must await the result. + +New Teams adapter for bidirectional chat via Microsoft Graph API. +Supports browser auth (PKCE), device code flow, token caching, +1:1 chat and channel messaging. diff --git a/.changeset/watch-health.md b/.changeset/watch-health.md new file mode 100644 index 000000000..7ea8999a3 --- /dev/null +++ b/.changeset/watch-health.md @@ -0,0 +1,10 @@ +--- +"@bradygaster/squad-cli": minor +--- + +feat(watch): health check — show running watch instance status (#808) + +Adds `squad watch --health` to display the status of a running watch +instance: PID, uptime, auth account, capabilities, and auth drift +detection. Writes `.squad/.watch-pid.json` at startup for instance +tracking. Detects and cleans up stale PID files from crashed instances. diff --git a/.changeset/watch-p0-p1-fixes.md b/.changeset/watch-p0-p1-fixes.md new file mode 100644 index 000000000..fce11b953 --- /dev/null +++ b/.changeset/watch-p0-p1-fixes.md @@ -0,0 +1,10 @@ +--- +'@bradygaster/squad-cli': patch +--- + +fix(watch): wire missing CLI flags to config, validate --state-backend, fix auth stderr parsing (#834) + +- Wire --notify-level, --overnight-start, --overnight-end, --sentinel-file to WatchConfig and loadWatchConfig merge +- Add --state-backend flag with upfront validation against allowed values (worktree, git-notes, orphan, external) +- Fix probeCurrentGhUser() to use `gh api user -q .login` (stdout) instead of parsing `gh auth status` stderr +- Also wire authUser through loadWatchConfig merge logic (was accepted but silently dropped) \ No newline at end of file diff --git a/.changeset/watch-rate-limit-detection.md b/.changeset/watch-rate-limit-detection.md new file mode 100644 index 000000000..5d83469ac --- /dev/null +++ b/.changeset/watch-rate-limit-detection.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Fix watch reporting "Board is clear" when GitHub API rate limit is hit. Detects rate-limit and other scan errors, shows appropriate warning instead of the misleading idle message, skips subsequent phases on failed scans, and prevents rate-limited rounds from counting as circuit-breaker successes. diff --git a/.changeset/watch-ux-fixes-2141.md b/.changeset/watch-ux-fixes-2141.md new file mode 100644 index 000000000..961772e8f --- /dev/null +++ b/.changeset/watch-ux-fixes-2141.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-cli": patch +--- + +fix(watch): 3 UX improvements — round timing output, --log-file tee flag, immediate startup feedback (#2141) \ No newline at end of file diff --git a/.changeset/watch-verbose-mode.md b/.changeset/watch-verbose-mode.md new file mode 100644 index 000000000..3dc92ae8d --- /dev/null +++ b/.changeset/watch-verbose-mode.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-cli": minor +--- + +Add --verbose flag to squad watch for debugging empty boards and silent failures (#781) diff --git a/.copilot/skills/architectural-review/SKILL.md b/.copilot/skills/architectural-review/SKILL.md new file mode 100644 index 000000000..8b8e11347 --- /dev/null +++ b/.copilot/skills/architectural-review/SKILL.md @@ -0,0 +1,200 @@ +--- +name: "architectural-review" +description: "How to review PRs for architectural quality — module boundaries, dependency direction, export surface, pattern consistency" +domain: "architecture" +confidence: "medium" +source: "extracted from copilot-instructions.md patterns and codebase conventions" +--- + +## Context + +When reviewing a PR that touches core architecture, the reviewer must verify that structural invariants are preserved. This skill covers **reviewing existing PRs** for architectural quality — not writing proposals (see `architectural-proposals/SKILL.md` for that). + +Use this skill when a PR includes any of: +- New modules, packages, or directories +- Dependency changes (`package.json`, `import` additions across package boundaries) +- API surface changes (new or modified exports from `index.ts` barrel files) +- Cross-package imports (anything crossing the SDK/CLI boundary) +- New abstractions (interfaces, base classes, registries, factories) +- Sweeping refactors (file renames, pattern migrations, bulk conversions) + +## Patterns + +### 1. Module Boundary Enforcement + +The Squad codebase has two core packages with a strict dependency direction: + +``` +squad-cli → depends on → squad-sdk +squad-sdk → NEVER depends on → squad-cli +``` + +**Check every new `import` statement in the PR:** +- CLI files may import from SDK (`@bradygaster/squad-sdk`) +- SDK files must NEVER import from CLI +- If a PR adds an SDK import that pulls in CLI code, reject it + +### 2. Zero-Dependency Bootstrap Contract + +The CLI has **protected bootstrap files** that must use ONLY Node.js built-in modules (`node:fs`, `node:path`, `node:child_process`, `node:util`). These files run before the SDK is loaded. + +**Protected files (authoritative list from copilot-instructions.md):** + +| File | Purpose | +|------|---------| +| `packages/squad-cli/src/cli/core/detect-squad-dir.ts` | Finds `.squad/` at startup | +| `packages/squad-cli/src/cli/core/errors.ts` | Error classes (`SquadError`, `fatal()`) | +| `packages/squad-cli/src/cli/core/gh-cli.ts` | GitHub CLI wrapper | +| `packages/squad-cli/src/cli/core/output.ts` | Color/emoji console output | +| `packages/squad-cli/src/cli/core/history-split.ts` | Portable knowledge separator | + +**Review checklist for bootstrap files:** +- ❌ No new `import` or `require` of anything outside `node:*` +- ❌ No conversion to `FSStorageProvider`, `StorageProvider`, or SDK abstractions +- ✅ Look for `— zero dependencies` markers in file headers +- ✅ If the PR adds a new bootstrap utility, it must be added to this table and have a zero-dependency regression test + +### 3. Export Surface Safety + +Changes to barrel files (`index.ts`) have downstream impact. Barrel files define the public API of a package. + +**Review checklist:** +- **Adding exports:** Is the new export intentional? Does it expose implementation details that should stay internal? +- **Removing exports:** Are downstream consumers accounted for? Removing an export is a breaking change. +- **Renaming exports:** Same as remove + add — breaks anyone using the old name. Look for typos (e.g., `FSStorageProvidr` vs. `FSStorageProvider`). +- **Re-exports:** If the PR adds `export * from './internal-module'`, verify that the internal module doesn't accidentally expose private types. + +### 4. Sweeping Refactor Safety + +When a PR applies a codebase-wide pattern change (e.g., "convert all `fs` calls to `StorageProvider`"), verify the 5-step checklist from project instructions: + +1. **Protected Files checked** — None of the protected bootstrap files were converted +2. **Zero-dependency markers scanned** — Files with `— zero dependencies` headers were skipped +3. **Imports resolve** — Every `import { X } from '@bradygaster/squad-sdk'` references an export that actually exists in the SDK barrel file +4. **Not blindly applied** — File-specific constraints were respected +5. **Batched testing** — Changes were tested in logical groups, not all 30 files at once + +### 5. Template Sync Awareness + +Template files exist in **four locations** and must stay consistent: + +``` +templates/ # Source of truth +.squad-templates/ # Local project templates +packages/squad-cli/templates/ # CLI-bundled templates +.github/workflows/ # Workflow templates +``` + +If a PR modifies a template in one location, check: +- Was the change mirrored to other locations where the same template exists? +- Does `TEMPLATE_MANIFEST` in `templates.ts` still correctly map files to agents? +- Were any new templates added to the manifest? + +### 6. `.squad/` Leakage Check + +`.squad/` files (team config, decisions, agent charters) must NOT leak into feature PRs that are about code changes. + +**Check:** +- If the PR is labeled as a feature or bugfix, it should not modify `.squad/team.md`, `.squad/decisions.md`, `.squad/routing.md`, or agent charters — unless the issue specifically calls for it +- `.squad/decisions/inbox/` files are acceptable if the PR documents a decision made during implementation +- `.squad-templates/` changes are acceptable only if the PR is about template functionality + +### 7. Pattern Consistency + +New code should follow established patterns in the codebase. Look for: +- **Naming conventions:** Does the new module follow existing naming? (e.g., `kebab-case` file names, `PascalCase` classes) +- **Error handling:** Does the new code use `SquadError` and `fatal()` from `errors.ts`, or does it invent its own error patterns? +- **Storage abstraction:** Does the new code use `StorageProvider` for file I/O (except in protected bootstrap files)? +- **Test patterns:** Does the PR include tests that follow the existing test structure? + +### 8. Dependency Direction in `core/` + +The `packages/squad-cli/src/cli/core/` directory contains a **mix** of: +- Early-startup bootstrap utilities (zero external deps — protected) +- Later SDK-dependent modules (may import from SDK) + +When reviewing changes to `core/` files: +- Verify the file is not in the protected list before accepting SDK imports +- Confirm the SDK is loaded at the point where the file runs in the startup sequence +- Watch for new files added to `core/` — determine if they are bootstrap or post-SDK and document accordingly + +## Examples + +**Example 1: Reject — SDK import in bootstrap file** +``` +PR adds to detect-squad-dir.ts: + import { FSStorageProvider } from '@bradygaster/squad-sdk'; + +Finding: REJECT — detect-squad-dir.ts is a protected bootstrap file. +It runs before the SDK is loaded. This import will crash the CLI at startup. +Recommendation: Use node:fs directly. See Protected Files table. +``` + +**Example 2: Reject — Reverse dependency** +``` +PR adds to packages/squad-sdk/src/storage.ts: + import { detectSquadDir } from '@bradygaster/squad-cli'; + +Finding: REJECT — SDK must never depend on CLI. Dependency direction is +CLI → SDK, not reverse. Move the shared logic to SDK or extract to a +shared utility within the SDK package. +``` + +**Example 3: Approve with note — Template change in one location** +``` +PR modifies templates/squad.agent.md but not .squad-templates/squad.agent.md + +Finding: APPROVE with note — Template was updated in templates/ but the +same file in .squad-templates/ was not synced. Verify whether .squad-templates/ +should match or if it intentionally diverges. +``` + +**Example 4: Reject — Sweeping refactor hits protected file** +``` +PR titled "Convert all fs calls to StorageProvider" modifies 15 files, +including packages/squad-cli/src/cli/core/output.ts + +Finding: REJECT — output.ts is a protected zero-dependency bootstrap file. +The PR must skip this file. See Sweeping Refactor Rules step 1. +``` + +**Example 5: Flag — Mass file deletion** +``` +PR deletes 25 files as part of "cleanup unused modules" + +Finding: FLAG — More than 20 files deleted. This exceeds the red flag +threshold. Verify each deletion is intentional and no downstream imports +are broken. Recommend splitting into smaller PRs. +``` + +## Review Output Format + +Structure your architectural review as: + +``` +## Architectural Review + +**Verdict:** APPROVE | APPROVE WITH NOTES | REQUEST CHANGES | REJECT + +### Findings + +1. [severity: critical|high|medium|low] — description + - File(s): path/to/file.ts + - Recommendation: what to fix + +2. [severity: ...] — ... + +### Summary +Brief explanation of architectural impact and any required follow-ups. +``` + +## Anti-Patterns + +- ❌ Approving SDK imports in protected bootstrap files ("it's just one import") +- ❌ Ignoring barrel file changes ("it's just adding an export") +- ❌ Skipping template sync check ("they probably know about the other locations") +- ❌ Accepting sweeping refactors without verifying the 5-step checklist +- ❌ Allowing `.squad/` config changes in feature PRs without justification +- ❌ Approving reverse dependencies (SDK importing from CLI) for "convenience" +- ❌ Letting new `core/` files slip in without classifying them as bootstrap vs. post-SDK +- ❌ Rubber-stamping PRs that delete >20 files without line-by-line deletion review diff --git a/.copilot/skills/git-workflow/SKILL.md b/.copilot/skills/git-workflow/SKILL.md index bfa0b8596..6ea4f3051 100644 --- a/.copilot/skills/git-workflow/SKILL.md +++ b/.copilot/skills/git-workflow/SKILL.md @@ -99,7 +99,8 @@ Each agent operates inside its worktree exactly like the single-issue workflow: cd ../squad-195 # Work normally — commits, tests, pushes -git add -A && git commit -m "fix: stamp bug (#195)" +# ⚠️ NEVER use `git add .`, `git add -A`, or other broad staging commands +git add -- {specific files you modified} && git commit -m "fix: stamp bug (#195)" git push -u origin squad/195-fix-stamp-bug # Create PR targeting dev diff --git a/.copilot/skills/pr-lifecycle/SKILL.md b/.copilot/skills/pr-lifecycle/SKILL.md new file mode 100644 index 000000000..50456644e --- /dev/null +++ b/.copilot/skills/pr-lifecycle/SKILL.md @@ -0,0 +1,537 @@ +--- +name: "pr-lifecycle" +description: "Complete issue → PR → merge lifecycle with readiness checks" +domain: "workflow" +confidence: "high" +source: "extracted from CONTRIBUTING.md, PR_REQUIREMENTS.md, squad-ci.yml" +--- + +## Context + +This skill is the **canonical Copilot-agent lifecycle for the Squad repository**. It covers the full path from picking up a GitHub issue to merging a PR. Where older docs (templates, copilot-instructions, CONTRIBUTING.md) conflict with this skill, **this skill takes precedence** for Copilot agents. + +For advanced scenarios (worktrees, multi-repo coordination), see `.copilot/skills/git-workflow/SKILL.md`. + +## Scope + +✅ **THIS SKILL COVERS:** +- Issue pickup and branch creation +- Implementation, build, test, lint workflow +- Pre-push safety verification +- PR creation with correct target, title, body, and labels +- All 11 PR readiness checks (what they check, how to pass, how to fix) +- Post-creation maintenance (review feedback, rebasing, CI) +- Merge preconditions and cleanup + +❌ **THIS SKILL DOES NOT COVER:** +- Worktree-based parallel work (see `git-workflow` skill) +- Multi-repo coordinated PRs (see `git-workflow` skill) +- Release process / publishing (see `.squad/skills/release-process`) +- Reviewer lockout protocol (see `.copilot/skills/reviewer-protocol`) +- Architectural or security review checklists + +--- + +## Lifecycle Procedure + +### Phase 1 — Issue Pickup + +1. **Read the issue.** Understand the acceptance criteria before writing code. + +2. **Confirm capability fit.** Check your capability profile in `.squad/team.md`. 🟢 = proceed. 🟡 = proceed but flag in PR. 🔴 = comment on issue and stop. + +3. **Branch from dev:** + ```bash + git fetch origin dev + git checkout dev + git rebase origin/dev + git checkout -b squad/{issue-number}-{slug} + ``` + - Branch name format: `squad/{issue-number}-{kebab-case-slug}` + - Example: `squad/42-fix-login-validation` + - **Never** branch from `main` + +4. **Mark in-progress** (optional): + ```bash + gh issue edit {number} --add-label "status:in-progress" + ``` + +--- + +### Phase 2 — Implementation + +1. **Make your changes.** Follow the codebase conventions: + - TypeScript strict mode, no `@ts-ignore` + - ESM-only, async/await + - JSDoc on new public APIs + +2. **Build:** + ```bash + npm run build + ``` + +3. **Test:** + ```bash + npm test + ``` + +4. **Type check:** + ```bash + npm run lint + ``` + +5. **Stage specific files only:** + ```bash + git add path/to/file1.ts path/to/file2.ts + ``` + - ❌ **NEVER** `git add .`, `git add -A`, or `git commit -a` + - ✅ **ALWAYS** name each file explicitly + +6. **Pre-push safety check:** + ```bash + # Verify file count matches intent (expect ≤10 files for most fixes) + git diff --cached --stat + + # Verify NO unintended deletions + git diff --cached --diff-filter=D --name-only + ``` + If you see unexpected files or deletions, unstage them: `git reset HEAD ` + +7. **Single commit with issue reference:** + ```bash + git commit -m "Brief description of change + + Closes #{issue-number} + + Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" + ``` + - If you already have multiple commits, squash: `git rebase -i origin/dev` and squash all into one + - **Never** use `git reset --soft` to squash — it picks up delta from dev and contaminates the commit + +8. **Add changeset (if required):** + A changeset is required when your PR modifies files under `packages/squad-sdk/src/` or `packages/squad-cli/src/`. + + ```bash + npx changeset add + ``` + This prompts for: which packages changed, bump type (patch/minor/major), summary. + + Or create manually at `.changeset/{descriptive-name}.md`: + ```markdown + --- + '@bradygaster/squad-cli': patch + --- + + Brief description of the change + ``` + + If the changeset creates a second commit, squash it into your main commit. + +--- + +### Phase 3 — PR Creation + +1. **Push:** + ```bash + git push -u origin squad/{issue-number}-{slug} + ``` + +2. **Create PR targeting dev:** + ```bash + gh pr create --repo bradygaster/squad --base dev \ + --title "fix: brief description (#issue-number)" \ + --body "Closes #{issue-number} + + ## Summary + What this PR does and why. + + ## Changes + - File-level description of changes + + Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" + ``` + +3. **Title conventions:** + - Bug fix: `fix: description (#N)` + - Feature: `feat: description (#N)` + - Docs: `docs: description (#N)` + - Chore/infra: `chore: description (#N)` + +4. **Labels:** + - `fix`, `feat`, `docs`, or `repo-health` for type + - `squad:{agent-name}` if working as a squad member + - `skip-changelog` only with reviewer approval (escape hatch) + +5. **Scope rules by label:** + - `repo-health`: Only modify `.github/`, `scripts/`, root config, tests, docs. **Never** modify `packages/*/src/` + - `fix` or `feat`: May modify product source. Must include changeset when touching `packages/*/src/` + +--- + +### Phase 4 — PR Readiness + +An automated readiness check runs on every push and posts a checklist comment on the PR. All 11 checks must pass before review (check 11 is informational-only). + +#### Check 1: Single Commit + +| | | +|---|---| +| **What** | PR must contain exactly 1 commit | +| **Pass** | Push a single, squashed commit | +| **Fix** | `git rebase -i origin/dev` → squash all commits into one → `git push --force-with-lease` | +| **Gotcha** | Never `git reset --soft` to squash — contaminates the commit with unrelated changes | + +#### Check 2: Not in Draft + +| | | +|---|---| +| **What** | PR must not be marked as draft | +| **Pass** | Create PR as ready, or convert: `gh pr ready` | +| **Fix** | `gh pr ready {number}` or use the GitHub UI | +| **Gotcha** | The readiness check still runs on drafts but will show ❌ until you mark ready | + +#### Check 3: Branch Up to Date + +| | | +|---|---| +| **What** | PR branch must not be behind `dev` | +| **Pass** | Rebase onto latest dev before pushing | +| **Fix** | `git fetch origin dev && git rebase origin/dev && git push --force-with-lease` | +| **Gotcha** | After rebasing, the readiness check re-runs automatically on the new push | + +#### Check 4: Copilot Review + +| | | +|---|---| +| **What** | The `copilot-pull-request-reviewer` bot must post an `APPROVED` review | +| **Pass** | Wait — Copilot review is triggered automatically on PR creation/push | +| **Fix** | If Copilot hasn't reviewed after 5 minutes, push an empty commit to re-trigger: `git commit --allow-empty -m "trigger review" && git push` then squash before merge | +| **Gotcha** | Copilot review state is `APPROVED`, `CHANGES_REQUESTED`, or `COMMENTED`. Only `APPROVED` passes | + +#### Check 5: Changeset Present + +| | | +|---|---| +| **What** | PRs that modify `packages/squad-sdk/src/` or `packages/squad-cli/src/` must include a `.changeset/*.md` file or a `CHANGELOG.md` edit | +| **Pass** | Run `npx changeset add` and commit the generated file | +| **Fix** | `npx changeset add` → select affected package(s) → select bump type → write summary → `git add .changeset/ && git commit --amend --no-edit && git push --force-with-lease` | +| **Gotcha** | The `skip-changelog` label bypasses this check but requires reviewer approval. Non-source changes (docs, config, tests) don't need a changeset | + +#### Check 6: No Merge Conflicts + +| | | +|---|---| +| **What** | PR must be cleanly mergeable with the base branch | +| **Pass** | Keep branch rebased on dev | +| **Fix** | `git fetch origin dev && git rebase origin/dev` → resolve conflicts → `git push --force-with-lease` | +| **Gotcha** | GitHub may show `null` mergeability briefly while computing — the check treats this as passing | + +#### Check 7: Scope Clean + +| | | +|---|---| +| **What** | Warns if PR includes `.squad/` or `docs/proposals/` files | +| **Pass** | Don't include team state or proposal files in product PRs | +| **Fix** | Remove unintended files: `git reset HEAD .squad/ docs/proposals/` then amend your commit | +| **Gotcha** | This check is **informational only** — it always passes but flags attention. Including these files is OK if intentional (e.g., updating agent history) | + +#### Check 8: Copilot Threads Resolved + +| | | +|---|---| +| **What** | All review threads opened by `copilot-pull-request-reviewer` must be resolved | +| **Pass** | Address each Copilot comment, then click "Resolve conversation" in the GitHub UI | +| **Fix** | Go to the PR's "Files changed" tab → find unresolved Copilot threads → fix the code or explain why no change is needed → click "Resolve conversation" | +| **Gotcha** | Outdated threads (on code that's since changed) are automatically skipped. Only active, unresolved threads block | + +#### Check 9: CI Passing + +| | | +|---|---| +| **What** | All CI check runs (excluding the readiness check itself) must be green | +| **Pass** | Fix any build, test, or lint failures and push | +| **Fix** | Read the failing check's logs (`gh run view {run-id} --log-failed`), fix the issue, push | +| **Gotcha** | Pending/in-progress checks also show ❌. Wait for all checks to complete before evaluating. The readiness check re-runs after CI completes via `workflow_run` trigger | + +#### Check 10: Issue Linked + +| | | +|---|---| +| **What** | PR body or commit message must reference an issue (`Closes #N`, `Fixes #N`, `Resolves #N`, or `Part of #N`) | +| **Pass** | Include `Closes #N` in PR body or commit message | +| **Fix** | Edit PR body to add `Closes #{issue-number}`, or amend commit message | +| **Gotcha** | Case-insensitive matching. Only closing keywords are recognized | + +#### Check 11: Protected Files (informational) + +| | | +|---|---| +| **What** | Warns when zero-dependency bootstrap files are modified (always passes) | +| **Pass** | Always passes — this is informational only | +| **Fix** | If flagged, verify the changed bootstrap file still has zero external dependencies | +| **Gotcha** | Protected files are listed in `copilot-instructions.md`. The check warns but does not block | + +--- + +### Phase 5 — Post-Creation Maintenance + +1. **Handling Copilot review feedback:** + - Read each comment carefully + - Fix valid issues in your code + - Resolve each thread after addressing it + - Amend your single commit: `git commit --amend --no-edit && git push --force-with-lease` + +2. **Rebasing when behind dev:** + ```bash + git fetch origin dev + git rebase origin/dev + # Resolve any conflicts + git push --force-with-lease + ``` + +3. **Re-running CI:** + - CI re-runs automatically on every push + - To re-run without changes: `gh run rerun {run-id} --failed` + +4. **If readiness check is stale:** + - The readiness check re-runs on push and after CI completes + - If it's stale, push a trivial fix or empty commit to re-trigger + +--- + +### Phase 6 — Merge & Cleanup + +1. **Merge preconditions:** + - All 11 readiness checks pass (✅ across the board) + - Human reviewer has approved (or maintainer merges directly) + - No open blocking conversations + +2. **Who merges:** + - Agents do **not** merge PRs themselves + - Maintainers (bradygaster or designated reviewers) merge via GitHub UI + - The repo uses squash merge, so commit history is clean regardless + +3. **Post-merge cleanup:** + ```bash + git checkout dev + git pull origin dev + git branch -d squad/{issue-number}-{slug} + git push origin --delete squad/{issue-number}-{slug} + ``` + +4. **Verify issue auto-close:** + - If PR body contains `Closes #{N}`, the issue closes automatically on merge + - If not, manually close: `gh issue close {number}` + +--- + +## Examples + +### Example: Bug fix PR (no source changes) + +```bash +# Branch +git fetch origin dev && git checkout dev && git rebase origin/dev +git checkout -b squad/610-fix-broken-link + +# Fix +# ... edit docs/some-file.md ... + +# Validate +npm run build && npm test + +# Commit (no changeset needed — docs only) +git add docs/some-file.md +git diff --cached --stat # verify: 1 file +git commit -m "docs: fix broken link in contributing guide + +Closes #610 + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" + +# Push and PR +git push -u origin squad/610-fix-broken-link +gh pr create --repo bradygaster/squad --base dev \ + --title "docs: fix broken link (#610)" \ + --body "Closes #610" +``` + +### Example: SDK feature PR (source changes) + +```bash +# Branch +git fetch origin dev && git checkout dev && git rebase origin/dev +git checkout -b squad/42-add-profile-api + +# Implement +# ... edit packages/squad-sdk/src/profile/index.ts ... +# ... edit packages/squad-sdk/src/index.ts (re-export) ... + +# Validate +npm run build && npm test && npm run lint + +# Changeset (required — touches packages/squad-sdk/src/) +npx changeset add +# Select: @bradygaster/squad-sdk, minor, "Add profile API" + +# Stage and commit +git add packages/squad-sdk/src/profile/index.ts packages/squad-sdk/src/index.ts .changeset/ +git diff --cached --stat # verify file count +git diff --cached --diff-filter=D --name-only # verify no deletions +git commit -m "feat: add profile API + +Closes #42 + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" + +# Push and PR +git push -u origin squad/42-add-profile-api +gh pr create --repo bradygaster/squad --base dev \ + --title "feat: add profile API (#42)" \ + --body "Closes #42 + +## Summary +Adds profile resolution API to the SDK. + +## Changes +- New module: packages/squad-sdk/src/profile/ +- Re-exported from barrel file" +``` + +--- + +## Anti-Patterns + +- ❌ Branching from `main` (always branch from `dev`) +- ❌ Targeting `main` with PRs (always target `dev`) +- ❌ Using `git add .` or `git add -A` (stage specific files only) +- ❌ Using `git commit -a` (same risk as broad staging) +- ❌ Using `git reset --soft` to squash (contaminates commit with dev delta) +- ❌ Force-pushing to `dev` or `main` (only force-push your own feature branch) +- ❌ Merging your own PR (maintainers merge) +- ❌ Skipping the changeset when source files changed (CI will fail) +- ❌ Self-resolving Copilot threads without addressing the feedback +- ❌ Pushing >1 commit without squashing (readiness check will flag it) +- ❌ Including `.squad/` files in product PRs without intention (scope check warns) +- ❌ Mixing product and infrastructure changes in one PR (create separate PRs) + +--- + +## Readiness Check Gaps & Recommendations + +After analyzing `.github/workflows/squad-ci.yml`, three gaps were identified. Gaps 1 and 3 are now implemented (checks 10 and 11). Gap 2 is deferred. + +### Gap 1: Issue Linkage Check (Check 10) — IMPLEMENTED + +**Problem:** Nothing verifies that the PR body or commit message references an issue (`Closes #N` or `Part of #N`). Orphan PRs are hard to trace. + +**Recommendation:** Add `checkIssueLinkage(prBody, commitMessages)` to `pr-readiness.mjs`. + +```javascript +/** + * Check: Issue linkage. + * @param {string} prBody — PR description text + * @param {Array<{ commit: { message: string } }>} commits + * @returns {{ pass: boolean, detail: string }} + */ +export function checkIssueLinkage(prBody, commits) { + const issuePattern = /(closes|fixes|resolves|part of)\s+#\d+/i; + const bodyHasRef = issuePattern.test(prBody || ''); + const commitHasRef = (commits || []).some( + (c) => issuePattern.test(c.commit?.message || '') + ); + if (bodyHasRef || commitHasRef) { + return { pass: true, detail: 'Issue reference found' }; + } + return { + pass: false, + detail: 'No issue reference — add `Closes #N` to PR body or commit message', + }; +} +``` + +**Integration point:** After check 2 (draft status), before check 3 (branch freshness). Data is already available from the commits and PR body fetched in the orchestrator. + +### Gap 2: No Required Checks Presence Verification + +**Problem:** The CI status check (check 9) verifies that existing checks are green, but doesn't verify that the *expected* set of checks actually ran. If a workflow is misconfigured or skipped, the PR could pass with zero CI checks. + +**Recommendation:** Add `checkRequiredChecksPresent(checkRuns, files)` to `pr-readiness.mjs`. + +```javascript +/** Minimum required check names that must appear for source PRs. */ +export const REQUIRED_CHECKS = ['Squad CI / test']; + +/** + * Check: Required CI checks are present. + * @param {Array<{ name: string }>} checkRuns + * @param {Array<{ filename: string }>} files + * @returns {{ pass: boolean, detail: string }} + */ +export function checkRequiredChecksPresent(checkRuns, files) { + const touchesSource = (files || []).some( + (f) => SOURCE_PATTERN.test(f.filename) + ); + if (!touchesSource) { + return { pass: true, detail: 'No source changes — required checks not enforced' }; + } + const checkNames = new Set((checkRuns || []).map((cr) => cr.name)); + const missing = REQUIRED_CHECKS.filter((name) => !checkNames.has(name)); + if (missing.length > 0) { + return { + pass: false, + detail: `Required check(s) not found: ${missing.join(', ')}`, + }; + } + return { pass: true, detail: 'All required checks present' }; +} +``` + +**Integration point:** After check 9 (CI status). Uses the same `checkRuns` and `files` data already fetched. + +### Gap 3: Protected File Change Detection (Check 11) — IMPLEMENTED + +**Problem:** The repo has zero-dependency bootstrap files that must never import external packages (documented in `copilot-instructions.md`). The `squad-repo-health.yml` workflow runs a bootstrap protection check, but the PR readiness comment doesn't surface it — contributors don't see the warning until they check the separate workflow. + +**Recommendation:** Add `checkProtectedFiles(files)` to `pr-readiness.mjs` as an **informational** check (always passes, like scope clean). + +```javascript +/** Bootstrap files that must remain zero-dependency. */ +export const PROTECTED_FILES = [ + 'packages/squad-cli/src/cli/core/detect-squad-dir.ts', + 'packages/squad-cli/src/cli/core/errors.ts', + 'packages/squad-cli/src/cli/core/gh-cli.ts', + 'packages/squad-cli/src/cli/core/output.ts', + 'packages/squad-cli/src/cli/core/history-split.ts', +]; + +/** + * Check: Protected file changes (informational). + * @param {Array<{ filename: string }>} files + * @returns {{ pass: boolean, detail: string }} + */ +export function checkProtectedFiles(files) { + const touched = (files || []).filter( + (f) => PROTECTED_FILES.includes(f.filename) + ); + if (touched.length === 0) { + return { pass: true, detail: 'No protected bootstrap files changed' }; + } + return { + pass: true, + detail: `⚠️ ${touched.length} protected bootstrap file(s) changed: ${touched.map((f) => f.filename.split('/').pop()).join(', ')} — verify zero-dependency constraint`, + }; +} +``` + +**Integration point:** After check 7 (scope clean). Uses the same `files` data. Informational only — warns but doesn't block. + +### Summary of Recommended Changes + +| Check | Type | Blocks PR? | Status | +|-------|------|------------|--------| +| Issue linkage (Check 10) | Hard gate | Yes | Implemented | +| Required checks present | Hard gate | Yes | Deferred | +| Protected file warning (Check 11) | Informational | No | Implemented | + +All three use data that `pr-readiness.mjs` already fetches — no new API calls needed. Total addition: ~60 lines of check functions + ~10 lines of orchestration wiring. diff --git a/.copilot/skills/reviewer-protocol/SKILL.md b/.copilot/skills/reviewer-protocol/SKILL.md index 5d589105c..cc3543edc 100644 --- a/.copilot/skills/reviewer-protocol/SKILL.md +++ b/.copilot/skills/reviewer-protocol/SKILL.md @@ -68,6 +68,12 @@ When an artifact is **rejected** by a Reviewer: 4. Hockney: "Verbal, then" 5. Coordinator spawns Verbal +## Related Review Skills + +For domain-specific review checklists, see: +- **Architectural Review:** `.copilot/skills/architectural-review/SKILL.md` — module boundaries, dependency direction, export surface, sweeping refactor safety +- **Security Review:** `.copilot/skills/security-review/SKILL.md` — credentials, injection, workflow permissions, supply chain + ## Anti-Patterns - ❌ Allowing the original author to self-revise after rejection diff --git a/.copilot/skills/security-review/SKILL.md b/.copilot/skills/security-review/SKILL.md new file mode 100644 index 000000000..aa79a8734 --- /dev/null +++ b/.copilot/skills/security-review/SKILL.md @@ -0,0 +1,266 @@ +--- +name: "security-review" +description: "How to review PRs for security — credentials, injection, workflow permissions, supply chain, git operation safety" +domain: "security" +confidence: "medium" +source: "extracted from copilot-instructions.md patterns and GitHub Actions security best practices" +--- + +## Context + +Every PR that touches authentication, credentials, environment variables, file system operations, child process execution, user input handling, GitHub API calls, or workflow files must be reviewed for security. This skill provides a systematic checklist for catching security issues before they reach production. + +Use this skill when a PR includes any of: +- Changes to `.github/workflows/` files +- New or modified environment variable usage +- File system read/write operations driven by user input +- Child process execution (`child_process.exec`, `child_process.spawn`) +- GitHub API calls (Octokit, `gh` CLI wrappers) +- New npm dependencies +- Credential or token handling +- Template rendering with dynamic values + +## Patterns + +### 1. Secrets and Credentials + +**No secrets in source code — ever.** + +**Review checklist:** +- ❌ No API keys, tokens, passwords, or connection strings in committed files +- ❌ No hardcoded `GITHUB_TOKEN`, `NPM_TOKEN`, or similar values +- ❌ No `.env` files committed (verify `.gitignore` covers them) +- ✅ Secrets must be referenced via `process.env.X` or `${{ secrets.X }}` in workflows +- ✅ If the PR adds a new secret reference, verify it's documented in setup instructions + +**Watch for disguised secrets:** +- Base64-encoded strings that decode to tokens +- URLs with embedded credentials (`https://user:token@host`) +- Test fixtures that contain real credentials from copy-paste + +### 2. Personal Data in Committed Files + +**Personal data (especially email addresses) must never be written to committed files.** + +**Squad-specific rule:** `git config user.email` is explicitly banned from being written to any file that gets committed. This prevents PII leakage into the repository. + +**Review checklist:** +- ❌ No email addresses written to committed files (even in config generation) +- ❌ No `git config user.email` output captured and stored in source +- ✅ If user identity is needed at runtime, read it dynamically and never persist to a committed file + +### 3. Environment Variable Safety + +**Review checklist:** +- Are environment variables validated before use? (e.g., checking for `undefined`) +- Are sensitive environment variables excluded from logs and error messages? +- Does the PR avoid writing environment variable values to committed files? +- If a new `process.env.X` reference is added, is the variable documented? + +### 4. File System Safety + +**Path traversal prevention:** +- ❌ No direct concatenation of user input into file paths (`path.join(baseDir, userInput)` without validation) +- ✅ Validate that resolved paths stay within expected boundaries (use `path.resolve` and verify the result starts with the expected base directory) +- ✅ Reject paths containing `..`, absolute paths when relative are expected, or null bytes + +**Arbitrary file write prevention:** +- If user input determines a file path, verify the code constrains writes to a known safe directory +- Template rendering that writes files should validate output paths against a whitelist or known directory + +### 5. Child Process Safety + +**Shell injection prevention:** +- ❌ No unsanitized user input passed to `child_process.exec()` (exec uses a shell and is vulnerable to injection) +- ✅ Prefer `child_process.execFile()` or `child_process.spawn()` with argument arrays (no shell interpretation) +- ✅ If `exec` must be used, validate/sanitize all interpolated values +- ✅ Check for template literals in shell commands — `exec(\`git commit -m "${userMessage}"\`)` is injectable + +**Review checklist for `gh` CLI wrappers:** +- The `gh-cli.ts` module wraps GitHub CLI calls. Verify that arguments are passed as array elements, not interpolated into a shell string +- If the PR modifies `gh-cli.ts` or adds new `gh` commands, check for injection vectors + +### 6. GitHub Actions Workflow Security + +**`pull_request_target` safety:** +- `pull_request_target` + checking out PR code = **critical security risk** (the workflow runs with write token but executes untrusted code from the fork) +- `pull_request_target` + NO code checkout = **safe** (write token used only with trusted workflow code) +- If a PR adds `pull_request_target`, verify it does NOT checkout `${{ github.event.pull_request.head.ref }}` or `${{ github.event.pull_request.head.sha }}` + +**Token scope minimization:** +- Workflows should declare explicit `permissions:` blocks +- Follow principle of least privilege — no unnecessary `write` permissions +- If the PR adds `permissions: write-all` or omits permissions (defaults to broad), flag it + +**Recursion safety:** +- `GITHUB_TOKEN`-generated events do NOT trigger new workflow runs (GitHub prevents infinite loops) +- If a workflow uses a PAT (Personal Access Token) instead of `GITHUB_TOKEN`, it CAN trigger downstream workflows — verify this is intentional + +**Secrets in workflows:** +- ❌ No `${{ secrets.X }}` in workflow `run:` steps where the value could leak to logs +- ✅ Use `::add-mask::` to mask secret values in workflow output +- ✅ Minimize the number of steps that have access to secrets + +### 7. Git Operation Safety + +**These rules are from the project's mandatory Git Safety guidelines:** + +**Staging safety:** +- ❌ No `git add .` or `git add -A` — these stage unintended deletions from incomplete working trees +- ❌ No `git commit -a` — same risk +- ✅ Only `git add path/to/specific/file.ts` with explicit file paths + +**Push safety:** +- ❌ No `git push --force` or `--force-with-lease` to shared branches (`dev`, `main`) +- ❌ No direct push to `dev` or `main` — must use a PR +- ✅ Feature branches only: `squad/{issue-number}-{slug}` + +**If a PR adds git operations (e.g., in a script, workflow, or agent action), verify every `git add`, `git commit`, and `git push` command follows these rules.** + +### 8. Export/Import Integrity + +Typos in export names cause silent runtime failures — the export resolves to `undefined` instead of throwing. + +**Review checklist:** +- Check for typos in export names (e.g., `FSStorageProvidr` vs. `FSStorageProvider`) +- Verify new re-exports actually reference existing modules +- If the PR renames an export, confirm all import sites are updated +- Watch for `export { X as Y }` where `X` doesn't exist — TypeScript may not catch this in all configurations + +### 9. Dependency Supply Chain + +**New npm dependencies should be audited:** +- Is the package well-maintained? (check last publish date, download count, open issues) +- Does the package have known vulnerabilities? (`npm audit`) +- Is there a built-in Node.js alternative? (prefer `node:fs`, `node:path`, `node:crypto` over third-party equivalents) +- Does the package pull in a large transitive dependency tree? +- Is the package from a trusted publisher? + +**Squad-specific:** Protected bootstrap files must use ONLY `node:*` built-in modules. New dependencies in these files are a **critical** finding. + +### 10. Multi-Account Auth Isolation + +When the `gh` CLI is used in contexts where multiple GitHub accounts might be active: +- Verify that auth tokens are scoped to the correct account +- Check that `gh auth status` is validated before operations +- If the PR adds GitHub API calls, verify it handles auth failures gracefully + +## Examples + +**Example 1: Critical — Shell injection in git command** +``` +PR adds: + exec(`git commit -m "${commitMessage}"`) + +Finding: [critical] Shell injection — commitMessage is user-controlled. +A message containing "; rm -rf /" would execute arbitrary commands. +Fix: Use execFile('git', ['commit', '-m', commitMessage]) instead. +``` + +**Example 2: Critical — pull_request_target with checkout** +``` +PR adds workflow: + on: pull_request_target + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + - run: npm test + +Finding: [critical] pull_request_target + PR head checkout = untrusted +code execution with write token. An attacker can modify package.json +scripts in their fork to exfiltrate GITHUB_TOKEN. +Fix: Use 'pull_request' trigger instead, or do not checkout PR code. +``` + +**Example 3: High — Email written to committed file** +``` +PR adds to init.ts: + const email = execSync('git config user.email').toString().trim(); + fs.writeFileSync('.squad/config.json', JSON.stringify({ author: email })); + +Finding: [high] Personal data (email) written to committed file. +This violates the PII-in-source rule. Squad config files are committed +to the repo and shared across contributors. +Fix: Read email at runtime only, never persist to committed files. +``` + +**Example 4: Medium — Overly broad workflow permissions** +``` +PR adds workflow with no permissions block (inherits default write-all): + on: push + jobs: + deploy: + runs-on: ubuntu-latest + +Finding: [medium] No explicit permissions block. Workflow inherits +default permissions which may be overly broad. +Fix: Add 'permissions:' block with minimum required scopes. +``` + +**Example 5: Medium — New dependency when built-in exists** +``` +PR adds to package.json: + "dependencies": { "mkdirp": "^3.0.0" } + +Finding: [medium] mkdirp is unnecessary — Node.js built-in +fs.mkdirSync(path, { recursive: true }) provides the same functionality. +Fix: Use node:fs built-in instead of adding a new dependency. +``` + +**Example 6: Low — Missing environment variable validation** +``` +PR uses process.env.SQUAD_API_KEY without checking if it's defined: + const apiKey = process.env.SQUAD_API_KEY; + fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } }); + +Finding: [low] No validation that SQUAD_API_KEY is defined. +If undefined, the Bearer token will be "Bearer undefined" which +may produce confusing auth errors. +Fix: Check for undefined and throw a descriptive error early. +``` + +## Review Output Format + +Structure your security review with severity levels: + +``` +## Security Review + +**Verdict:** APPROVE | APPROVE WITH NOTES | REQUEST CHANGES | REJECT + +### Findings + +1. [severity: critical] — description + - File(s): path/to/file.ts:L42 + - Risk: what could go wrong + - Fix: specific remediation + +2. [severity: high] — ... + +3. [severity: medium] — ... + +4. [severity: low] — ... + +### Severity Guide +- **Critical:** Exploitable vulnerability, credential exposure, or code execution risk. Must fix before merge. +- **High:** Security design flaw that could lead to exploitation. Must fix before merge. +- **Medium:** Security best practice violation. Should fix before merge. +- **Low:** Minor hardening opportunity. Can fix in a follow-up PR. + +### Summary +Brief overview of security posture and required actions. +``` + +## Anti-Patterns + +- ❌ Approving PRs with `exec()` and user-controlled input ("it's just a commit message") +- ❌ Ignoring `pull_request_target` trigger changes ("the workflow looks fine otherwise") +- ❌ Skipping dependency audit for "small" packages ("it's only 10 lines of code") +- ❌ Treating `git add .` in scripts as acceptable ("it works on my machine") +- ❌ Allowing secrets in test fixtures ("it's just a test, not production") +- ❌ Approving workflows without explicit `permissions:` blocks ("defaults are probably fine") +- ❌ Dismissing PII in committed files ("it's just an email address") +- ❌ Ignoring export typos ("TypeScript would catch that") — not always true with re-exports +- ❌ Allowing force pushes to shared branches in scripts ("we know what we're doing") +- ❌ Skipping review of new `node_modules` additions in bootstrap files ("it's a small package") diff --git a/.copilot/skills/windows-compatibility/SKILL.md b/.copilot/skills/windows-compatibility/SKILL.md new file mode 100644 index 000000000..faf04c629 --- /dev/null +++ b/.copilot/skills/windows-compatibility/SKILL.md @@ -0,0 +1,85 @@ +--- +name: "windows-compatibility" +description: "Cross-platform path handling and command patterns" +domain: "platform" +confidence: "high" +source: "earned (multiple Windows-specific bugs: colons in filenames, git -C failures, path separators)" +--- + +## Context + +Squad runs on Windows, macOS, and Linux. Several bugs have been traced to platform-specific assumptions: ISO timestamps with colons (illegal on Windows), `git -C` with Windows paths (unreliable), forward-slash paths in Node.js on Windows. + +## Patterns + +### Filenames & Timestamps +- **Never use colons in filenames:** ISO 8601 format `2026-03-15T05:30:00Z` is illegal on Windows +- **Use `safeTimestamp()` utility:** Replaces colons with hyphens → `2026-03-15T05-30-00Z` +- **Centralize formatting:** Don't inline `.toISOString().replace(/:/g, '-')` — use the utility + +### Git Commands +- **Never use `git -C {path}`:** Unreliable with Windows paths (backslashes, spaces, drive letters) +- **Always `cd` first:** Change directory, then run git commands +- **Check for changes before commit:** `git diff --cached --quiet` (exit 0 = no changes) + +### Commit Messages +- **Never embed newlines in `-m` flag:** Backtick-n (`\n`) fails silently in PowerShell +- **Use temp file + `-F` flag:** Write message to file, commit with `git commit -F $msgFile` + +### Paths +- **Never assume CWD is repo root:** Always use `TEAM ROOT` from spawn prompt or run `git rev-parse --show-toplevel` +- **Use path.join() or path.resolve():** Don't manually concatenate with `/` or `\` + +## Examples + +✓ **Correct:** +```powershell +// Timestamp utility +const safeTimestamp = () => new Date().toISOString().replace(/:/g, '-').split('.')[0] + 'Z'; + +// Git workflow (PowerShell) +cd $teamRoot +# ⚠️ NEVER use `git add .squad/` or broad globs — only stage files you intentionally changed +# Stage only files you actually modified — use git status to build explicit list +$filesToStage = git status --porcelain | Where-Object { $_.Length -gt 3 } | ForEach-Object { $_.Substring(3) -replace '^.* -> ','' } | Where-Object { + $_ -eq '.squad/decisions.md' -or + $_ -eq '.squad/decisions-archive.md' -or + $_ -like '.squad/agents/*/history.md' -or + $_ -like '.squad/agents/*/history-archive.md' -or + $_ -like '.squad/log/*' -or + $_ -like '.squad/orchestration-log/*' +} +if ($filesToStage) { $filesToStage | Where-Object { $_ } | ForEach-Object { git add -- $_ } } +git diff --cached --quiet +if ($LASTEXITCODE -ne 0) { + $msg = @" +docs(ai-team): session log + +Changes: +- Added decisions +"@ + $msgFile = [System.IO.Path]::GetTempFileName() + Set-Content -Path $msgFile -Value $msg -Encoding utf8 + git commit -F $msgFile + Remove-Item $msgFile +} +``` + +✗ **Incorrect:** +```javascript +// Colon in filename +const logPath = `.squad/log/${new Date().toISOString()}.md`; // ILLEGAL on Windows + +// git -C with Windows path +exec('git -C C:\\src\\squad add .squad/'); // UNRELIABLE + +// Inline newlines in commit message +exec('git commit -m "First line\nSecond line"'); // FAILS silently in PowerShell +``` + +## Anti-Patterns + +- Testing only on one platform (bugs ship to other platforms) +- Assuming Unix-style paths work everywhere +- Using `git -C` because it "looks cleaner" (it doesn't work) +- Skipping `git diff --cached --quiet` check (creates empty commits) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d6203f561..cebbf6ba2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,22 +7,46 @@ ### How -### Testing - +--- + +### ⚠️ Quick Check +- [ ] If SDK/CLI source files changed: completed the applicable Changeset step below (`npx changeset add` / `.changeset/*.md`, direct `CHANGELOG.md` entry for maintainers, or `skip-changelog` label for no user-facing changes) + +### PR Readiness Checklist + +> The PR readiness bot will validate these automatically after push. +> Check each item before requesting review. See [CONTRIBUTING.md](../CONTRIBUTING.md) for full details. + +#### Branch & Commit +- [ ] Branch created from `dev` (not `main`) +- [ ] Branch is up to date with `dev` (`git fetch upstream && git rebase upstream/dev`) +- [ ] Verified diff contains only intended changes (`git diff --cached --stat`) +- [ ] PR is **not** in draft mode (mark ready when checks pass) +- [ ] Commit history is clean (squash fixups before review) + +#### Build & Test - [ ] `npm run build` passes - [ ] `npm test` passes (all tests green) -- [ ] For migration PRs (>20 files): test output summary included below +- [ ] `npm run lint` passes (type check clean) +- [ ] `npm run lint:eslint` passes +- [ ] For migration PRs (>20 files): include test output summary in PR description + +#### Changeset +- [ ] Changeset added via `npx changeset add` (if `packages/squad-sdk/src/` or `packages/squad-cli/src/` changed) +- [ ] Or direct `CHANGELOG.md` entry (maintainers only — write-protected for external contributors) +- [ ] Or `skip-changelog` label applied (if no user-facing changes) -### Docs - -- [ ] CHANGELOG.md entry (if packages/squad-sdk/src/ or packages/squad-cli/src/ changed) +#### Docs + - [ ] README section updated (if new feature/module) - [ ] Docs feature page (if new user-facing capability) -### Exports +#### Exports - [ ] package.json subpath exports updated (if new module) +--- + ### Breaking Changes diff --git a/.github/actions/setup-squad-node/action.yml b/.github/actions/setup-squad-node/action.yml new file mode 100644 index 000000000..b1bc0d54a --- /dev/null +++ b/.github/actions/setup-squad-node/action.yml @@ -0,0 +1,77 @@ +# Composite action: Setup Node.js with npm cache and retry logic +# Created to DRY up the repeated setup-node + npm install retry pattern +# used across squad-ci.yml, ci-rerun.yml, squad-npm-publish.yml, +# and squad-insider-publish.yml. +# +# Usage: +# - uses: ./.github/actions/setup-squad-node +# with: +# node-version: '22' # default: 22 +# install-command: 'ci' # default: ci (alternatives: install) +# registry-url: '' # optional, for publish workflows +# working-directory: '.' # optional, for subdirectory installs + +name: 'Setup Squad Node.js' +description: 'Setup Node.js with npm cache and retry install logic for Squad CI' + +inputs: + node-version: + description: 'Node.js version to install' + required: false + default: '22' + registry-url: + description: 'npm registry URL (set for publish workflows that need .npmrc)' + required: false + default: '' + install-command: + description: 'npm install command to run: ci (strict lockfile) or install (flexible)' + required: false + default: 'ci' + working-directory: + description: 'Working directory for npm install' + required: false + default: '.' + +runs: + using: 'composite' + steps: + - name: Validate inputs + shell: bash + env: + INPUT_CMD: ${{ inputs.install-command }} + INPUT_DIR: ${{ inputs.working-directory }} + run: | + if [[ "$INPUT_CMD" != "ci" && "$INPUT_CMD" != "install" ]]; then + echo "::error::install-command must be 'ci' or 'install'" + exit 1 + fi + if ! echo "$INPUT_DIR" | LC_ALL=C grep -qE '^[A-Za-z0-9/_.\-]+$'; then + echo "::error::working-directory contains invalid characters (only alphanumeric, /, ., -, _ allowed)" + exit 1 + fi + + - uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node-version }} + registry-url: ${{ inputs.registry-url }} + cache: 'npm' + + - name: Install dependencies (with retry) + shell: bash + working-directory: ${{ inputs.working-directory }} + env: + INPUT_CMD: ${{ inputs.install-command }} + run: | + success=false + for i in 1 2 3; do + if npm "$INPUT_CMD"; then + success=true + break + fi + echo "Retry $i/3 — npm \"$INPUT_CMD\" failed, retrying in 5s..." + sleep 5 + done + if [ "$success" = false ]; then + echo "::error::npm \"$INPUT_CMD\" failed after 3 attempts" + exit 1 + fi diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 655641842..01e18dfad 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -19,6 +19,7 @@ You are **Squad (Coordinator)** — the orchestrator for this project's AI team. - You may NOT generate domain artifacts (code, designs, analyses) — spawn an agent - You may NOT bypass reviewer approval on rejected work - You may NOT invent facts or assumptions — ask the user or spawn an agent who knows + - You may NOT do work yourself — ALWAYS delegate to a team member, even for small tasks. The only exception is Direct Mode (status checks, factual questions, and simple answers from context — see Response Mode Selection). Check: Does `.squad/team.md` exist? (fall back to `.ai-team/team.md` for repos migrating from older installs) - **No** → Init Mode @@ -104,7 +105,7 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **If you wrote code, generated artifacts, or produced domain work without dispatching to an agent, you violated this rule. The coordinator ROUTES — it does not BUILD. No exceptions.** -**On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.squad/` paths must be resolved relative to it. Pass the team root into every spawn prompt as `TEAM_ROOT` and the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. Check `.squad/identity/now.md` if it exists — it tells you what the team was last focused on. Update it if the focus has shifted. +**On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.squad/` paths must be resolved relative to it. Pass the team root and the current datetime (from `` in your system context) into every spawn prompt as `TEAM_ROOT` and `CURRENT_DATETIME` respectively. Pass the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. Check `.squad/identity/now.md` if it exists — it tells you what the team was last focused on. Update it if the focus has shifted. **⚡ Context caching:** After the first message in a session, `team.md`, `routing.md`, and `registry.json` are already in your context. Do NOT re-read them on subsequent messages — you already have the roster, routing rules, and cast names. Only re-read if the user explicitly modifies the team (adds/removes members, changes routing). @@ -330,6 +331,7 @@ description: "{emoji} {Name}: {brief task summary}" prompt: | You are {Name}, the {Role} on this project. TEAM ROOT: {team_root} + CURRENT_DATETIME: {current_datetime} WORKTREE_PATH: {worktree_path} WORKTREE_MODE: {true|false} **Requested by:** {current user name} @@ -348,7 +350,7 @@ prompt: | ⚠️ RESPONSE ORDER: After ALL tool calls, write a plain text summary as FINAL output. ``` -For read-only queries, use the explore agent: `agent_type: "explore"` with `"You are {Name}, the {Role}. {question} TEAM ROOT: {team_root}"` +For read-only queries, use the explore agent: `agent_type: "explore"` with `"You are {Name}, the {Role}. CURRENT_DATETIME: {current_datetime} — {question} TEAM ROOT: {team_root}"` ### Per-Agent Model Selection @@ -499,7 +501,7 @@ The `sql` tool is **CLI-only**. It does not exist on VS Code, JetBrains, or GitH MCP (Model Context Protocol) servers extend Squad with tools for external services — Trello, Aspire dashboards, Azure, Notion, and more. The user configures MCP servers in their environment; Squad discovers and uses them. -> **Full patterns:** Read `.squad/skills/mcp-tool-discovery/SKILL.md` for discovery patterns, domain-specific usage, graceful degradation. Read `.squad/templates/mcp-config.md` for config file locations, sample configs, and authentication notes. +> **Config details:** Read `.squad/templates/mcp-config.md` for config file locations, sample configs, and authentication notes. #### Detection @@ -623,15 +625,17 @@ Squad and all spawned agents may be running inside a **git worktree** rather tha **How the Coordinator resolves the team root (on every session start):** -1. Run `git rev-parse --show-toplevel` to get the current worktree root. -2. Check if `.squad/` exists at that root (fall back to `.ai-team/` for repos that haven't migrated yet). +1. **Check CWD first** — does `.squad/` exist in the current working directory? + - **Yes** → Team root = CWD. This handles monorepos where `.squad/` lives in a subfolder. +2. If not, run `git rev-parse --show-toplevel` to get the current worktree root. +3. Check if `.squad/` exists at that root (fall back to `.ai-team/` for repos that haven't migrated yet). - **Yes** → use **worktree-local** strategy. Team root = current worktree root. - **No** → use **main-checkout** strategy. Discover the main working tree: ``` git worktree list --porcelain ``` The first `worktree` line is the main working tree. Team root = that path. -3. The user may override the strategy at any time (e.g., *"use main checkout for team state"* or *"keep team state in this worktree"*). +4. The user may override the strategy at any time (e.g., *"use main checkout for team state"* or *"keep team state in this worktree"*). **Passing the team root to agents:** - The Coordinator includes `TEAM_ROOT: {resolved_path}` in every spawn prompt. @@ -769,6 +773,7 @@ prompt: | {paste contents of .squad/agents/{name}/charter.md here} TEAM ROOT: {team_root} + CURRENT_DATETIME: {current_datetime} All `.squad/` paths are relative to this root. PERSONAL_AGENT: {true|false} # Whether this is a personal agent @@ -815,6 +820,7 @@ prompt: | Do the work. Respond as {Name}. ⚠️ OUTPUT: Report outcomes in human terms. Never expose tool internals or SQL. + ⚠️ DATES: When writing dates in any file (decisions, history, logs), use ONLY the CURRENT_DATETIME value above. Never infer or guess the date. AFTER work: 1. APPEND to .squad/agents/{name}/history.md under "## Learnings": @@ -871,6 +877,7 @@ description: "📋 Scribe: Log session & merge decisions" prompt: | You are the Scribe. Read .squad/agents/scribe/charter.md. TEAM ROOT: {team_root} + CURRENT_DATETIME: {current_datetime} SPAWN MANIFEST: {spawn_manifest} @@ -882,7 +889,7 @@ prompt: | 4. SESSION LOG: Write .squad/log/{timestamp}-{topic}.md. Brief. Use ISO 8601 UTC timestamp. 5. CROSS-AGENT: Append team updates to affected agents' history.md. 6. HISTORY SUMMARIZATION [HARD GATE]: If any history.md >= 15360 bytes (15KB), summarize now. - 7. GIT COMMIT: git add .squad/ && commit (write msg to temp file, use -F). Skip if nothing staged. + 7. GIT COMMIT: Stage only the exact `.squad/` files Scribe wrote in this session. Use `git status --porcelain` filtered to allowed paths (decisions.md, decisions-archive.md, agents/{name}/history.md, agents/{name}/history-archive.md, log/*, orchestration-log/*). Stage each file individually with `git add -- `. Handle renames by extracting destination path (`-replace '^.* -> ',''`). Commit with -F (write msg to temp file). Skip if nothing staged. ⚠️ NEVER use `git add .squad/` or broad globs. 8. HEALTH REPORT: Log decisions.md before/after size, inbox count processed, history files summarized. Never speak to user. ⚠️ End with plain text summary after all tool calls. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f1528d90d..5271c6c53 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -25,12 +25,59 @@ Before pushing any commit, verify: 3. `npm run build` — build succeeds with your changes 4. Commit message references the issue: `Closes #N` +### Branch Contamination Prevention +- When creating PR branches, always branch from the latest upstream dev: `git fetch upstream && git checkout dev && git rebase upstream/dev && git checkout -b ` +- NEVER use `git reset --soft` to squash on branches behind dev — it picks up all the delta between your branch and dev, contaminating the commit +- To squash safely: use `git rebase -i` or amend the single commit with `git commit --amend` +- Before committing, ALWAYS verify the diff: `git diff --cached --stat` should show ONLY your intended files +- If you see unexpected files in the diff, unstage them: `git reset HEAD ` + ### Red Flags — STOP and Ask If you see any of these, STOP immediately and comment on the issue asking for guidance: - More than 20 files in your diff - ANY file deletions you didn't explicitly intend - Changes outside the scope of your assigned issue +## Protected Files — Zero External Dependencies + +**Some files MUST only use Node.js built-in modules. Do NOT add npm packages, SDK imports, or any non-`node:*` dependencies to them.** + +These are bootstrap utilities that run **before** the Squad SDK is loaded. If they import SDK code (e.g., `FSStorageProvider`, anything from `squad-sdk`), the CLI breaks at startup. + +### Protected file list +| File | Purpose | +|------|---------| +| `packages/squad-cli/src/cli/core/detect-squad-dir.ts` | Finds `.squad/` directory at startup — runs before SDK init | +| `packages/squad-cli/src/cli/core/errors.ts` | Error classes (`SquadError`, `fatal()`) — used by all CLI entry points | +| `packages/squad-cli/src/cli/core/gh-cli.ts` | GitHub CLI wrapper — uses only `node:child_process` and `node:util` | +| `packages/squad-cli/src/cli/core/output.ts` | Color/emoji console output — pure ANSI codes, zero imports | +| `packages/squad-cli/src/cli/core/history-split.ts` | Separates portable knowledge from project data — pure string logic | + +### Rules +- ❌ **NEVER** convert these files to use `FSStorageProvider`, `StorageProvider`, or any SDK abstraction +- ❌ **NEVER** add `import` or `require` statements referencing packages outside `node:*` built-ins +- ✅ **ONLY** use `node:fs`, `node:path`, `node:child_process`, `node:util`, and other Node.js built-in modules +- ✅ **DO** check this list before sweeping refactors (e.g., "convert all fs calls to StorageProvider") +- ✅ **LOOK** for `— zero dependencies` markers in file headers as a signal + +### Why this matters +Regression tests guard these files (`detect-squad-dir-zero-deps.test.ts` verifies zero external dependencies), but **prevention is better than detection**. A broken bootstrap means the entire CLI fails to start — no helpful error, just a crash. + +> **When adding new bootstrap utilities**, add them to this table and write a matching zero-dependency regression test. + +### SDK/CLI package boundary +The CLI (`squad-cli`) depends on the SDK (`squad-sdk`). Some CLI files run **before** the SDK is fully loaded. The `packages/squad-cli/src/cli/core/` directory contains a mix of early-startup/bootstrap utilities and later SDK-dependent modules, so treat every file in it with extra caution. The protected list above is the authoritative set of zero-dependency bootstrap files. If you need to add SDK imports to another `core/` file, verify it is not in the protected list above and confirm the SDK is loaded at that point in the startup sequence. + +## Sweeping Refactor Rules + +When applying a codebase-wide pattern change (e.g., "convert all `fs` calls to `StorageProvider`"), follow these steps **before** converting each file: + +1. **Check the Protected Files list above.** If the file is listed, do NOT convert it. +2. **Scan for zero-dependency markers.** Look for `— zero dependencies` in the file's header comment. If present, do NOT convert it. +3. **Verify imports resolve.** When adding `import { X } from '@bradygaster/squad-sdk'`, confirm `X` is actually exported from the SDK's barrel file (`packages/squad-sdk/src/index.ts`). Unresolved imports cause build failures. +4. **Never convert ALL files blindly.** Some files have specific constraints documented in their headers or in this instructions file. Read before you refactor. +5. **Test after each logical group.** Don't convert 30 files and then run the build — convert in small batches and verify each one compiles. + ## Team Context Before starting work on any issue: @@ -66,6 +113,39 @@ When opening a PR: - If this is a 🟡 needs-review task, add to the PR description: `⚠️ This task was flagged as "needs review" — please have a squad member review before merging.` - Follow any project conventions in `.squad/decisions.md` +## PR Review Skills + +When reviewing or creating PRs, consult these skills for domain-specific checklists: + +- **Reviewer Protocol:** `.copilot/skills/reviewer-protocol/SKILL.md` — rejection workflow, lockout rules +- **Architectural Review:** `.copilot/skills/architectural-review/SKILL.md` — module boundaries, dependency safety, pattern consistency +- **Security Review:** `.copilot/skills/security-review/SKILL.md` — credentials, injection, workflow permissions, supply chain + +Read the relevant skill(s) before submitting or reviewing a PR that touches their domain. + +## PR Scope Rules + +Scope is determined by PR labels, not by branch name. Continue using the squad branch convention: `squad/{issue-number}-{slug}`. +- **Repo-health PRs** (label `repo-health`): Only modify `.github/`, `scripts/`, root config files, test files, and docs. NEVER modify files under `packages/*/src/`. +- **Product PRs** (label `fix` or `feat`): May modify product source code. Must include changesets when required by the rules below. +- If a task requires both infrastructure and product changes, create separate PRs. + +## Changeset Requirement + +Any PR that modifies files under `packages/squad-cli/src/` or `packages/squad-sdk/src/` MUST include a changeset file. + +- Run `npx changeset add` and select the affected package(s) +- Or manually create `.changeset/{descriptive-name}.md` with format: + ``` + --- + '@bradygaster/squad-cli': patch + --- + Brief description of the change + ``` +- Use `patch` for bug fixes, `minor` for new features, `major` for breaking changes +- The `changelog-gate` CI check will fail without this +- Escape hatch: add the `skip-changelog` label (use sparingly) + ## Decisions If you make a decision that affects other team members, write it to: diff --git a/.github/workflows/ci-rerun.yml b/.github/workflows/ci-rerun.yml index de65c8312..3652ce909 100644 --- a/.github/workflows/ci-rerun.yml +++ b/.github/workflows/ci-rerun.yml @@ -21,6 +21,7 @@ permissions: jobs: test: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Fetch PR metadata id: pr @@ -65,6 +66,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 22 + cache: 'npm' - name: Fix stale lockfile entries run: | @@ -85,10 +87,36 @@ jobs: " - name: Install dependencies - run: npm install + run: | + success=false + for i in 1 2 3; do + if npm install; then + success=true + break + fi + echo "Retry $i/3 — npm install failed, retrying in 5s..." + sleep 5 + done + if [ "$success" = false ]; then + echo "::error::npm install failed after 3 attempts" + exit 1 + fi - name: Install docs dependencies - run: npm ci + run: | + success=false + for i in 1 2 3; do + if npm ci; then + success=true + break + fi + echo "Retry $i/3 — npm ci failed, retrying in 5s..." + sleep 5 + done + if [ "$success" = false ]; then + echo "::error::npm ci failed after 3 attempts" + exit 1 + fi working-directory: docs - name: Install Playwright browsers diff --git a/.github/workflows/squad-ci.yml b/.github/workflows/squad-ci.yml index d3003bb92..0dffd2f1f 100644 --- a/.github/workflows/squad-ci.yml +++ b/.github/workflows/squad-ci.yml @@ -10,9 +10,53 @@ on: permissions: contents: read +# CI Hardening Phase 3 item A1: Prevent parallel runs from competing for resources +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + # ── Path filter for conditional job execution ────────────────────────── + # Detects which areas of the repo changed so downstream jobs can skip + # when their inputs haven't changed (e.g., skip docs-quality on code-only PRs). + changes: + runs-on: ubuntu-latest + timeout-minutes: 3 + outputs: + docs: ${{ steps.filter.outputs.docs }} + code: ${{ steps.filter.outputs.code }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Detect changed paths + id: filter + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + BASE="${{ github.event.pull_request.base.sha }}" + HEAD="${{ github.event.pull_request.head.sha }}" + CHANGED=$(git diff --name-only "$BASE"..."$HEAD") + else + # On push, compare against parent commit + CHANGED=$(git diff --name-only HEAD~1...HEAD 2>/dev/null || echo "docs/") + fi + if echo "$CHANGED" | grep -qE '^(docs/|README\.md|\.markdownlint|\.cspell|cspell\.json)'; then + echo "docs=true" >> "$GITHUB_OUTPUT" + else + echo "docs=false" >> "$GITHUB_OUTPUT" + fi + # Detect non-docs changes (code, config, workflows, etc.) + if echo "$CHANGED" | grep -qvE '^(docs/|README\.md|\.markdownlint|\.cspell|cspell\.json)'; then + echo "code=true" >> "$GITHUB_OUTPUT" + else + echo "code=false" >> "$GITHUB_OUTPUT" + fi + docs-quality: + needs: changes + if: "!cancelled() && (github.event_name == 'push' || needs.changes.outputs.docs == 'true')" runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v4 @@ -21,7 +65,20 @@ jobs: node-version: 22 - name: Install docs tools - run: npm install --no-save markdownlint-cli2 cspell + run: | + success=false + for i in 1 2 3; do + if npm install --no-save markdownlint-cli2 cspell; then + success=true + break + fi + echo "Retry $i/3 — npm install failed, retrying in 5s..." + sleep 5 + done + if [ "$success" = false ]; then + echo "::error::npm install failed after 3 attempts" + exit 1 + fi - name: Lint docs markdown run: npx markdownlint-cli2 @@ -30,13 +87,22 @@ jobs: run: npx cspell --no-progress --dot "docs/src/content/**/*.md" "README.md" test: + needs: changes + # Fail-open: run test if changes job failed (don't let path filter break testing) + if: >- + always() && !cancelled() + && (github.event_name == 'push' + || needs.changes.result != 'success' + || needs.changes.outputs.code == 'true') runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 + cache: 'npm' - name: Fix stale lockfile entries run: | @@ -57,10 +123,36 @@ jobs: " - name: Install dependencies - run: npm install + run: | + success=false + for i in 1 2 3; do + if npm install; then + success=true + break + fi + echo "Retry $i/3 — npm install failed, retrying in 5s..." + sleep 5 + done + if [ "$success" = false ]; then + echo "::error::npm install failed after 3 attempts" + exit 1 + fi - name: Install docs dependencies - run: npm ci + run: | + success=false + for i in 1 2 3; do + if npm ci; then + success=true + break + fi + echo "Retry $i/3 — npm ci failed, retrying in 5s..." + sleep 5 + done + if [ "$success" = false ]; then + echo "::error::npm ci failed after 3 attempts" + exit 1 + fi working-directory: docs - name: Install Playwright browsers @@ -127,88 +219,196 @@ jobs: # to the label sync config to auto-create them in new repos. # ════════════════════════════════════════════════════════════════════════ - changelog-gate: - # ── Local testing ────────────────────────────────────────────────────── - # To test this gate locally: - # 1. Identify the merge-base: git merge-base dev HEAD - # 2. Check for SDK/CLI source changes: - # git diff --name-only ...HEAD | grep -E '^packages/squad-(sdk|cli)/src/' - # 3. If any match, verify CHANGELOG.md is also in the diff: - # git diff --name-only ...HEAD | grep -E '^CHANGELOG\.md$' - # ────────────────────────────────────────────────────────────────────── - if: github.event_name == 'pull_request' + # ═══════════════════════════════════════════════════════════════════════ + # Consolidated policy gates — runs 4 lightweight checks on a single + # runner. Saves ~3 runner boots per PR push vs separate jobs. + # Individual gates: changelog-gate, changelog-protection, + # workspace-integrity, prerelease-version-guard. + # ═══════════════════════════════════════════════════════════════════════ + policy-gates: + name: Policy Gates + needs: changes + if: >- + github.event_name == 'pull_request' + && !cancelled() + && (needs.changes.outputs.code == 'true' + || needs.changes.result == 'failure') runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Check feature flag - id: flag - # Default: gate is ENABLED. When vars.SQUAD_CHANGELOG_CHECK is - # undefined (not set in repo/org variables), the bash comparison - # [ "" = "false" ] evaluates to false, so skip stays "false" and - # the gate runs. Set vars.SQUAD_CHANGELOG_CHECK to "false" to - # explicitly disable. - run: | - if [ "${{ vars.SQUAD_CHANGELOG_CHECK }}" = "false" ]; then - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "CHANGELOG gate disabled via vars.SQUAD_CHANGELOG_CHECK" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - fi + - uses: actions/setup-node@v4 + with: + node-version: 22 - - name: Check skip label - if: steps.flag.outputs.skip == 'false' - id: label + - name: Fetch PR labels + id: labels run: | LABELS=$(gh pr view ${{ github.event.pull_request.number }} --json labels --jq '.labels[].name' 2>/dev/null || echo "") - if echo "$LABELS" | grep -q "skip-changelog"; then - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "Skipping CHANGELOG gate (skip-changelog label present)" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - fi + echo "all<> "$GITHUB_OUTPUT" + echo "$LABELS" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + # Per-gate skip flags + echo "$LABELS" | grep -q "skip-changelog" && echo "skip_changelog=true" >> "$GITHUB_OUTPUT" || echo "skip_changelog=false" >> "$GITHUB_OUTPUT" + echo "$LABELS" | grep -q "skip-workspace-check" && echo "skip_workspace=true" >> "$GITHUB_OUTPUT" || echo "skip_workspace=false" >> "$GITHUB_OUTPUT" + echo "$LABELS" | grep -q "skip-version-check" && echo "skip_version=true" >> "$GITHUB_OUTPUT" || echo "skip_version=false" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ github.token }} - - name: Require CHANGELOG update for SDK/CLI source changes - if: steps.flag.outputs.skip == 'false' && steps.label.outputs.skip != 'true' + # ─── Changelog Gate ─────────────────────────────────────────────── + - name: "Gate: Changelog" + if: >- + always() + && steps.labels.outputs.skip_changelog != 'true' + && vars.SQUAD_CHANGELOG_CHECK != 'false' run: | + echo "## 📋 Changelog Gate" >> $GITHUB_STEP_SUMMARY BASE="${{ github.event.pull_request.base.sha }}" HEAD="${{ github.event.pull_request.head.sha }}" - # Three-dot diff (base...head) finds the merge-base automatically, - # so it works correctly even when the PR branch contains merge - # commits from syncing with the base branch. It compares against - # the common ancestor, not the literal base SHA. CHANGED=$(git diff --name-only "$BASE"..."$HEAD") - - # Change detection regex: ^packages/squad-(sdk|cli)/src/ - # Matches any file under packages/squad-sdk/src/ or packages/squad-cli/src/ - # This intentionally excludes config files, tests, and docs — only source changes - # require a CHANGELOG entry. SDK_CLI_CHANGED=$(echo "$CHANGED" | grep -E '^packages/squad-(sdk|cli)/src/' || true) if [ -z "$SDK_CLI_CHANGED" ]; then echo "No SDK/CLI source changes detected -- CHANGELOG gate not applicable" + echo "✅ Not applicable (no SDK/CLI source changes)" >> $GITHUB_STEP_SUMMARY exit 0 fi - echo "SDK/CLI source files changed:" echo "$SDK_CLI_CHANGED" - - # Regex: ^CHANGELOG\.md$ — exact match on root CHANGELOG.md only + CHANGESET_ADDED=$(git diff --diff-filter=AM --name-only "$BASE"..."$HEAD" | grep -E '^\.changeset/[^/]+\.md$' | grep -vxF '.changeset/README.md' || true) CHANGELOG_CHANGED=$(echo "$CHANGED" | grep -E '^CHANGELOG\.md$' || true) - if [ -z "$CHANGELOG_CHANGED" ]; then - echo "" - echo "::error::CHANGELOG.md was not updated but SDK/CLI source files were changed." - echo "::error::Please add a CHANGELOG.md entry describing your changes." - echo "::error::If this is intentional, add the 'skip-changelog' label to your PR." - exit 1 + if [ -n "$CHANGESET_ADDED" ]; then + echo "Changeset file(s) detected -- gate passed" + echo "$CHANGESET_ADDED" + echo "✅ Changeset file detected" >> $GITHUB_STEP_SUMMARY + exit 0 + fi + if [ -n "$CHANGELOG_CHANGED" ]; then + echo "CHANGELOG.md updated -- gate passed" + echo "✅ CHANGELOG.md updated" >> $GITHUB_STEP_SUMMARY + exit 0 + fi + echo "" + echo "::error::No changeset or CHANGELOG.md update found, but SDK/CLI source files were changed." + echo "::error::Preferred: run 'npx changeset add' to create a .changeset/*.md file." + echo "::error::Alternative: edit CHANGELOG.md directly." + echo "::error::Escape hatch: add the 'skip-changelog' label to your PR." + echo "❌ No changeset or CHANGELOG.md update" >> $GITHUB_STEP_SUMMARY + exit 1 + + # ─── CHANGELOG Write Protection ────────────────────────────────── + - name: "Gate: CHANGELOG Write Protection" + if: always() + env: + APPROVED_AUTHORS: 'bradygaster github-actions[bot] copilot-swe-agent[bot]' + PR_AUTHOR: ${{ github.event.pull_request.user.login }} + run: | + echo "## 🔒 CHANGELOG Write Protection" >> $GITHUB_STEP_SUMMARY + CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep '^CHANGELOG.md$' || true) + if [ -n "$CHANGED" ]; then + if echo "$APPROVED_AUTHORS" | tr ' ' '\n' | grep -qxF "$PR_AUTHOR"; then + echo "✅ $PR_AUTHOR is approved to modify CHANGELOG.md" + echo "✅ $PR_AUTHOR is approved" >> $GITHUB_STEP_SUMMARY + else + echo "❌ $PR_AUTHOR is not approved to modify CHANGELOG.md directly" + echo "" + echo "CHANGELOG.md is managed by the release process." + echo "Use 'npx changeset add' to add a changeset file instead." + echo "See CONTRIBUTING.md for details." + echo "❌ $PR_AUTHOR is not approved to modify CHANGELOG.md" >> $GITHUB_STEP_SUMMARY + exit 1 + fi + else + echo "✅ CHANGELOG.md not modified" + echo "✅ CHANGELOG.md not modified" >> $GITHUB_STEP_SUMMARY fi - echo "CHANGELOG.md updated -- gate passed" + # ─── Workspace Integrity ────────────────────────────────────────── + - name: "Gate: Workspace Integrity" + if: >- + always() + && steps.labels.outputs.skip_workspace != 'true' + && vars.SQUAD_WORKSPACE_CHECK != 'false' + run: | + echo "## 🔗 Workspace Integrity" >> $GITHUB_STEP_SUMMARY + node -e " + const fs = require('fs'); + const lock = JSON.parse(fs.readFileSync('package-lock.json', 'utf8')); + const pkgs = lock.packages || {}; + const problems = []; + for (const [key, val] of Object.entries(pkgs)) { + if (!key.includes('node_modules/@bradygaster/squad-')) continue; + if (val.resolved && val.resolved.startsWith('https://')) { + problems.push({ path: key, resolved: val.resolved }); + } + } + if (problems.length > 0) { + console.error('::error::WORKSPACE INTEGRITY FAILURE — npm resolved registry packages instead of local workspace copies.'); + console.error('::error::This likely means a version mismatch between workspace packages (see PR #640).'); + console.error(''); + problems.forEach(p => { + console.error(' STALE: ' + p.path + (p.resolved ? ' → ' + p.resolved : ' (version: ' + p.version + ', not a workspace link)')); + }); + console.error(''); + console.error('To fix: ensure all workspace package version ranges match local versions,'); + console.error('then run npm install at the repo root to regenerate the lockfile.'); + process.exit(1); + } + console.log('✅ All workspace packages resolve to local file: links'); + " + echo "✅ All workspace packages resolve to local links" >> $GITHUB_STEP_SUMMARY + + # ─── Prerelease Version Guard ───────────────────────────────────── + - name: "Gate: Prerelease Version Guard" + if: >- + always() + && steps.labels.outputs.skip_version != 'true' + && vars.SQUAD_VERSION_CHECK != 'false' + && github.event.pull_request.base.ref != 'insider' + run: | + echo "## 🏷️ Prerelease Version Guard" >> $GITHUB_STEP_SUMMARY + node -e " + const fs = require('fs'); + const path = require('path'); + const pkgDirs = fs.readdirSync('packages', { withFileTypes: true }) + .filter(d => d.isDirectory()) + .map(d => d.name); + const violations = []; + for (const dir of pkgDirs) { + const pkgPath = path.join('packages', dir, 'package.json'); + if (!fs.existsSync(pkgPath)) continue; + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + if (pkg.version && /-/.test(pkg.version)) { + violations.push({ name: pkg.name, version: pkg.version, path: pkgPath }); + } + } + if (violations.length > 0) { + console.error('::error::PRERELEASE VERSION DETECTED — packages with prerelease versions cannot merge to dev/main.'); + console.error(''); + violations.forEach(v => { + console.error(' ' + v.name + '@' + v.version + ' (' + v.path + ')'); + }); + console.error(''); + console.error('Prerelease suffixes (-build, -alpha, -beta, -rc) must be removed before merging.'); + console.error('To fix: update the version field in each listed package.json to a release version.'); + console.error('To skip: add the \"skip-version-check\" label to your PR.'); + process.exit(1); + } + console.log('✅ All package versions are release versions (no prerelease suffixes)'); + pkgDirs.forEach(dir => { + const pkgPath = path.join('packages', dir, 'package.json'); + if (fs.existsSync(pkgPath)) { + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + if (pkg.version) console.log(' ' + pkg.name + '@' + pkg.version); + } + }); + " + echo "✅ All versions are release versions" >> $GITHUB_STEP_SUMMARY exports-map-check: + needs: changes # ── Local testing ────────────────────────────────────────────────────── # To test this gate locally: # 1. Check for SDK source changes: @@ -216,8 +416,13 @@ jobs: # 2. If any match, run the exports map script: # node scripts/check-exports-map.mjs # ────────────────────────────────────────────────────────────────────── - if: github.event_name == 'pull_request' + if: >- + !cancelled() + && (github.event_name != 'pull_request' + || needs.changes.outputs.code == 'true' + || needs.changes.result == 'failure') runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v4 with: @@ -283,13 +488,18 @@ jobs: run: node scripts/check-exports-map.mjs samples-build: + needs: changes # ── Local testing ────────────────────────────────────────────────────── # To test this gate locally: # 1. Build the SDK: npm run build -w packages/squad-sdk # 2. Loop over samples: for d in samples/*/; do (cd "$d" && npm install && npm run build); done # 3. Or test a single sample: cd samples/ && npm install && npm run build && npm test # ────────────────────────────────────────────────────────────────────── - if: github.event_name == 'pull_request' + if: >- + !cancelled() + && (github.event_name != 'pull_request' + || needs.changes.outputs.code == 'true' + || needs.changes.result == 'failure') runs-on: ubuntu-latest timeout-minutes: 15 steps: @@ -351,6 +561,7 @@ jobs: if: steps.flag.outputs.skip == 'false' && steps.label.outputs.skip != 'true' && steps.changes.outputs.skip != 'true' run: | npm ci --ignore-scripts + node packages/squad-cli/scripts/patch-esm-imports.mjs npm run build -w packages/squad-sdk - name: Build and test samples @@ -423,6 +634,7 @@ jobs: publish-policy: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v4 @@ -447,190 +659,11 @@ jobs: fi echo "✅ All npm publish commands are workspace-scoped" - workspace-integrity: - # ────────────────────────────────────────────────────────────────────── - # Workspace Integrity Check - # Purpose: Verify workspace packages resolve to local file: links, - # not stale registry versions in the lockfile. - # Catches: npm silently resolving a published registry copy instead - # of the local workspace symlink due to version mismatches. - # Why: Added after PR #640 prerelease version incident where - # >=0.9.0 didn't match 0.9.1-build.4, so npm pulled the - # stale published SDK from the registry. - # Cost: Zero-install — reads package-lock.json only. - # ────────────────────────────────────────────────────────────────────── - # ── Local testing ────────────────────────────────────────────────────── - # To test this gate locally: - # 1. Inspect lockfile entries for workspace packages: - # node -e "const l=JSON.parse(require('fs').readFileSync('package-lock.json','utf8')); - # Object.entries(l.packages||{}).filter(([k])=>k.includes('@bradygaster/squad-')) - # .forEach(([k,v])=>console.log(k, v.resolved||v.version, v.link?'(link)':''))" - # 2. All entries should show "(link)" — any with https:// URLs are stale. - # ────────────────────────────────────────────────────────────────────── - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Check feature flag - id: flag - # Default: gate is ENABLED. When vars.SQUAD_WORKSPACE_CHECK is - # undefined (not set in repo/org variables), the bash comparison - # [ "" = "false" ] evaluates to false, so skip stays "false" and - # the gate runs. Set vars.SQUAD_WORKSPACE_CHECK to "false" to - # explicitly disable. - run: | - if [ "${{ vars.SQUAD_WORKSPACE_CHECK }}" = "false" ]; then - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "Workspace integrity check disabled via vars.SQUAD_WORKSPACE_CHECK" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - fi - - - name: Check skip label - if: steps.flag.outputs.skip == 'false' - id: label - run: | - LABELS='${{ toJSON(github.event.pull_request.labels.*.name) }}' - if echo "$LABELS" | grep -q "skip-workspace-check"; then - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "Skipping workspace integrity check (skip-workspace-check label present)" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - fi - - - name: Verify workspace packages resolve locally - if: steps.flag.outputs.skip == 'false' && steps.label.outputs.skip != 'true' - run: | - node -e " - const fs = require('fs'); - const lock = JSON.parse(fs.readFileSync('package-lock.json', 'utf8')); - const pkgs = lock.packages || {}; - const problems = []; - - for (const [key, val] of Object.entries(pkgs)) { - if (!key.includes('node_modules/@bradygaster/squad-')) continue; - if (val.resolved && val.resolved.startsWith('https://')) { - problems.push({ path: key, resolved: val.resolved }); - } - } - - if (problems.length > 0) { - console.error('::error::WORKSPACE INTEGRITY FAILURE — npm resolved registry packages instead of local workspace copies.'); - console.error('::error::This likely means a version mismatch between workspace packages (see PR #640).'); - console.error(''); - problems.forEach(p => { - console.error(' STALE: ' + p.path + (p.resolved ? ' → ' + p.resolved : ' (version: ' + p.version + ', not a workspace link)')); - }); - console.error(''); - console.error('To fix: ensure all workspace package version ranges match local versions,'); - console.error('then run npm install at the repo root to regenerate the lockfile.'); - process.exit(1); - } - - console.log('✅ All workspace packages resolve to local file: links'); - " - - prerelease-version-guard: - # ────────────────────────────────────────────────────────────────────── - # Prerelease Version Guard - # Purpose: Prevent prerelease version strings (-build, -alpha, -beta, - # -rc) from being committed to dev or main. - # Catches: Forgotten prerelease suffixes that break semver range - # resolution in workspace dependencies. - # Why: Added after PR #640 prerelease version incident where a - # -build.N suffix caused npm to skip the local workspace - # copy during dependency resolution. - # Cost: Zero-install — reads package.json files only. - # ────────────────────────────────────────────────────────────────────── - # ── Local testing ────────────────────────────────────────────────────── - # To test this gate locally: - # 1. Scan for prerelease versions: - # node -e "require('fs').readdirSync('packages').forEach(d=>{ - # const p=require('./packages/'+d+'/package.json'); - # if(/-/.test(p.version)) console.log(p.name+'@'+p.version+' ← prerelease!')})" - # 2. A clean run should produce no output. - # ────────────────────────────────────────────────────────────────────── - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Check feature flag - id: flag - # Default: gate is ENABLED. When vars.SQUAD_VERSION_CHECK is - # undefined (not set in repo/org variables), the bash comparison - # [ "" = "false" ] evaluates to false, so skip stays "false" and - # the gate runs. Set vars.SQUAD_VERSION_CHECK to "false" to - # explicitly disable. - run: | - if [ "${{ vars.SQUAD_VERSION_CHECK }}" = "false" ]; then - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "Prerelease version guard disabled via vars.SQUAD_VERSION_CHECK" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - fi - - - name: Check skip label - if: steps.flag.outputs.skip == 'false' - id: label - run: | - LABELS='${{ toJSON(github.event.pull_request.labels.*.name) }}' - if echo "$LABELS" | grep -q "skip-version-check"; then - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "Skipping prerelease version guard (skip-version-check label present)" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - fi - - - name: Scan packages for prerelease versions - if: steps.flag.outputs.skip == 'false' && steps.label.outputs.skip != 'true' - run: | - node -e " - const fs = require('fs'); - const path = require('path'); - const pkgDirs = fs.readdirSync('packages', { withFileTypes: true }) - .filter(d => d.isDirectory()) - .map(d => d.name); - - const violations = []; - for (const dir of pkgDirs) { - const pkgPath = path.join('packages', dir, 'package.json'); - if (!fs.existsSync(pkgPath)) continue; - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - // Version regex: /-/ — matches any hyphen in the version string. - // In semver, a hyphen after the patch number indicates a prerelease - // suffix (e.g. 1.0.0-alpha.1, 0.9.1-build.4). Stable releases - // like 1.0.0 contain no hyphen and pass this check. - if (pkg.version && /-/.test(pkg.version)) { - violations.push({ name: pkg.name, version: pkg.version, path: pkgPath }); - } - } - - if (violations.length > 0) { - console.error('::error::PRERELEASE VERSION DETECTED — packages with prerelease versions cannot merge to dev/main.'); - console.error(''); - violations.forEach(v => { - console.error(' ' + v.name + '@' + v.version + ' (' + v.path + ')'); - }); - console.error(''); - console.error('Prerelease suffixes (-build, -alpha, -beta, -rc) must be removed before merging.'); - console.error('To fix: update the version field in each listed package.json to a release version.'); - console.error('To skip: add the \"skip-version-check\" label to your PR.'); - process.exit(1); - } - - console.log('✅ All package versions are release versions (no prerelease suffixes)'); - pkgDirs.forEach(dir => { - const pkgPath = path.join('packages', dir, 'package.json'); - if (fs.existsSync(pkgPath)) { - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - if (pkg.version) console.log(' ' + pkg.name + '@' + pkg.version); - } - }); - " + # workspace-integrity and prerelease-version-guard are now part of + # the consolidated policy-gates job above. export-smoke-test: + needs: changes # ────────────────────────────────────────────────────────────────────── # Export Smoke Test # Purpose: Verify that subpath exports actually resolve after build. @@ -656,8 +689,13 @@ jobs: # 3. To create a test PR to verify this gate: # gh pr edit --add-label skip-export-smoke # ────────────────────────────────────────────────────────────────────── - if: github.event_name == 'pull_request' + if: >- + !cancelled() + && (github.event_name != 'pull_request' + || needs.changes.outputs.code == 'true' + || needs.changes.result == 'failure') runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v4 with: @@ -721,6 +759,7 @@ jobs: if: steps.flag.outputs.skip == 'false' && steps.label.outputs.skip != 'true' && steps.changes.outputs.skip != 'true' run: | npm ci --ignore-scripts + node packages/squad-cli/scripts/patch-esm-imports.mjs npm run build -w packages/squad-sdk - name: Smoke test all subpath exports @@ -782,3 +821,32 @@ jobs: console.log('✅ All ' + passed + ' subpath exports resolve and import successfully'); " + + scope-check: + # Enforces PR scope boundary: repo-health PRs must not touch product source code. + # Replaces the removed squad-scope-check.yml workflow. + if: >- + github.event_name == 'pull_request' + && contains(github.event.pull_request.labels.*.name, 'repo-health') + runs-on: ubuntu-latest + timeout-minutes: 3 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check for product source changes in repo-health PR + run: | + BASE="${{ github.event.pull_request.base.sha }}" + HEAD="${{ github.event.pull_request.head.sha }}" + CHANGED=$(git diff --name-only "$BASE"..."$HEAD" | grep -E '^packages/squad-(cli|sdk)/src/' || true) + if [ -n "$CHANGED" ]; then + echo "::error::SCOPE VIOLATION — repo-health PRs must not modify product source code." + echo "The following product source files were changed:" + echo "$CHANGED" | while read -r f; do echo " - $f"; done + echo "" + echo "Product source (packages/squad-cli/src/ and packages/squad-sdk/src/) is off-limits for repo-health PRs." + echo "If this change requires product source edits, use a 'fix' or 'feat' label instead." + exit 1 + fi + echo "✅ No product source files modified — scope boundary respected" diff --git a/.github/workflows/squad-docs-links.yml b/.github/workflows/squad-docs-links.yml deleted file mode 100644 index db3380bfa..000000000 --- a/.github/workflows/squad-docs-links.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Docs — Weekly Link Check - -on: - workflow_dispatch: - -jobs: - linkcheck: - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - uses: actions/checkout@v4 - - - name: Check external links - id: lychee - uses: lycheeverse/lychee-action@v2 - with: - args: >- - --verbose - --no-progress - --timeout 30 - --max-retries 3 - --accept 200..=299,403,429 - 'docs/src/content/**/*.md' - 'README.md' - fail: false - - - name: Create issue on broken links - if: steps.lychee.outputs.exit_code != 0 - uses: peter-evans/create-issue-from-file@v5 - with: - title: '🔗 Broken external links detected' - content-filepath: ./lychee/out.md - labels: docs, automated diff --git a/.github/workflows/squad-heartbeat.yml b/.github/workflows/squad-heartbeat.yml index c75ed0638..0e3826b0c 100644 --- a/.github/workflows/squad-heartbeat.yml +++ b/.github/workflows/squad-heartbeat.yml @@ -5,6 +5,15 @@ name: Squad Heartbeat (Ralph) # - .squad/templates/workflows/squad-heartbeat.yml (installed template) # - .github/workflows/squad-heartbeat.yml (active workflow) # Run 'squad upgrade' to sync installed copies from source templates. +# +# CI Hardening: Cron schedule audit (item 8, refs diberry/squad#120) +# ───────────────────────────────────────────────────────────────── +# Audit finding: No cron schedule is configured here, which is INTENTIONAL. +# Ralph should only run reactively (on issue/PR events) or manually. +# A cron schedule would waste Actions minutes (~20 min/week) for minimal +# benefit since Ralph's triage logic is event-driven. +# If scheduled runs appear in Actions history, they are event-triggered +# (issue close/label), not cron-triggered. Do NOT add a schedule trigger. on: # React to completed work or new squad work @@ -21,12 +30,38 @@ permissions: contents: read pull-requests: read +# CI Hardening Phase 3 item A1: Prevent parallel runs from competing for resources +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number || github.ref }} + cancel-in-progress: true + jobs: heartbeat: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + # CI Hardening: GitHub API rate limit monitoring (item 9) + # Ralph makes multiple API calls (list issues, add labels, create comments). + # Check rate limit early to avoid silent failures mid-workflow. + - name: "📊 Check GitHub API rate limit" + uses: actions/github-script@v7 + with: + script: | + const { data: rateLimit } = await github.rest.rateLimit.get(); + const { remaining, limit, reset } = rateLimit.rate; + // Security: mask numeric values to prevent fingerprinting via CI logs + core.setSecret(String(remaining)); + core.setSecret(String(limit)); + const resetDate = new Date(reset * 1000).toISOString(); + if (remaining < 10) { + core.setFailed(`❌ Rate limit: CRITICAL — below minimum threshold. Aborting to prevent silent failures.`); + } else if (remaining < 100) { + core.warning(`⚠️ Rate limit: WARNING — below safe threshold (resets ${resetDate})`); + } else { + core.info(`✅ Rate limit: OK — above safe threshold (resets ${resetDate})`); + } + - name: Ralph — Repair orphaned squad labels uses: actions/github-script@v7 with: diff --git a/.github/workflows/squad-insider-publish.yml b/.github/workflows/squad-insider-publish.yml index 05e86a6fe..acc830de8 100644 --- a/.github/workflows/squad-insider-publish.yml +++ b/.github/workflows/squad-insider-publish.yml @@ -5,63 +5,91 @@ on: branches: - insider +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + permissions: contents: read jobs: - build: + test: runs-on: ubuntu-latest + timeout-minutes: 15 strategy: matrix: - node-version: [20] + node-version: [22] steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: ./.github/actions/setup-squad-node with: node-version: ${{ matrix.node-version }} - - name: Install dependencies - run: npm ci - - name: Build run: npm run build - test: - needs: build - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20] - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} + - name: Install Playwright browsers + run: npx playwright install chromium --with-deps - - name: Install dependencies - run: npm ci + - name: Run tests (skip docs-build — needs Docker unavailable on publish runner) + run: npx vitest run --exclude 'test/docs-build.test.ts' - - name: Run tests - run: npm test + # CI Hardening: npm registry health check (item 10) + # Fail early if registry is unreachable instead of failing mid-publish. + registry-check: + needs: test + runs-on: ubuntu-latest + timeout-minutes: 3 + steps: + - name: "🌐 Verify npm registry is reachable" + run: | + echo "Checking npm registry health before insider publish..." + success=false + for i in 1 2 3; do + if npm ping 2>&1; then + success=true + break + fi + echo "Registry ping attempt $i/3 failed, retrying in 5s..." + sleep 5 + done + if [ "$success" = false ]; then + echo "::error::npm registry is unreachable after 3 attempts — publish aborted" + echo "::error::Check https://status.npmjs.org/ for registry status" + exit 1 + fi + echo "✅ npm registry is reachable" + if ! npm view @bradygaster/squad-sdk version 2>/dev/null; then + echo "::warning::Cannot verify @bradygaster/squad-sdk on registry — package may not exist yet or registry is degraded" + else + echo "✅ @bradygaster package namespace is accessible" + fi publish: - needs: test + needs: [test, registry-check] runs-on: ubuntu-latest + timeout-minutes: 10 strategy: matrix: - node-version: [20] + node-version: [22] steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: ./.github/actions/setup-squad-node with: node-version: ${{ matrix.node-version }} registry-url: 'https://registry.npmjs.org' - - name: Install dependencies - run: npm ci + - name: Validate npm token + run: | + if [ -z "$NODE_AUTH_TOKEN" ]; then + echo "::error::NPM_TOKEN secret is not configured — publish will fail" + exit 1 + fi + echo "✅ NPM_TOKEN is set" + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Build packages run: npm -w packages/squad-sdk run build && npm -w packages/squad-cli run build diff --git a/.github/workflows/squad-insider-release.yml b/.github/workflows/squad-insider-release.yml index 36a1121bf..f266b1fe2 100644 --- a/.github/workflows/squad-insider-release.yml +++ b/.github/workflows/squad-insider-release.yml @@ -4,6 +4,10 @@ on: push: branches: [insider] +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + permissions: contents: write diff --git a/.github/workflows/squad-issue-assign.yml b/.github/workflows/squad-issue-assign.yml index 9aef424df..ff3f309be 100644 --- a/.github/workflows/squad-issue-assign.yml +++ b/.github/workflows/squad-issue-assign.yml @@ -8,6 +8,11 @@ permissions: issues: write contents: read +# CI Hardening Phase 3 item A1: Prevent parallel runs from competing for resources +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number || github.ref }} + cancel-in-progress: true + jobs: assign-work: # Only trigger on squad:{member} labels (not the base "squad" label) @@ -16,6 +21,26 @@ jobs: steps: - uses: actions/checkout@v4 + # CI Hardening: GitHub API rate limit monitoring (item 9) + # Issue assignment makes multiple API calls (read team file, add labels, comment, assign). + - name: "📊 Check GitHub API rate limit" + uses: actions/github-script@v7 + with: + script: | + const { data: rateLimit } = await github.rest.rateLimit.get(); + const { remaining, limit, reset } = rateLimit.rate; + // Security: mask numeric values to prevent fingerprinting via CI logs + core.setSecret(String(remaining)); + core.setSecret(String(limit)); + const resetDate = new Date(reset * 1000).toISOString(); + if (remaining < 10) { + core.setFailed(`❌ Rate limit: CRITICAL — below minimum threshold. Aborting to prevent silent failures.`); + } else if (remaining < 100) { + core.warning(`⚠️ Rate limit: WARNING — below safe threshold (resets ${resetDate})`); + } else { + core.info(`✅ Rate limit: OK — above safe threshold (resets ${resetDate})`); + } + - name: Identify assigned member and trigger work uses: actions/github-script@v7 with: diff --git a/.github/workflows/squad-label-enforce.yml b/.github/workflows/squad-label-enforce.yml index 633d220df..d1bb96788 100644 --- a/.github/workflows/squad-label-enforce.yml +++ b/.github/workflows/squad-label-enforce.yml @@ -8,6 +8,11 @@ permissions: issues: write contents: read +# CI Hardening Phase 3 item A1: Prevent parallel runs from competing for resources +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number || github.ref }} + cancel-in-progress: true + jobs: enforce: runs-on: ubuntu-latest diff --git a/.github/workflows/squad-npm-publish.yml b/.github/workflows/squad-npm-publish.yml index 876f3bb09..bee98cf8c 100644 --- a/.github/workflows/squad-npm-publish.yml +++ b/.github/workflows/squad-npm-publish.yml @@ -8,6 +8,10 @@ on: description: 'Version to publish (e.g., 0.9.1)' required: true type: string + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false permissions: contents: read id-token: write @@ -15,6 +19,7 @@ jobs: preflight: name: Pre-publish dependency validation runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v4 with: @@ -50,6 +55,41 @@ jobs: exit 1 fi echo "✅ No file: references found — safe to publish" + # CI Hardening: Lockfile stability check (item 6) + # Ensures workspace packages in package-lock.json resolve to local links, + # not stale registry URLs. Catches lockfile drift before publish — + # prevents v0.9.1-style incidents where stale entries break installs. + - name: "🔒 Lockfile stability check" + run: | + echo "Validating package-lock.json hasn't drifted..." + node -e " + const fs = require('fs'); + const lock = JSON.parse(fs.readFileSync('package-lock.json', 'utf8')); + const pkgs = lock.packages || {}; + const stale = Object.keys(pkgs).filter(k => + k.includes('node_modules/@bradygaster/squad-') && + pkgs[k].resolved && pkgs[k].resolved.startsWith('https://') + ); + if (stale.length) { + console.error('::error::LOCKFILE DRIFT DETECTED — workspace packages resolve to registry URLs instead of local links.'); + stale.forEach(k => console.error(' STALE: ' + k + ' → ' + pkgs[k].resolved)); + console.error(''); + console.error('Fix: Run npm install at the repo root to regenerate the lockfile.'); + process.exit(1); + } + const missingIntegrity = Object.keys(pkgs).filter(k => + k.includes('node_modules/@bradygaster/squad-') && + pkgs[k].resolved && !pkgs[k].resolved.startsWith('file:') && + (!pkgs[k].integrity || !pkgs[k].integrity.startsWith('sha512-')) + ); + if (missingIntegrity.length) { + console.error('::error::MISSING INTEGRITY HASHES — workspace packages resolved from registry lack sha512 hashes:'); + missingIntegrity.forEach(k => console.error(' ' + k + ' (resolved: ' + pkgs[k].resolved + ')')); + console.error('Fix: Run npm install at the repo root to regenerate the lockfile.'); + process.exit(1); + } + console.log('✅ Lockfile stable — all workspace packages resolve to local links'); + " - name: Validate semver versions run: | echo "Validating version numbers are valid semver..." @@ -77,6 +117,7 @@ jobs: name: CLI packaging smoke test needs: preflight runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v4 with: @@ -84,8 +125,22 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 22 + cache: 'npm' - name: Install dependencies - run: npm ci + run: | + success=false + for i in 1 2 3; do + if npm ci; then + success=true + break + fi + echo "Retry $i/3 — npm ci failed, retrying in 5s..." + sleep 5 + done + if [ "$success" = false ]; then + echo "::error::npm ci failed after 3 attempts" + exit 1 + fi - name: Build run: npm run build env: @@ -98,10 +153,43 @@ jobs: echo "✅ Both packages pack successfully" - name: Run CLI smoke tests run: npx vitest run test/cli-packaging-smoke.test.ts + # CI Hardening: npm registry health check (item 10) + # Verify the npm registry is reachable before starting any publish jobs. + # Prevents wasted CI time and partial publishes when the registry is down. + registry-check: + name: npm registry health check + needs: [preflight, smoke-test] + runs-on: ubuntu-latest + timeout-minutes: 3 + steps: + - name: "🌐 Verify npm registry is reachable" + run: | + echo "Checking npm registry health before publish..." + success=false + for i in 1 2 3; do + if npm ping 2>&1; then + success=true + break + fi + echo "Registry ping attempt $i/3 failed, retrying in 5s..." + sleep 5 + done + if [ "$success" = false ]; then + echo "::error::npm registry is unreachable after 3 attempts — publish aborted" + echo "::error::Check https://status.npmjs.org/ for registry status" + exit 1 + fi + echo "✅ npm registry is reachable" + if ! npm view @bradygaster/squad-sdk version 2>/dev/null; then + echo "::warning::Cannot verify @bradygaster/squad-sdk on registry — package may not exist yet or registry is degraded" + else + echo "✅ @bradygaster package namespace is accessible" + fi publish-sdk: name: Publish @bradygaster/squad-sdk - needs: [preflight, smoke-test] + needs: [preflight, smoke-test, registry-check] runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v4 with: @@ -110,6 +198,16 @@ jobs: with: node-version: 22 registry-url: 'https://registry.npmjs.org' + cache: 'npm' + - name: Validate npm token + run: | + if [ -z "$NODE_AUTH_TOKEN" ]; then + echo "::error::NPM_TOKEN secret is not configured — publish will fail" + exit 1 + fi + echo "✅ NPM_TOKEN is set" + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Determine version id: version run: | @@ -122,7 +220,20 @@ jobs: echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "Publishing version: ${VERSION}" - name: Install dependencies - run: npm ci + run: | + success=false + for i in 1 2 3; do + if npm ci; then + success=true + break + fi + echo "Retry $i/3 — npm ci failed, retrying in 5s..." + sleep 5 + done + if [ "$success" = false ]; then + echo "::error::npm ci failed after 3 attempts" + exit 1 + fi - name: Build squad-sdk run: npm -w packages/squad-sdk run build - name: Verify package version matches target @@ -150,8 +261,9 @@ jobs: echo "Registry propagation slow but package was published successfully" publish-cli: name: Publish @bradygaster/squad-cli - needs: [preflight, smoke-test, publish-sdk] + needs: [preflight, smoke-test, registry-check, publish-sdk] runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v4 with: @@ -160,6 +272,16 @@ jobs: with: node-version: 22 registry-url: 'https://registry.npmjs.org' + cache: 'npm' + - name: Validate npm token + run: | + if [ -z "$NODE_AUTH_TOKEN" ]; then + echo "::error::NPM_TOKEN secret is not configured — publish will fail" + exit 1 + fi + echo "✅ NPM_TOKEN is set" + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Determine version id: version run: | @@ -172,7 +294,20 @@ jobs: echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "Publishing version: ${VERSION}" - name: Install dependencies - run: npm ci + run: | + success=false + for i in 1 2 3; do + if npm ci; then + success=true + break + fi + echo "Retry $i/3 — npm ci failed, retrying in 5s..." + sleep 5 + done + if [ "$success" = false ]; then + echo "::error::npm ci failed after 3 attempts" + exit 1 + fi - name: "Guard against file: dependencies" run: | SDK_DEP=$(node -p "require('./packages/squad-cli/package.json').dependencies['@bradygaster/squad-sdk']") diff --git a/.github/workflows/squad-preview.yml b/.github/workflows/squad-preview.yml index 9df39e079..da3e308ca 100644 --- a/.github/workflows/squad-preview.yml +++ b/.github/workflows/squad-preview.yml @@ -4,6 +4,10 @@ on: push: branches: [preview] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/squad-promote.yml b/.github/workflows/squad-promote.yml index 9d315b1d1..ed71bd79c 100644 --- a/.github/workflows/squad-promote.yml +++ b/.github/workflows/squad-promote.yml @@ -10,6 +10,10 @@ on: type: choice options: ['false', 'true'] +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + permissions: contents: write diff --git a/.github/workflows/squad-release.yml b/.github/workflows/squad-release.yml index 6ae0f07fd..fca4f27db 100644 --- a/.github/workflows/squad-release.yml +++ b/.github/workflows/squad-release.yml @@ -4,6 +4,10 @@ on: push: branches: [main] +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + permissions: contents: write diff --git a/.github/workflows/squad-triage.yml b/.github/workflows/squad-triage.yml index a58be9b29..ecc946645 100644 --- a/.github/workflows/squad-triage.yml +++ b/.github/workflows/squad-triage.yml @@ -8,6 +8,11 @@ permissions: issues: write contents: read +# CI Hardening Phase 3 item A1: Prevent parallel runs from competing for resources +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number || github.ref }} + cancel-in-progress: true + jobs: triage: if: github.event.label.name == 'squad' @@ -15,6 +20,26 @@ jobs: steps: - uses: actions/checkout@v4 + # CI Hardening: GitHub API rate limit monitoring (item 9) + # Triage makes multiple API calls (list issues, add labels, create comments). + - name: "📊 Check GitHub API rate limit" + uses: actions/github-script@v7 + with: + script: | + const { data: rateLimit } = await github.rest.rateLimit.get(); + const { remaining, limit, reset } = rateLimit.rate; + // Security: mask numeric values to prevent fingerprinting via CI logs + core.setSecret(String(remaining)); + core.setSecret(String(limit)); + const resetDate = new Date(reset * 1000).toISOString(); + if (remaining < 10) { + core.setFailed(`❌ Rate limit: CRITICAL — below minimum threshold. Aborting to prevent silent failures.`); + } else if (remaining < 100) { + core.warning(`⚠️ Rate limit: WARNING — below safe threshold (resets ${resetDate})`); + } else { + core.info(`✅ Rate limit: OK — above safe threshold (resets ${resetDate})`); + } + - name: Triage issue via Lead agent uses: actions/github-script@v7 with: @@ -110,9 +135,11 @@ jobs: return; } + function slugify(t) { return t.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); } + // Build triage context const memberList = members.map(m => - `- **${m.name}** (${m.role}) → label: \`squad:${m.name.toLowerCase()}\`` + `- **${m.name}** (${m.role}) → label: \`squad:${slugify(m.name)}\`` ).join('\n'); // Determine best assignee based on issue content and routing @@ -189,7 +216,7 @@ jobs: } const isCopilot = assignedMember.name === '@copilot'; - const assignLabel = isCopilot ? 'squad:copilot' : `squad:${assignedMember.name.toLowerCase()}`; + const assignLabel = isCopilot ? 'squad:copilot' : `squad:${slugify(assignedMember.name)}`; // Add the member-specific label await github.rest.issues.addLabels({ diff --git a/.github/workflows/sync-squad-labels.yml b/.github/workflows/sync-squad-labels.yml index fbcfd9cc2..be3b15230 100644 --- a/.github/workflows/sync-squad-labels.yml +++ b/.github/workflows/sync-squad-labels.yml @@ -7,6 +7,10 @@ on: - '.ai-team/team.md' workflow_dispatch: +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + permissions: issues: write contents: read @@ -103,6 +107,8 @@ jobs: { name: 'priority:p2', color: 'FBCA04', description: 'Next sprint' } ]; + function slugify(t) { return t.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); } + // Ensure the base "squad" triage label exists const labels = [ { name: 'squad', color: SQUAD_COLOR, description: 'Squad triage inbox — Lead will assign to a member' } @@ -110,7 +116,7 @@ jobs: for (const member of members) { labels.push({ - name: `squad:${member.name.toLowerCase()}`, + name: `squad:${slugify(member.name)}`, color: MEMBER_COLOR, description: `Assigned to ${member.name} (${member.role})` }); diff --git a/.gitignore b/.gitignore index d459415f4..0eeb162c3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ coverage/ .squad/decisions/inbox/ .squad/sessions/ .squad/config.json + # Test temp dirs (created in cwd by vitest tests, cleaned in afterEach) .test-* # Docs site generated files @@ -21,7 +22,8 @@ docs/src/content/docs/reference/api/ docs/tests/screenshots/ # Squad: SubSquad activation file (local to this machine) .squad-workstream -.squad/.first-run +.squad/.first-run +.squad/.watch-pids # Images folder (root only — don't ignore docs/public/images/) /images/ diff --git a/.squad-templates/issue-lifecycle.md b/.squad-templates/issue-lifecycle.md index 574c205a1..aea93654e 100644 --- a/.squad-templates/issue-lifecycle.md +++ b/.squad-templates/issue-lifecycle.md @@ -231,7 +231,8 @@ Working as {member} ({role}) **Update workflow:** ```bash # Make changes -git add . +# ⚠️ NEVER use `git add .` or `git add -A` — only stage files you intentionally changed +git add -- {specific files you modified} git commit -m "fix: address review feedback" git push ``` diff --git a/.squad-templates/mcp-config.md b/.squad-templates/mcp-config.md index 2e361ee4b..f38425e4c 100644 --- a/.squad-templates/mcp-config.md +++ b/.squad-templates/mcp-config.md @@ -2,8 +2,6 @@ MCP (Model Context Protocol) servers extend Squad with tools for external services — Trello, Aspire dashboards, Azure, Notion, and more. The user configures MCP servers in their environment; Squad discovers and uses them. -> **Full patterns:** Read `.squad/skills/mcp-tool-discovery/SKILL.md` for discovery patterns, domain-specific usage, and graceful degradation. - ## Config File Locations Users configure MCP servers at these locations (checked in priority order): diff --git a/.squad-templates/ralph-triage.js b/.squad-templates/ralph-triage.js index cf3023961..0f533ee48 100644 --- a/.squad-templates/ralph-triage.js +++ b/.squad-templates/ralph-triage.js @@ -55,6 +55,8 @@ function normalizeEol(content) { return content.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); } +function slugify(text) { return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); } + function parseRoutingRules(routingMd) { const table = parseTableSection(routingMd, /^##\s*work\s*type\s*(?:→|->)\s*agent\b/i); if (!table) return []; @@ -124,7 +126,7 @@ function parseRoster(teamMd) { members.push({ name, role, - label: `squad:${name.toLowerCase()}`, + label: `squad:${slugify(name)}`, }); } diff --git a/.squad-templates/scribe-charter.md b/.squad-templates/scribe-charter.md index eae443a64..308b780e2 100644 --- a/.squad-templates/scribe-charter.md +++ b/.squad-templates/scribe-charter.md @@ -43,7 +43,7 @@ After every substantial work session: - **Exact duplicates:** If two blocks share the same heading, keep the first and remove the rest. - **Overlapping decisions:** Compare block content across all remaining blocks. If two or more blocks cover the same area (same topic, same architectural concern, same component) but were written independently (different dates, different authors), consolidate them: a. Synthesize a single merged block that combines the intent and rationale from all overlapping blocks. - b. Use today's date and a new heading: `### {today}: {consolidated topic} (consolidated)` + b. Use the CURRENT_DATETIME value from your spawn prompt and a new heading: `### {CURRENT_DATETIME}: {consolidated topic} (consolidated)` c. Credit all original authors: `**By:** {Name1}, {Name2}` d. Under **What:**, combine the decisions. Note any differences or evolution. e. Under **Why:**, merge the rationale, preserving unique reasoning from each. @@ -61,7 +61,26 @@ After every substantial work session: Do NOT embed newlines in `git commit -m` (backtick-n fails silently in PowerShell). Instead: - `cd` into the team root first. - - Stage all `.squad/` files: `git add .squad/` + - Stage only files Scribe actually modified in this session. + Use `git status --porcelain` to build an explicit file list filtered to allowed `.squad/` paths: + ```powershell + $allowed = @( + '.squad/decisions.md', + '.squad/decisions-archive.md' + ) + $allowedPatterns = @( + '.squad/agents/*/history.md', + '.squad/agents/*/history-archive.md', + '.squad/log/*', + '.squad/orchestration-log/*' + ) + $filesToStage = git status --porcelain | Where-Object { $_.Length -gt 3 } | ForEach-Object { $_.Substring(3) -replace '^.* -> ','' } | Where-Object { + $f = $_ + ($f -in $allowed) -or ($allowedPatterns | Where-Object { $f -like $_ }) + } + if ($filesToStage) { $filesToStage | Where-Object { $_ } | ForEach-Object { git add -- $_ } } + ``` + ⚠️ NEVER use `git add .squad/` or broad globs — only stage specific files you wrote in this session. - Check for staged changes: `git diff --cached --quiet` If exit code is 0, no changes — skip silently. - Write the commit message to a temp file, then commit with `-F`: diff --git a/.squad-templates/squad.agent.md b/.squad-templates/squad.agent.md index 655641842..01e18dfad 100644 --- a/.squad-templates/squad.agent.md +++ b/.squad-templates/squad.agent.md @@ -19,6 +19,7 @@ You are **Squad (Coordinator)** — the orchestrator for this project's AI team. - You may NOT generate domain artifacts (code, designs, analyses) — spawn an agent - You may NOT bypass reviewer approval on rejected work - You may NOT invent facts or assumptions — ask the user or spawn an agent who knows + - You may NOT do work yourself — ALWAYS delegate to a team member, even for small tasks. The only exception is Direct Mode (status checks, factual questions, and simple answers from context — see Response Mode Selection). Check: Does `.squad/team.md` exist? (fall back to `.ai-team/team.md` for repos migrating from older installs) - **No** → Init Mode @@ -104,7 +105,7 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **If you wrote code, generated artifacts, or produced domain work without dispatching to an agent, you violated this rule. The coordinator ROUTES — it does not BUILD. No exceptions.** -**On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.squad/` paths must be resolved relative to it. Pass the team root into every spawn prompt as `TEAM_ROOT` and the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. Check `.squad/identity/now.md` if it exists — it tells you what the team was last focused on. Update it if the focus has shifted. +**On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.squad/` paths must be resolved relative to it. Pass the team root and the current datetime (from `` in your system context) into every spawn prompt as `TEAM_ROOT` and `CURRENT_DATETIME` respectively. Pass the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. Check `.squad/identity/now.md` if it exists — it tells you what the team was last focused on. Update it if the focus has shifted. **⚡ Context caching:** After the first message in a session, `team.md`, `routing.md`, and `registry.json` are already in your context. Do NOT re-read them on subsequent messages — you already have the roster, routing rules, and cast names. Only re-read if the user explicitly modifies the team (adds/removes members, changes routing). @@ -330,6 +331,7 @@ description: "{emoji} {Name}: {brief task summary}" prompt: | You are {Name}, the {Role} on this project. TEAM ROOT: {team_root} + CURRENT_DATETIME: {current_datetime} WORKTREE_PATH: {worktree_path} WORKTREE_MODE: {true|false} **Requested by:** {current user name} @@ -348,7 +350,7 @@ prompt: | ⚠️ RESPONSE ORDER: After ALL tool calls, write a plain text summary as FINAL output. ``` -For read-only queries, use the explore agent: `agent_type: "explore"` with `"You are {Name}, the {Role}. {question} TEAM ROOT: {team_root}"` +For read-only queries, use the explore agent: `agent_type: "explore"` with `"You are {Name}, the {Role}. CURRENT_DATETIME: {current_datetime} — {question} TEAM ROOT: {team_root}"` ### Per-Agent Model Selection @@ -499,7 +501,7 @@ The `sql` tool is **CLI-only**. It does not exist on VS Code, JetBrains, or GitH MCP (Model Context Protocol) servers extend Squad with tools for external services — Trello, Aspire dashboards, Azure, Notion, and more. The user configures MCP servers in their environment; Squad discovers and uses them. -> **Full patterns:** Read `.squad/skills/mcp-tool-discovery/SKILL.md` for discovery patterns, domain-specific usage, graceful degradation. Read `.squad/templates/mcp-config.md` for config file locations, sample configs, and authentication notes. +> **Config details:** Read `.squad/templates/mcp-config.md` for config file locations, sample configs, and authentication notes. #### Detection @@ -623,15 +625,17 @@ Squad and all spawned agents may be running inside a **git worktree** rather tha **How the Coordinator resolves the team root (on every session start):** -1. Run `git rev-parse --show-toplevel` to get the current worktree root. -2. Check if `.squad/` exists at that root (fall back to `.ai-team/` for repos that haven't migrated yet). +1. **Check CWD first** — does `.squad/` exist in the current working directory? + - **Yes** → Team root = CWD. This handles monorepos where `.squad/` lives in a subfolder. +2. If not, run `git rev-parse --show-toplevel` to get the current worktree root. +3. Check if `.squad/` exists at that root (fall back to `.ai-team/` for repos that haven't migrated yet). - **Yes** → use **worktree-local** strategy. Team root = current worktree root. - **No** → use **main-checkout** strategy. Discover the main working tree: ``` git worktree list --porcelain ``` The first `worktree` line is the main working tree. Team root = that path. -3. The user may override the strategy at any time (e.g., *"use main checkout for team state"* or *"keep team state in this worktree"*). +4. The user may override the strategy at any time (e.g., *"use main checkout for team state"* or *"keep team state in this worktree"*). **Passing the team root to agents:** - The Coordinator includes `TEAM_ROOT: {resolved_path}` in every spawn prompt. @@ -769,6 +773,7 @@ prompt: | {paste contents of .squad/agents/{name}/charter.md here} TEAM ROOT: {team_root} + CURRENT_DATETIME: {current_datetime} All `.squad/` paths are relative to this root. PERSONAL_AGENT: {true|false} # Whether this is a personal agent @@ -815,6 +820,7 @@ prompt: | Do the work. Respond as {Name}. ⚠️ OUTPUT: Report outcomes in human terms. Never expose tool internals or SQL. + ⚠️ DATES: When writing dates in any file (decisions, history, logs), use ONLY the CURRENT_DATETIME value above. Never infer or guess the date. AFTER work: 1. APPEND to .squad/agents/{name}/history.md under "## Learnings": @@ -871,6 +877,7 @@ description: "📋 Scribe: Log session & merge decisions" prompt: | You are the Scribe. Read .squad/agents/scribe/charter.md. TEAM ROOT: {team_root} + CURRENT_DATETIME: {current_datetime} SPAWN MANIFEST: {spawn_manifest} @@ -882,7 +889,7 @@ prompt: | 4. SESSION LOG: Write .squad/log/{timestamp}-{topic}.md. Brief. Use ISO 8601 UTC timestamp. 5. CROSS-AGENT: Append team updates to affected agents' history.md. 6. HISTORY SUMMARIZATION [HARD GATE]: If any history.md >= 15360 bytes (15KB), summarize now. - 7. GIT COMMIT: git add .squad/ && commit (write msg to temp file, use -F). Skip if nothing staged. + 7. GIT COMMIT: Stage only the exact `.squad/` files Scribe wrote in this session. Use `git status --porcelain` filtered to allowed paths (decisions.md, decisions-archive.md, agents/{name}/history.md, agents/{name}/history-archive.md, log/*, orchestration-log/*). Stage each file individually with `git add -- `. Handle renames by extracting destination path (`-replace '^.* -> ',''`). Commit with -F (write msg to temp file). Skip if nothing staged. ⚠️ NEVER use `git add .squad/` or broad globs. 8. HEALTH REPORT: Log decisions.md before/after size, inbox count processed, history files summarized. Never speak to user. ⚠️ End with plain text summary after all tool calls. diff --git a/.squad-templates/workflows/squad-triage.yml b/.squad-templates/workflows/squad-triage.yml index a58be9b29..d118a2813 100644 --- a/.squad-templates/workflows/squad-triage.yml +++ b/.squad-templates/workflows/squad-triage.yml @@ -110,9 +110,11 @@ jobs: return; } + function slugify(t) { return t.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); } + // Build triage context const memberList = members.map(m => - `- **${m.name}** (${m.role}) → label: \`squad:${m.name.toLowerCase()}\`` + `- **${m.name}** (${m.role}) → label: \`squad:${slugify(m.name)}\`` ).join('\n'); // Determine best assignee based on issue content and routing @@ -189,7 +191,7 @@ jobs: } const isCopilot = assignedMember.name === '@copilot'; - const assignLabel = isCopilot ? 'squad:copilot' : `squad:${assignedMember.name.toLowerCase()}`; + const assignLabel = isCopilot ? 'squad:copilot' : `squad:${slugify(assignedMember.name)}`; // Add the member-specific label await github.rest.issues.addLabels({ diff --git a/.squad-templates/workflows/sync-squad-labels.yml b/.squad-templates/workflows/sync-squad-labels.yml index fbcfd9cc2..699fc680f 100644 --- a/.squad-templates/workflows/sync-squad-labels.yml +++ b/.squad-templates/workflows/sync-squad-labels.yml @@ -103,6 +103,8 @@ jobs: { name: 'priority:p2', color: 'FBCA04', description: 'Next sprint' } ]; + function slugify(t) { return t.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); } + // Ensure the base "squad" triage label exists const labels = [ { name: 'squad', color: SQUAD_COLOR, description: 'Squad triage inbox — Lead will assign to a member' } @@ -110,7 +112,7 @@ jobs: for (const member of members) { labels.push({ - name: `squad:${member.name.toLowerCase()}`, + name: `squad:${slugify(member.name)}`, color: MEMBER_COLOR, description: `Assigned to ${member.name} (${member.role})` }); diff --git a/.squad/agents/eecom/history.md b/.squad/agents/eecom/history.md index de1038df9..cf00ea8ac 100644 --- a/.squad/agents/eecom/history.md +++ b/.squad/agents/eecom/history.md @@ -4,6 +4,38 @@ ## Learnings +### PR #942 rebase — cherry-pick from insider-based fork branch (2026-04-12) + +**Context:** PR #942 from tamirdresher's fork was retargeted from `insider` to `dev`, causing 29 files in the diff when only 3 commits (4 files relevant to dev) were the actual fix. Cherry-picked the 3 fix commits onto a clean `squad/942-rebase-type-safety` branch from dev, resolving conflicts where insider-only files (skill.ts, cross-package-exports.test.ts) didn't exist on dev. Dropped the `escapeYamlValue` import and APM YAML generation function from init.ts since skill.ts doesn't exist on dev. Opened #963 as the clean replacement, closed #942. + +**Key lesson:** When cherry-picking from an insider-based branch to dev, expect modify/delete conflicts for files that only exist on insider. Always verify the base assumptions of each change — imports referencing insider-only modules must be dropped or adapted. + +### Loop command: second-round review fixes (#767) (2025-07-26) + +**Context:** Three Copilot review comments on PR #767: (1) `teamRoot` was set to `workTreeRoot` but `.squad/` may live in the main checkout when running inside a git worktree — should derive from `detectSquadDir().path`, (2) `generateLoopFile()` hardcoded the full loop.md scaffold inline, duplicating `templates/loop.md`, (3) docs said `gh` was optional but code hard-requires `gh copilot` unless `--agent-cmd` is passed. + +**Fixes:** +1. **teamRoot:** Changed `const teamRoot = workTreeRoot` to `path.dirname(squadDirInfo.path)` — `.squad/`-relative operations now always use the directory where `.squad/` was actually found. +2. **Template dedup:** Replaced 48-line hardcoded template with `readFileSync` reading from `templates/loop.md`. Used `import.meta.url` + `fileURLToPath` to resolve the path from the compiled file (3 levels up to package root). Created `packages/squad-cli/templates/loop.md` since it was missing from the CLI package's templates dir. +3. **Docs:** Updated prerequisites to state `gh` + `gh copilot` are required by default, with `--agent-cmd` as the escape hatch. + +**Test impact:** Tests mock `node:fs` globally, so `readFileSync` in `generateLoopFile()` needed mock setup. Added `beforeAll` using `vi.importActual('node:fs')` to read the REAL template file, then `beforeEach` to set the mock return value. This keeps tests validating the actual template content. + +**Pattern:** When reading template files in code that has mocked `node:fs` tests, use `vi.importActual('node:fs')` in `beforeAll` to get real filesystem access for loading test fixtures. + +### Loop command: streaming output, worktree CWD, docs alignment (#767) (2025-07-25) + +**Context:** Copilot code review on PR #767 flagged three issues in the loop command: (1) `execFile` buffered stdout/stderr but never printed it — users saw no Copilot output during loop rounds, (2) `loop.md` was resolved relative to `dest` but execution used `teamRoot` (derived from `.squad/` parent), creating a CWD mismatch in worktree scenarios, (3) docs said `description` defaults to `""` but code uses `'Squad Loop'`. + +**Fixes:** +1. **Streaming:** Added `.on('data')` listeners to `currentChild.stdout` and `currentChild.stderr` after `execFile` spawn. Since output streams in real-time, the callback no longer re-writes buffered stdout/stderr on error (would duplicate). +2. **Worktree CWD:** Introduced `workTreeRoot = path.resolve(dest)` and set `teamRoot = workTreeRoot`. Both file resolution and execution CWD now use the same root. +3. **Docs:** Updated `docs/src/content/docs/features/loop.md` description default from `""` to `"Squad Loop"`. + +**Key file:** `packages/squad-cli/src/cli/commands/loop.ts` — `runLoop()` entry point (~line 302), `executeRound()` inner function (~line 427). + +**Pattern:** When using Node's `execFile` for interactive/long-running child processes, always attach stream listeners for real-time output. The callback's `stdout`/`stderr` args are the same buffered content — writing both duplicates output. + ### archiveDecisions() count-based fallback (#626) (2025-07-24) **Context:** `archiveDecisions()` in `packages/squad-cli/src/cli/core/nap.ts` silently returned `null` when all `###` entries were <30 days old (`old.length === 0`), even if the file was well over 20KB. Active projects generating many decisions per session could hit 145KB+ — 35K tokens burned per agent spawn. diff --git a/.squad/decisions-archive.md b/.squad/decisions-archive.md index 3d75e459f..d44f3a31e 100644 --- a/.squad/decisions-archive.md +++ b/.squad/decisions-archive.md @@ -1,4290 +1,7852 @@ -# Decisions Archive +# Decisions Archive + +> Old decisions (2026-02-21 through 2026-03-25) preserved append-only. Managed by Scribe. For active decisions, see .squad/decisions.md. + +--- +### 2026-02-21: User directive — no temp/memory files in repo root +**By:** Brady (via Copilot) +**What:** NEVER write temp files, issue files, or memory files to the repo root. All squad state/scratch files belong in .squad/ and ONLY .squad/. Root tree of a user's repo is sacred. +**Why:** User request — hard rule. Captured for all agents. + +### 2026-02-21: npm workspace protocol for monorepo +**By:** Edie (TypeScript Engineer) +**What:** Use npm-native workspace resolution (version-string references) instead of `workspace:*` protocol for cross-package dependencies. +**Why:** The `workspace:*` protocol is pnpm/Yarn-specific. npm workspaces resolve workspace packages automatically. +**Impact:** All inter-package dependencies in `packages/*/package.json` should use the actual version string, not `workspace:*`. + +### 2026-02-21: Distribution is npm-only (GitHub-native removed) +**By:** Rabin (Distribution) + Fenster (Core Dev) +**What:** Squad packages (`@bradygaster/squad-sdk` and `@bradygaster/squad-cli`) are distributed exclusively via npmjs.com. The GitHub-native `npx github:bradygaster/squad` path has been removed. +**Why:** npm is the standard distribution channel. One distribution path reduces confusion and maintenance burden. Root `cli.js` prints deprecation warning if anyone still hits the old path. + +### 2026-02-21: Coordinator prompt structure — three routing modes +**By:** Verbal (Prompt Engineer) +**What:** Coordinator uses structured response format: `DIRECT:` (answer inline), `ROUTE:` + `TASK:` + `CONTEXT:` (single agent), `MULTI:` (fan-out). Unrecognized formats fall back to `DIRECT`. +**Why:** Keyword prefixes are cheap to parse and reliable. Fallback-to-direct prevents silent failures. +### `.squad/` Directory Scope — Owner Directive +**By:** Brady (project owner, PR #326 review) +**Date:** 2026-03-10 + +**Directive:** The `.squad/` directory is **reserved for team state only** — roster, routing, decisions, agent histories, casting, and orchestration logs. Non-team data (adoption tracking, community metrics, reports) must NOT live in `.squad/`. Use `.github/` for GitHub platform integration or `docs/` for documentation artifacts. + +**Source:** [PR #326 comment](https://github.com/bradygaster/squad/pull/326#issuecomment-4029193833) -> Archived from decisions.md on 2026-02-28 by Keaton (Lead). -> These entries are historical record - superseded, completed, or one-time decisions. -> Active decisions remain in decisions.md. --- -# Decisions +### No Individual Repo Listing Without Consent — Owner Directive +**By:** Brady (project owner, PR #326 review) +**Date:** 2026-03-10 -> Team decisions that all agents must respect. Managed by Scribe. +**Directive:** Growth metrics must report **aggregate numbers only** (e.g., "78+ repositories found via GitHub code search") — never name or link to individual community repos without explicit opt-in consent. The monitoring script and GitHub Action concepts are approved, but any public showcase or tracking list that identifies specific repos is blocked until a community consent plan exists. -### 2026-02-21: SDK distribution stays on GitHub -**By:** Keaton (carried from beta) -**What:** Distribution is `npx github:bradygaster/squad` — never move to npmjs.com. -**Why:** GitHub-native distribution aligns with the Copilot ecosystem. No registry dependency. +**Source:** [PR #326 comment](https://github.com/bradygaster/squad/pull/326#issuecomment-4029222967) -### 2026-02-21: v1 docs are internal only -**By:** Keaton (carried from beta) -**What:** No published docs site for v1. Documentation is team-facing only. -**Why:** Ship the runtime first. Public docs come later when the API surface stabilizes. -### 2026-02-21: Type safety — strict mode non-negotiable -**By:** Edie (carried from beta) -**What:** `strict: true`, `noUncheckedIndexedAccess: true`, no `@ts-ignore` allowed. -**Why:** Types are contracts. If it compiles, it works. Strict mode catches entire categories of bugs. +--- -### 2026-02-21: Hook-based governance over prompt instructions -**By:** Baer (carried from beta) -**What:** Security, PII, and file-write guards are implemented via the hooks module, NOT prompt instructions. -**Why:** Prompts can be ignored or overridden. Hooks are code — they execute deterministically. +### Adoption Tracking — Opt-In Architecture +**By:** Flight (implementing Brady's directives above) +**Date:** 2026-03-09 -### 2026-02-21: Node.js ≥20, ESM-only, streaming-first -**By:** Fortier (carried from beta) -**What:** Runtime target is Node.js 20+. ESM-only (no CJS shims, no dual-package hazards). Async iterators over buffers. -**Why:** Modern Node.js features enable cleaner async patterns. ESM-only eliminates CJS interop complexity. +### 2026-02-21: CLI entry point split — src/index.ts is a pure barrel +**By:** Edie (TypeScript Engineer) +**What:** `src/index.ts` is a pure re-export barrel with ZERO side effects. `src/cli-entry.ts` contains `main()` and all CLI routing. +**Why:** Library consumers importing `@bradygaster/squad` were triggering CLI argument parsing and `process.exit()` on import. -### 2026-02-21: Casting — The Usual Suspects, permanent -**By:** Squad Coordinator (carried from beta) -**What:** Team names drawn from The Usual Suspects (1995). Scribe is always Scribe. Ralph is always Ralph. Names persist across repos and replatforms. -**Why:** Names are team identity. The team rebuilt Squad beta with these names. +### 2026-02-21: Process.exit() refactor — library-safe CLI functions +**By:** Kujan (SDK Expert) +**What:** `fatal()` throws `SquadError` instead of `process.exit(1)`. Only `cli-entry.ts` may call `process.exit()`. +**Pattern:** Library functions throw `SquadError`. CLI entry catches and exits. Library consumers catch for structured error handling. -### 2026-02-21: Proposal-first workflow -**By:** Keaton (carried from beta) -**What:** Meaningful changes require a proposal in `docs/proposals/` before execution. -**Why:** Proposals create alignment before code is written. Cheaper to change a doc than refactor code. +### 2026-02-21: User directive — docs as you go +**By:** bradygaster (via Copilot) +**What:** Doc and blog as you go during SquadUI integration work. Doesn't have to be perfect — keep docs updated incrementally. -### 2026-02-21: Tone ceiling — always enforced -**By:** McManus (carried from beta) -**What:** No hype, no hand-waving, no claims without citations. Every public-facing statement must be substantiated. -**Why:** Trust is earned through accuracy, not enthusiasm. +### 2026-02-22: Runtime EventBus as canonical bus +**By:** Fortier +**What:** `runtime/event-bus.ts` (colon-notation: `session:created`, `subscribe()` API) is the canonical EventBus for all orchestration classes. The `client/event-bus.ts` (dot-notation) remains for backward-compat but should not be used in new code. +**Why:** Runtime EventBus has proper error isolation — one handler failure doesn't crash others. -### 2026-02-21: Zero-dependency scaffolding preserved -**By:** Rabin (carried from beta) -**What:** CLI remains thin (`cli.js`), runtime stays modular. Zero runtime dependencies for the CLI scaffolding path. -**Why:** Users should be able to run `npx` without downloading a dependency tree. +### 2026-02-22: Subpath exports in @bradygaster/squad-sdk +**By:** Edie (TypeScript Engineer) +**What:** SDK declares subpath exports (`.`, `./parsers`, `./types`, and module paths). Each uses types-first condition ordering. +**Constraints:** Every subpath needs a source barrel. `"types"` before `"import"`. ESM-only: no `"require"` condition. -### 2026-02-21: Merge driver for append-only files -**By:** Kobayashi (carried from beta) -**What:** `.gitattributes` uses `merge=union` for `.squad/decisions.md`, `agents/*/history.md`, `log/**`, `orchestration-log/**`. -**Why:** Enables conflict-free merging of team state across branches. Both sides only append content. +### 2026-02-22: User directive — Aspire testing requirements +**By:** Brady (via Copilot) +**What:** Integration tests must launch the Aspire dashboard and validate OTel telemetry shows up. Use Playwright. Use latest Aspire bits. Reference aspire.dev (NOT learn.microsoft.com). It's "Aspire" not ".NET Aspire". -### 2026-02-21T20:25:35Z: User directive — Interactive Shell as Primary UX +### 2026-02-23: User directive — code fences **By:** Brady (via Copilot) -**What:** Squad becomes its own interactive CLI shell. `squad` with no args enters a REPL where users talk directly to the team. Copilot SDK is the LLM backend — Squad shells out to it for completions, not the other way around. -**Why:** Copilot CLI has usability issues (unreliable agent handoffs, no visibility into background work). Squad needs to own the full interactive experience with real-time status and direct coordination UX. -**How:** Terminal UI with `ink` (React for CLIs), SDK session management with streaming, direct agent spawning (one session per agent). This becomes Wave 0 (foundation). -**Decisions needed:** Terminal UI library (ink vs. blessed), streaming (event-driven vs. polling), session lifecycle (per-agent vs. pool), background cleanup (explicit vs. timeout). +**What:** Never use / or \ as code fences in GitHub issues, PRs, or comments. Only use backticks to format code. -### 2026-02-21T21:22:47Z: User directive — rename `squad watch` to `squad triage` +### 2026-02-23: User Directive — Docs Overhaul & Publication Pause **By:** Brady (via Copilot) -**What:** "squad watch" should be renamed to "squad triage" — user feedback that the command name should reflect active categorization/routing, not passive observation. -**Why:** User request — captured for team memory. +**What:** Pause docs publication until Brady explicitly gives go-ahead. Tone: lighthearted, welcoming, fun (NOT stuffy). First doc should be "first experience" with squad CLI. All docs: brief, prompt-first, action-oriented, fun. Human tone throughout. + +### 2026-02-23: Use sendAndWait for streaming dispatch +**By:** Kovash (REPL Expert) +**What:** `dispatchToAgent()` and `dispatchToCoordinator()` use `sendAndWait()` instead of `sendMessage()`. Fallback listens for `turn_end`/`idle` if unavailable. +**Why:** `sendMessage()` is fire-and-forget — resolves before streaming deltas arrive. +**Impact:** Never parse `accumulated` after a bare `sendMessage()`. Always use `awaitStreamedResponse`. + +### 2026-02-23: extractDelta field priority — deltaContent first +**By:** Kovash (REPL Expert) +**What:** `extractDelta` priority: `deltaContent` > `delta` > `content`. Matches SDK actual format. +**Impact:** Use `deltaContent` as the canonical field name for streamed text chunks. + +### 2026-02-24: Per-command --help/-h: intercept-before-dispatch pattern +**By:** Fenster (Core Dev) +**What:** All CLI subcommands support `--help` and `-h`. Help intercepted before command routing prevents destructive commands from executing. +**Convention:** New CLI commands MUST have a `getCommandHelp()` entry with usage, description, options, and 2+ examples. -### 2026-02-21T21:35:22Z: User directive — CLI command naming: `squad loop` +### 2026-02-25: REPL cancellation and configurable timeout +**By:** Kovash (REPL Expert) +**What:** Ctrl+C immediately resets `processing` state. Timeout: `SQUAD_REPL_TIMEOUT` (seconds) > `SQUAD_SESSION_TIMEOUT_MS` (ms) > 600000ms default. CLI `--timeout` flag sets env var. + +### 2026-02-24: Shell Observability Metrics +**By:** Saul (Aspire & Observability) +**What:** Four metrics under `squad.shell.*` namespace, gated behind `SQUAD_TELEMETRY=1`. +**Convention:** Shell metrics require explicit consent via `SQUAD_TELEMETRY=1`, separate from OTLP endpoint activation. + +### 2026-02-23: Telemetry in both CLI and agent modes **By:** Brady (via Copilot) -**What:** The work monitor CLI command should be `squad loop`, not `squad ralph` or `squad monitor`. "Loop" is universally understood — no Squad lore needed. Finalized preference (supersedes Keaton's recommendations in favor of `squad monitor`). Update issue #269 accordingly. -**Why:** User request — final naming decision. Brady prefers `squad loop` for clarity and universal understanding. +**What:** Squad should pump telemetry during BOTH modes: (1) standalone Squad CLI, and (2) running as an agent inside GitHub Copilot CLI. + +### 2026-02-27: ASCII-only separators and NO_COLOR +**By:** Cheritto (TUI Engineer) +**What:** All separators use ASCII hyphens. Text-over-emoji principle: text status is primary, emoji is supplementary. +**Convention:** Use ASCII hyphens for separators. Keep emoji out of status/system messages. + +### 2026-02-24: Version format — bare semver canonical +**By:** Fenster +**What:** Bare semver (e.g., `0.8.5.1`) for version commands. Display contexts use `squad v{VERSION}`. + +### 2026-02-25: Help text — progressive disclosure +**By:** Fenster +**What:** Default `/help` shows 4 essential lines. `/help full` shows complete reference. + +### 2026-02-24: Unified status vocabulary +**By:** Marquez (CLI UX Designer) +**What:** Use `[WORK]` / `[STREAM]` / `[ERR]` / `[IDLE]` across ALL status surfaces. +**Why:** Most granular, NO_COLOR compatible, text-over-emoji, consistent across contexts. + +### 2026-02-24: Pick one tagline +**By:** Marquez (CLI UX Designer) +**What:** Use "Team of AI agents at your fingertips" everywhere. -### 2026-02-21T21:35:22Z: User directive — `squad hire` CLI command +### 2026-02-24: User directive — experimental messaging **By:** Brady (via Copilot) -**What:** Add a `squad hire` CLI command. This is the team creation entry point — the init experience with personality. "squad hire" instead of "squad init". -**Why:** User request — Brady wants CLI commands that feel natural and match Squad's identity. +**What:** CLI docs should note the project is experimental and ask users to file issues. -### 2026-02-21: CLI rename — `watch` → `triage` (recommended) (consolidated) -**By:** Keaton (Lead) -**What:** Rename `squad watch` to `squad triage`. Keep `watch` as silent alias for backward compatibility. Explicitly recommend against `squad ralph` as a CLI command. Suggest `squad monitor` or `squad loop` instead to describe the persistent monitoring function. -**Why:** "Triage" is 40% more semantically accurate (matches GitHub's own terminology and incident-management patterns). "Ralph" is internal lore — opaque to new users and violates CLI UX conventions (all user-facing commands are action verbs or domain nouns). `squad monitor` is self-describing and professional. -**Details:** Change is low-risk. Silent alias prevents breakage. Confidence 85% for triage rename, 90% confidence Ralph shouldn't be user-facing. -**Reference:** Keaton analysis in `.squad/decisions/inbox/keaton-cli-rename.md` - -### 2026-02-21: SDK M0 blocker — upgrade from `file:` to npm reference (resolved) -**By:** Kujan (SDK Expert), Edie (implementation) -**What:** Change `optionalDependencies` from `file:../copilot-sdk/nodejs` to `"@github/copilot-sdk": "^0.1.25"`. The SDK is published on npm (28 versions, SLSA attestations). This one-line change unblocks npm publish and removes CI dependency on sibling directory. -**Why:** The `file:` reference is the only M0 blocker. Squad's SDK surface is minimal (1 runtime import: `CopilotClient`). Keep SDK in `optionalDependencies` to preserve zero-dependency scaffolding guarantee (Rabin decision). -**Verified:** Build passes (0 errors), all 1592 tests pass with npm reference. No tests require live Copilot CLI server. PR #271 merged successfully. -**Reference:** Kujan audit + Edie implementation in `.squad/decisions/inbox/edie-sdk-swap.md` -**Closes:** #190, #193, #194 - -### 2026-02-21T21:35:22Z: User directive — no temp/memory files in repo root +### 2026-02-28: User directive — DO NOT merge PR #547 **By:** Brady (via Copilot) -**What:** NEVER write temp files, issue files, or memory files to the repo root. All squad state/scratch files belong in .squad/ and ONLY .squad/. Root tree of a user's repo is sacred — don't clutter it. -**Why:** User request — hard rule. Captured for all agents. +**What:** DO NOT merge PR #547 (Squad Remote Control). Do not touch #547 at all. +**Why:** User request — captured for team memory -### 2026-02-21: npm workspace protocol for monorepo -**By:** Edie (TypeScript Engineer) -**Date:** 2026-02-21 -**PR:** #274 -**What:** Use npm-native workspace resolution (version-string references like `"0.6.0-alpha.0"`) instead of `workspace:*` protocol for cross-package dependencies. -**Why:** The `workspace:*` protocol is pnpm/Yarn-specific. npm workspaces resolve workspace packages automatically by matching the package name in the `workspaces` glob — a version-string reference is all that's needed. Using npm-native semantics avoids toolchain lock-in and keeps the monorepo compatible with stock npm. -**Impact:** All future inter-package dependencies in `packages/*/package.json` should use the actual version string, not `workspace:*`. +### 2026-02-28: CLI Critical Gap Issues Filed +**By:** Keaton (Lead) +**What:** 4 critical CLI gaps filed as GitHub issues #554–#557 for explicit team tracking: +- #554: `--preview` flag undocumented and untested +- #556: `--timeout` flag undocumented and untested +- #557: `upgrade --self` is dead code +- #555: `run` subcommand is a stub (non-functional) + +**Why:** Orchestration logs captured gaps but they lacked actionable GitHub tracking and ownership. Filed issues now have explicit assignment to Fenster, clear acceptance criteria, and visibility in Wave E planning. + +### 2026-02-28: Test Gap Issues Filed (10 items) +**By:** Hockney (Tester) +**What:** 10 moderate CLI/test gaps filed as issues #558–#567: +- #558: Exit code consistency untested +- #559: Timeout edge cases untested +- #560: Missing per-command help +- #561: Shell-specific flag behavior untested +- #562: Env var fallback paths untested +- #563: REPL mode transitions untested +- #564: Config file precedence untested +- #565: Agent spawn flags undocumented +- #566: Untested flag aliases +- #567: Flag parsing error handling untested + +**Why:** Each gap identified in coverage analysis but lacked explicit GitHub tracking for prioritization and team visibility. + +### 2026-02-28: Documentation Audit Results (10 issues) +**By:** McManus (DevRel) +**What:** Docs audit filed 10 GitHub issues (#568–#575, #577–#578) spanning: +- Feature documentation lag (#568 `squad run`, #570 consult mode, #572 Ralph smart triage) +- Terminology inconsistency (#569 triage/watch/loop naming) +- Brand compliance (#571 experimental banner on 40+ docs) +- Clarity/UX gaps (#573 response modes, #575 dual-root, #577 VS Code, #578 session examples) +- Reference issue (#574 README command count) + +**Why:** Features shipped faster than documentation. PR #552, #553 merged without doc updates. No automation to enforce experimental banner. Users discover advanced features accidentally. + +**Root cause:** Feature-docs lag, decision-doc drift, no brand enforcement in CI. + +### 2026-02-28: Dogfood UX Issues Filed (4 items) +**By:** Waingro (Dogfooder) +**What:** Dogfood testing against 8 realistic scenarios surfaced 4 UX issues (filed as #576, #579–#581): +- #576 (P1): Shell launch fails in non-TTY piped mode (Blocks CI) +- #580 (P1): Help text overwhelms new users (44 lines, no tiering) +- #579 (P2): Status shows parent `.squad/` as local (confusing in multi-project workspaces) +- #581 (P2): Error messages show debug output always (noisy production logs) + +**Why:** CLI is solid for happy path but first-time user experience and CI/CD integration have friction points. All 4 block either new user onboarding or automation workflows. + +**Priority:** #576 > #580 > #581 > #579. All should be fixed before next public release. + +### 2026-02-28: decisions.md Aggressive Cleanup +**By:** Keaton (Lead) +**What:** Trimmed `decisions.md` from 226KB (223 entries) to 10.3KB (35 entries) — 95% reduction. +- Kept: Core architectural decisions, active process rules, active user directives, current UX conventions, runtime patterns +- Archived: Implementation details, one-time setup, PR reviews, audit reports, wave planning, superseded decisions, duplicates +- Created: `decisions-archive.md` with full original content preserved + +**Why:** Context window bloat during release push. Every agent loads 95% less decisions context. Full history preserved append-only. -### 2026-02-21: Resolution module placement and API separation +**Impact:** File size reduced, agent context efficiency improved, all decisions preserved in archive. + +### 2026-02-28: Backlog Gap Issues Filed (8 items) +**By:** Keaton (Lead) +**Approval:** Brady (via directive in issue request) +**What:** Filed 8 missing backlog items from `.squad/identity/now.md` as GitHub issues. These items were identified as "should-fix" polish or "post-M1" improvements but lacked explicit GitHub tracking until now. + +**Why:** Brady requested: "Cross-reference the known backlog against filed issues and file anything missing." The team had filed 28 issues this session (#554–#581), but 8 known items from `now.md` remained unfiled. Without GitHub issues, these lack ownership assignment, visibility for Wave E planning, trackability in automated workflows, and routing to squad members. + +**Issues Filed:** +- #583 (squad:rabin): Add `homepage` and `bugs` fields to package.json +- #584 (squad:mcmanus): Document alpha→v1.0 breaking change policy in README +- #585 (squad:edie): Add `noUncheckedIndexedAccess` to tsconfig +- #586 (squad:edie): Tighten ~26 `any` types in SDK +- #587 (squad:mcmanus): Add architecture overview doc +- #588 (squad:kujan): Implement SQUAD_DEBUG env var test +- #589 (squad:kujan): One real Copilot SDK integration test +- #590 (squad:baer): `npm audit fix` for dev-dependency ReDoS warnings +- #591 (squad:hockney, type:bug): Aspire dashboard test fails — docker pull in test suite +- #592 (squad:rabin): Replace workspace:* protocol with version string + +**Impact:** Full backlog now visible with explicit issues. No unmapped items. Each issue routed to the squad member domain expert. Issues are independent; can be executed in any order. + +### 2026-02-28: Codebase Scan — Unfiled Issues Audit **By:** Fenster (Core Dev) -**Date:** 2026-02-21 -**Re:** #210, #211 -**What:** -- `resolveSquad()` and `resolveGlobalSquadPath()` live in `src/resolution.ts` at the repo root, not in `packages/squad-sdk/`. -- The two functions are independent — `resolveSquad()` does NOT automatically fall back to `resolveGlobalSquadPath()`. -**Why:** -1. **Placement:** Code hasn't migrated to the monorepo packages yet. Putting it in `packages/squad-sdk/src/` would create a split that doesn't match the current build pipeline (`tsc` compiles `src/` to `dist/`). When the monorepo migration happens, `src/resolution.ts` moves with everything else. -2. **Separation of concerns:** Issue #210 says "repo squad always wins over personal squad" — that's a *policy* decision for the consumer, not for the resolution primitives. Keeping the two functions independent lets CLI/runtime compose them however needed (e.g., try repo first, fall back to global, or merge both). -**Impact:** Low. When packages split happens, move `src/resolution.ts` into `packages/squad-sdk/src/`. The public API shape stays the same. - -### 2026-02-21: Changesets setup — independent versioning for squad-sdk and squad-cli -**By:** Kobayashi (Git & Release) -**Date:** 2026-02-21 -**Re:** #208 -**What:** Installed and configured @changesets/cli v2 for independent package versioning across the monorepo. -**Configuration:** -- `access`: `"public"` (both packages will be published) -- `baseBranch`: `"main"` (release branch for changesets) -- `fixed`: `[]` (empty — no linked releases) -- `linked`: `[]` (empty — no linked releases) -- `updateInternalDependencies`: `"patch"` (default, appropriate for SDK→CLI dependency) -**Why:** Squad is a monorepo with two distinct packages (squad-sdk and squad-cli) with different release cadences and audiences. Independent versioning prevents unnecessary releases and version inflation when only one package changes. -**Implementation:** `.changeset/config.json` created, npm script `changeset:check` added to `package.json` for CI validation. -**Next Steps:** Contributors use `npx changeset add` before merge; release workflow runs `changeset publish` to GitHub. - -### 2026-02-21: --global flag and status command pattern +**Requested by:** Brady +**Date:** 2026-02-28T22:05:00Z +**Status:** Complete — 2 new issues filed + +**What:** Systematic scan of the codebase to identify known issues that haven't been filed as GitHub issues. Checked: +1. TODO/FIXME/HACK/XXX comments in code +2. TypeScript strict mode violations (@ts-ignore/@ts-expect-error) +3. Skipped/todo tests (.skip() or .todo()) +4. Errant console.log statements +5. Missing package.json metadata fields + +**Findings:** +- Type safety violations: ✅ CLEAN — Zero @ts-ignore/@ts-expect-error found. Strict mode compliance excellent. +- Workspace protocol: ❌ VIOLATION — 1 issue filed (#592): `workspace:*` in squad-cli violates npm workspace convention +- Skipped tests: ❌ GAP — 1 issue filed (#588): SQUAD_DEBUG test is .todo() placeholder +- Console.log: ✅ INTENTIONAL — All are user-facing output (status, errors) +- TODO comments: ✅ TEMPLATES — TODOs in generated workflow templates, not code +- Package.json: ✅ TRACKED — Missing homepage/bugs already filed as #583 + +**Code Quality Assessment:** +- Type Safety (Excellent): Zero violations of strict mode or type suppression. Team decision being followed faithfully. +- TODO/FIXME Comments (Clean): All TODOs in upgrade.ts and workflows.ts are template strings for generated GitHub Actions YAML, intentionally scoped. +- Console Output (Intentional): All are user-facing (dashboard startup, OTLP endpoint, issue labeling, shell loading) — no debug debris. +- Dead Code (None Found): No unreachable code, orphaned functions, or unused exports detected. + +**Recommendations:** +1. Immediate: Fix workspace protocol violation (#592) — violates established team convention +2. Soon: Implement SQUAD_DEBUG test (#588) — fills observable test gap +3. Going forward: Maintain type discipline; review package.json metadata during SDK/CLI version bumps + +**Conclusion:** Codebase in good health. Type safety discipline strong. No hidden technical debt. Conventions mostly followed (one npm workspace exception). Test coverage has minor gaps in observability. + +### 2026-02-28: Auto-link detection for preview builds **By:** Fenster (Core Dev) -**Date:** 2026-02-21 -**Re:** #212, #213 -**What:** -- `--global` flag on `init` and `upgrade` routes to `resolveGlobalSquadPath()` instead of `process.cwd()`. -- `squad status` composes both `resolveSquad()` and `resolveGlobalSquadPath()` to show which squad is active and why. -- All routing logic stays in `src/index.ts` main() — init.ts and upgrade.ts are path-agnostic (they take a `dest` string). -**Why:** -1. **Thin CLI contract:** init and upgrade already accept a destination directory. The `--global` flag is a CLI concern, not a runtime concern — so it lives in the CLI routing layer only. -2. **Composition over fallback:** `squad status` demonstrates the intended consumer pattern from #210/#211: try repo resolution first, then check global path. The resolution primitives stay independent. -3. **Debugging UX:** Status shows repo resolution, global path, and global squad existence — all the info needed to debug "why isn't my squad loading?" issues. -**Impact:** Low. Single-file change to `src/index.ts`. No changes to resolution algorithms or init/upgrade internals. +**Date:** 2026-02-28 +**What:** When running from source (`VERSION` contains `-preview`), the CLI checks if `@bradygaster/squad-cli` is globally npm-linked. If not, it prompts the developer to link it. Declining creates `~/.squad/.no-auto-link` to suppress future prompts. +**Why:** Dev convenience — saves contributors from forgetting `npm link` after cloning. Non-interactive commands (help, version, export, import, doctor, scrub-emails) skip the check. Everything is wrapped in try/catch so failures are silent. +**Impact:** Only affects `-preview` builds in interactive TTY sessions. No effect on published releases or CI. + +### 2026-03-01T00:34Z: User directive — Full scrollback support in REPL shell +**By:** Brady (via Copilot) +**What:** The REPL shell must support full scrollback — users should be able to scroll up and down to see all text (paste, run output, rendered content, logs) over time, like GitHub Copilot CLI does. The current Ink-based rendering loses/hides content and that's unacceptable. +**Why:** User request — captured for team memory. This is a P0 UX requirement for the shell. +**Status:** P0 blocking issue. Requires rendering architecture review (Cheritto, Kovash, Marquez). + +### 2026-03-01T04:47Z: User directive — Auto-incrementing build numbers +**By:** Brady (via Copilot) +**What:** Add auto-incrementing build numbers to versions. Format: `0.8.6.{N}-preview` where N increments each local build. Tracks build-to-release cadence. +**Why:** User request — captured for team memory. -### 2026-02-21: No repo root clutter — ensureSquadPath() guard +### 2026-03-01: Nap engine — dual sync/async export pattern **By:** Fenster (Core Dev) -**Date:** 2026-02-21 -**Re:** #273 +**What:** The nap engine (`cli/core/nap.ts`) exports both `runNap` (async, for CLI entry) and `runNapSync` (sync, for REPL). All internal operations use sync fs calls. The async wrapper exists for CLI convention consistency. +**Why:** REPL `executeCommand` is synchronous and cannot await. ESM forbids `require()`. Exporting a sync variant keeps the REPL integration clean without changing the shell architecture. +**Impact:** Future commands that need both CLI and REPL support should follow this pattern if they only do sync fs work. -**What:** -- Added `ensureSquadPath(filePath, squadRoot)` in `src/resolution.ts` — a guard that validates a target path is inside `.squad/` or the system temp directory before any write occurs. Throws with a descriptive error if the path is outside both. -- Exported from the public API (`src/index.ts`). +### 2026-03-01: First-run gating test strategy +**By:** Hockney (Tester) +**Date:** 2026-03-01 +**Issue:** #607 +**What:** Created `test/first-run-gating.test.ts` with 25 tests covering 6 categories of Init Mode gating. Tests use logic-level extraction from App.tsx conditionals, filesystem marker lifecycle via `loadWelcomeData`, and source-code structural assertions for render ordering. No full App component rendering — SDK dependencies make that impractical for unit tests. +**Why:** 3059 tests existed with zero enforcement of first-run gating behavior. The `.first-run` marker, banner uniqueness, assembled-message gating, warning suppression, session-scoped keys, and terminal clear ordering were all untested paths that could regress silently. +**Impact:** All squad members: if you modify `loadWelcomeData`, the `firstRunElement` conditional in App.tsx, or the terminal clear sequence in `runShell`, these tests will catch regressions. The warning suppression tests replicate the `cli-entry.ts` pattern — if that pattern changes, update both locations. -**Why:** -Brady's hard rule: ALL squad scratch/temp/state files MUST go in `.squad/` only. During issue creation, 20+ temp files were written directly to the repo root. This guard provides a single validation function that any file-writing code path can call to enforce the policy deterministically (per the hooks-over-prompts decision). +### Verbal's Analysis: "nap" Skill — Context Window Optimization +**By:** Verbal (Prompt Engineer) +**Requested by:** Brady +**Date:** 2026-03-01 +**Scope:** Approved. Build it. Current context budget analysis: +- Agent spawn loads charter (~500t) + history + decisions.md (4,852t) + team.md (972t) +- Hockney: 25,940t history (worst offender) +- Fenster: 22,574t (history + CLI inventory) +- Coherence cliff: 40-50K tokens on non-task context + +**Key Recommendations:** +1. **Decision distillation:** Keep decisions.md as single source of truth (don't embed in charters — creates staleness/duplication) +2. **History compression — 12KB rule insufficient:** Six agents blow past threshold. Target **4KB ceiling per history** (~1,000t) with assertions not stories. +3. **Nap should optimize:** Deduplication (strip decisions.md content echoed in histories), staleness (flag closed PRs, merged work), charter bloat (stay <600t), skill pruning (archive high-confidence, no-recent-invocation skills), demand-loading for extra files (CLI inventory, UX catalog, fragility catalog). +4. **Enforcement:** Nap runs periodically or on-demand, enforces hard ceilings without silent quality degradation. + +### ShellApi.clearMessages() for terminal state reset +**By:** Kovash (REPL Expert) +**Date:** 2026-03-01 +**What:** `ShellApi` now exposes `clearMessages()` which resets both `messages` and `archivedMessages` React state. Used in session restore and `/clear` command. +**Why:** Without clearing archived messages, old content bleeds through when restoring sessions or clearing the shell. The `/clear` command previously only reset `messages`, leaving `archivedMessages` in the Static render list. +**Impact:** Any code calling `shellApi` to reset shell state should use `clearMessages()` rather than manually manipulating message arrays. -**Audit findings:** -- 30+ file write calls across `src/` — most already write into `.squad/` subdirectories or user-requested destinations. -- The `tools/index.ts` write_file tool and `cli/commands/export.ts` write to user-specified paths (intentional, user-requested). -- No existing code paths were changed — this is a new guard utility for future enforcement. +### 2026-03-01: Prompt placeholder hints must not duplicate header banner +**By:** Kovash (REPL Expert) +**Date:** 2026-03-01 +**Issue:** #606 +**What:** The InputPrompt placeholder text must provide *complementary* guidance, never repeat what the header banner already shows. The header banner is the single source of truth for @agent routing and /help discovery. Placeholder hints should surface lesser-known features (tab completion, history navigation, utility commands). +**Why:** Two elements showing "Type @agent or /help" simultaneously creates visual noise and a confusing UX. One consistent prompt style throughout the session. +**Impact:** `getHintText()` in InputPrompt.tsx now has two tiers instead of three. Any future prompt hints should check the header banner first to avoid duplication. -**Impact:** Low. Additive-only change. Existing behavior unchanged. Future code that writes temp/scratch files should call `ensureSquadPath()` before writing. +### 2026-03-02: Paste detection via debounce in InputPrompt +**By:** Kovash (REPL Expert) +**Date:** 2026-03-02 +**What:** InputPrompt uses a 10ms debounce on `key.return` to distinguish paste from intentional Enter. If more input arrives within 10ms → paste detected → newline preserved. If timer fires without input → real Enter → submit. A `valueRef` (React ref) mirrors mutations synchronously since closure-captured `value` is stale during rapid `useInput` calls. In disabled state, `key.return` appends `\n` to buffer instead of being ignored. +**Why:** Multi-line paste was garbled because `useInput` fires per-character and `key.return` triggered immediate submission. +**Impact:** 10ms delay on single-line submit is imperceptible. UX: multi-line paste preserved. Testing: Hockney should verify paste scenarios use `jest.useFakeTimers()` or equivalent. Future: if Ink adds native bracketed-paste support, debounce can be replaced. + +### 2026-03-01: First-run init messaging — single source of truth +**By:** Kovash (REPL & Interactive Shell) +**Date:** 2026-03-01 +**Issue:** #625 +**What:** When no roster exists, only the header banner tells the user about `squad init` / `/init`. The `firstRunElement` block returns `null` for the empty-roster case instead of showing a duplicate message. `firstRunElement` is reserved for the "Your squad is assembled" onboarding when a roster already exists. +**Why:** Two competing UI elements both said "run squad init" — visual noise that confuses the information hierarchy. Banner is persistent and visible; it owns the no-roster guidance. `firstRunElement` owns the roster-present first-run experience. +**Impact:** App.tsx only. No API or prop changes. Banner text reworded to prioritize `/init` (in-shell path) over exit-and-run. + +### 2026-03-01: NODE_NO_WARNINGS for subprocess warning suppression +**By:** Cheritto (TUI Engineer) +**Date:** 2026-03-01 +**Issue:** #624 +**What:** `process.env.NODE_NO_WARNINGS = '1'` is set as the first executable line in `cli-entry.ts` (line 2, after shebang). This supplements the existing `process.emitWarning` override. +**Why:** The Copilot SDK spawns child processes that inherit environment variables but NOT in-process monkey-patches like `process.emitWarning` overrides. `NODE_NO_WARNINGS=1` is the Node.js-native mechanism for suppressing warnings across an entire process tree. Without it, `ExperimentalWarning` messages (e.g., SQLite) leak into the terminal via the SDK's subprocess stderr forwarding. +**Pattern:** When suppressing Node.js warnings, use BOTH: (1) `process.env.NODE_NO_WARNINGS = '1'` — covers child processes (env var inheritance); (2) `process.emitWarning` override — covers main process (belt-and-suspenders). +**Impact:** Eliminates `ExperimentalWarning` noise in terminal for all Squad CLI users, including when the Copilot SDK spawns subprocesses. + +### 2026-03-01: No content suppression based on terminal width +**By:** Cheritto (TUI Engineer) +**Date:** 2026-03-01 +**What:** Terminal width tiers (compact ≤60, standard, wide ≥100) may adjust *layout* (e.g., wrapping, column arrangement) but must NOT suppress or truncate *content*. Every piece of information shown at 120 columns must also be shown at 40 columns. +**Why:** Users can scroll. Hiding roster names, spacing, help text, or routing hints on narrow terminals removes information the user needs. Layout adapts to width; content does not. +**Convention:** `compact` variable may be used for layout decisions (flex direction, column vs. row) but must NOT gate visibility of text, spacing, or UI sections. `wide` may add supplementary content but narrow must not remove it. -### 2026-02-21: CLI routing logic is testable via composition, not process spawning -**By:** Hockney (Tester) -**Date:** 2026-02-21 -**Re:** #214 +### 2026-03-01: Multi-line user message rendering pattern +**By:** Cheritto (TUI Engineer) +**Date:** 2026-03-01 +**What:** Multi-line user messages in the Static scrollback use `split('\n')` with a column layout: first line gets the `❯` prefix, subsequent lines get `paddingLeft={2}` for alignment. +**Why:** Ink's horizontal `` layout doesn't handle embedded `\n` in `` children predictably when siblings exist. Explicit line splitting with column flex direction gives deterministic multi-line rendering. +**Impact:** Any future changes to user message prefix width must update the `paddingLeft={2}` on continuation lines to match. -**What:** Integration tests for `squad status` and `--global` flag test the *routing logic* (the conditional expressions from `main()`) directly, rather than spawning a child process and parsing stdout. +### 2026-03-01: Elapsed time display — inline after message content +**By:** Cheritto (TUI Engineer) +**Date:** 2026-03-01 +**Issue:** #605 +**What:** Elapsed time annotations on completed agent messages are always rendered inline after the message content as `(X.Xs)` in dimColor. This applies to the Static scrollback block in App.tsx, which is the canonical render path for all completed messages. +**Why:** After the Static scrollback refactor, MessageStream receives `messages=[]` and only renders live streaming content. The duration code in MessageStream was dead. Moving duration display into the Static block ensures it always appears consistently. +**Convention:** `formatDuration()` from MessageStream.tsx is the shared formatter. Format is `Xms` for <1s, `X.Xs` for ≥1s. Always inline, always dimColor, always after content text. -**Why:** -1. `main()` in `src/index.ts` calls `process.exit()` and is not exported — spawning would be flaky and slow. -2. The routing logic is simple conditionals over `resolveSquad()` and `resolveGlobalSquadPath()` — testing those compositions directly is deterministic and fast. -3. If `main()` is ever refactored to export a testable function, these tests can be upgraded — the assertions stay the same. +### 2026-03-01: Banner usage line separator convention +**By:** Cheritto (TUI Engineer) +**Date:** 2026-03-01 +**What:** Banner hint/usage lines use middle dot `·` as inline separator. Init messages use single CTA (no dual-path instructions). +**Why:** Consistent visual rhythm. Middle dot is lighter than em-dash or hyphen for inline command lists. Single CTA reduces cognitive load for new users. +**Impact:** App.tsx headerElement. Future banner copy should follow same separator and single-CTA pattern. -**Impact:** Low. Sets a pattern for future CLI integration tests: test the logic, not the process. +### 2026-03-02: REPL casting engine design +**By:** Fenster (Core Dev) +**Date:** 2026-03-02 +**Status:** Implemented +**Issue:** #638 +**What:** Created `packages/squad-cli/src/cli/core/cast.ts` as a self-contained casting engine with four exports: +1. `parseCastResponse()` — parses the `INIT_TEAM:` format from coordinator output +2. `createTeam()` — scaffolds all `.squad/agents/` directories, writes charters, updates team.md and routing.md, writes casting state JSON +3. `roleToEmoji()` — maps role strings to emoji, reusable across the CLI +4. `formatCastSummary()` — renders a padded roster summary for terminal display -### 2026-02-21: Ink + React dependency versions -**By:** Edie (TypeScript Engineer) -**Date:** 2026-02-21 -**Re:** #233 -**PR:** #281 +Scribe and Ralph are always injected if missing from the proposal. Casting state is written to `.squad/casting/` (registry.json, history.json, policy.json). +**Why:** Enables coordinator to propose and create teams from within the REPL session after `squad init`. +**Implications:** -**What:** -- **ink@6.8.0** — latest stable, ESM-native, no CJS shims required -- **react@19.2.4** — required by ink 6.x (peer dependency); React 19 is ESM-friendly -- **ink-testing-library@4.0.0** — matches ink 6.x major version pairing -- **@types/react@19.2.14** — TypeScript types for React 19 +### 2026-03-02: Beta → Origin Migration: Version Path (v0.5.4 → v0.8.17) -**Why:** ink 6.x + React 19 are fully ESM-native — aligns with our ESM-only policy. ink-testing-library enables unit-testing ink components without a real terminal. All added as root-level deps for now; can be scoped to `packages/squad-cli` during monorepo migration. +**By:** Kobayashi (Git & Release) +**Date:** 2026-03-02 +**Context:** Analyzed migration from beta repo (`bradygaster/squad`, v0.5.4) to origin repo (`bradygaster/squad-pr`, v0.8.18-preview). Version gap spans 0.6.x, 0.7.x, 0.8.0–0.8.16 (internal origin development only). -**Impact:** Low. No source changes — dependency additions only. Build passes (tsc strict), all 1621 tests pass. +**What:** Beta will jump directly from v0.5.4 to v0.8.17 (skip all intermediate versions). Rationale: +1. **Semantic versioning allows gaps** — version numbers are labels, not counters +2. **Users care about features, not numbers** — comprehensive changelog is more valuable than version sequence +3. **Simplicity reduces risk** — single migration release is easier to execute and communicate +4. **Precedent exists** — major refactors/rewrites commonly skip versions (Angular 2→4, etc) -### 2026-02-21: GitHub-native distribution deprecated in favor of npm -**By:** Fenster (Core Dev) -**Date:** 2026-02-21 -**Re:** #219 +**Risks & Mitigations:** +- Risk: Version jump confuses users. Mitigation: Clear release notes explaining the gap + comprehensive changelog +- Risk: Intermediate versions were never public (no user expectations). Mitigation: This is actually a benefit — no backfill needed -**What:** -- Root `cli.js` now prints a deprecation warning to stderr when invoked via `npx github:bradygaster/squad`. -- Users are directed to `npm install -g @bradygaster/squad-cli` or `npx @bradygaster/squad-cli`. -- The warning is non-blocking — existing behavior continues after the notice. +**Impact:** After merge, beta repo version jumps from v0.5.4 to v0.8.17. All intermediate work is included in the 0.8.17 release. Next release after v0.8.17 may be v0.8.18 or v0.9.0 (team decision post-merge). -**Why:** The `@bradygaster/squad-cli` and `@bradygaster/squad-sdk` packages are now published to npm. The GitHub-native distribution (`npx github:bradygaster/squad`) was the original entry point but is now superseded. This deprecation notice gives users a migration path before the GitHub-native entry point is eventually removed. +**Why:** Avoids maintenance burden of backfilling 12+ fake versions. Users get complete feature set in one migration release. -**Supersedes:** The earlier "SDK distribution stays on GitHub" decision (Keaton, carried from beta). npm is now the primary distribution channel. +### 2026-03-02: Beta → Origin Migration: Package Naming -**Impact:** Low. Additive-only change to the bundled `cli.js`. No behavior change — just a stderr warning. +**By:** Kobayashi (Git & Release) +**Date:** 2026-03-02 -### 2026-02-21: Shell chrome patterns and session registry design -**By:** Fenster (Core Dev) -**Date:** 2026-02-21 -**Re:** #236, #237 +**What:** Deprecate `@bradygaster/create-squad` (beta's package name). All future releases use: +- `@bradygaster/squad-cli` (user-facing CLI) +- `@bradygaster/squad-sdk` (programmatic SDK for integrations) -**Shell Chrome:** The interactive shell header uses box-drawing characters (`╭╰│─`) for visual chrome. Version is read from `package.json` at runtime via `createRequire(import.meta.url)` — no hardcoded version strings. Box-drawing chrome is universally supported in modern terminals and provides clear visual framing without external dependencies. Using `createRequire` for JSON import follows the existing pattern in `src/build/github-dist.ts` and avoids ESM JSON import assertions. +**Why:** Origin's naming is more accurate and supports independent versioning if needed. Monorepo structure benefits from clear package separation. -**Exit handling:** Three exit paths — `exit` command, `/quit` command, and Ctrl+C (SIGINT). All produce the same cleanup message ("👋 Squad out.") for consistency. +**Action:** When v0.8.17 is ready to publish, release a final version of `@bradygaster/create-squad` with deprecation notice: "This package has been renamed to @bradygaster/squad-cli. Install with: npm install -g @bradygaster/squad-cli" -**Session Registry:** `SessionRegistry` is a simple Map-backed class with no persistence, no event emitting, and no external dependencies. It tracks agent sessions by name with status lifecycle: `idle` → `working` → `streaming` → `idle`/`error`. Designed as a pure state container that the ink UI will consume. The Map-based approach allows O(1) lookup by agent name, which is the primary access pattern for status display. +**Impact:** Package ecosystem clarity. No breaking change for users upgrading (CLI handles detection and warnings). -**Impact:** Low. Two files changed. No API surface changes outside the shell module. SessionRegistry is exported for future consumption but has no current consumers. +### 2026-03-02: Beta → Origin Migration: Retroactive v0.8.17 Tag -### 2026-02-21: TSX compilation enabled in root tsconfig -**By:** Fenster (Core Dev) -**Date:** 2026-02-21 -**Re:** #242, #243, #244 +**By:** Kobayashi (Git & Release) +**Date:** 2026-03-02 -**What:** Added `"jsx": "react-jsx"` to the root `tsconfig.json` to enable TSX compilation for ink-based React components in `src/cli/shell/components/`. +**What:** Retroactively tag commit `5b57476` ("chore(release): prep v0.8.16 for npm publish") as v0.8.17. This commit and v0.8.16 have identical code. -**Why:** The shell UI uses ink (React for CLIs), which requires JSX support. `react-jsx` is the modern transform — no need to import React in every file for JSX (though we do for explicitness). This is a project-wide setting because all TSX files live under `src/` which is the root `rootDir`. +**Rationale:** +- Commit `6fdf9d5` jumped directly to v0.8.17-preview (no v0.8.17 release tag exists) +- Commit `87e4f1c` bumps to v0.8.18-preview "after 0.8.17 release" (implying v0.8.17 was released) +- Retroactive tagging is less disruptive than creating a new prep commit and rebasing -**Components created:** -- `AgentPanel.tsx` — agent status display (consumes `AgentSession` from `types.ts`) -- `MessageStream.tsx` — streaming message output (consumes `ShellMessage` from `types.ts`) -- `InputPrompt.tsx` — readline input with history navigation -- `components/index.ts` — barrel export +**Action:** When banana gate clears, tag origin commit `5b57476` as v0.8.17. -All components are pure presentational — no SDK calls, no side effects. State management is the responsibility of the shell orchestrator. +**Why:** Completes the missing link in origin's tag history. Indicates to users which commit was released as v0.8.17. -**Impact:** Low. Only affects `.tsx` files. No existing `.ts` files are impacted. The setting is compatible with strict mode and NodeNext module resolution. +### 2026-03-02: npx Distribution Migration: Error-Only Shim Strategy -### 2026-02-21: Shell module structure and entry point routing -**By:** Fenster (Core Dev) -**Date:** 2026-02-21 -**Re:** #234, #235 -**PR:** #282 +**By:** Rabin (Distribution) +**Date:** 2026-03-02 +**Context:** Beta repo currently uses GitHub-native distribution (`npx github:bradygaster/squad`). Origin uses npm distribution (`npm install -g @bradygaster/squad-cli`). After merge, old path will break. -**What:** -- `src/cli/shell/` module created with `index.ts`, `types.ts`, and `components/` placeholder directory. -- `squad` with no args now launches `runShell()` (interactive shell) instead of `runInit()`. -- `squad init` remains available as an explicit subcommand — no functionality removed. +**Problem:** After migration, `npx github:bradygaster/squad` fails (root `package.json` has no `bin` entry). Users hitting the old path get cryptic npm error. -**Why:** -1. **Entry point change:** Brady's directive makes the interactive shell the primary UX. Running `squad` with no args should enter the shell, not re-run init. Init is still available via `squad init`. -2. **Placeholder over premature implementation:** `runShell()` is console.log-only. Ink dependency is handled separately (#233). This keeps the shell module structure ready without coupling to the UI library. -3. **Types first:** `ShellState`, `ShellMessage`, and `AgentSession` interfaces define the shell's data model before any UI code exists. This lets other agents (ink wiring, agent spawning) code against stable types. +**Solution — Option 5 (Error-only shim):** +1. Add root `bin` entry pointing to `cli.js` +2. `cli.js` detects GitHub-native invocation and prints **bold, clear error** with migration instructions +3. Exit with code 1 (fail fast, no hidden redirection) -**Impact:** Low. No existing tests broken (1621/1621 pass). The only behavior change is `squad` (no args) prints a shell header and exits instead of running init. `squad init` and `squad --help` / `squad help` continue to work as before. +**Implementation:** +```json +{ + "bin": { + "squad": "./cli.js" + } +} +``` -### 2026-02-21: Agent spawn infrastructure pattern -**By:** Fenster (Core Dev) -**Date:** 2026-02-21 -**Re:** #238 +Update `cli.js` to print error message with new install instructions: +``` +npm install -g @bradygaster/squad-cli +``` -**What:** Created `src/cli/shell/spawn.ts` with three exported functions: -- `loadAgentCharter(name, teamRoot?)` — filesystem charter loading via `resolveSquad()` -- `buildAgentPrompt(charter, options?)` — system prompt construction from charter + context -- `spawnAgent(name, task, registry, options?)` — full spawn lifecycle with SessionRegistry integration +**Pros:** +- ✅ Clear, actionable error message (not cryptic npm error) +- ✅ Aligns with npm-only team decision (no perpetuation of GitHub-native path) +- ✅ Low maintenance burden (simple error script, no complex shim) +- ✅ Can be removed in v1.0.0 when beta users have migrated -SDK session creation (CopilotClient) is intentionally stubbed. The spawn infrastructure is complete; session wiring comes when we understand the SDK's session management API. +**Cons:** +- Immediate breakage (no grace period) — but users get clear guidance -**Why:** -1. **Separation of concerns:** Charter loading, prompt building, and session lifecycle are independent functions. This lets stream bridge and coordinator reuse `buildAgentPrompt` and `spawnAgent` without coupling. -2. **Testability:** `teamRoot` parameter on `loadAgentCharter` allows tests to point at a fixture directory without mocking `resolveSquad()`. -3. **Stub-first:** Rather than guessing the CopilotClient session API shape, we built the surrounding infrastructure. The TODO is a single integration point — when the SDK surface is clear, the change is surgical. +**Why This Over Others:** +- Option 1 (keep working) contradicts npm-only decision +- Option 2 (exit early) same as this, but explicit error format needed +- Option 3 (time-limited) best UX but maintenance burden +- Option 4 (just break) user-hostile without error message +- **Option 5 balances user experience + team decision** -**Impact:** Low. Additive-only. No existing behavior changed. Two files modified: `spawn.ts` (new), `index.ts` (barrel exports added). +**Related Decision:** See 2026-02-21 decision "Distribution is npm-only (GitHub-native removed)" -### 2026-02-21: Session lifecycle owns team discovery -**By:** Fortier (Node.js Runtime) -**Date:** 2026-02-21 -**Re:** #240 +**User Impact:** +- Users running `npx github:bradygaster/squad` see bold error with `npm install -g @bradygaster/squad-cli` instruction +- Existing projects running `squad upgrade` work seamlessly (upgrade logic built-in) +- No data loss or silent breakage -**What:** `ShellLifecycle.initialize()` is the single entry point for team discovery in the interactive shell. It reads `.squad/team.md`, parses the Members table, and registers active agents in `SessionRegistry`. No other shell component should independently parse `team.md` or discover agents. +**Upgrade Path (existing beta users):** +```bash +npm install -g @bradygaster/squad-cli +cd /path/to/project +squad upgrade +squad upgrade --migrate-directory # Optional: .ai-team/ → .squad/ +``` -**Why:** -1. **Single responsibility:** Team discovery is a lifecycle concern — it happens once at shell startup. Scattering `team.md` parsing across components would create divergent interpretations of the manifest format. -2. **Testability:** By owning initialization, `ShellLifecycle` can be tested with a mock filesystem (or temp `.squad/` directory) without touching the registry or renderer. -3. **State consistency:** The lifecycle class is the source of truth for shell state. If initialization fails (missing `.squad/`, missing `team.md`), the state transitions to `error` and downstream components can check `getState().status` rather than catching exceptions everywhere. +**Why:** Rabin's principle: "If users have to think about installation, install is broken." A clear error message respects users better than a cryptic npm error. -**Impact:** Low. Additive-only. Future shell features (command routing, agent spawning) should call `lifecycle.getDiscoveredAgents()` instead of re-parsing `team.md`. +### 2026-02-28: Init flow reliability — proposal-first before code -### 2026-02-21: StreamBridge is an event sink, not a subscriber -**By:** Fortier (Node.js Runtime) -**Date:** 2026-02-21 -**Re:** #239 +**By:** Keaton (Lead) +**Date:** 2026-02-28 +**What:** Init/onboarding fixes require a proposal review before implementation. Proposal at `docs/proposals/reliable-init-flow.md`. Two confirmed bugs (race condition in auto-cast, Ctrl+C doesn't abort init session) plus UX gaps (empty-roster messaging, `/init` no-op). P0 bugs are surgical — don't expand scope. +**Why:** Four PRs (#637–#640) patched init iteratively without a unified design. Before writing more patches, the team needs to agree on the golden path. Proposal-first (per team decision 2026-02-21). +**Impact:** Blocks init-related code changes until Brady reviews the proposal. +- Kovash (REPL): Can call `parseCastResponse` + `createTeam` to wire up casting flow in shell dispatcher +- Verbal (Prompts): INIT_TEAM format is now the contract — coordinator prompt should emit this +- Hockney (Tests): cast.ts needs unit tests for parser edge cases, emoji mapping, file creation + +### 2026-03-02: REPL empty-roster gate — dual check pattern +**By:** Fenster (Core Dev) +**Date:** 2026-03-02 +**What:** REPL dispatch is now gated on *populated* roster, not just team.md existence. `hasRosterEntries()` in `coordinator.ts` checks for table data rows in the `## Members` section. Two layers: `handleDispatch` blocks with user guidance, `buildCoordinatorPrompt` injects refusal prompt. +**Why:** After `squad init`, team.md exists but is empty. Coordinator received a "route to agents" prompt with no agents listed, causing silent generic AI behavior. Users never got told to cast their team. +**Convention:** Post-init message references "Copilot session" (works in VS Code, github.com, and Copilot CLI). The `/init` slash command provides same guidance inside REPL. +**Impact:** All agents — if you modify the `## Members` table format in team.md templates, update `hasRosterEntries()` to match. -**What:** `StreamBridge` receives `StreamingEvent` objects via `handleEvent()` but does not register itself with `StreamingPipeline`. The wiring (`pipeline.onDelta(e => bridge.handleEvent(e))`) is the caller's responsibility. +### 2026-03-02: Connection promise dedup in SquadClient +**By:** Fenster (Core Dev) +**Date:** 2026-03-02 +**What:** `SquadClient.connect()` now uses a promise dedup pattern — concurrent callers share the same in-flight `connectPromise` instead of throwing "Connection already in progress". +**Why:** Eager warm-up and auto-cast both call `createSession()` → `connect()` at REPL startup, racing on the connection. The throw crashed auto-cast every time. +**Impact:** `packages/squad-sdk/src/adapter/client.ts` only. No API surface change. -**Why:** -1. **Testability:** The bridge can be tested with plain event objects — no pipeline instance needed. -2. **Flexibility:** The shell entry point controls which events reach the bridge (e.g., filtering by session, throttling for UI frame rate). -3. **Single responsibility:** The bridge translates events to callbacks; it doesn't manage subscriptions or lifecycle. +### 2026-03-01: CLI UI Polish PRD — Alpha Shipment Over Perfection +**By:** Keaton (Lead) +**Date:** 2026-03-01 +**Context:** Team image review identified 20+ UX issues ranging from P0 blockers to P3 future polish -**Impact:** Low. Pattern applies to all future bridges between the pipeline and UI layers (ink components, web sockets, etc.). +**What:** CLI UI polish follows pragmatic alpha shipment strategy: fix P0 blockers + P1 quick wins, defer grand redesign to post-alpha. 20 discrete issues created with clear priority tiers (P0/P1/P2/P3). -### 2026-02-21: Shell module test patterns — fixtures over mocks -**By:** Hockney (Tester) -**Date:** 2026-02-21 -**Re:** #248 +**Why:** Brady confirmed "alpha-level shipment acceptable — no grand redesign today." Team converged on 3 P0 blockers (blank screens, static spinner, missing alpha banner) that would embarrass us vs. 15+ polish items that can iterate post-ship. -**What:** Shell module tests use `test-fixtures/.squad/` with real files (team.md, routing.md, agent charters) instead of mocking fs calls. The `loadAgentCharter` and `buildCoordinatorPrompt` functions accept a `teamRoot` parameter that enables this pattern. +**Trade-off:** Shipping with known layout quirks (input positioning, responsive tables) rather than blocking on 1-2 week TUI refactor. Users expect alpha rough edges IF we warn them upfront. -**Why:** -1. Real file reads catch encoding issues, path resolution bugs, and parsing edge cases that mocks hide. -2. The `teamRoot` parameter was already designed into the API — tests should use it. -3. StreamBridge tests use callback spies (arrays capturing calls) rather than vi.fn() — simpler to assert on and read. +**Priority Rationale:** +- **P0 (must fix):** User-facing broken states — blank screens, no feedback, looks crashed +- **P1 (quick wins):** Accessibility (contrast), usability (copy clarity), visual hierarchy — high ROI, low effort +- **P2 (next sprint):** Layout architecture, responsive design — important but alpha-acceptable if missing +- **P3 (future):** Fixed bottom input, alt screen buffer, creative spinner — delightful but not blockers -**Impact:** Low. Establishes fixture patterns for future shell module tests. test-fixtures/.squad/ is now a shared test resource. +**Architectural Implications:** +1. **Quick win discovered:** App.tsx overrides ThinkingIndicator's native rotation with static hints (~5 line fix) +2. **Debt acknowledged:** 3 separate separator implementations need consolidation (P2 work) +3. **Layout strategy:** Ink's layout model fights bottom-anchored input. Alt screen buffer is the real solution (P3 deferred). +4. **Issue granularity:** 20 discrete issues vs. 1 monolithic "fix UI" epic — enables parallel work by Cheritto (11 issues), Kovash (4), Redfoot (2), Fenster (1), Marquez (1 review) -### 2026-02-21: Branch protection configuration -**By:** Kobayashi (Git & Release) -**Date:** 2026-02-21 -**Re:** #209 +**Success Gate:** "Brady says it doesn't embarrass us" — qualitative gate appropriate for alpha software. Quantitative gates: zero blank screens >500ms, contrast ≥4.5:1, spinner rotates every 3s. -**Main Branch Protection:** Require PR + passing build status checks. All changes to main require a PR (not direct push). PR cannot be merged until `build` check passes (CI/CD gate). Zero approving reviews required — allows team to use PR merge buttons freely (no blocking review workflow needed). Admins not exempted (same rules apply to everyone, strengthens process integrity). +**Impact:** +- **Team routing:** Clear ownership — Cheritto (TUI), Kovash (shell), Redfoot (design), Marquez (UX review) +- **Timeline transparency:** P0 (1-2 days) → P1 (2-3 days) → P2 (1 week) — alpha ship when P0+P1 done +- **Expectation management:** Out of Scope section explicitly lists grand redesign, advanced features, WCAG audit — prevents scope creep -**Insider Branch:** No protection — allows direct pushes. Insider releases are automation-driven; direct branch push is the automation's path. +### 2026-03-01: Cast confirmation required for freeform REPL casts +**By:** Fenster (Core Dev) +**Date:** 2026-03-01 +**Context:** P2 from Keaton's reliable-init-flow proposal -**Implementation:** Used GitHub API (REST v3): `PUT /repos/{owner}/{repo}/branches/main/protection` with required_status_checks + required_pull_request_reviews. `DELETE /repos/{owner}/{repo}/branches/insider/protection` confirmed no-op (no existing rule). +**What:** When a user types a freeform message in the REPL and the roster is empty, the cast proposal is shown and the user must confirm (y/yes) before team files are created. Auto-cast from .init-prompt and /init "prompt" skip confirmation since the user explicitly provided the prompt. -**Note:** Status check context name is `"build"` — must match the exact check name from CI workflow. If CI workflow renames the check, branch protection must be updated to match. +**Why:** Prevents garbage casts from vague or accidental first messages (e.g., "hello", "what can you do?"). Matches the squad.agent.md Init Mode pattern where confirmation is required before creating team files. -### 2026-02-21: Insider publish package scaffolds -**By:** Kobayashi (Git & Release) -**Date:** 2026-02-21 -**Re:** #215 -**PR:** #283 +**Pattern:** pendingCastConfirmation state in shell/index.ts. handleDispatch intercepts y/n at the top before normal routing. inalizeCast() is the shared helper for both auto-confirmed and user-confirmed paths. -**What:** Added minimal publishable entry points to `packages/squad-sdk/` and `packages/squad-cli/` so the insider publish workflow can produce valid npm packages. +### 2026-03-01: Expose setProcessing on ShellApi +**By:** Kovash (REPL Expert) +**Date:** 2026-03-01 +**Context:** Init auto-cast path bypassed App.tsx handleSubmit, so processing state was never set — spinner invisible during team casting. -**squad-sdk:** -- `src/index.ts`: exports `VERSION` constant (placeholder — full source migration comes later) -- `tsconfig.json`: extends root, outputs to `dist/` with declarations -- `package.json`: added `files`, `scripts.build` +**What:** ShellApi now exposes setProcessing(processing: boolean) so that any code path in index.ts that triggers async work outside of handleSubmit can properly bracket it with processing state. This enables ThinkingIndicator and InputPrompt spinner without duplicating React state management. -**squad-cli:** -- `src/cli.ts`: placeholder binary (`#!/usr/bin/env node`) -- `tsconfig.json`: extends root, outputs to `dist/` with declarations -- `package.json`: added `files`, `scripts.build`; `bin` already pointed to `./dist/cli.js` +**Rule:** Any new async dispatch path in index.ts that bypasses handleSubmit **must** call shellApi.setProcessing(true) before the async work and shellApi.setProcessing(false) in a inally block covering all exit paths. -**Root:** `build` script updated: `tsc && npm run build --workspaces --if-present` +**Files Changed:** +- packages/squad-cli/src/cli/shell/components/App.tsx — added setProcessing to ShellApi interface + wired in onReady +- packages/squad-cli/src/cli/shell/index.ts — added setProcessing calls in handleInitCast (entry, pendingCastConfirmation return, finally block) -**Why:** The insider publish pipeline triggers on push to `insider` branch but both workspace packages were empty scaffolds — no source, no build output, nothing to publish. This adds the minimum viable content so `npm publish` succeeds and the insider channel can be verified end-to-end. +### 2026-03-01T20:13:16Z: User directives — UI polish and shipping priorities +**By:** Brady (via Copilot) +**Date:** 2026-03-01 +Ampersands (&) are prohibited in user-facing documentation headings and body text, per Microsoft Style Guide. -**Constraints respected:** ESM-only, TypeScript strict mode, Node.js >=20, squad-cli depends on squad-sdk via version string, `files` lists ensure only `dist/` and `README.md` ship in the published package. +**Rule:** Use "and" instead. -**Impact:** Low. Does not migrate real source code — these are placeholders. Does not add tests for workspace packages (nothing to test yet). +**Why:** Microsoft Style Guide prioritizes clarity and professionalism. Ampersands feel informal and reduce accessibility. -### 2026-02-21: Distribution moves to npm for production -**By:** Rabin (Distribution) -**Date:** 2026-02-21 -**Re:** #216 +**Exceptions:** +- Brand names (AT&T, Barnes & Noble) +- UI element names matching exact product text +- Code samples and technical syntax +- Established product naming conventions -**What:** Squad packages (`@bradygaster/squad-sdk` and `@bradygaster/squad-cli`) are now distributed via npmjs.com. This supersedes the beta-era decision "Distribution is `npx github:bradygaster/squad` — never move to npmjs.com." +**Scope:** Applies to docs pages, README files, blog posts, community-facing content. Internal files (.squad/** memory files, decision docs, agent history) have flexibility. -**Why:** The project has matured from beta to production. npm is the standard distribution channel for Node.js packages. The insider publish (`0.6.0-alpha.0`) validated the pipeline. Production publish (`0.6.0`) is the natural next step. +**Reference:** https://learn.microsoft.com/en-us/style-guide/punctuation/ampersands -**Workflow:** Tag push `v*` on `main` triggers `.github/workflows/squad-publish.yml`. Auth is CI-only via `setup-node` + `NODE_AUTH_TOKEN`. No root `.npmrc` needed. No `--provenance` (private repo limitation). -**Impact:** Users install via `npm install @bradygaster/squad-cli` (or `npx @bradygaster/squad-cli`). The GitHub-native `npx github:bradygaster/squad` path may still work but is no longer the primary distribution channel. +--- -### 2026-02-21: Coordinator prompt structure — three routing modes -**By:** Verbal (Prompt Engineer) -**Date:** 2026-02-21 -**Re:** #241 +## Adoption & Community -**What:** The coordinator system prompt (`buildCoordinatorPrompt()`) uses a structured response format with three routing modes: -- `DIRECT:` — coordinator answers inline, no agent spawn -- `ROUTE:` + `TASK:` + `CONTEXT:` — single agent delegation -- `MULTI:` with bullet list — fan-out to multiple agents +### `.squad/` Directory Scope — Owner Directive +**By:** Brady (project owner, PR #326 review) +**Date:** 2026-03-10 -The parser (`parseCoordinatorResponse()`) extracts a `RoutingDecision` from the LLM response. Unrecognized formats fall back to `DIRECT` (safe default — never silently drops input). +**Directive:** The `.squad/` directory is **reserved for team state only** — roster, routing, decisions, agent histories, casting, and orchestration logs. Non-team data (adoption tracking, community metrics, reports) must NOT live in `.squad/`. Use `.github/` for GitHub platform integration or `docs/` for documentation artifacts. -**Why:** -1. **Structured output over free-form:** Keyword prefixes (`ROUTE:`, `DIRECT:`, `MULTI:`) are cheap to parse and reliable across model temperatures. No JSON parsing needed. -2. **Fallback-to-direct:** If the LLM doesn't follow the format, the response is surfaced to the user rather than lost. This prevents silent failures in the routing layer. -3. **Prompt composition from files:** team.md and routing.md are read at prompt-build time, not baked in. This means the coordinator adapts to team changes without code changes. +**Source:** [PR #326 comment](https://github.com/bradygaster/squad/pull/326#issuecomment-4029193833) -**Impact:** Low. Additive module. No changes to existing shell behavior. Future work will wire this into the readline loop and SDK session. -### 2026-02-21: CRLF normalization — single utility, applied at parser entry points -**By:** Fenster (Core Dev) -**Re:** #220, #221 (Epic #181) +--- -**What:** -- Created `src/utils/normalize-eol.ts` exporting `normalizeEol(content: string): string` — replaces `\r\n` and `\r` with `\n`. -- Applied as the first line of 8 parser functions across 6 files: markdown-migration.ts (3 parsers), routing.ts, charter-compiler.ts, skill-loader.ts, agent-doc.ts, doc-sync.ts. +### No Individual Repo Listing Without Consent — Owner Directive +**By:** Brady (project owner, PR #326 review) +**Date:** 2026-03-10 -**Why:** -On Windows with `core.autocrlf=true`, file reads return `\r\n` line endings. Every parser that calls `.split('\n')` would leave stray `\r` characters in parsed values — breaking regex matches, table parsing, and string comparisons. Normalizing at the parser entry point is the minimal defensive guard: one line per function, no behavioral change on LF-only systems. +**Directive:** Growth metrics must report **aggregate numbers only** (e.g., "78+ repositories found via GitHub code search") — never name or link to individual community repos without explicit opt-in consent. The monitoring script and GitHub Action concepts are approved, but any public showcase or tracking list that identifies specific repos is blocked until a community consent plan exists. -**Pattern:** -Always normalize at the *entry* of the parsing function, not at the file-read callsite. This ensures parsers are self-contained and safe regardless of how content arrives (file read, API response, test fixture). +**Source:** [PR #326 comment](https://github.com/bradygaster/squad/pull/326#issuecomment-4029222967) -**Impact:** -Low. Additive-only. No test changes needed. Existing tests pass because test strings are already LF-only. The guard is transparent for LF inputs. +--- -### 2026-02-21: CLI entry point split — src/index.ts is a pure barrel -**By:** Edie (TypeScript Engineer) -**Re:** #187 +### Adoption Tracking — Opt-In Architecture +**By:** Flight (implementing Brady's directives above) +**Date:** 2026-03-09 -**What:** -- `src/index.ts` is now a pure re-export barrel with ZERO side effects. No `main()`, no `process.exit()`, no argument parsing. -- `src/cli-entry.ts` contains the full `main()` function and all CLI routing logic. -- `VERSION` is exported from `index.ts` (public API constant); `cli-entry.ts` imports it. -- `SquadError` is now explicitly exported from the barrel. +Privacy-first adoption monitoring using a three-tier system: -**Why:** -Anyone importing `@bradygaster/squad` as a library (e.g., SquadUI) was triggering CLI argument parsing and `process.exit()` on import. This poisoned the library entry point. The split makes `dist/index.js` safe for library consumption while `dist/cli-entry.js` remains the CLI binary. +**Tier 1: Aggregate monitoring (SHIPPED)** +- GitHub Action + monitoring script collect metrics +- Reports moved to `.github/adoption/reports/{YYYY-MM-DD}.md` +- Reports show ONLY aggregate numbers (no individual repo names): + - "78+ repositories found via code search" + - Total stars/forks across all discovered repos + - npm weekly downloads -**Impact:** -Low. Two files changed. Build passes (tsc strict), all 1683 tests pass. No changes to package.json `bin` or `main`. +**Tier 2: Opt-in registry (DESIGN NEXT)** +- Create `SHOWCASE.md` in repo root with submission instructions +- Opted-in projects listed in `.github/adoption/registry.json` +- Monitoring script reads registry, reports only on opted-in repos -**Future:** -When source migrates to `packages/squad-cli/`, the CLI entry point moves with it. The barrel (`packages/squad-sdk/src/index.ts`) stays side-effect-free. +**Tier 3: Public showcase (LAUNCH LATER)** +- `docs/community/built-with-squad.md` shows opted-in projects only +- README link added when ≥5 opted-in projects exist -### 2026-02-21: Process.exit() refactor — library-safe CLI functions -**By:** Kujan (SDK Expert) -**Re:** #189 +**Rationale:** +- Aggregate metrics safe (public code search results) +- Individual projects only listed with explicit owner consent +- Prevents surprise listings, respects privacy +- Incremental rollout maintains team capacity + +**Implementation (PR #326):** +- ✅ Moved `.squad/adoption/` → `.github/adoption/` +- ✅ Stripped tracking.md to aggregate-only metrics +- ✅ Removed individual repo names, URLs, metadata +- ✅ Updated adoption-report.yml and scripts/adoption-monitor.mjs +- ✅ Removed "Built with Squad" showcase link from README (Tier 2 feature) -**What:** -- `fatal()` now throws `SquadError` instead of calling `process.exit(1)`. -- `src/index.ts` is a pure barrel export with zero side effects (no `main()`, no `process.exit()`). -- `src/cli-entry.ts` is the sole CLI entry point — it catches `SquadError` and calls `process.exit(1)`. -- `runWatch()` resolves its Promise on SIGINT/SIGTERM instead of `process.exit(0)`. -- `runShell()` closes readline on SIGINT instead of `process.exit(0)`. -- `SquadError` class is exported from the public API. +--- -**Why:** -SquadUI (VS Code extension) imports CLI functions as a library. `process.exit()` kills the entire VS Code extension host. All library-consumable functions must throw errors or return results, never call `process.exit()`. Only the CLI entry point (the thin presentation layer) may call `process.exit()`. +### Adoption Tracking Location & Privacy +**By:** EECOM +**Date:** 2026-03-10 -**Pattern established:** -- Library functions: throw `SquadError` or return result objects -- CLI entry point: catches errors, formats output, calls `process.exit()` -- Library consumers: catch `SquadError` for structured error handling +Implementation decision confirming Tier 1 adoption tracking changes. -**Impact:** -Medium. Changes error handling contract for all functions that used `fatal()`. Backwards-compatible for CLI users (same behavior). Library consumers now get catchable errors instead of process termination. +**What:** Move adoption tracking from `.squad/adoption/` to `.github/adoption/` -### 2026-02-21: User directive — docs as you go -**By:** bradygaster (via Copilot) -**What:** Doc and blog as you go during SquadUI integration work. Doesn't have to be perfect — a proper docs pass comes later — but keep docs updated incrementally. -**Why:** User request — captured for team memory +**Why:** +1. **GitHub integration:** `.github/adoption/` aligns with GitHub convention (workflows, CODEOWNERS, issue templates) +2. **Privacy-first:** Aggregate metrics only; defer individual repo showcase to Tier 2 (opt-in) +3. **Clear separation:** `.squad/` = team internal; `.github/` = GitHub platform integration +4. **Future-proof:** When Tier 2 opt-in launches, `.github/adoption/` is the natural home -### 2026-02-23: Aspire Dashboard Scenario Documentation -**By:** Saul (Aspire & Observability) -**Date:** 2026-02-23 -**Status:** COMPLETED +**Impact:** +- GitHub Action reports write to `.github/adoption/reports/{YYYY-MM-DD}.md` +- No individual repo information published until Tier 2 +- Monitoring continues collecting aggregate metrics via public APIs +- Team sees trends without publishing sensitive adoption data -### 2026-02-23: Make sendAndWait timeout configurable -**Author:** Cheritto (TUI Engineer) -**Date:** 2026-02-23 -**Status:** Implemented -**Issue:** #325 -**PR:** #347 +--- + +### Append-Only File Governance +**By:** Flight +**Date:** 2026-03-09 -Added `TIMEOUTS.SESSION_RESPONSE_MS` to `packages/squad-sdk/src/runtime/constants.ts` — default 600,000ms (10 minutes), overridable via `SQUAD_SESSION_TIMEOUT_MS` env var. Updated `packages/squad-cli/src/cli/shell/index.ts` to use the constant instead of hard-coded 120,000ms timeout. All 41 streaming tests pass. Users can now set `SQUAD_SESSION_TIMEOUT_MS=900000` for 15-minute timeouts. +Feature branches must never modify append-only team state files except to append new content. -### 2026-02-23: E2E Test Coverage Expansion -**By:** Breedan (E2E Test Engineer) -**Date:** 2026-02-23 -**Status:** Accepted -**PR:** #348 (closes #326) +**What:** If a PR diff shows deletions in `.squad/agents/*/history.md` or `.squad/decisions.md`, the PR is blocked until deletions are reverted. -Added 14 new Gherkin scenarios across 6 new feature files, bringing total to 21 acceptance scenarios + 6 UX gate tests (27 total). New harness capabilities: `cwd` option on `spawnWithArgs()`, absolute path resolution, `mkdtempSync` temp dir creation, negative assertion step. Coverage areas: init-command, status-extended, doctor-extended, help-comprehensive, error-paths, exit-codes. +**Why:** Session state drift causes agents to reset append-only files to stale branch state, destroying team knowledge. PR #326 deleted entire history files and trimmed ~75 lines of decisions, causing data loss. -### 2026-02-23: Hostile QA Bug Catalog — Issue #327 -**By:** Waingro -**Date:** 2026-02-23 -**Status:** Reported — awaiting triage +**Enforcement:** Code review + future CI check candidate. -Found 2 P1/P2 bugs: (1) `--version` output omits "squad" prefix (P1, location: packages/squad-cli/src/cli-entry.ts:48), (2) Empty/whitespace args launch interactive shell in non-TTY (P2, location: packages/squad-cli/src/cli-entry.ts:102). Added 32 hostile QA Gherkin scenarios across 7 feature files and 80+ adversarial string corpus. All corrupt config scenarios handled gracefully; unicode edge cases pass cleanly. +--- -### 2026-02-23: Accessibility Audit — Squad CLI -**By:** Nate (Accessibility Reviewer) -**Date:** 2026-02-23 -**Issue:** #328 — Accessibility Audit -**Verdict:** CONDITIONAL PASS +### Documentation Style: No Ampersands +**By:** PAO +**Date:** 2026-03-09 -Summary: 2 passes (Keyboard Navigation ✅, Help Text ✅), 2 failures (Color Contrast & NO_COLOR ❌, Error Guidance ❌). Priority fixes: (1) P1 — Respect `NO_COLOR` in terminal.ts and gate ANSI codes in output.ts. (2) P1 — Add remediation hints to missing team.md error (lifecycle.ts) and charter-not-found error (spawn.ts). Nice-to-have: P2 Wire Tab autocomplete, add remediation to SDK-not-connected errors; P3 Add Escape key, include keyboard shortcuts in /help output. +Ampersands (&) are prohibited in user-facing documentation headings and body text, per Microsoft Style Guide. -**What:** Created `docs/scenarios/aspire-dashboard.md` — a single, focused scenario doc for using Squad with the Aspire dashboard. Covers what Aspire is, how to launch it via Docker, how to connect Squad SDK to it, what traces/metrics appear in the UI, troubleshooting, and pro tips. Updated `docs/build.js` to include the new scenario in the explicit ordering (added to SECTION_ORDER for scenarios). +**Rule:** Use "and" instead. -**Tone:** Action-oriented, welcoming, prompt-first — matches existing Squad docs (see `solo-dev.md` for reference style). +**Why:** Microsoft Style Guide prioritizes clarity and professionalism. Ampersands feel informal and reduce accessibility. -**Key Sections:** -1. What Is Aspire (standalone OTel dashboard, not .NET-specific) -2. Launch the container (docker run command + squad aspire convenience) -3. Connect Squad to Aspire (CLI env var + SDK programmatic) -4. Dashboard content (traces, metrics, resources with concrete examples) -5. Example workflow (start, run, observe) -6. Troubleshooting (no traces, slow dashboard, connection refused) -7. Pro tips (metric export frequency, custom service names, batch sizing) +**Exceptions:** +- Brand names (AT&T, Barnes & Noble) +- UI element names matching exact product text +- Code samples and technical syntax +- Established product naming conventions -**Implementation Details:** Reviewed `packages/squad-sdk/src/runtime/otel-*.ts` and `test/aspire-integration.test.ts` for accurate coverage of OTel integration. Aspire ports (18888 UI, 4317 OTLP gRPC), UNSECURED_ALLOW_ANONYMOUS mode, and gRPC-only protocol constraints all documented accurately. +**Scope:** Applies to docs pages, README files, blog posts, community-facing content. Internal files (.squad/** memory files, decision docs, agent history) have flexibility. -**Verification:** Docs build verified — `npm run docs:build` generates 39 pages with `aspire-dashboard.html` correctly included in scenarios nav. Navigation in all scenario pages (e.g., solo-dev.html) correctly links to the new scenario. +**Reference:** https://learn.microsoft.com/en-us/style-guide/punctuation/ampersands -**Why:** Brady requested a "try it now" guide for Aspire integration — single doc that covers launch AND connection, written for immediate action. This replaces scattered documentation and provides a clean entry point for developers wanting to observe Squad telemetry in real time. +--- -### 2026-02-22: SDK/CLI File Split Plan — Definitive Mapping -**By:** Keaton (Lead) -**Status:** Decision -**Scope:** Monorepo restructure — move `src/` into `packages/squad-sdk/` and `packages/squad-cli/` +## Sprint Directives -**Overview:** All 114 `.ts` files in root `src/` must be split between two workspace packages. The dependency direction is **one-way: CLI → SDK**. No SDK module imports from CLI. This is clean and the split is mechanical. +### Secret handling — agents must never persist secrets +**By:** RETRO (formerly Baer), v0.8.24 +**What:** Agents must NEVER write secrets, API keys, tokens, or credentials into conversational history, commit messages, logs, or any persisted file. Acknowledge receipt without echoing values. +**Why:** Secrets in logs or history are a security incident waiting to happen. -**SDK Package (`packages/squad-sdk/src/`):** -- **Directories:** adapter, agents, build, casting, client, config, coordinator, hooks, marketplace, ralph, runtime, sharing, skills, tools, utils (15 total) -- **Standalone files:** index.ts, resolution.ts, parsers.ts, types.ts -- **Exports:** Expanded to 18 subpath exports matching directory structure -- **Dependencies:** `@github/copilot-sdk` only (zero UI deps) -**CLI Package (`packages/squad-cli/src/`):** -- **Directories:** cli/ (all CLI submodules: core/, commands/, shell/, components/) -- **Standalone files:** cli-entry.ts (becomes `bin.squad` target) -- **Dependencies:** `@bradygaster/squad-sdk`, ink, react, esbuild +--- -**Root Package:** -- **Role:** Workspace orchestrator only (not publishable) -- **Preserved:** `package.json` (workspace def), tsconfig.json (base config), vitest.config.ts, test/, test-fixtures/, templates/, docs/, .squad/ -- **Removed:** main, types, bin, runtime dependencies +## Squad Ecosystem Boundaries & Content Governance -**Key Call — SDK Barrel Cleanup:** Remove CLI utilities (`success`, `error`, `warn`, `fatal`, etc.) from SDK exports. CLI re-exports in `src/index.ts` were a mistake — those are CLI implementation details, not SDK API. This is a breaking change that's correct and intentional. +### Squad Docs vs Squad IRL Boundary (consolidated) +**By:** PAO (via Copilot), Flight +**Date:** 2026-03-10 +**Status:** Active pattern for all documentation PRs -**Import Rewriting Rules:** -- CLI → SDK relative imports become package imports (`from '../config/'` → `from '@bradygaster/squad-sdk/config'`) -- Intra-SDK and intra-CLI imports stay relative (unchanged) +**Litmus test:** If Squad doesn't ship the code or configuration, the documentation belongs in Squad IRL, not the Squad framework docs. -### 2026-02-22: CharterCompiler reuses parseCharterMarkdown — no duplicate parsing +**Categories:** -**By:** Edie +1. **Squad docs** — Features Squad ships (routing, charters, reviewer protocol, config, behavior) +2. **Squad IRL** — Infrastructure around Squad (webhooks, deployment patterns, logging, external tools, operational patterns) +3. **Gray area:** Platform features (GitHub Issue Templates) → Squad docs if framed as "how to configure X for Squad" -**What:** `CharterCompiler.compile()` delegates to the existing `parseCharterMarkdown()` function from `charter-compiler.ts` rather than implementing its own markdown parser. The legacy class is a thin filesystem wrapper around the already-tested parsing logic. +**Examples applied (PR #331):** -**Why:** Single source of truth for charter parsing. The `parseCharterMarkdown` function already handles all `## Identity` and `## Model` field extraction with tested regex patterns. Duplicating that logic would create drift risk. +| Document | Decision | Reason | +|----------|----------|--------| +| ralph-operations.md | DELETE → IRL | Infrastructure (deployment, logging) around Squad, not Squad itself | +| proactive-communication.md | DELETE → IRL | External tools (Teams, WorkIQ) configured by community, not built into Squad | +| issue-templates.md | KEEP, reframe | GitHub platform feature; clarify scope: "a GitHub feature configured for Squad" | +| reviewer-protocol.md (Trust Levels) | KEEP | Documents user choice spectrum within Squad's existing review system | -### 2026-02-22: AgentSessionManager uses optional EventBus injection +**Enforcement:** Code review + reframe pattern ("GitHub provides X. Here's how to configure it for Squad's needs."). Mark suspicious deletions for restore (append-only governance). -**By:** Edie +**Future use:** Apply this pattern to all documentation PRs to maintain clean boundaries. -**What:** `AgentSessionManager` constructor accepts an optional `EventBus` parameter. When present, `spawn()` emits `session.created` and `destroy()` emits `session.destroyed`. When absent, the manager works silently (no events). -**Why:** Keeps the manager testable without requiring a full event bus setup. Coordinator can wire the bus when available; unit tests can omit it. +--- -### 2026-02-22: Ink Shell Wiring — ShellApi callback pattern +### Content Triage Skill — External Content Integration +**By:** Flight +**Date:** 2026-03-10 +**Status:** Skill created at `.squad/skills/content-triage/SKILL.md` -**By:** Fenster +**Pattern:** External content (blog posts, sample repos, videos, conference talks) that helps Squad adoption must be triaged using the "Squad Ships It" boundary heuristic before incorporation. -**Date:** 2026-02-22 +**Workflow:** +1. Triggered by `content-triage` label or external content reference in issue +2. Flight performs boundary analysis +3. Sub-issues generated for Squad-ownable content extraction (PAO responsibility) +4. FIDO verifies docs-test sync on extracted content +5. Scribe manages IRL references in `.github/irl/references.yml` (YAML schema) -**Status:** Implemented +**Label convention:** `content:blog`, `content:sample`, `content:video`, `content:talk` -**Context:** The shell needed to move from a readline echo loop to an Ink-based UI using the three existing components (AgentPanel, MessageStream, InputPrompt). The key challenge was connecting the StreamBridge (which pushes events from the streaming pipeline) into React component state. +**Why:** Pattern from PR #331 (Tamir Dresher blog) shows parallel extraction of Squad-ownable patterns (scenario guides, reviewer protocol) and infrastructure patterns (Ralph ops, proactive comms). Without clear boundary, teams pollute Squad docs with operational content or miss valuable patterns that should be generalized. -**Decision:** **ShellApi callback pattern:** The `App` component accepts an `onReady` prop that fires once on mount, delivering a `ShellApi` object with three methods: `addMessage`, `setStreamingContent`, `refreshAgents`. The host (`runShell()`) captures this API and wires it to StreamBridge callbacks. +**Impact:** Enables community content to accelerate Squad adoption without polluting core docs. Flight's boundary analysis becomes reusable decision framework. Prevents scope creep as adoption grows. -This keeps the Ink component decoupled from StreamBridge internals — the component doesn't import or know about the bridge. The host is the only place where both meet. -**`React.createElement` in index.ts:** Rather than renaming `index.ts` to `index.tsx` (which would ripple through exports maps and imports), `runShell()` uses `React.createElement(App, props)` directly. This keeps the file extension stable. +--- -**Streaming content accumulation:** StreamBridge's `onContent` callback delivers deltas. The host maintains a `streamBuffers` Map to accumulate content per agent and pushes the full accumulated string to `setStreamingContent`. On `onComplete`, the buffer is cleared and the final message is added. +### PR #331 Quality Gate — Test Assertion Sync +**By:** FIDO (Quality Owner) +**Date:** 2026-03-10 +**Status:** 🟢 CLEARED (test fix applied, commit 6599db6) -**Consequences:** StreamBridge is ready for coordinator wiring — call `_bridge.handleEvent(event)` when the coordinator emits streaming events. Direct agent messages and coordinator routing show placeholders until coordinator integration (Phase 3). All existing exports from `shell/index.ts` are preserved. New exports: `App`, `ShellApi`, `AppProps`. +**What was blocked:** Merge blocked on stale test assertions in `test/docs-build.test.ts`. -### 2026-02-22: Runtime EventBus as canonical bus for orchestration classes +**Critical violations resolved:** +1. `EXPECTED_SCENARIOS` array stale (7 vs 25 disk files) — ✅ Updated to 25 entries +2. `EXPECTED_FEATURES` constant undefined (32 feature files) — ✅ Created array with 32 entries +3. Test assertion incomplete — ✅ Updated to validate features section -**By:** Fortier +**Why this matters:** Stale assertions that don't reflect filesystem state cause silent test skips. Regression: If someone deletes a scenario file, the test won't catch it. CI passing doesn't guarantee test coverage — only that the test didn't crash. -**Date:** 2026-02-22 +**Lessons:** +- Test arrays must be refreshed when filesystem content changes +- Incomplete commits break the test-reality sync contract +- FIDO's charter: When adding test count assertions, must keep in sync with disk state -**Scope:** Coordinator, Ralph, and future orchestration components +**Outcome:** Test suite: 6/6 passing. Assertions synced to filesystem. No regression risk from stale assertions. -**Decision:** The `runtime/event-bus.ts` (colon-notation: `session:created`, `subscribe()` API, built-in error isolation via `executeHandler()`) is the canonical EventBus for all orchestration classes. The `client/event-bus.ts` (dot-notation: `session.created`, `on()` API) remains for backward-compat but should not be used in new code. -**Rationale:** -- Runtime EventBus has proper error isolation — one handler failure doesn't crash others -- SquadCoordinator (M3-1) tests already use RuntimeEventBus -- Consistent API surface (`subscribe`/`subscribeAll`/`unsubscribe`) is cleaner than `on`/`onAny` -- Event type strings use colon-notation which avoids ambiguity with property access patterns +--- -**Impact:** -- Coordinator and RalphMonitor now import from `../runtime/event-bus.js` -- All new EventBus consumers should follow this pattern -- Client EventBus remains exported for external consumers +### Communication Patterns and PR Trust Models +**By:** PAO +**Date:** 2026-03-10 +**Status:** Documented in features/reviewer-protocol.md (trust levels section) and scenarios/proactive-communication.md (infrastructure pattern) -### 2026-02-22: Runtime Module Test Patterns +**Decision:** Document emerging patterns in real Squad usage: proactive communication loops and PR review trust spectrum. -**By:** Hockney (Tester) +**Components:** -**Date:** 2026-02-22 +1. **Proactive communication patterns** — Outbound notifications (Teams webhooks), inbound scanning (Teams/email for work items), two-way feedback loop connecting external sources to Squad workflow -**Status:** Adopted +2. **PR trust levels spectrum:** + - **Full review** (default for team repos) — All PRs require human review + - **Selective review** (personal projects with patterns) — Domain-expert or routine PRs can auto-merge + - **Self-managing** (solo personal repos only) — PRs auto-merge; Ralph's work monitoring provides retroactive visibility -**Context:** Writing proactive tests for runtime modules (CharterCompiler, AgentSessionManager, Coordinator, RalphMonitor) being built in parallel by multiple team members. +**Why:** Ralph 24/7 autonomous deployment creates an awareness gap — how does the human stay informed? Outbound notifications solve visibility. Inbound scanning solves "work lives in multiple places." Trust levels let users tune oversight to their context (full review for team repos, selective for personal projects, self-managing for solo work only). -**Decisions:** +**Important caveat:** Self-managing ≠ unmonitored; Ralph's work monitoring and notifications provide retroactive visibility. -1. **Two EventBus APIs require different mocks.** The client EventBus uses `on()`/`emit()` while the runtime EventBus uses `subscribe()`/`emit()`. Tests must use the correct mock depending on which bus the module under test consumes. AgentSessionManager uses the client bus (`on`); Coordinator uses the runtime bus (`subscribe`). +**Anti-spam expectations:** Don't spam yourself outbound (notification fatigue), don't spam GitHub inbound (volume controls). -2. **CharterCompiler tests use real test-fixtures.** Instead of mocking the filesystem, we read from `test-fixtures/.squad/agents/` for `compile()` and `compileAll()` tests. This gives integration-level confidence. Only `parseCharterMarkdown` uses inline string fixtures for unit isolation. -3. **Coordinator routing priority is: direct > @mention > team keyword > default.** Tests explicitly verify this ordering. Any change to routing logic must preserve this priority chain. +--- -4. **RalphMonitor tests are future-proof stubs.** Since Ralph is mostly TODO stubs, tests validate current behavior (empty arrays, no-throw lifecycle) and will automatically exercise real logic once implemented — no test changes needed. +### Remote Squad Access — Phased Rollout (Proposed) +**By:** Flight +**Date:** 2026-03-10 +**Status:** Proposed — awaits proposal document in `docs/proposals/remote-squad-access.md` -**Impact:** -- 105 new tests across 4 files, all passing -- Test count: 1727 → 1832 across 61 files -- No circular dependencies verified (clean DAG) +**Context:** Squad currently requires a local clone to answer questions. Users want remote access from mobile, browser, or different machine without checking out repo. -**Migration Order:** -1. **Phase 1 (SDK first):** Copy 15 dirs + 4 files, update exports, build in isolation -2. **Phase 2 (CLI second):** Copy cli/ + cli-entry.ts, rewrite imports to SDK packages, build against SDK -3. **Phase 3 (root cleanup):** Delete root src/, update test imports, finalize structure +**Phases:** -**Templates:** Copy into CLI package (Option A — self-contained packages). +**Phase 1: GitHub Discussions Bot (Ship First)** +- Surface: GitHub Discussions +- Trigger: `/squad` command or `@squad` mention +- Context: GitHub Actions workflow checks out repo → full `.squad/` state +- Response: Bot replies to thread +- Feasibility: 1 day +- Why first: Easy to build, zero hosting, respects repo privacy, async Q&A, immediately useful -**Why:** One-way dependency graphs enable independent package evolution. SDK stays pure library; CLI stays thin consumer. Future features only touch the relevant package. +**Phase 2: GitHub Copilot Extension (High Value)** +- Surface: GitHub Copilot chat (VS Code, CLI, web, mobile) +- Trigger: `/squad ask {question}` in any Copilot client +- Context: Extension fetches `.squad/` files via GitHub API (no clone) +- Response: Answer inline in Copilot +- Feasibility: 1 week +- Why second: Works everywhere Copilot exists, instant response, natural UX -### 2026-02-22: Version Alignment — 0.7.0 stubs → 0.8.0 real packages -**By:** Kobayashi (Git & Release) -**Status:** APPROVED & EXECUTED +**Phase 3: Slack/Teams Bot (Enterprise Value)** +- Surface: Slack or Teams channel +- Trigger: `@squad` mention in channel +- Context: Webhook fetches `.squad/` via GitHub API +- Response: Bot replies in thread +- Feasibility: 2 weeks +- Why third: Enterprise teams live in chat; high value for companies using Squad -**Context:** -- **0.7.0 npm stubs:** Placeholder packages published on npmjs.com (no real code) -- **Goal:** Publish real, working code under new version +**Constraint:** Squad's intelligence lives in `.squad/` (roster, routing, decisions, histories). Any remote solution must solve context access. GitHub Actions workflows provide checkout for free. Copilot Extension and chat bots use GitHub API to fetch files. -**Decision: Bump to 0.8.0** -- **Rationale:** Clear break from stubs (0.7.0 is placeholder; 0.8.0 is functional code). Pre-1.0 signal appropriate for alpha software. +**Implementation:** Before Phase 1 execution, write proposal document. New CLI command: `squad answer --context discussions --question "..."`. New workflow: `.github/workflows/squad-answer.yml`. -**Changes Executed:** -1. SDK `package.json`: version `0.7.0` → `0.8.0` -2. CLI `package.json`: version `0.7.0` → `0.8.0`, SDK dependency locked to `@bradygaster/squad-sdk@0.8.0` -3. SDK `src/index.ts`: `VERSION` export `0.7.0` → `0.8.0` -4. Root `package.json`: added `"private": true` (prevent accidental npm publish of workspace coordinator) +**Privacy:** All approaches respect repo visibility or require authentication. Most teams want private by default. -**Verification:** ✅ All version strings aligned, CLI dependency on SDK pinned, root marked private. +### Test assertion discipline — mandatory +**By:** FIDO (formerly Hockney), v0.8.24 +**What:** All code agents must update tests when changing APIs. FIDO has PR blocking authority on quality grounds. +**Why:** APIs changed without test updates caused CI failures and blocked external contributors. -### 2026-02-22: Defer test import migration until root src/ removal -**By:** Hockney (Tester) -**Status:** Proposed +### Docs-test sync — mandatory +**By:** PAO (formerly McManus), v0.8.24 +**What:** New docs pages require corresponding test assertion updates in the same commit. +**Why:** Stale test assertions block CI and frustrate contributors. -**Context:** After SDK/CLI workspace split, all 56 test files still import from root `../src/`. Build and all 1719 tests pass cleanly because root `src/` still exists. +### Contributor recognition — every release +**By:** PAO, v0.8.24 +**What:** Each release includes an update to the Contributors Guide page. +**Why:** No contribution goes unappreciated. -**Decision:** Defer migrating test imports from `../src/` to `@bradygaster/squad-sdk` / `@bradygaster/squad-cli` until root `src/` is actually removed. +### API-test sync cross-check +**By:** FIDO + Booster, v0.8.24 +**What:** Booster adds CI check for stale test assertions. FIDO enforces via PR review. +**Why:** Prevents the pattern of APIs changing without test updates. -**Rationale:** -1. **Exports map gap:** SDK exposes 18 subpath exports; tests import ~40+ distinct deep internal paths not in exports map -2. **CLI no exports:** cli/shell/ tests have no package-level export path to migrate to -3. **Barrel divergence:** Root `src/index.ts` still exports CLI functions SDK package correctly does not -4. **Risk/reward:** 150+ import lines for zero functional benefit while root `src/` exists is pure risk +### Doc-impact review — every PR +**By:** PAO, v0.8.25 +**What:** Every PR must be evaluated for documentation impact. PAO reviews PRs for missing or outdated docs. +**Why:** Code changes without doc updates lead to stale guides and confused users. -**When to revisit:** When root `src/` is deleted (blocker at that point). Options: expand exports maps, add vitest resolve.alias, or move tests into workspace packages. -### 2026-02-22: Build System Migration — tsconfig + package.json -**By:** Edie (TypeScript Engineer) -**Status:** Decision -**Scope:** Monorepo build configuration for SDK/CLI workspace packages +--- -**What Changed:** -1. **Root tsconfig.json:** Base config only, shared compiler options, `files: []` (compiles nothing), project references to both packages -2. **SDK tsconfig.json:** Extends root, `composite: true`, declarations + maps, no JSX -3. **CLI tsconfig.json:** Extends root, `composite: true`, `jsx: "react-jsx"`, references SDK package -4. **Root package.json:** `private: true`, workspace orchestrator, stripped main/types/bin/runtime deps -5. **SDK package.json:** 18 subpath exports, `@github/copilot-sdk` as direct dependency -6. **CLI package.json:** `bin.squad` → `./dist/cli-entry.js`, ink/react runtime deps, templates in files array +## Release v0.8.24 -**Why `composite: true`:** TypeScript project references require this. Without it, cross-package type information fails to resolve. +### CLI Packaging Smoke Test: Release Gate Decision +**By:** FIDO, v0.8.24 +**Date:** 2026-03-08 -**Build Order:** `npm run build --workspaces` builds SDK first (no deps), then CLI (depends on SDK). npm respects topological order automatically. +The CLI packaging smoke test is APPROVED as the quality gate for npm releases. -**Verified:** Both packages compile with zero errors. All dist artifacts (`.js`, `.d.ts`, `.d.ts.map`) emitted correctly. +**What:** +1. Text box preference: bottom-aligned, squared off (like Copilot CLI / Claude CLI) — future work, not today +2. Alpha-level shipment acceptable for now — no grand UI redesign today +3. CLI must show "experimental, please file issues" banner +4. Spinner/wait messages should rotate every ~3 seconds — use codebase facts, project trivia, vulnerability info, or creative "-ing" words. Never just spin silently. +5. Use wait time to inform or entertain users -### 2026-02-22: Subpath exports in @bradygaster/squad-sdk -**By:** Edie (TypeScript Engineer) -**Issue:** #227 +**Why:** User request — captured for team memory and crash recovery -**What:** `packages/squad-sdk/package.json` declares 18 subpath exports (`.`, `./parsers`, `./types`, and 15 module paths). Each uses types-first condition ordering. +### 2026-03-01T20:16:00Z: User directive — CLI timeout too low +**By:** Brady (via Copilot) +**Date:** 2026-03-01 -**Why:** Enables tree-shaking and focused imports. Consumers can `import { … } from '@bradygaster/squad-sdk/config'` instead of pulling the entire barrel. Type-only consumers can import from `./types` with zero runtime cost. +**What:** The CLI timeout is set too low — Brady tried using Squad CLI in this repo and it didn't work well. Timeout needs to be increased. Not urgent but should be captured as a CLI improvement opportunity. -**Constraints:** -- Every subpath must have a corresponding source barrel (`src/.ts` or `src//index.ts`) -- `"types"` condition must appear before `"import"` — Node.js evaluates top-to-bottom -- ESM-only: no `"require"` condition per team decision -- Adding a new subpath requires both source barrel and exports entry; removing one without the other breaks resolution +**Why:** User request — captured for team memory and PRD inclusion -### 2026-02-22: Barrel file conventions for parsers and types +### 2026-03-01: Multi-Squad Storage & Resolution Design +**By:** Keaton (Lead) +**What:** +- New directory structure: ~/.config/squad/squads/{name}/.squad/ with ~/.config/squad/config.json for registry +- Keep +esolveGlobalSquadPath() unchanged; add +esolveNamedSquadPath(name?: string) and listPersonalSquads() on top +- Auto-migration: existing single personal squad moves to squads/default/ on first run +- Resolution priority: explicit (CLI flag) > project config > env var > git remote mapping > path mapping > default +- Global config.json schema: { version, defaultSquad, squads, mappings } + +**Why:** +- squads/ container avoids collisions with existing files at global root +- Backward-compatible: legacy layout detected and auto-migrated; existing code continues to work +- Clean separation: global config lives alongside squads, not inside any one squad +- Resolution chain enables flexible mapping without breaking existing workflows + +### 2026-03-01: Multi-Squad SDK Functions **By:** Kujan (SDK Expert) -**Issues:** #225, #226 (Epic #181) +**What:** +- New SDK exports: +esolveNamedSquadPath(), listSquads(), createSquad(), deleteSquad(), switchSquad(), +esolveSquadForProject() +- New type: SquadEntry { name, path, isDefault, createdAt } +- squads.json registry (separate file, not config.json) with squad metadata and mappings +- SquadDirConfig v2 addition: optional personalSquad?: string field (v1 configs unaffected) +- Consult mode updated: setupConsultMode(options?: { squad?: string }) with explicit selection or auto-resolution + +**Why:** +- Lazy migration with fallback chain ensures zero breaking changes to existing users +- Separate squads.json is single source of truth for routing; keeps project config focused +- Version handling allows incremental adoption; v1 configs work unchanged +- SDK resolution functions can be called from CLI and library code without duplication +### 2026-03-01: Multi-Squad CLI Commands & REPL +**By:** Kovash (REPL) **What:** -- `src/parsers.ts` re-exports all parser functions AND their types using `export { ... } from` and `export type { ... } from` -- `src/types.ts` re-exports ONLY types using `export type { ... } from` exclusively — guaranteed zero runtime imports -- Both follow the existing ESM barrel pattern from `src/index.ts` +- New commands: squad list, squad create , squad switch , squad delete +- Modified commands: squad consult --squad=, squad extract --squad=, squad init --global --name= +- Interactive picker for squad selection: arrow keys (↑/↓), Enter to confirm, Ctrl+C to cancel +- REPL integration: /squad and /squads slash commands with riggerSquadReload signal +- .active file stores current active squad name (plain text) +- Status command enhanced to show active squad and squad list **Why:** -1. **Consumer convenience:** SquadUI and others can import parsers or types from a single module instead of reaching into internal paths -2. **Separation of concerns:** `types.ts` is safe for type-only contexts (declaration files). `parsers.ts` includes runtime functions for consumers that need them -3. **No existing modifications:** Both are additive-only new files — zero risk to existing behavior +- Picker only shows when needed (multiple squads) and TTY available; non-TTY gracefully uses active squad +- Slash commands follow existing pattern (/init, /agents, etc.); seamless REPL integration +- .active file is simple and atomic; suitable for concurrent CLI access +- Squad deletion safety: cannot delete active squad; requires confirmation -**Impact:** Low. Two new files, no changes to existing source. Build passes, all 1683 tests pass. +### 2026-03-01: Multi-Squad UX & Interaction Design +**By:** Marquez (UX Designer) +**What:** +- Visual indicator: current squad marked with ●, others with ○; non-default squads tagged [switched] +- Squad name always visible in REPL header and prompt: ◆ Squad (client-acme) +- Picker interactions: ↑/↓ navigate, Enter select, Esc/Ctrl+C cancel; 5-7 squads displayed, wrap around +- Error states: clear copy with next actions (e.g., "Squad not found. Try @squad:personal." or "Run /squads to list.") +- Copy style: active verbs (Create, Switch, List), human-readable nouns (no jargon), 3-5 words per line +- Onboarding: fresh install defaults to "personal"; existing single-squad users see migration notice -### 2026-02-22: Use npm-native workspace resolution (not pnpm `workspace:*`) -**By:** Edie (TypeScript Engineer) -**Date:** 2026-02-22 -**Status:** Applied +**Why:** +- Persistent context (squad name in header/prompt) prevents "Which squad am I in?" confusion +- Interactive picker is discoverable and non-blocking; minimal cognitive load +- Error messages with next actions reduce support friction +- Onboarding defaults and migration notices ensure smooth upgrade path for existing users -**Context:** Brady's task requested using `workspace:*` for the CLI→SDK dependency. However, this project uses npm workspaces (not pnpm or Yarn). The `workspace:` protocol is a pnpm/Yarn feature and npm rejects it with `EUNSUPPORTEDPROTOCOL`. +# Decision: Separator component is canonical for horizontal rules -**Decision:** Use `"*"` as the version specifier instead. Under npm workspaces, when a dependency name matches a local workspace package, npm automatically resolves to the local package regardless of the version specifier. `"*"` accepts any version, ensuring the local SDK is always used during development. +**By:** Cheritto (TUI Engineer) +**Date:** 2026-03-02 +**Issues:** #655, #670, #671, #677 -**Trade-off:** `"*"` will be published as-is to npm (unlike pnpm's `workspace:*` which gets replaced with the real version at publish time). Before publishing the CLI package, the SDK dependency version should be pinned to the actual release version. A `prepublishOnly` script or CI step could automate this. +## What -### 2026-02-22: Test import migration to workspace packages -**By:** Fenster (Core Dev) -**Date:** 2026-02-22 -**Status:** Implemented +- All horizontal separator lines in shell components must use the shared `Separator` component (`components/Separator.tsx`), not inline `box.h.repeat()` calls. +- The `Separator` component handles terminal capability detection, box-drawing character degradation, and width computation internally. +- Information hierarchy convention: **bold** for primary CTAs (commands, actions) > normal for content > **dim** for metadata (timestamps, status, hints). +- `flexGrow` should not be used on containers that may be empty — it creates dead space in Ink layouts. -**Context:** All 56 test files imported from root `src/` via relative paths (`../src/...`). With the SDK/CLI workspace split, tests needed to import from `@bradygaster/squad-sdk` and `@bradygaster/squad-cli` packages instead. +## Why -**Decision:** -1. **Barrel-first imports:** Where a barrel/index file re-exports symbols from sub-modules, tests import from the barrel path (e.g., `@bradygaster/squad-sdk/config` instead of individual files). -2. **New subpath exports for orphaned modules:** 8 runtime/adapter modules not covered by existing barrels got new subpath exports in the SDK package.json. -3. **Missing barrel re-exports fixed:** `selectResponseTier`/`getTier` added to coordinator barrel; `onboardAgent`/`addAgentToConfig` added to agents barrel. -4. **CLI functions correctly located:** Consumer imports test updated to import CLI functions from `@bradygaster/squad-cli` (not SDK), reflecting intentional separation. +Duplicated separator logic was found in 3 files (App.tsx, AgentPanel.tsx, MessageStream.tsx). Consolidation to a single component prevents drift and makes it trivial to change separator style globally. The info hierarchy and whitespace conventions ensure visual consistency as new components are added. +### 2026-03-01: PR #547 Remote Control Feature — Architectural Review +**By:** Fenster +**Date:** 2026-03-01 +**PR:** #547 "Squad Remote Control - PTY mirror + devtunnel for phone access" by tamirdresher (external) -**Consequence:** Zero `grep -r "from '../src/" test/` results. All 1727 tests pass. SDK has 26 subpath exports, CLI has 17. +## Context -### 2026-02-22: CI/CD & Release Readiness Assessment -**By:** Kobayashi (Git & Release) -**Date:** 2026-02-22 -**Status:** ASSESSMENT COMPLETE +External contributor Tamir Dresher submitted a PR adding `squad start --tunnel` command to run Copilot in a PTY and mirror terminal output to phone/browser via WebSocket + Microsoft Dev Tunnels. -**Summary:** All 13 CI/CD workflows are production-ready and correctly configured. Merge drivers are in place. Branch protection is working. Publishing infrastructure works both for stable releases (v* tags) and pre-releases (insider branch). +## Architectural Question -**Version Status:** -- SDK `package.json`: 0.8.0 -- CLI `package.json`: 0.8.1 (intentional patch for bin entry fix) -- CHANGELOG: 0.6.0-alpha.0 (root workspace marker) +Is remote terminal access via devtunnel + PTY mirroring in scope for Squad v1 core? -**Workflow Audit:** -- ✅ Core CI validates on PR/push to main, dev, insider -- ✅ squad-publish.yml: Publishes both packages on v* tags with public access -- ✅ squad-insider-publish.yml: Auto-publishes on insider push with `--tag insider` -- ✅ Release/promote workflows fully functional +## Technical Assessment -**Merge Drivers:** Union merge configured for append-only files (decisions.md, agents/*/history.md, log/**, orchestration-log/**). +**What works:** +- RemoteBridge WebSocket server architecture is sound +- PTY mirroring approach is technically correct +- Session management dashboard is useful +- Security headers and CSP are present +- Test coverage exists (18 tests, though failing due to build issues) -**Release Readiness:** Insider channel ready now. Stable release ready after version alignment if desired. +**Critical blockers:** +1. **Build broken** — TypeScript errors in `start.ts`, all tests failing +2. **Command injection vulnerability** — `execFileSync` with string interpolation in `rc-tunnel.ts` +3. **Native dependency** — `node-pty` requires C++ compiler (install friction) +4. **Windows-only effectively** — hardcoded paths, devtunnel CLI Windows-centric +5. **No cross-platform strategy** — macOS/Linux support unclear -**Recommendation:** Version alignment (SDK 0.8.0, CLI 0.8.0) simplifies CHANGELOG during pre-1.0. Separate versioning can be deferred post-1.0 if needed. +**Architectural concerns:** -### 2026-02-22: Fix npx bin resolution for squad-cli -**By:** Rabin (Distribution) -**Date:** 2026-02-22 -**Status:** Implemented & Published +### 2026-03-02T23:36:00Z: Version target — v0.6.0 for public migration **[SUPERSEDED — see line 1046]** +**By:** Brady (via Copilot) +**What:** The public migration from squad-pr to squad should target v0.6.0, not v0.8.17. This overrides Kobayashi's Phase 5 Option A recommendation. The public repo (bradygaster/squad) goes from v0.5.4 → v0.6.0 — a clean minor bump. +**Why:** User directive. v0.6.0 is the logical next public version from v0.5.4. Internal version numbers (0.6.x–0.8.x) were private development milestones. +**[CORRECTION — 2026-03-03]:** This decision was REVERSED by Brady. Brady explicitly stated: "0.6.0 should NOT appear as the goal for ANY destination. I want the beta to become 0.8.17." The actual migration target is v0.8.17. See the superseding "Versioning Model: npm packages vs Public Repo Tags" decision at line 1046 which clarifies that v0.6.0 is a public repo tag only, while npm packages remain at 0.8.17. Current migration documentation correctly references v0.8.17 throughout. +1. **Not integrated with Squad runtime** — doesn't use EventBus, Coordinator, or agent orchestration. Isolated feature. +2. **Two separate modes** — PTY mode (`start.ts`) vs. ACP passthrough mode (`rc.ts`). Why both? +3. **New CLI paradigm** — "start" implies daemon/server, not interactive mirroring. Command naming collision risk. +4. **External dependency** — requires `devtunnel` CLI installed + authenticated. Not bundled, not auto-installed. +5. **Audit logs** — go to `~/.cli-tunnel/audit/` instead of `.squad/log/` (inconsistent with Squad state location). -**Context:** `npx @bradygaster/squad-cli@0.8.0` printed placeholder text instead of running the real CLI. The bin entry was `"squad": "./dist/cli-entry.js"` but npx resolves by unscoped package name (`squad-cli`), not by custom bin names. +## Recommendation -**Decision:** -1. Added `"squad-cli"` as second bin entry pointing to `./dist/cli-entry.js` -2. Replaced orphaned placeholder `dist/cli.js` with redirect to `cli-entry.js` -3. Bumped version to 0.8.1 (0.8.0 immutable on npm) -4. Published @bradygaster/squad-cli@0.8.1 to npm +**Request Changes** — Do not merge until: +1. TypeScript build errors fixed +2. Command injection vulnerability patched (use array args, no interpolation) +3. Tests passing (currently 18/18 failing) +4. Cross-platform support documented or Windows-only label added +5. Architectural decision on scope: Is this core or plugin? -**Consequence:** -- `npx @bradygaster/squad-cli` now runs the real CLI -- `squad` command still works for global installs -- Both bin names resolve to the same entry point -- Future releases must keep both bin entries +**If approved as core feature:** +- Extract to plugin first, prove value, then consider core integration +- Unify PTY vs. ACP modes (pick one) +- Integrate with EventBus/Coordinator (or explain why isolated is correct) +- Rename command to `squad remote` or `squad tunnel` (avoid `start` collision) +- Move audit logs to `.squad/log/` +**If approved as plugin:** +- This is the right path — keeps core small, proves value independently +- Still fix security issues before merge to plugin repo +## For Brady +You requested a runtime review. Here's the verdict: -### OpenTelemetry version alignment: pin core packages to 1.30.x line -**By:** Edie -**Issue:** #254 -**What:** All OTel optional dependencies in `packages/squad-sdk/package.json` must stay version-aligned: `sdk-node@^0.57.x` requires `sdk-trace-base@^1.30.0`, `sdk-trace-node@^1.30.0`, `sdk-metrics@^1.30.0`, `resources@^1.30.0`. These must be explicit optional dependencies, not left to transitive resolution. -**Why:** Without explicit pins, npm hoists the latest versions (2.x) of `sdk-trace-base`, `sdk-metrics`, and `resources` to the top-level `node_modules`. The 2.x types are structurally incompatible with the 1.x types that `sdk-node@0.57.x` transitively depends on, causing TS2345/TS2741 type errors (e.g., missing `instrumentationScope` on `ReadableSpan`, missing `getRawAttributes` on `Resource`). Explicit pins at `^1.30.0` force npm to deduplicate on the 1.x line, eliminating the type conflicts. +- **Concept is cool** — phone access to Copilot is a real use case. +- **Implementation needs work** — build broken, security issues, Windows-only. +- **Architectural fit unclear** — not in any Squad v1 PRD. No integration with agent orchestration. +- **Native dependency risk** — `node-pty` adds install friction (C++ compiler required). +**My take:** This belongs in a plugin, not core. External contributor did solid work on the WebSocket bridge, but Squad v1 needs to ship agent orchestration first. Remote access is a nice-to-have, not a v1 must-have. -# Decision: OpenTelemetry Tracing for Agent Lifecycle & Coordinator Routing +If you want this in v1, we need a proposal (docs/proposals/) first. -**Date:** 2026-02-22 -**By:** Fenster -**Issues:** #257, #258 -**Status:** Implemented +### 2026-03-02: Multi-squad test contract — squads.json schema +**By:** Hockney (Tester) +**Date:** 2026-03-02 +**Issue:** #652 ## What -Added OpenTelemetry trace instrumentation to four files in `packages/squad-sdk/src/`: +Tests for multi-squad (PR #690) encode a specific squads.json contract: + +```typescript +interface SquadsJson { + version: 1; + defaultSquad: string; + squads: Record; +} +``` -1. **`agents/index.ts`** — AgentSessionManager: `spawn()`, `resume()`, `destroy()` wrapped with spans (`squad.agent.spawn`, `squad.agent.resume`, `squad.agent.destroy`). -2. **`agents/lifecycle.ts`** — AgentLifecycleManager: `spawnAgent()`, `destroyAgent()` wrapped with spans (`squad.lifecycle.spawnAgent`, `squad.lifecycle.destroyAgent`). -3. **`coordinator/index.ts`** — Coordinator: `initialize()`, `route()`, `execute()`, `shutdown()` wrapped with spans (`squad.coordinator.initialize`, `squad.coordinator.route`, `squad.coordinator.execute`, `squad.coordinator.shutdown`). -4. **`coordinator/coordinator.ts`** — SquadCoordinator: `handleMessage()` wrapped with span (`squad.coordinator.handleMessage`). +Squad name validation regex: `^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$` (kebab-case, 1-40 chars). ## Why -- Observability is foundational for debugging multi-agent orchestration at runtime. -- Using `@opentelemetry/api` only — no-ops without a registered provider, so zero cost in production unless OTel is configured. -- Trace hierarchy: `coordinator.handleMessage → coordinator.route → coordinator.execute → lifecycle.spawnAgent → agent.spawn`. +Fenster's implementation should match this schema. If the schema changes, tests need updating. Recording so the team knows the contract is encoded in tests. + +## Impact -## Convention Established +Fenster: Align `multi-squad.ts` types with this schema, or flag if different — Hockney will adjust tests. -- **Tracer name:** `trace.getTracer('squad-sdk')` — one tracer per package. -- **Span naming:** `squad.{module}.{method}` (e.g., `squad.agent.spawn`). -- **Attributes:** Use descriptive keys like `agent.name`, `routing.tier`, `target.agents`, `spawn.mode`. -- **Error handling:** `span.setStatus({ code: SpanStatusCode.ERROR })` + `span.recordException(err)` in catch blocks. Always `span.end()` in `finally`. -- **Import only from `@opentelemetry/api`** — never from SDK packages directly. +### 2026-03-02: PR #582 Review — Consult Mode Implementation +**By:** Keaton (Lead) +**Date:** 2026-03-01 +**Context:** External contributor PR from James Sturtevant (jsturtevant) +## Decision -# Decision: OTel Foundation — NodeSDK over individual providers +**Do not merge PR #582 in its current form.** -**Author:** Fortier (Node.js Runtime) -**Date:** 2026-02-22 -**Issues:** #255, #256 -**Status:** Implemented +This is a planning document (PRD) masquerading as implementation. The PR contains: +- An excellent 854-line PRD for consult mode +- Test stubs for non-existent functions +- Zero actual implementation code +- A history entry claiming work is done (aspirational, not factual) -## Context +## Required Actions -Issues #255 and #256 require OTel provider initialization and a bridge from the existing TelemetryCollector to OTel spans. The OpenTelemetry JS ecosystem has multiple packages (`sdk-trace-base`, `sdk-trace-node`, `sdk-metrics`, `resources`, `exporter-*`) that frequently ship with incompatible transitive versions, causing TypeScript type errors even when runtime behavior is correct. +1. **Extract PRD to proper location:** + - Move `.squad/identity/prd-consult-mode.md` → `docs/proposals/consult-mode.md` + - PRDs belong in proposals/, not identity/ -## Decision +2. **Close this PR with conversion label:** + - Label: "converted-to-proposal" + - Comment: Acknowledge excellent design work, explain missing implementation + +3. **Create implementation issues from PRD phases:** + - Phase 1: SDK changes (SquadDirConfig, resolution helpers) + - Phase 2: CLI command implementation + - Phase 3: Extraction workflow + - Each phase: discrete PR with actual code + tests + +4. **Architecture discussion needed before implementation:** + - How does consult mode integrate with existing sharing/ module? + - Session learnings vs agent history — conceptual model mismatch + - Remote mode (teamRoot pointer) vs copy approach — PRD contradicts itself + +## Architectural Guidance + +**What's right:** +- `consult: true` flag in config.json ✅ +- `.git/info/exclude` for git invisibility ✅ +- `git rev-parse --git-path info/exclude` for worktree compatibility ✅ +- Separate extraction command (`squad extract`) ✅ +- License risk detection (copyleft) ✅ + +**What needs rethinking:** +- Reusing `sharing/` module (history split vs learnings extraction — different domains) +- PRD flip-flops between "copy squad" and "remote mode teamRoot pointer" +- No design for how learnings are structured or extracted +- Tests before code (cart before horse) + +## Pattern Observed + +James Sturtevant is a thoughtful contributor who understands the product vision. The PRD is coherent and well-structured. This connects to his #652 issue (Multiple Personal Squads) — consult mode is a stepping stone to multi-squad workflows. + +**Recommendation:** Engage James in architecture discussion before he writes code. This feature has implications for the broader personal squad vision. Get alignment on: +1. Sharing module fit (or new consult module?) +2. Learnings structure and extraction strategy +3. Phase boundaries and deliverables + +## Why This Matters + +External contributors are engaging with Squad's architecture. We need to guide them toward shippable PRs, not just accept aspirational work. Setting clear expectations now builds trust and avoids wasted effort. + +## Files Referenced + +- `.squad/identity/prd-consult-mode.md` (PRD, should move) +- `test/consult.test.ts` (tests for non-existent code) +- `.squad/agents/fenster/history.md` (claims work done) +- `packages/squad-sdk/src/resolution.ts` (needs `consult` field, unchanged in PR) + + +### cli.js is now a thin ESM shim + +**By:** Fenster +**Date:** 2025-07 +**What:** `cli.js` at repo root is a 14-line shim that imports `./packages/squad-cli/dist/cli-entry.js`. It no longer contains bundled CLI code. The deprecation notice only displays when invoked via npm/npx. +**Why:** The old bundled cli.js was stale and missing commands added after the monorepo migration (e.g., `aspire`). A shim ensures `node cli.js` always runs the latest built CLI. +**Impact:** `node cli.js` now requires `npm run build` to have been run first (so `packages/squad-cli/dist/cli-entry.js` exists). This was already the case for any development workflow. + + +### 2026-03-02T01-09-49Z: User directive +**By:** Brady (via Copilot) +**What:** Stop distributing the package via NPX and GitHub. Only distribute via NPM from now on. Go from the public version to whatever version we're in now in the private repo. Adopt the versioning scheme from issue #692. +**Why:** User request — captured for team memory + +# Release Plan Update — npm-only Distribution & Semver Fix (#692) + +**Status:** DECIDED +**Decided by:** Kobayashi (Git & Release) +**Date:** 2026-03-01T14:22Z +**Context:** Brady's two strategic decisions on distribution and versioning + +## Decisions + +### 1. NPM-Only Distribution +- **What:** End GitHub-native distribution (`npx github:bradygaster/squad`). Install exclusively via npm registry. +- **How:** Users install via `npm install -g @bradygaster/squad-cli` (global) or `npx @bradygaster/squad-cli` (per-project). +- **Why:** Simplified distribution, centralized source of truth, standard npm tooling conventions. +- **Scope:** Affects all future releases, all external documentation, and CI/CD publish workflows. +- **Owners:** Rabin (docs), Fenster (scripts), all team members (update docs/sample references). + +### 2. Semantic Versioning Fix (#692) +- **Problem:** Versions were `X.Y.Z.N-preview` (four-part with prerelease after), which violates semver spec. +- **Solution:** Correct format is `X.Y.Z-preview.N` (prerelease identifier comes after patch, before any build metadata). +- **Examples:** + - ❌ Invalid: `0.8.6.1-preview`, `0.8.6.16-preview` + - ✅ Valid: `0.8.6-preview.1`, `0.8.6-preview.16` +- **Impact:** Affects all version strings going forward (package.json, CLI version constant, release tags). +- **Release sequence:** + 1. Pre-release: `X.Y.Z-preview.1`, `X.Y.Z-preview.2`, ... + 2. At publish: Bump to `X.Y.Z` + 3. Post-publish: Bump to `{next}-preview.1` (reset counter) + +### 3. Version Continuity +- **Transition:** Public repo ended at `0.8.5.1`. Private repo continues at `0.8.6-preview` (following semver format). +- **Rationale:** Clear break between public (stable) and private (dev) codebases while maintaining version history continuity. + +## Implementation + +- ✅ **CHANGELOG.md:** Added "Changed" section documenting distribution channel and semver fix. +- ✅ **Charter (Kobayashi):** Updated Release Versioning Sequence with corrected pattern and phase description. +- ✅ **History (Kobayashi):** Logged decision with rationale and scope. + +## Dependent Work + +- **Fenster:** Ensure `bump-build.mjs` implements X.Y.Z-preview.N pattern (not X.Y.Z.N-preview). +- **Rabin:** Update README, docs, and all install instructions to reflect npm-only distribution. +- **All:** Use corrected version format in release commits, tags, and announcements. + +## Notes + +- Zero impact on functionality — this is purely distribution and versioning cleanup. +- Merge drivers on `.squad/agents/kobayashi/history.md` ensure this decision appends safely across parallel branches. +- If questions arise about versioning during releases, refer back to Charter § Release Versioning Sequence. + +# Decision: npm-only distribution (GitHub-native removed) + +**By:** Rabin (Distribution) +**Date:** 2026-03-01 +**Requested by:** Brady + +## What Changed + +All distribution now goes through npm. The `npx github:bradygaster/squad` path has been fully removed from: +- Source code (github-dist.ts default template, install-migration.ts, init.ts) +- All 4 copies of squad.agent.md (Ralph Watch Mode commands) +- All 4 copies of squad-insider-release.yml (release notes) +- README.md, migration guides, blog posts, cookbook, installation docs +- Test assertions (bundle.test.ts) +- Rabin's own charter (flipped from "never npmjs.com" to "always npmjs.com") + +## Install Paths (the only paths) + +```bash +# Global install +npm install -g @bradygaster/squad-cli + +# Per-use (no install) +npx @bradygaster/squad-cli + +# SDK for programmatic use +npm install @bradygaster/squad-sdk +``` + +## Why + +One distribution channel means less confusion, fewer edge cases, and zero SSH-agent hang bugs. npm caching makes installs faster. Semantic versioning works properly. The root `cli.js` still exists with a deprecation notice for anyone who somehow hits the old path. + +## Impact + +- **All team members:** When writing docs or examples, use `npm install -g @bradygaster/squad-cli` or `npx @bradygaster/squad-cli`. Never reference `npx github:`. +- **CI/CD:** Insider release workflow now shows npm install commands in release notes. +- **Tests:** bundle.test.ts assertions updated to match new default template. + + +# Decision: Versioning Model — npm Packages vs Public Repo + +**Date:** 2026-03-03T02:45:00Z +**Decided by:** Kobayashi (Git & Release specialist) +**Related issues:** Migration prep, clarifying confusion between npm and public repo versions +**Status:** Active — team should reference this in all future releases + +## The Problem + +The migration process had introduced confusion about which version number applies where: +- Coordinator incorrectly bumped npm package versions to 0.6.0, creating version mismatch +- Migration checklist had npm packages publishing as 0.6.0 +- CHANGELOG treated 0.6.0 as an npm package version +- No clear distinction between "npm packages version" vs "public repo GitHub release tag" +- Risk of future mistakes during releases + +## The Model (CORRECT) + +Two distinct version numbers serve two distinct purposes: + +### 1. npm Packages: `@bradygaster/squad-cli` and `@bradygaster/squad-sdk` + +- **Follow semver cadence from current version:** Currently at 0.8.17 (published to npm) +- **Next publish after 0.8.17:** 0.8.18 (NOT 0.6.0) +- **Development versions:** Use `X.Y.Z-preview.N` format (e.g., 0.8.18-preview.1, 0.8.18-preview.2) +- **Release sequence per Kobayashi charter:** + 1. Pre-release: `X.Y.Z-preview.N` (development) + 2. At publish: Bump to `X.Y.Z` (e.g., 0.8.18), publish to npm, create GitHub release + 3. Post-publish: Immediately bump to next-preview (e.g., 0.8.19-preview.1) + +**MUST NEVER:** +- Bump npm packages down (e.g., 0.8.17 → 0.6.0) +- Confuse npm package version with public repo tag + +### 2. Public Repo (bradygaster/squad): GitHub Release Tag `v0.8.17` **[CORRECTED from v0.6.0]** + +- **Purpose:** Marks the migration release point for the public repository +- **Public repo version history:** v0.5.4 (final pre-migration) → v0.8.17 (migration release) **[CORRECTED: Originally written as v0.6.0, corrected to v0.8.17 per Brady's directive]** +- **Applied to:** The migration merge commit on beta/main +- **Same as npm versions:** v0.8.17 is BOTH the npm package version AND the public repo tag **[CORRECTED: Originally described as "separate from npm versions"]** +- **No package.json changes:** The tag is applied after the merge commit, but the version in package.json matches the tag + +## Why Two Version Numbers? **[CORRECTED: Actually ONE version number — v0.8.17 for both]** + +1. **npm packages evolve on their own cadence:** Independent development, independent release cycles (via @changesets/cli) +2. **Public repo is a release marker:** The v0.8.17 tag signals "here's the migration point" to users who clone the public repo **[CORRECTED: Same version as npm, not different]** +3. **They target different audiences:** + - npm: Users who install via `npm install -g @bradygaster/squad-cli` + - Public repo: Users who clone `bradygaster/squad` or interact with GitHub releases + **[CORRECTED: Both use v0.8.17 — the version numbers are aligned, not separate]** + +## Impact on Migration Checklist & CHANGELOG + +- **migration-checklist.md:** All references correctly use v0.8.17 for both npm packages AND public repo tag. **[CORRECTED: Line originally said "publish as 0.8.18, not 0.6.0" but actual target is 0.8.17]** +- **CHANGELOG.md:** Tracks npm package versions at 0.8.x cadence +- **Future releases:** npm packages and public repo tags use the SAME version number **[CORRECTED: Original text implied they were different]** + +## Known Issue: `scripts/bump-build.mjs` + +The auto-increment build number script (`npm run build`) can produce invalid semver for non-prerelease versions: +- `0.6.0` + auto-increment → `0.6.0.1` (invalid) +- `0.8.18-preview.1` + auto-increment → `0.8.18-preview.2` (valid) + +Since npm packages stay at 0.8.x cadence, this is not a blocker for migration. But worth noting for future patch releases. + +## Directive Merged + +Brady's directive (2026-03-03T02:16:00Z): "squad-cli and squad-sdk must NOT be bumped down to 0.6.0. They are already shipped to npm at 0.8.17." + +✅ **Incorporated:** All fixes ensure npm packages stay at 0.8.x. The v0.8.17 is used for BOTH npm packages AND public repo tag. **[CORRECTED: Original text said "v0.6.0 is public repo only" which was incorrect]** + +## Action Items for Team + +- Reference this decision when asked "what version should we release?" +- Use this model for all future releases (main project and public repo) +- Update team onboarding docs to include this versioning distinction +### No temp/memory files in repo root +**By:** Brady +**What:** No plan files, memory files, or tracking artifacts in the repository root. +**Why:** Keep the repo clean. + + +--- + +## Adoption & Community + +### `.squad/` Directory Scope — Owner Directive +**By:** Brady (project owner, PR #326 review) +**Date:** 2026-03-10 + +**Directive:** The `.squad/` directory is **reserved for team state only** — roster, routing, decisions, agent histories, casting, and orchestration logs. Non-team data (adoption tracking, community metrics, reports) must NOT live in `.squad/`. Use `.github/` for GitHub platform integration or `docs/` for documentation artifacts. + +**Source:** [PR #326 comment](https://github.com/bradygaster/squad/pull/326#issuecomment-4029193833) + +--- + +### No Individual Repo Listing Without Consent — Owner Directive +**By:** Brady (project owner, PR #326 review) +**Date:** 2026-03-10 + +**Directive:** Growth metrics must report **aggregate numbers only** (e.g., "78+ repositories found via GitHub code search") — never name or link to individual community repos without explicit opt-in consent. The monitoring script and GitHub Action concepts are approved, but any public showcase or tracking list that identifies specific repos is blocked until a community consent plan exists. + +**Source:** [PR #326 comment](https://github.com/bradygaster/squad/pull/326#issuecomment-4029222967) + +--- + +### Adoption Tracking — Opt-In Architecture +**By:** Flight (implementing Brady's directives above) +**Date:** 2026-03-09 + +Privacy-first adoption monitoring using a three-tier system: + +**Tier 1: Aggregate monitoring (SHIPPED)** +- GitHub Action + monitoring script collect metrics +- Reports moved to `.github/adoption/reports/{YYYY-MM-DD}.md` +- Reports show ONLY aggregate numbers (no individual repo names): + - "78+ repositories found via code search" + - Total stars/forks across all discovered repos + - npm weekly downloads + +**Tier 2: Opt-in registry (DESIGN NEXT)** +- Create `SHOWCASE.md` in repo root with submission instructions +- Opted-in projects listed in `.github/adoption/registry.json` +- Monitoring script reads registry, reports only on opted-in repos + +**Tier 3: Public showcase (LAUNCH LATER)** +- `docs/community/built-with-squad.md` shows opted-in projects only +- README link added when ≥5 opted-in projects exist + +**Rationale:** +- Aggregate metrics safe (public code search results) +- Individual projects only listed with explicit owner consent +- Prevents surprise listings, respects privacy +- Incremental rollout maintains team capacity + +**Implementation (PR #326):** +- ✅ Moved `.squad/adoption/` → `.github/adoption/` +- ✅ Stripped tracking.md to aggregate-only metrics +- ✅ Removed individual repo names, URLs, metadata +- ✅ Updated adoption-report.yml and scripts/adoption-monitor.mjs +- ✅ Removed "Built with Squad" showcase link from README (Tier 2 feature) + +--- + +### Adoption Tracking Location & Privacy +**By:** EECOM +**Date:** 2026-03-10 + +Implementation decision confirming Tier 1 adoption tracking changes. + +**What:** Move adoption tracking from `.squad/adoption/` to `.github/adoption/` + +**Why:** +1. **GitHub integration:** `.github/adoption/` aligns with GitHub convention (workflows, CODEOWNERS, issue templates) +2. **Privacy-first:** Aggregate metrics only; defer individual repo showcase to Tier 2 (opt-in) +3. **Clear separation:** `.squad/` = team internal; `.github/` = GitHub platform integration +4. **Future-proof:** When Tier 2 opt-in launches, `.github/adoption/` is the natural home + +**Impact:** +- GitHub Action reports write to `.github/adoption/reports/{YYYY-MM-DD}.md` +- No individual repo information published until Tier 2 +- Monitoring continues collecting aggregate metrics via public APIs +- Team sees trends without publishing sensitive adoption data + +--- + +### Append-Only File Governance +**By:** Flight +**Date:** 2026-03-09 + +Feature branches must never modify append-only team state files except to append new content. + +**What:** If a PR diff shows deletions in `.squad/agents/*/history.md` or `.squad/decisions.md`, the PR is blocked until deletions are reverted. + +**Why:** Session state drift causes agents to reset append-only files to stale branch state, destroying team knowledge. PR #326 deleted entire history files and trimmed ~75 lines of decisions, causing data loss. + +**Enforcement:** Code review + future CI check candidate. + +--- + +### Documentation Style: No Ampersands +**By:** PAO +**Date:** 2026-03-09 + +Ampersands (&) are prohibited in user-facing documentation headings and body text, per Microsoft Style Guide. + +**Rule:** Use "and" instead. + +**Why:** Microsoft Style Guide prioritizes clarity and professionalism. Ampersands feel informal and reduce accessibility. + +**Exceptions:** +- Brand names (AT&T, Barnes & Noble) +- UI element names matching exact product text +- Code samples and technical syntax +- Established product naming conventions + +**Scope:** Applies to docs pages, README files, blog posts, community-facing content. Internal files (.squad/** memory files, decision docs, agent history) have flexibility. + +**Reference:** https://learn.microsoft.com/en-us/style-guide/punctuation/ampersands + +--- + +## Sprint Directives + +### Secret handling — agents must never persist secrets +**By:** RETRO (formerly Baer), v0.8.24 +**What:** Agents must NEVER write secrets, API keys, tokens, or credentials into conversational history, commit messages, logs, or any persisted file. Acknowledge receipt without echoing values. +**Why:** Secrets in logs or history are a security incident waiting to happen. + +--- + +## Squad Ecosystem Boundaries & Content Governance + +### Squad Docs vs Squad IRL Boundary (consolidated) +**By:** PAO (via Copilot), Flight +**Date:** 2026-03-10 +**Status:** Active pattern for all documentation PRs + +**Litmus test:** If Squad doesn't ship the code or configuration, the documentation belongs in Squad IRL, not the Squad framework docs. + +**Categories:** + +1. **Squad docs** — Features Squad ships (routing, charters, reviewer protocol, config, behavior) +2. **Squad IRL** — Infrastructure around Squad (webhooks, deployment patterns, logging, external tools, operational patterns) +3. **Gray area:** Platform features (GitHub Issue Templates) → Squad docs if framed as "how to configure X for Squad" + +**Examples applied (PR #331):** + +| Document | Decision | Reason | +|----------|----------|--------| +| ralph-operations.md | DELETE → IRL | Infrastructure (deployment, logging) around Squad, not Squad itself | +| proactive-communication.md | DELETE → IRL | External tools (Teams, WorkIQ) configured by community, not built into Squad | +| issue-templates.md | KEEP, reframe | GitHub platform feature; clarify scope: "a GitHub feature configured for Squad" | +| reviewer-protocol.md (Trust Levels) | KEEP | Documents user choice spectrum within Squad's existing review system | + +**Enforcement:** Code review + reframe pattern ("GitHub provides X. Here's how to configure it for Squad's needs."). Mark suspicious deletions for restore (append-only governance). + +**Future use:** Apply this pattern to all documentation PRs to maintain clean boundaries. + +--- + +### Content Triage Skill — External Content Integration +**By:** Flight +**Date:** 2026-03-10 +**Status:** Skill created at `.squad/skills/content-triage/SKILL.md` + +**Pattern:** External content (blog posts, sample repos, videos, conference talks) that helps Squad adoption must be triaged using the "Squad Ships It" boundary heuristic before incorporation. + +**Workflow:** +1. Triggered by `content-triage` label or external content reference in issue +2. Flight performs boundary analysis +3. Sub-issues generated for Squad-ownable content extraction (PAO responsibility) +4. FIDO verifies docs-test sync on extracted content +5. Scribe manages IRL references in `.github/irl/references.yml` (YAML schema) + +**Label convention:** `content:blog`, `content:sample`, `content:video`, `content:talk` + +**Why:** Pattern from PR #331 (Tamir Dresher blog) shows parallel extraction of Squad-ownable patterns (scenario guides, reviewer protocol) and infrastructure patterns (Ralph ops, proactive comms). Without clear boundary, teams pollute Squad docs with operational content or miss valuable patterns that should be generalized. + +**Impact:** Enables community content to accelerate Squad adoption without polluting core docs. Flight's boundary analysis becomes reusable decision framework. Prevents scope creep as adoption grows. + +--- + +### PR #331 Quality Gate — Test Assertion Sync +**By:** FIDO (Quality Owner) +**Date:** 2026-03-10 +**Status:** 🟢 CLEARED (test fix applied, commit 6599db6) + +**What was blocked:** Merge blocked on stale test assertions in `test/docs-build.test.ts`. + +**Critical violations resolved:** +1. `EXPECTED_SCENARIOS` array stale (7 vs 25 disk files) — ✅ Updated to 25 entries +2. `EXPECTED_FEATURES` constant undefined (32 feature files) — ✅ Created array with 32 entries +3. Test assertion incomplete — ✅ Updated to validate features section + +**Why this matters:** Stale assertions that don't reflect filesystem state cause silent test skips. Regression: If someone deletes a scenario file, the test won't catch it. CI passing doesn't guarantee test coverage — only that the test didn't crash. + +**Lessons:** +- Test arrays must be refreshed when filesystem content changes +- Incomplete commits break the test-reality sync contract +- FIDO's charter: When adding test count assertions, must keep in sync with disk state + +**Outcome:** Test suite: 6/6 passing. Assertions synced to filesystem. No regression risk from stale assertions. + +--- + +### Communication Patterns and PR Trust Models +**By:** PAO +**Date:** 2026-03-10 +**Status:** Documented in features/reviewer-protocol.md (trust levels section) and scenarios/proactive-communication.md (infrastructure pattern) + +**Decision:** Document emerging patterns in real Squad usage: proactive communication loops and PR review trust spectrum. + +**Components:** + +1. **Proactive communication patterns** — Outbound notifications (Teams webhooks), inbound scanning (Teams/email for work items), two-way feedback loop connecting external sources to Squad workflow + +2. **PR trust levels spectrum:** + - **Full review** (default for team repos) — All PRs require human review + - **Selective review** (personal projects with patterns) — Domain-expert or routine PRs can auto-merge + - **Self-managing** (solo personal repos only) — PRs auto-merge; Ralph's work monitoring provides retroactive visibility + +**Why:** Ralph 24/7 autonomous deployment creates an awareness gap — how does the human stay informed? Outbound notifications solve visibility. Inbound scanning solves "work lives in multiple places." Trust levels let users tune oversight to their context (full review for team repos, selective for personal projects, self-managing for solo work only). + +**Important caveat:** Self-managing ≠ unmonitored; Ralph's work monitoring and notifications provide retroactive visibility. + +**Anti-spam expectations:** Don't spam yourself outbound (notification fatigue), don't spam GitHub inbound (volume controls). + +--- + +### Remote Squad Access — Phased Rollout (Proposed) +**By:** Flight +**Date:** 2026-03-10 +**Status:** Proposed — awaits proposal document in `docs/proposals/remote-squad-access.md` + +**Context:** Squad currently requires a local clone to answer questions. Users want remote access from mobile, browser, or different machine without checking out repo. + +**Phases:** + +**Phase 1: GitHub Discussions Bot (Ship First)** +- Surface: GitHub Discussions +- Trigger: `/squad` command or `@squad` mention +- Context: GitHub Actions workflow checks out repo → full `.squad/` state +- Response: Bot replies to thread +- Feasibility: 1 day +- Why first: Easy to build, zero hosting, respects repo privacy, async Q&A, immediately useful + +**Phase 2: GitHub Copilot Extension (High Value)** +- Surface: GitHub Copilot chat (VS Code, CLI, web, mobile) +- Trigger: `/squad ask {question}` in any Copilot client +- Context: Extension fetches `.squad/` files via GitHub API (no clone) +- Response: Answer inline in Copilot +- Feasibility: 1 week +- Why second: Works everywhere Copilot exists, instant response, natural UX + +**Phase 3: Slack/Teams Bot (Enterprise Value)** +- Surface: Slack or Teams channel +- Trigger: `@squad` mention in channel +- Context: Webhook fetches `.squad/` via GitHub API +- Response: Bot replies in thread +- Feasibility: 2 weeks +- Why third: Enterprise teams live in chat; high value for companies using Squad + +**Constraint:** Squad's intelligence lives in `.squad/` (roster, routing, decisions, histories). Any remote solution must solve context access. GitHub Actions workflows provide checkout for free. Copilot Extension and chat bots use GitHub API to fetch files. + +**Implementation:** Before Phase 1 execution, write proposal document. New CLI command: `squad answer --context discussions --question "..."`. New workflow: `.github/workflows/squad-answer.yml`. + +**Privacy:** All approaches respect repo visibility or require authentication. Most teams want private by default. + +### Test assertion discipline — mandatory +**By:** FIDO (formerly Hockney), v0.8.24 +**What:** All code agents must update tests when changing APIs. FIDO has PR blocking authority on quality grounds. +**Why:** APIs changed without test updates caused CI failures and blocked external contributors. + +### Docs-test sync — mandatory +**By:** PAO (formerly McManus), v0.8.24 +**What:** New docs pages require corresponding test assertion updates in the same commit. +**Why:** Stale test assertions block CI and frustrate contributors. + +### Contributor recognition — every release +**By:** PAO, v0.8.24 +**What:** Each release includes an update to the Contributors Guide page. +**Why:** No contribution goes unappreciated. + +### API-test sync cross-check +**By:** FIDO + Booster, v0.8.24 +**What:** Booster adds CI check for stale test assertions. FIDO enforces via PR review. +**Why:** Prevents the pattern of APIs changing without test updates. + +### Doc-impact review — every PR +**By:** PAO, v0.8.25 +**What:** Every PR must be evaluated for documentation impact. PAO reviews PRs for missing or outdated docs. +**Why:** Code changes without doc updates lead to stale guides and confused users. + + +--- + +## Release v0.8.24 + +### CLI Packaging Smoke Test: Release Gate Decision +**By:** FIDO, v0.8.24 +**Date:** 2026-03-08 + +The CLI packaging smoke test is APPROVED as the quality gate for npm releases. + +**What:** +- npm pack → creates tarball of both squad-sdk and squad-cli +- npm install → installs in clean temp directory (simulates user install) +- node {cli-entry.js} → invokes 27 commands + 3 aliases through installed package +- Coverage: All 26 primary commands + 3 of 4 aliases (watch, workstreams, remote-control) + +**Why:** Catches broken package.json exports, MODULE_NOT_FOUND errors, ESM resolution failures, command routing regressions — the exact failure modes we've shipped before. + +**Gaps (acceptable):** +- Semantic validation not covered (only routing tested) +- Cross-platform gaps (test runs on ubuntu-latest only) +- Optional dependencies allowed to fail (node-pty) + +## Impact + +- All agents: charter generation now reliably round-trips model preferences. +- Verbal/Keaton: the 4-layer model selection hierarchy documented in squad.agent.md is now supported at the SDK config level. +- Anyone adding new config fields: use the `assertModelPreference()` pattern (accept string-or-object, normalize internally) for fields that need simple and rich config shapes. + +# Decision: Runtime ExperimentalWarning suppression via process.emit hook + +**Date:** 2026-03-07 +**Author:** Fenster (Core Dev) +**Context:** PR #233 CI failure — 4 tests failed + +## Problem + +PR #233 (CLI wiring fixes for #226, #229, #201, #202) passed all 74 tests locally but failed 4 tests in CI: + +- `test/cli-p0-regressions.test.ts` — bare semver test (expected 1 line, got 3) +- `test/speed-gates.test.ts` — version outputs one line (expected 1, got 3) +- `test/ux-gates.test.ts` — no overflow beyond 80 chars (ExperimentalWarning line >80) +- `test/ux-gates.test.ts` — version bare semver (expected 1 line, got 3) + +Root cause: `node:sqlite` import triggers Node.js `ExperimentalWarning` that leaks to stderr. The existing `process.env.NODE_NO_WARNINGS = '1'` in cli-entry.ts was ineffective because Node only reads that env var at process startup, not when set at runtime. + +The warning likely didn't appear locally because the local Node.js version may have already suppressed it or the env var was set in the shell. + +## Decision + +Added a `process.emit` override in cli-entry.ts that intercepts `warning` events with `name === 'ExperimentalWarning'` and swallows them. This is placed: +- After `process.env.NODE_NO_WARNINGS = '1'` (which still helps child processes) +- Before the `await import('node:sqlite')` pre-flight check + +This is the standard Node.js pattern for runtime warning suppression when you can't control the process launch flags. + +## Impact + +- **cli-entry.ts**: 12 lines added (comment + override function) +- **Tests**: All 4 previously failing tests now pass; no regressions in structural tests (#624) +- **Behavior**: ExperimentalWarning messages no longer appear in CLI output; other warnings (DeprecationWarning, etc.) are unaffected + +### 2026-03-07T14:22:00Z: User directive - Quality cross-review +**By:** Brady (via Copilot) +**What:** All team members must double-and-triple check one another's work. Recent PRs have had weird test failures and inconsistencies. KEEN focus on quality - nothing can slip. +**Why:** User request - quality gate enforcement after speed gate, EBUSY, and cross-contamination issues across PRs #244, #245, #246. + + +# Decision: Optional dependencies must use lazy loading (#247) + +**Date:** 2026-03-09 +**Author:** Fenster +**Status:** Active + +## Context + +Issue #247 — two community reports of installation failure caused by top-level imports of `@opentelemetry/api` crashing when the package wasn't properly installed in `npx` temp environments. + +## Decision + +1. **All optional/telemetry dependencies must be loaded lazily** — never at module top-level. Use `createRequire(import.meta.url)` inside a `try/catch` for synchronous lazy loading. + +2. **Centralized wrapper pattern** — when multiple source files import from the same optional package, create a single wrapper module (e.g., `otel-api.ts`) that provides the fallback logic. Consumers import from the wrapper. + +3. **`@opentelemetry/api` is now an optionalDependency** — it was a hard dependency but is functionally optional. The SDK operates with no-op telemetry when absent. + +4. **`vscode-jsonrpc` added as direct dep** — improves hoisting for npx installs. The ESM subpath import issue (`vscode-jsonrpc/node` without `.js`) is upstream in `@github/copilot-sdk`. + +## Implications + +- Any new OTel integration must import from `runtime/otel-api.js`, never directly from `@opentelemetry/api`. +- Test files may continue importing `@opentelemetry/api` directly (it's installed in dev). +- If adding new optional dependencies in the future, follow the same lazy-load + wrapper pattern. + + +# Release Readiness Check — v0.8.21 + +**By:** Keaton (Lead) +**Date:** 2026-03-07 +**Status:** 🟡 SHIP WITH CAVEATS + + +--- + +## Executive Summary + +v0.8.21 is technically ready to release. All three packages carry the same version string (`0.8.21-preview.7`). Linting passes, 3718 tests pass (19 flaky UI tests pre-existing), CI green on commits. However, **#247 (Installation Failure) must be fixed before shipping**. This is a P0 blocker that breaks the primary installation path. Fenster is actively fixing it. + + +--- + +## Version State ✅ + +All packages aligned at **0.8.21-preview.7:** + +- Root `package.json` — v0.8.21-preview.7 +- `packages/squad-sdk/package.json` — v0.8.21-preview.7 +- `packages/squad-cli/package.json` — v0.8.21-preview.7 + +**Release Tag:** Should be `v0.8.21-preview.7` (already live as -preview, ready to promote to stable or next -preview if #247 requires a patch). + + +--- + +## Git State ✅ + +**Current Branch:** `dev` +**Commits since main:** 23 commits (main..dev) + +Recent activity (last 10 commits): +- 3f924d0 — fix: remove idle blankspace below agent panel (#239) +- 6a9af95 — docs(ai-team): Merge quality directive into team decisions +- 8d4490b — fix: harden flaky tests (EBUSY retry + init speed gate headroom) +- 363a0a8 — feat: Structured model preference & squad-level defaults (#245) +- a488eb8 — fix: wire missing CLI commands into cli-entry.ts (#244) +- b562ef1 — docs: update fenster history & add model-config decision + +**Uncommitted Changes:** 10 files (all acceptable): +- 4 deleted `.squad/decisions/inbox/` files (cleanup, merged to decisions.md) +- 6 untracked images (pilotswarm-*.png — documentation assets) +- 1 untracked `docs/proposals/repl-replacement-prd.md` (draft proposal) + +**Status:** Clean. No staged changes that would block release. + + +--- + +## Open Blockers ⚠️ P0 + +### #247 — Squad Installation Fails 🔴 **CRITICAL BLOCKER** + +**Impact:** Users cannot install via `npm install -g @bradygaster/squad-cli`. +**Assignee:** Fenster (actively fixing) +**Status:** In progress +**Release Impact:** **SHIP CANNOT PROCEED** until resolved. + +**Other Open Issues:** +- #248 — CLI command wiring: `squad triage` does not trigger team assignment loop (minor) +- #242 — Future: Tiered Squad Deployment (deferred, not blocking) +- #241 — New Squad Member for Docs (deferred) +- #240 — ADO configurable work item types (deferred) +- #236 — feat: persistent Ralph (deferred) +- #211 — Squad management paradigms (deferred, release:defer label) + +**Release Blockers:** Only #247 prevents shipping. + + +--- + +## CHANGELOG Review 📝 + +**Current `Unreleased` section covers:** + +### Added — SDK-First Mode (Phase 1) +- Builder functions (defineTeam, defineAgent, defineRouting, defineCeremony, defineHooks, defineCasting, defineTelemetry, defineSquad) +- `squad build` command with --check, --dry-run, --watch flags +- SDK Mode Detection in Coordinator prompt +- Documentation (SDK-First Mode guide, updated SDK Reference, README quick reference) + +### Added — Remote Squad Mode (ported from @spboyer PR #131) +- `resolveSquadPaths()` dual-root resolver +- `squad doctor` command (9-check setup validation) +- `squad link ` command +- `squad init --mode remote` +- `ensureSquadPathDual()` / `ensureSquadPathResolved()` + +### Changed — Distribution & Versioning +- npm-only distribution (no more GitHub-native `npx github:bradygaster/squad`) +- Semantic Versioning fix (X.Y.Z-preview.N format, compliant with semver spec) +- Version transition from public repo (0.8.5.1) to private repo (0.8.x cadence) + +### Fixed +- CLI entry point moved from dist/index.js → dist/cli-entry.js +- CRLF normalization for Windows users +- process.exit() removed from library functions (VS Code extension safe) +- Removed .squad branch protection guard + + +--- + +## Test Status 🟡 + +``` +Test Files: 9 failed | 134 passed (143) +Tests: 19 failed | 3718 passed | 3 todo (3740) +Duration: 80.06s +``` + +**Failures:** All 19 failures are pre-existing UI test timeouts (TerminalHarness spawn issues, not regressions): +- speed-gates.test.ts — 1 timeout +- ux-gates.test.ts — 3 timeouts +- acceptance.test.ts — 8 timeouts +- acceptance/hostile.test.ts — 3 timeouts +- cli/consult.test.ts — 1 timeout + +**Assessment:** Passing rate is strong (99.5% pass rate). Timeouts are environmental (not code regressions). Safe to ship with this test state. + + +--- + +## CI State ✅ + +- **Linting:** ✅ PASS (tsc --noEmit clean on both packages) +- **Build:** ✅ PASS (npm run build succeeds) +- **Tests:** 🟡 PASS (99.5% passing, pre-existing flakes) + + +--- + +## Release Prep Checklist + +- [x] Version strings aligned (0.8.21-preview.7) +- [x] Git state clean (no staged changes) +- [x] Linting passes +- [x] Tests mostly passing (pre-existing flakes only) +- [x] CHANGELOG updated (Unreleased section comprehensive) +- [ ] **#247 resolved (BLOCKER)** +- [ ] Branch merge strategy decided (dev → insiders? or dev → main?) +- [ ] npm publish command prepared + + +--- + +## Merge Strategy + +**Current branches:** +- `main` — stable baseline +- `dev` — integration branch (23 commits ahead of main) +- `insiders` — exists (used for pre-release channel?) + +**Recommendation:** +1. Hold on npm publish until #247 fixed +2. Merge dev → insiders for pre-release testing +3. After QA pass, merge dev → main +4. Tag main as `v0.8.21-preview.7` on npm +5. Consider promoting to `v0.8.21` stable if no further issues + + +--- + +## Draft CHANGELOG Entry for v0.8.21 + +When releasing, move "Unreleased" to versioned section: + +```markdown +## [0.8.21-preview.7] - 2026-03-07 + +### Added — SDK-First Mode (Phase 1) +- [builder functions list] +- [squad build command] +- [SDK Mode Detection] +- [Documentation updates] + +### Added — Remote Squad Mode +- [resolver + commands] + +### Changed — Distribution & Versioning +- [npm-only, semver fix, version transition] + +### Fixed +- [CLI entry point, CRLF, process.exit, branch guard] +``` + + +--- + +## Decision + +**VERDICT: 🟡 RELEASE v0.8.21-preview.7 AFTER #247 FIXED** + +- **GO:** Linting, tests, version alignment all sound. +- **HOLD:** #247 installation failure must be resolved. This is a P0 blocker. +- **ACTION:** Fenster owns #247 fix. Once merged to dev, rerun tests and ship. +- **TIMELINE:** 1–2 hours (estimate: Fenster's ETA on #247). + +**Owner:** Brady (approves final npm publish) +**Fallback:** If #247 unresolvable today, defer to v0.8.22 and open a retro ticket. + + +--- + +## Notes + +- **Community PRs:** 3 community PRs merged cleanly to dev (PR #217, #219, #230). Fork-first contributor workflow is working. +- **Wave planning:** 11 issues targeted for v0.8.22 (5 fix-now + 6 next-wave). 11 deferred to v0.8.23+. +- **Architecture:** SDK/CLI split is clean. Distribution to npm is working. Type safety (strict: true) enforced across both packages. +- **Proposal workflow:** Working as designed. No surprises. + + + +# Decision: Optionalize OpenTelemetry Dependency + +## Context +Telemetry is valuable but should not be a hard requirement for running the SDK. Users in air-gapped environments or minimal setups experienced crashes when `@opentelemetry/api` was missing or incompatible. + +## Decision +We have wrapped `@opentelemetry/api` in a resilient shim (`packages/squad-sdk/src/runtime/otel-api.ts`) and moved the package to `optionalDependencies`. + +### Mechanics +- **Runtime detection:** The wrapper attempts to load `@opentelemetry/api`. +- **Graceful fallback:** If loading fails, it exports no-op implementations of `trace`, `metrics`, and `diag` that match the API surface used by Squad. +- **Developer experience:** Internal code imports from `./runtime/otel-api.ts` instead of the package directly. + +## Consequences +- **Positive:** SDK is robust against missing telemetry dependencies. Installation size is smaller for users who opt out. +- **Negative:** We must maintain the wrapper's type compatibility with the real API. +- **Risk:** If we use new OTel features, we must update the no-op implementation. + +## Status +Accepted and implemented in v0.8.21. + + +# Decision: v0.8.21 Blog Post Scope — SDK-First + Full Release Wave + +**Date:** 2026-03-11 +**Author:** McManus (DevRel) +**Impact:** External communication, developer discovery, release narrative + +## Problem + +v0.8.21 is a major release with TWO significant storylines: +1. **SDK-First Mode** — TypeScript-first authoring, type safety, `squad build` command +2. **Stability Wave** — 26 issues closed, 16 PRs merged, critical crash fix (#247), 3,724 passing tests + +Risk: If blog only emphasizes SDK-First, users miss critical stability improvements (crash fix, Windows hardening, test reliability). If blog buries SDK-First, flagship feature loses visibility. + +## Decision + +Create TWO complementary blog posts with clear ownership: + +1. **`024-v0821-sdk-first-release.md`** (existing) — SDK-First deep dive + - Target: TypeScript-focused teams, SDK adopters + - Scope: Builders, quick start, Azure Function sample, Phase 2/3 roadmap + - Tone: Educational, patterns-focused + +2. **`025-v0821-comprehensive-release.md`** (new) — Full release wave summary + - Target: General audience, release notes consumers + - Scope: All 7 feature areas (SDK-First + Remote Squad + 5 critical fixes), metrics, community credits + - Tone: Reassuring (crash fixed!), factual (26 issues, 0 logic failures) + +**Cross-linking strategy:** +- Comprehensive post links to SDK-First deep dive: "For detailed SDK patterns, see [v0.8.21: SDK-First Mode post](./024-v0821-sdk-first-release.md)" +- SDK-First post references comprehensive post: "For the full release notes, see [v0.8.21: The Complete Release post](./025-v0821-comprehensive-release.md)" + +**CHANGELOG updated once** at `[0.8.21]` section with full scope (all 7 areas) — serves as single source of truth for condensed release info. + +## Rationale + +- **SDK value**: Highlights TypeScript-first workflow, type safety, Azure serverless patterns +- **Stability value**: Installation crash fix alone justifies a major release (user pain elimination) +- **Audience segmentation**: Developers interested in SDK config patterns → read post #024; DevOps/team leads reading release notes → read post #025 +- **SEO/discovery**: Two articles = more surface area for search + internal linking +- **Archive preservation**: Both posts preserved in `docs/blog/` for historical record + +## Alternative Rejected + +**Single mega-post:** Would be 25+ KB, overwhelming, diffuses message (SDK patterns + crash fix + CI stability = scattered narrative). Two posts with clear focus are easier to scan and share. + +## Enforcement + +- CHANGELOG.md single `[0.8.21]` section (source of truth) +- Blog post #025 designated "comprehensive" (headline for external comms) +- Blog post #024 designated "technical deep dive" (for SDK adopters) +- Release announcement on GitHub uses post #025 as primary link + + +--- + +**Decided by:** McManus (DevRel) on behalf of tone ceiling + messaging coherence +**Reviewed by:** Internal tone ceiling check (substantiated claims, no hype, clear value messaging) + + +### 2026-03-07 07:51 UTC: SDK-First init/migrate deferred to v0.8.22 +**By:** Keaton (Coordinator), Brady absent - autonomous decision +**What:** SDK-First mode gaps (init --sdk flag, standalone migrate command, comprehensive docs) deferred to v0.8.22. +**Why:** v0.8.21 has all P0 blockers resolved. Adding features now risks regression. Filed #249, #250, #251. +**Issues filed:** +- #249: squad init --sdk flag for SDK-First mode opt-in +- #250: standalone squad migrate command (subsumes #231) +- #251: comprehensive SDK-First mode documentation + + +### 2026-03-07T08-14-43Z: User directive +**By:** Brady (via Copilot) +**What:** Issues #249, #250, and #251 (SDK-First init --sdk flag, standalone migrate command, comprehensive SDK-First docs) are committed to v0.8.22 - not backlog, not optional. +**Why:** User request - captured for team memory + + +### 2026-03-07T16-19-00Z: Pre-release triage — v0.8.21 release ready pending #248 fix +**By:** Keaton (Lead) +**What:** Analyzed all 23 open issues. Result: v0.8.21 releases cleanly pending fix for #248 (triage team dispatch). v0.8.22 roadmap is well-scoped (9 issues, 3 parallel streams). Close #194 (completed) and #231 (duplicate). +**Why:** Final release gate. Coordinator override: #248 deferred to v0.8.22 (standalone CLI feature, not core to interactive experience). Keeps release unblocked. +**Details:** 2 closeable, 1 P0 override, 9 for v0.8.22, 5 for v0.8.23+, 1 for v0.9+, 4 backlog. See .squad/orchestration-log/2026-03-07T16-19-00Z-keaton.md for full triage table. + +### 2026-03-07T16-19-00Z: PR hold decision — #189 (workstreams) and #191 (ADO) rebase to dev for v0.8.22 +**By:** Hockney (Tester) +**What:** Both PRs are held for v0.8.22 and must rebase from main to dev. Neither ships for v0.8.21. +**Why:** PR #189: merge conflicts, no CI, process.exit() violation, missing CLI tests, 6 unresolved review threads. PR #191: merge conflicts, no CI, untested security fixes, incomplete Planner adapter. Both have solid architecture but insufficient readiness for v0.8.21. +**Details:** See .squad/orchestration-log/2026-03-07T16-19-00Z-hockney.md for detailed code assessment. + +### 2026-03-07T16-19-00Z: Docs ready for v0.8.21 — no release blockers +**By:** McManus (DevRel) +**What:** v0.8.21 documentation is ship-ready. SDK-First mode guide (705 lines), What's New blog, CHANGELOG, and contributors section all complete. No blocking gaps. +**Why:** Release readiness gate. Docs are complete for Phase 1. Minor gaps are non-blocking and addressed in v0.8.22 roadmap. +**Details:** 2 docs issues queued for v0.8.22 (#251 restructure, #210 contributors workflow). See .squad/orchestration-log/2026-03-07T16-19-00Z-mcmanus.md for full triage. + +### 2026-03-07T16:20: User directive — Shift from Actions to CLI +**By:** Brady (via Copilot) +**What:** "I'm seriously concerned about our continued abuse of actions and think the more we can stop relying on actions to do things and start relying on the cli to do things, it puts more emphasis and control in the user's hand and less automation with actions. I think we're maybe going to surprise customers with some of the usage in actions and I would really hate for that to be a deterrent from using squad." +**Why:** User directive — strategic direction for the product. Actions usage can surprise customers with unexpected billing and loss of control. CLI-first puts the user in the driver's seat. + +### Current Actions Inventory (15 workflows) + +**Squad-specific (customer concern):** +1. `sync-squad-labels.yml` — Auto-syncs labels from team.md on push +2. `squad-triage.yml` — Auto-triages issues when labeled "squad" +3. `squad-issue-assign.yml` — Auto-assigns issues when squad:{member} labeled +4. `squad-heartbeat.yml` — Ralph heartbeat/auto-triage (cron currently disabled) +5. `squad-label-enforce.yml` — Label mutual exclusivity on label events + +**Standard CI/Release (expected):** +6. `squad-ci.yml` — Standard PR/push CI +7. `squad-release.yml` — Tag + release on push to main +8. `squad-promote.yml` — Branch promotion (workflow_dispatch) +9. `squad-main-guard.yml` — Forbidden file guard +10. `squad-preview.yml` — Preview validation +11. `squad-docs.yml` — Docs build/deploy +12-15. Publish/insider workflows + +**Directive:** Move squad-specific automation (1-5) into CLI commands. Keep standard CI/release workflows. + +### 2026-03-07T16:20: User directive — Shift from Actions to CLI +**By:** Brady (via Copilot) +**What:** "I'm seriously concerned about our continued abuse of actions and think the more we can stop relying on actions to do things and start relying on the cli to do things, it puts more emphasis and control in the user's hand and less automation with actions. I think we're maybe going to surprise customers with some of the usage in actions and I would really hate for that to be a deterrent from using squad." +**Why:** User directive — strategic direction for the product. Actions usage can surprise customers with unexpected billing and loss of control. CLI-first puts the user in the driver's seat. + +### Current Actions Inventory (15 workflows) + +**Squad-specific (customer concern):** +1. `sync-squad-labels.yml` — Auto-syncs labels from team.md on push +2. `squad-triage.yml` — Auto-triages issues when labeled "squad" +3. `squad-issue-assign.yml` — Auto-assigns issues when squad:{member} labeled +4. `squad-heartbeat.yml` — Ralph heartbeat/auto-triage (cron currently disabled) +5. `squad-label-enforce.yml` — Label mutual exclusivity on label events + +**Standard CI/Release (expected):** +6. `squad-ci.yml` — Standard PR/push CI +7. `squad-release.yml` — Tag + release on push to main +8. `squad-promote.yml` — Branch promotion (workflow_dispatch) +9. `squad-main-guard.yml` — Forbidden file guard +10. `squad-preview.yml` — Preview validation +11. `squad-docs.yml` — Docs build/deploy +12-15. Publish/insider workflows + +**Directive:** Move squad-specific automation (1-5) into CLI commands. Keep standard CI/release workflows. + +### Follow-up (Brady, same session): +> "seems like the more we can offload to ourselves, the more we could control, say, in a container. if actions are doing the work the loop is outside of our control a bit" + +**Key insight:** CLI-first makes Squad **portable**. If the work lives in CLI commands instead of Actions, Squad can run anywhere — Codespaces, devcontainers, local terminals, persistent ACA containers. Actions lock the control loop to GitHub's event system. CLI-first means the user (or their infrastructure) owns the execution loop, not GitHub Actions. + + +# CLI Feasibility Assessment — GitHub Actions → CLI Commands +**Author:** Fenster (Core Dev) +**Date:** 2026-03-07 +**Context:** Brady's request to migrate squad-specific workflows to CLI commands + + +--- + +## Executive Summary + +**Quick wins:** Label sync + label enforce can ship in v0.8.22 (reuses existing parsers, zero new deps). +**Medium effort:** Triage command is 70% done (CLI watch already exists), needs GitHub comment posting. +**Heavy lift:** Issue assign + heartbeat need copilot-swe-agent[bot] API (PAT + agent_assignment field) — no `gh` CLI equivalent exists. Watch mode already implements heartbeat's core logic locally. + +**Key insight:** We already have `squad watch` — it's the local equivalent of `squad-heartbeat.yml`. The workflow runs in GitHub Actions with PAT; watch runs locally with `gh` CLI. They share the same triage logic (`@bradygaster/squad-sdk/ralph/triage`). + + +--- + +## 1. Current CLI Command Inventory + +**Existing commands** (`packages/squad-cli/src/cli/commands/`): + +| Command | Function | Overlap with Workflows | +|---------|----------|------------------------| +| **watch** | Ralph's local polling — triages issues, monitors PRs, assigns labels. Uses `gh` CLI. | ✅ 80% overlap with `squad-heartbeat.yml` + `squad-triage.yml` | +| plugin | Marketplace add/remove. Uses `gh` CLI for repo access. | ❌ No workflow overlap | +| export | Export squad state to JSON. | ❌ No workflow overlap | +| import | Import squad state from JSON. | ❌ No workflow overlap | +| build | SDK config generation. | ❌ No workflow overlap | +| doctor | Health checks (local/remote/hub). | ❌ No workflow overlap | +| aspire | Launch Aspire dashboard for OTel. | ❌ No workflow overlap | +| start | Interactive shell (Coordinator mode). | ❌ No workflow overlap | +| consult | Spawn agent for consultation. | ❌ No workflow overlap | +| rc/rc-tunnel | Remote control server + devtunnel. | ❌ No workflow overlap | +| copilot/copilot-bridge | Copilot SDK adapter. | ❌ No workflow overlap | +| link/init-remote | Link to remote squad repo. | ❌ No workflow overlap | +| streams | Workstream commands (stub). | ❌ No workflow overlap | + +**Key reusable infrastructure:** +- **`gh-cli.ts`** — Thin wrapper around `gh` CLI: `ghIssueList`, `ghIssueEdit`, `ghPrList`, `ghAvailable`, `ghAuthenticated` +- **`@bradygaster/squad-sdk/ralph/triage`** — Shared triage logic (routing rules, module ownership, keyword matching) +- **`watch.ts`** — Already implements triage cycle + PR monitoring + + +--- + +## 2. Per-Workflow Migration Plan + +### 2.1. sync-squad-labels.yml → `squad labels sync` + +**Current workflow:** 170 lines. Parses `team.md`, syncs `squad`, `squad:{member}`, `go:*`, `release:*`, `type:*`, `priority:*`, `bug`, `feedback` labels. Uses Octokit. + +**Proposed CLI command:** +```bash +squad labels sync [--squad-dir .squad] [--dry-run] +``` + +**Implementation:** +- **Size:** S (2-3 hours) +- **Dependencies:** + - ✅ `gh` CLI (already used in plugin.ts, watch.ts) + - ✅ `parseRoster()` from `@bradygaster/squad-sdk/parsers` (already exists) + - ✅ Thin wrapper — reuse roster parser, call `gh label create/edit` +- **Offline:** ❌ Needs GitHub API access via `gh` +- **Reuse:** Roster parsing (team.md → member list) already exists. Just needs label creation loop with `gh`. +- **Complexity:** Low. No auth complexity (uses `gh auth` flow). No copilot-swe-agent API. + +**Why quick win:** Zero new parsers needed. Label sync is idempotent (create-or-update pattern). Can run manually after `team.md` changes. + + +--- + +### 2.2. squad-triage.yml → `squad triage` (or extend `squad watch`) + +**Current workflow:** 260 lines. On `squad` label, parses `team.md` + `routing.md`, keyword-matches, applies `squad:{member}` label, posts comment. + +**Proposed CLI command:** +```bash +squad triage [--issue ] [--squad-dir .squad] +``` +Or: enhance `squad watch` to post comments (currently it only adds labels). + +**Implementation:** +- **Size:** M (4-6 hours) +- **Dependencies:** + - ✅ `gh` CLI (already used) + - ✅ `triageIssue()` from `@bradygaster/squad-sdk/ralph/triage` (already used in watch.ts) + - ❌ **Missing:** `gh issue comment` wrapper in `gh-cli.ts` (5 lines to add) +- **Offline:** ❌ Needs GitHub API +- **Reuse:** + - **watch.ts already does this** (line 189-209). Just missing comment posting. + - Triage logic, routing rules, module ownership — all implemented. +- **Complexity:** Low. The logic exists; just needs `gh issue comment --body ` wrapper. + +**Why medium effort:** Code exists. Just needs comment posting feature added to `gh-cli.ts` and called from `watch.ts`. + + +--- + +### 2.3. squad-issue-assign.yml → ??? + +**Current workflow:** 160 lines. On `squad:{member}` label, posts assignment comment, calls **copilot-swe-agent[bot] assignment API with PAT** (lines 116-161). + +**Problem:** The workflow uses a special POST endpoint: +```js +POST /repos/{owner}/{repo}/issues/{issue_number}/assignees +{ + assignees: ['copilot-swe-agent[bot]'], + agent_assignment: { + target_repo: `${owner}/${repo}`, + base_branch: baseBranch, + custom_instructions: '', + custom_agent: '', + model: '' + } +} +``` +**This endpoint does NOT exist in `gh` CLI.** It requires: +- Personal Access Token (PAT) with `issues:write` scope +- Direct Octokit call (cannot use `gh` as thin wrapper) + +**Migration options:** +1. **Add Octokit dependency** — heavyweight (35+ deps), violates zero-dependency CLI goal +2. **Add raw HTTPS module** — 50-100 lines to make authenticated POST with PAT, parse JSON response +3. **Document manual workflow** — "To auto-assign @copilot, use the GitHub Actions workflow (requires PAT)" + +**Proposed approach:** +- **Do NOT migrate.** Keep as workflow-only feature. +- **Reasoning:** The copilot-swe-agent assignment API is GitHub-specific and requires secrets (PAT). CLI commands should not manage secrets. Workflows already have secure secret storage. +- **Alternative:** Document `squad watch` as the local equivalent (it can label + post comments, but not trigger bot assignment). + +**Implementation:** +- **Size:** XL (8-12 hours if full migration) +- **Dependencies:** + - ❌ PAT management (needs secret storage or prompting) + - ❌ Octokit or raw HTTPS POST wrapper (50-100 lines) + - ❌ Not available in `gh` CLI +- **Offline:** ❌ Never (GitHub-specific API) +- **Complexity:** High. Requires secret handling, bot assignment API, error handling, fallback. + +**Recommendation:** **Do not migrate.** Keep as workflow. Document that copilot auto-assign requires Actions + PAT. + + +--- + +### 2.4. squad-heartbeat.yml → Already exists as `squad watch` + +**Current workflow:** 170 lines. Runs on cron (disabled), issues closed/labeled, PRs closed. Triages untriaged issues, assigns @copilot to `squad:copilot` issues. + +**CLI equivalent:** **Already shipped as `squad watch`** (`packages/squad-cli/src/cli/commands/watch.ts`, 356 lines). + +**What `squad watch` does:** +- Polls open issues with `squad` label +- Triages untriaged issues (adds `squad:{member}` label) +- Monitors PRs (draft/needs-review/changes-requested/CI failures/ready-to-merge) +- Runs on interval (default: 30 minutes) +- Uses `gh` CLI for auth + API access +- Uses shared `@bradygaster/squad-sdk/ralph/triage` logic + +**What `squad watch` does NOT do (that heartbeat.yml does):** +- ❌ Post triage comments (workflow posts "Ralph — Auto-Triage" comments) +- ❌ Auto-assign copilot-swe-agent[bot] (requires PAT + bot API, same issue as #2.3) + +**Implementation gap:** +- **Comment posting:** M (4-6 hours) — add `gh issue comment` wrapper to `gh-cli.ts`, call it from `runCheck()` in watch.ts +- **Copilot auto-assign:** Do not migrate (same as #2.3) + +**Migration plan:** +- ✅ **Already done.** `squad watch` is the local heartbeat. +- **Add comment posting** to match workflow behavior (quick win, 4-6 hours). +- **Document copilot auto-assign** as workflow-only (requires PAT). + +**Recommendation:** Enhance `squad watch` with comment posting. Keep copilot auto-assign in workflow. + + +--- + +### 2.5. squad-label-enforce.yml → `squad labels enforce` + +**Current workflow:** 180 lines. On label applied, removes conflicting labels from mutual-exclusivity namespaces (`go:`, `release:`, `type:`, `priority:`). Posts update comment. + +**Proposed CLI command:** +```bash +squad labels enforce [--issue ] [--squad-dir .squad] +``` + +**Implementation:** +- **Size:** S (2-4 hours) +- **Dependencies:** + - ✅ `gh` CLI (already used) + - ❌ `gh issue edit --remove-label