From 00e51a599d376d68fd61c7c3aeea9264a5d43c0b Mon Sep 17 00:00:00 2001 From: Copilot Date: Mon, 25 May 2026 17:57:13 +0300 Subject: [PATCH 1/7] chore: add Dependabot configuration Configure weekly Dependabot updates for the root npm workspace and GitHub Actions, with grouped non-major npm updates and a dedicated OpenTelemetry group for easier CVE review. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/dependabot.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..e3e4ee1de --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,35 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + target-branch: "main" + schedule: + interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Etc/UTC" + open-pull-requests-limit: 5 + labels: + - "dependencies" + groups: + opentelemetry: + patterns: + - "@opentelemetry/*" + npm-minor-and-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "main" + schedule: + interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Etc/UTC" + open-pull-requests-limit: 5 + labels: + - "dependencies" From a2ca2f7da2797544c790166cef50b1e8e6d799a8 Mon Sep 17 00:00:00 2001 From: Tamir Dresher Date: Mon, 22 Jun 2026 18:11:19 +0300 Subject: [PATCH 2/7] =?UTF-8?q?chore:=20promote=20v0.10.1=20dev=20?= =?UTF-8?q?=E2=86=92=20main=20+=20v0.10=20stabilisation=20blog=20post=20(#?= =?UTF-8?q?1313)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: auto-scaffold Fact Checker agent during init and cast (#1222) The Fact Checker role landed in v0.10.0 (#789) with catalog entry, charter template, skill, AGENT_TEMPLATES map entry, and template manifest entry — but was never wired into the user-facing onboarding flow. Users running 'squad init' got Scribe/Ralph/Rai but never saw Fact Checker as a default or cast option. This mirrors how Rai was wired: - init.ts: adds 'fact-checker' to the default agents: array passed to sdkInitSquad() - cast.ts: adds factCheckerMember(), factCheckerCharter(), hasFactChecker branches in castTeam(), and the roster banner line Smoke-tested locally: 'squad init' in a clean repo now produces .squad/agents/fact-checker/charter.md alongside scribe/ralph/Rai. Closes #1222 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(upgrade): auto-scaffold Rai + Fact Checker on squad upgrade (#1222) Extends the #1222 fix to the third code path. \squad upgrade\ was intentionally silent on agents (preserves user state). For users upgrading from v0.9.x or earlier (no Rai) or v0.10.0 (no fact-checker), this means they'd never get the built-in agents unless they re-ran \squad init\ (which would overwrite other state). Adds \nsureBuiltinAgents()\ to \ unEnsureChecks()\. Idempotent — only scaffolds when the agent directory is absent. Never overwrites existing charters or history files. Sources content from the shipped \ emplates/{Rai,fact-checker}-charter.md\ templates (already present via TEMPLATE_MANIFEST). Scribe and Ralph are intentionally NOT scaffolded by upgrade — they predate this fix in every squad, and their charters are inlined in cast.ts (no shipped template file). Smoke tested locally: - Set up a simulated v0.9.4 squad (scribe + ralph only) - Ran \squad upgrade\ → 'scaffolded 2 built-in agent(s): Rai, fact-checker' - Ran upgrade again → no re-scaffold (idempotent) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(permissions): use 'approve-once' for Copilot CLI v1.0.54+ contract (#1192) The Copilot CLI post-v1.0.54 changed the permission handler contract to expect 'approve-once' instead of 'approved'. Update the handler, type definition, and error hint to match the new contract. Closes #1191 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: Squad.Agents.AI - Microsoft Agent Framework adapter for the Squad CLI (#1207) * feat: Squad.Agents.AI community NuGet for MAF integration Squad CLI as Microsoft.Extensions.AI IChatClient, composing GitHub.Copilot.SDK via AsAIAgent() from Microsoft.Agents.AI.GitHub.Copilot 1.7.0-preview. Closes Track A of the Q1-Q7 design lock (see tamresearch1 .squad/decisions.md Decisions 441, 443, 444, 447). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: add PR body for #3 * fix(SquadAgent): inherit AIAgent (was IChatClient force-cast) - SquadAgent now properly inherits from Microsoft.Agents.AI.AIAgent - Removed (IChatClient)(object)agent force-cast - Overrides all AIAgent abstract members (CreateSessionCoreAsync, RunCoreAsync, etc.) - DI registration now registers AIAgent (not IChatClient) - README updated to use AIAgent.RunAsync API - No more abstraction inversion; AIAgent is the correct layer Fixes the architectural error identified by Tamir. * docs(SquadAgent): rewrite README — prerequisites, Hello World, troubleshooting, preview callout Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(SquadAgent): GitHubTokenProvider callback + redact options ToString Adds async token provider pattern for production scenarios (KeyVault/MSI integration). - GitHubTokenProvider property takes precedence over GitHubToken - GitHubToken marked [JsonIgnore] to prevent serialization leaks - SquadAgentOptions.ToString() redacts GitHubToken field - Updated CreateCopilotClient to resolve token from provider first Mitigates P0 #3: token leakage via ILogger structured-log calls, IOptions snapshots, and serializers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(SquadAgent): document GitHubTokenProvider callback for production token management Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(SquadAgent): bind ConnectionStrings__squad via IConfigureOptions + add smoke tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(README): replace removed WithTeamRoot with positional teamRoot ctor WithTeamRoot was deleted in commit 35767c90 in favor of mandatory positional teamRoot constructor argument on AddSquad. The Aspire example in the Squad.Agents.AI README still showed the old fluent API, which would now fail at compile time for anyone copy-pasting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(squad-agents-ai): add routing integration tests Closes the routing-verification gap identified during squad-squad onboarding: the API surface existed but routing semantics weren't functionally tested. New tests verify persona pass-through, boundary-instruction injection on first turn, WorkingDirectory isolation (Decision 452a), and CopilotClientOptions-based routing (Decision 447). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci(squad-agents-ai): add .NET build/test/pack workflow PR #3 CI was Node/docs-only — adding the .NET gate so green actually reflects the package code. Matrix on ubuntu + windows, restore/build/test/pack, uploads TestResults and nupkg artifacts. Closes the build-verification gap identified during squad-squad onboarding (see .squad/decisions.md adoption record, 2026-06-02). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci(squad-agents-ai): NuGet publish workflow + Dependabot config - .github/workflows/squad-agents-ai-release.yml: workflow_dispatch and tag-driven publish to nuget.org with --skip-duplicate idempotency, fail-fast on missing NUGET_API_KEY secret, optional GitHub Release on tag - .github/dependabot.yml: nuget (src + test) + github-actions, weekly, M.A.AI major allowed, OpenTelemetry major deferred (per Decision 602) Closes the release-pipeline + supply-chain-tracking gaps identified during squad-squad onboarding. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(squad-agents-ai): release-ready docs + .csproj packaging metadata - README updates / XML docs on public surface - CHANGELOG.md with [0.1.0-preview] - 2026-06-02 entry - .csproj: Description, RepositoryUrl, Authors, PackageTags, PackageReadmeFile - Verified via dotnet pack — .nupkg contains README, LICENSE, xml-docs Closes the docs-readiness gap for v0.1-preview publish. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci(squad-agents-ai): switch release triggers to dev/main branch-driven Per Tamir's release-strategy directive (decisions.md 2026-06-02): - dev merges → prerelease publish (suffix scheme mirrors Squad CLI) - main merges → stable publish - workflow_dispatch retained as manual escape hatch - tag-driven trigger removed (branches are the source of truth) Version derivation pattern adapted from the Squad CLI's existing release workflow. --skip-duplicate retained for idempotent reruns. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs+fix: PR #3 review pass — hygiene, XML docs, cliArgs, multi-named connections - Rewrite the package README and PR validation flow around the AIAgent surface and ambient Copilot authentication. - Keep public docs free of internal process references and remove obsolete deferral language. - Preserve connection-string cliArgs through CopilotClientOptions and cover the behavior with a routing test. - Add named connection-string lookup via AddSquadAgent("name") using ConnectionStrings:squad-{name}. - Document the new public overloads and verify the package builds without warnings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: Round 2 — keyed DI, BYOK delegate, routing gate, security hardening - Add ConfigureCopilotClient delegate on SquadAgentOptions for BYOK - Add routing gate: snapshot/restore Cwd/CliPath/CliArgs after delegate (Picard C1) - Add 4 AddKeyedSquadAgent overloads with .NET 8+ keyed DI - Fix Environment credential leak: [JsonIgnore] on Environment, GitHubTokenProvider, ConfigureCopilotClient - ToString() redacts token-pattern keys (TOKEN/KEY/SECRET/HMAC/PASSWORD/CREDENTIAL) - Add 21 new tests (43 total): security redaction, keyed DI, BYOK routing gate - Update README: streaming, keyed DI, BYOK, security sections Complies with: Picard C1-C4, Worf SC-1 through SC-8. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: add Squad.Agents.AI sample app demonstrating DI, keyed DI, BYOK, and streaming - samples/squad-agents-ai-sample/Program.cs: four runnable flows Flow 1 -- AddSquadAgent + RunAsync (basic DI) Flow 2 -- AddKeyedSquadAgent x2 + GetRequiredKeyedService Flow 3 -- ConfigureCopilotClient delegate (BYOK token + env var injection) Flow 4 -- RunStreamingAsync with await foreach token-by-token output - samples/squad-agents-ai-sample/Squad.Agents.AI.Sample.csproj: net10.0, project reference to src/Squad.Agents.AI, Microsoft.Extensions.Hosting 10.0.0 - samples/squad-agents-ai-sample/README.md: prerequisites, run commands, per-flow walkthrough, troubleshooting table - .github/workflows/squad-agents-ai-ci.yml: adds paths trigger and restore + build steps for the sample (no run step -- requires live CLI) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(Squad.Agents.AI): co-locate sample under src/ and consolidate README - Moves the sample app from samples/squad-agents-ai-sample/ to src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/ so it lives alongside the package it demonstrates. - Folds the sample's standalone README into the package README, giving consumers one canonical doc for both the API and the runnable demo. - Adds to Squad.Agents.AI.csproj so the library's wildcard glob does not pick up Program.cs in the co-located samples subdirectory. - Updates the .csproj project reference, and CI workflow paths to match the new layout. - Verified end-to-end: dotnet build, dotnet test (43/43 passing), and a sample sanity-check run (clear CLI-not-found error, no stack trace). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(Squad.Agents.AI): remove outdated draft PR body file The standalone pr-body.md was an early draft authored before the live PR description took its final shape. The PR body on GitHub is the canonical source; this file is dead weight and would confuse maintainers reviewing the diff. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(Squad.Agents.AI): address PR #1207 reviewer feedback (12 items) - Snapshot CliArgs by value, not reference, so in-place mutation by SDK consumers is also caught by the routing guard. - Validate name/connectionName/serviceKey is non-empty in all DI registration overloads; previously null/whitespace produced invalid connection-string keys. - Replace ghp_-prefixed placeholder in the sample with a clearly-fake token to avoid tripping secret-scanning and to remove a real-token lookalike. - Remove brittle 'PR #3' references from README and CHANGELOG; describe the feature without tying to a specific PR thread. - Update NuGet metadata and README links to point to bradygaster/squad (canonical repo) instead of the tamirdresher fork. - Multi-target the test project to match the package's target framework set so CI exercises every framework the package ships against. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(Squad.Agents.AI): adopt DelegatingAIAgent base + MAF ctor pattern (PR #1207 r2) Addresses westey-m's review feedback: - Extend Microsoft.Agents.AI.DelegatingAIAgent — drops ~70 lines of manual Core* overrides; pass-through is provided by the base class. - Adopt MAF constructor pattern: \(string squadFolderPath, SquadAgentOptions? options = null, ILoggerFactory? loggerFactory = null)\. Required settings on the constructor, options optional, ILoggerFactory stays on ctor for DI injection. - Routing-guard, IAsyncDisposable, and security posture preserved. - Add Squad.Agents.AI.slnx solution for easy IDE open. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Reno (Copilot) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Co-authored-by: tamirdresher * ci(Squad.Agents.AI): switch NuGet publish to Trusted Publishing (OIDC) (#1240) Replace the long-lived NUGET_API_KEY repo secret with NuGet/login@v1 OIDC token exchange (1-hour API key) per the modern Trusted Publishing flow: https://learn.microsoft.com/nuget/nuget-org/trusted-publishing Why --- - No long-lived credentials stored in the repo. - Token is scoped to this workflow + repo + branch via the OIDC subject claim and the Trusted Publishing policy registered on nuget.org. - Eliminates the chicken-and-egg between needing admin to set NUGET_API_KEY and needing the package live to validate the workflow. What changed ------------ - Add `id-token: write` to the publish job (required for OIDC). - Drop the `Verify NuGet API key` step. - Drop the `--api-key ` reference to secrets.NUGET_API_KEY. - Add `NuGet/login@v1` step that exchanges the OIDC token for a short-lived API key, exposed via `steps.nuget-login.outputs.NUGET_API_KEY`. - Add a fail-fast check for the new `vars.NUGET_USER` repository variable (non-sensitive; the nuget.org profile name that performs the exchange). - Update the file header documentation to reflect the new flow and link to the Trusted Publishing setup page. Required configuration before first publish ------------------------------------------- 1. Create the `Squad` organization on nuget.org and add owners. 2. Configure a Trusted Publishing policy at https://www.nuget.org/account/trusted-publishing owned by the Squad org: Repository Owner: bradygaster Repository: squad Workflow File: squad-agents-ai-release.yml Environment: (empty) 3. Set repository variable NUGET_USER (Settings → Secrets and variables → Actions → Variables) to a Squad-org-member's nuget.org profile name (NOT email). Variable, not secret — the username is non-sensitive. Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(Squad.Agents.AI): expose SessionConfig + default OnPermissionRequest (#1252) Squad.Agents.AI 0.1.0-preview.2 calls `CopilotClient.AsAIAgent(instructions, name)` which leaves `SessionConfig.OnPermissionRequest` unset. The first call to `SquadAgent.CreateSessionAsync()` therefore throws: System.ArgumentException: An OnPermissionRequest handler is required when creating a session. For example, to allow all permissions, use CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll }); There was no public way for consumers to fix it from the outside — `ConfigureCopilotClient` only exposes `CopilotClientOptions`, not the per-session `SessionConfig` that owns the permission handler. What changed ------------ - `SquadAgent` now switches to the `AsAIAgent(client, sessionConfig, ...)` overload and constructs a `SessionConfig` with: - `OnPermissionRequest = PermissionHandler.ApproveAll` (sensible default for a host-process Squad adapter; the host already chose to instantiate Squad and is responsible for sandboxing). - `WorkingDirectory` defaulting to the resolved `Cwd` / `SquadFolderPath`. - `SystemMessage = new SystemMessageConfig { Content = Instructions }` when `SquadAgentOptions.Instructions` is set. - `SquadAgentOptions.ConfigureSession: Action?` is new — runs after Squad applies its defaults so a consumer can swap in a stricter permission handler, pin the model, restrict tools, etc. Tests ----- - New `SquadAgentSessionConfigTests` (4 cases) exercising the new surface: `ConfigureSession` is settable, runs against the live SessionConfig, can replace the permission handler, and can set `AvailableTools`. - All existing 43 tests still pass per TFM (141 total across net8/9/10). Verified -------- - The end-to-end consumer smoke test in `C:\Users\tamirdresher\source\repos\squad-agents-ai-consume-test` previously had to fall back to the raw SDK because of the missing handler. With this change, `SquadAgent.CreateSessionAsync()` returns successfully and `RunAsync` drives real 3-turn conversations against `.squad/`-init'd teams. Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Squad.Agents.AI 0.2.0: bump to MAF 1.10.0-rc1 / SDK 1.0.0 GA — file-IO works (#1259) * Squad.Agents.AI: bump to MAF 1.10.0-rc1 / SDK 1.0.0 GA — file-IO now works Microsoft.Agents.AI.GitHub.Copilot bumped from 1.7.0-preview to 1.10.0-rc1, which transitively brings GitHub.Copilot.SDK 1.0.0 GA. SDK 1.0.0 reshaped CopilotClientOptions and SessionConfig (the breaking namespace + property renames are mirrored below), but in return Copilot CLI 1.0.61 talks to the SDK over the new ACP-style protocol — meaning the agent's view/grep/powershell tools finally work end-to-end without 'permission errors' or 'content exclusion policy' hallucinations. SDK 1.0.0 API migrations applied: * Namespace GitHub.Copilot.SDK -> GitHub.Copilot. * CopilotClientOptions.Cwd -> WorkingDirectory. * CopilotClientOptions.CliPath + .CliArgs collapsed into a single CopilotClientOptions.Connection (RuntimeConnection). We now build the Connection via RuntimeConnection.ForStdio(CliPath, CliArgs) only when the consumer supplied either a custom CLI path or extra CLI args; otherwise the SDK's default child-process connection is used and the bundled copilot.exe (downloaded by the SDK's build/ targets) is invoked. * SessionConfig.ConfigDir -> SessionConfig.ConfigDirectory. * PermissionRequestHandler is no longer a named delegate type; we use type inference where the test code referenced it. Public Squad.Agents.AI surface is intentionally unchanged: SquadAgentOptions still exposes Cwd, CliPath, CliArgs, ConfigureSession, ConfigureCopilotClient. We translate to the SDK 1.0.0 shape internally. Routing gate (Picard Condition 1 / Worf SC-3) updated to snapshot and restore WorkingDirectory + Connection instead of the old Cwd / CliPath / CliArgs trio. A delegate that REPLACES Connection (e.g. via RuntimeConnection.ForStdio(...)) is reverted, mirroring the previous CliPath / CliArgs hijack tests. Verified end-to-end against a real .squad-initialised team root: the coordinator successfully reads .squad/team.md and enumerates every cast member with their role. The dotnet test suite passes 46/46 across net8.0, net9.0, and net10.0 (-1 vs baseline because the old CliPath and CliArgs hijack tests collapsed into a single Connection hijack test, which is the right granularity for SDK 1.0.0). Version bumped 0.1.0-preview -> 0.2.0 to surface the MAF/SDK transitive bump. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address Copilot review feedback (#1259) - Remove duplicate 'using GitHub.Copilot' in SquadAgentSessionConfigTests (regex replace inadvertently doubled the directive when migrating from the old GitHub.Copilot.SDK namespace). - Drop hardcoded teamRoot override I left behind in the sample Program.cs during the CLI-not-found debug session. The sample now correctly reads SQUAD_TEAM_ROOT (or falls back to CWD) as documented. - Fix mismatched comment in SquadAgent: the SDK-protocol section now correctly references --allow-all (matching the flag actually injected in CreateCopilotClient), not the narrower --allow-all-tools. - Detect more existing permission-opening flags before injecting our default --allow-all so a host that opts in via --allow-all-paths, --allow-all-urls, or the omnibus --yolo no longer gets --allow-all prepended on top. Comparison is now case-insensitive. Updated the connection-string test that asserted the old over-eager behavior. Also add .vs/, *.user, *.userprefs to .gitignore so VS solution junk doesn't surface in git status. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address pre-existing Copilot review comments from PR #1212 (#1258) - Workflow permissions: add issues: write to squad-pr-nudge, squad-impact, and squad-repo-health workflows that call issues.createComment - Logic bug: fix ahead_by → behind_by in pr-nudge stale branch check - Logic bug: fix PR_LABELS fallback producing string instead of null - Script fix: check legacy statuses for failure/error in checkCIStatus() - Script fix: truncation row column count mismatch in pr-readiness.mjs - Script fix: validate JSON.parse result is array before using as labels - Script fix: isNodeBuiltin now validates node: prefix against known builtins - YAML escaping: use JSON.stringify for skill descriptions in apm.yml Closes #1213 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: expose memory tools via MCP + route writes through governed pipeline (#1251) - Expose memory.classify, memory.write, memory.search, memory.promote, memory.delete, memory.audit through the squad_state MCP server (#1244) - Pin squad_state to user-level ~/.copilot/mcp-config.json during init/upgrade for external `copilot -p` mode compatibility (#1247) - Update squad.agent.md directive-capture and decision-recording instructions to route through memory.write instead of raw squad_state_write to the drop-box (#1246) Closes #1244 Closes #1247 Closes #1246 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(skills): update path references from .squad/skills/ to .copilot/skills/ (#1260) The CLI and SDK write skills to .copilot/skills/ by default, but docs still referenced .squad/skills/. Update all documentation to use the canonical .copilot/skills/ path and add a note about legacy fallback. Closes #1241 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(init): prompt to add @copilot as a team member during squad init (#1262) Add an interactive prompt during squad init that asks users if they want to add @copilot (the GitHub Copilot coding agent) as an autonomous team member. If accepted, adds the Coding Agent section to team.md and copies copilot-instructions.md into the project. Non-interactive mode skips silently with a hint to run `squad copilot enable` later. Closes #1147 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: align skills path + state-backend upgrade flow (#1241, #1226) (#1249) - Update all docs references from .squad/skills/ to .copilot/skills/ - Note that both paths are scanned at read time but .copilot/skills/ is write default - List all 6 git hooks (add pre-commit and post-commit to docs) - Correct 'read-only reference' claim about migrated files - Add recovery section for pre-commit hook refusal scenarios Closes #1241 Closes #1226 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Squad.Agents.AI 0.3.0: first-class subagent observability (#1265) Adds a typed OnSubagentTrace callback + OpenTelemetry ActivitySource so consumers can see subagent dispatch (the coordinator's 'task' tool spawning specialist sub-agents and their replies) without writing their own polymorphic dispatch over the raw GitHub.Copilot SessionEvent hierarchy. The previous 0.2.x surface required consumers to write the same boilerplate the aspire-squad-resource demo had to (CopilotSessionTraceMapper.cs): switch on every event subtype, unwrap subagent context, manage Activity lifetime. 0.3.0 makes that the SDK's job. New public surface: * SquadAgentOptions.OnSubagentTrace (Action) Set this to subscribe to subagent lifecycle (Selected / Started / Completed / Failed), assistant messages from coordinator AND subagents, tool start/complete, and SessionIdle. Setting OnSubagentTrace implicitly turns on SessionConfig.IncludeSubAgentStreamingEvents so subagent replies actually flow up to the parent session (otherwise they stay inside the subagent session and never reach the callback). * SquadAgentTraceEvent record — typed envelope (Kind, RawEventType, Timestamp, SdkAgentId, SubagentName, SubagentDisplayName, ToolCallId, Content, Success, RawEvent). Carries the original SessionEvent on RawEvent for advanced consumers but exposes everything else through neutral primitive types so the callback signature has no transitive dependency on GitHub.Copilot.SDK. * SquadAgentTraceEventKind enum — categorises the SessionEvent into the well-known cases that downstream observability surfaces want. * SquadAgentDiagnostics.ActivitySourceName ('Microsoft.Agents.AI.Squad') + SquadAgentDiagnostics.ActivitySource — one Activity per subagent dispatch is opened on SubagentStartedEvent and disposed on the matching SubagentCompletedEvent / SubagentFailedEvent, tagged with squad.subagent.name, squad.subagent.display_name, squad.subagent.sdk_agent_id, and squad.subagent.reply_preview (a short truncated copy of the subagent's assistant message). Hosts that .AddSource(SquadAgentDiagnostics.ActivitySourceName) on their OpenTelemetry tracer get these spans in their backend — the Aspire dashboard renders them in the trace view automatically. Internal: * SquadSubagentTraceMapper — wires SessionEvent -> SquadAgentTraceEvent and the Activity lifecycle. Held by SquadAgent and disposed during DisposeAsync to drain any subagent activities that never received a matching Completed event (e.g. session ended mid-dispatch). * InternalsVisibleTo=Squad.Agents.AI.Tests so the mapper can be unit tested directly without spinning a real CLI session. Tests: 54/54 passing across net8, net9, net10 (+8 new tests for the observability surface including Activity lifetime, tag propagation, mid-session disposal, and consumer-callback exception isolation). Verified end-to-end against the tamresearch1 Star Trek squad: the OnSubagentTrace callback observed two parallel subagent dispatches (Picard, Data), captured each of their replies attributed to the right SdkAgentId, and the matching 'squad.subagent Picard' / 'squad.subagent Data' OTel spans opened and closed cleanly with the reply tagged on each span. Version bumped 0.2.0 -> 0.3.0 to signal the new public surface. Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(lockfile): sync package-lock with package.json after v0.10.0 bump (#1266) After the v0.10.0 stable release on 2026-06-07, the root package-lock.json still recorded packages/squad-cli@0.9.6-preview.15 and packages/squad-sdk@0.9.6-preview.13 for the workspace entries. The package.json files were updated correctly during the release, but the lockfile workspace metadata was not regenerated. This causes `npm ci --ignore-scripts` to fail in the `sdk-exports-validation` CI job whenever package.json versions diverge from these stale lockfile entries (which happens on every build that runs `scripts/bump-build.mjs`). Confirmed on PR #1257. This commit regenerates only the workspace version metadata (`packages/squad-cli` and `packages/squad-sdk`) — no dependency trees are touched. Verified locally on Windows + Node v23.5.0: - `npm ci --ignore-scripts` at repo root: ✅ exit 0 (was already passing on dev because root package.json/lockfile match; failure mode is the workspace-entry mismatch surfacing under specific build conditions) - `npm install --ignore-scripts`: no further drift produced - Full `npm test` suite: 6535 passed / 134 failed / 60 skipped — the 134 failures are all pre-existing Windows file-locking flakiness (EBUSY/ENOTEMPTY/hook-timeout); identical failure mode and similar count on the immediate pre-merge commit cc37a2f7 (125 failures pre-merge, 134 post-merge — diff is within flake noise). - Targeted re-run of the 5 "newly failing" files in isolation: 93/93 passing — confirms the bulk-run failures are concurrency-induced flakes, not regressions from the recent merges (#1251, #1258, #1260, #1262, #1249). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(bump-build): also update package-lock workspace entries (#1267) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: release pipeline version pinning (#1203, #1204) (#1250) * fix: release pipeline version pinning (#1203, #1204) - Lower SDK dependency floor from >=0.10.0 to >=0.9.0 so the CLI tarball resolves against the last published SDK when current version isn't yet on the registry (Closes #1203) - Add isLocalOrUnpublishedVersion guard so local dev builds and versions with build metadata (+) fall back to @insider instead of writing unresolvable version strings into MCP config (Closes #1204) - Extend resolveSquadStateMcpSpec to short-circuit for build-metadata versions - Add CI step to verify SDK dependency is resolvable before CLI publish Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: reset accidentally-bumped version 0.10.0-build.2 -> 0.10.0 Pre-publish version guard rejects -build.N suffixes (release pipeline policy). The bump was made by an unintended local 'npm run build' run before commit. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher Co-authored-by: Copilot * chore: include CHANGELOG.md in published npm tarball (#1257) * chore: include CHANGELOG.md in published npm tarball Add CHANGELOG.md to the files array in both squad-cli and squad-sdk package.json files so changelogs are included in published npm tarballs. This enables offline what's-new prompts and removes the need for GitHub API calls to show release notes. Closes #1171 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: reset accidentally-bumped versions to 0.10.0 Pre-publish version guard rejects -build.N suffixes (release pipeline policy). Both SDK and CLI package.json had -build.4 from a local 'npm run build' run before commit. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher Co-authored-by: Copilot * Squad.Agents.AI 0.4.0: default-on subagent observability + Aspire-style connection-string lookup (#1271) Two small but high-impact changes that remove ~30 lines of boilerplate from every consumer (Aspire and otherwise) and make the OpenTelemetry story self-explanatory. ## 1. EmitSubagentActivities (default true) — telemetry independent of callback Today, the per-subagent OpenTelemetry Activity emission is a side-effect of setting `OnSubagentTrace`. A host that just wants `squad.subagent {Name}` spans in their backend has to wire a callback they don't need. `Microsoft.Agents.AI.Squad` is silent until then. 0.4.0 makes activity emission the default: * New `SquadAgentOptions.EmitSubagentActivities` (defaults to `true`). * `SquadAgent` installs `SquadSubagentTraceMapper` whenever `EmitSubagentActivities || OnSubagentTrace != null`, so spans flow with zero extra wiring. * `OnSubagentTrace` becomes a pure customisation hook (logging, dashboards, metrics) — independent of telemetry. Set `EmitSubagentActivities = false` to opt out of built-in spans when you want to handle telemetry yourself. Plus richer span shape: every lifecycle phase is now an `ActivityEvent` on the live subagent span (visible as annotated markers on the timeline in Aspire / Jaeger / etc.): * `squad.subagent.start` — on SubagentStarted * `squad.subagent.message` — on AssistantMessage (with message_preview tag) * `squad.subagent.completed` — on SubagentCompleted * `squad.subagent.failed` — on SubagentFailed Net effect for a consumer: builder.Services.AddOpenTelemetry() .WithTracing(t => t.AddSource(SquadAgentDiagnostics.ActivitySourceName)); builder.Services.AddSquadAgent(o => o.SquadFolderPath = "/team"); …and the dashboard lights up. No callback wiring, no Activity.Current?.AddEvent plumbing in the host. ## 2. Aspire-style connection-string lookup (with legacy fallback) Aspire injects connection strings under the literal resource name — e.g. an AppHost that calls `builder.AddSquad("research-squad", ...)` exposes `ConnectionStrings:research-squad` to the consumer. The 0.3.0 SDK only looked at `ConnectionStrings:squad-research-squad` (prefixed), so Aspire consumers had to manually call `Configuration.GetConnectionString(name)`, parse the URI, and feed `SquadFolderPath` into the configure callback themselves. 0.4.0 tries the literal name first and falls back to the legacy prefixed form: | Style | Example | Lookup | |--------------------------------------|---------------------------------------------|--------------------------------------------| | Aspire-style direct (tried first) | `AddSquadAgent("research-squad")` | `ConnectionStrings:research-squad` | | Legacy prefixed fallback | `AddSquadAgent("research")` | `ConnectionStrings:squad-research` | Both work. Existing consumers using `ConnectionStrings:squad-{name}` continue unchanged; new Aspire consumers get the natural one-line registration. ## Tests 54 → 64 tests, all passing on net8.0/9.0/10.0. New `SquadAgentDefaultObservabilityTests` covers: * Default-on activity emission (without consumer callback) * Opt-out path (`EmitSubagentActivities = false`) — span suppression + callback still fires * Each ActivityEvent name (`start` / `message` / `completed` / `failed`) * Connection-string precedence: Aspire-direct preferred, prefixed fallback used, same rule applies for keyed registrations Existing `SquadSubagentTraceTests` and the new class share an `[Collection("SquadActivityListeners")]` so they run serially — process-global `ActivityListener` state caused cross-test pollution otherwise. ## Files * `src/Squad.Agents.AI/SquadAgentOptions.cs` — new `EmitSubagentActivities` property + reworked `OnSubagentTrace` XML doc to clarify independence. * `src/Squad.Agents.AI/SquadAgent.cs` — install trace mapper when telemetry OR callback is requested. * `src/Squad.Agents.AI/SquadSubagentTraceMapper.cs` — accept `emitActivities` flag; gate `StartActivity`/`Dispose` on it; add `ActivityEvent` annotations at every lifecycle boundary. * `src/Squad.Agents.AI/SquadAgentOptionsConfigurator.cs` — accept a list of candidate connection-string names; first non-empty wins. * `src/Squad.Agents.AI/SquadServiceCollectionExtensions.cs` — new `GetConnectionStringNames` returns `[name, "squad-"+name]` so both Aspire and legacy conventions resolve. * `src/Squad.Agents.AI/Squad.Agents.AI.csproj` — bump 0.3.0 → 0.4.0. * `src/Squad.Agents.AI/README.md` — new "Subagent observability" section, updated "Aspire / configuration path" section, two new option rows in the Key Options table. ## Backward compatibility Fully backward compatible. The two-arg `SquadSubagentTraceMapper` constructor defaults `emitActivities` to `true`, `EmitSubagentActivities` defaults to `true`, and the legacy `ConnectionStrings:squad-{name}` lookup still resolves. Net change for an existing consumer that had OnSubagentTrace set: nothing (mapper runs in both 0.3.0 and 0.4.0 because OnSubagentTrace is non-null). Net change for a consumer that did NOT set OnSubagentTrace but did AddSource: they now get spans they always asked for. Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Squad.Agents.AI 0.5.0: auto-inject --agent squad (CLI parity by default) (#1275) * Squad.Agents.AI 0.5.0: auto-inject --agent squad (CLI parity by default) The whole point of SquadAgent is to wrap a Squad coordinator team — but the 0.4.x SDK launched the underlying copilot.exe with the CLI's built-in generic agent. The coordinator therefore had no instructions to eager-execute, fan out, or dispatch via the task tool, so it role-played responses inline. Concretely: this SDK call builder.Services.AddSquadAgent(o => o.SquadFolderPath = teamRoot); did NOT behave the same as running copilot --agent squad interactively against the same team root. Consumers had to remember to add `opts.CliArgs.Add(""--agent""); opts.CliArgs.Add(""squad"");` themselves, which is an SDK leak — the class is literally called SquadAgent. 0.5.0 makes --agent squad the SDK default: * New `SquadAgentOptions.AgentFileName` (defaults to `""squad""`). * On client construction, SquadAgent looks for `{teamRoot}/.github/agents/{AgentFileName}.agent.md`. If it exists, `--agent {AgentFileName}` is auto-prepended to the CLI args. * If the file is missing (folder not Squad-initialized), the inject is silently skipped and a Debug log line explains why. The CLI then starts with its default agent, which is what 0.4.x did anyway. * If the consumer already supplied `--agent X` in `CliArgs`, the explicit value wins and we do NOT add a second one. * Set `AgentFileName = null` (or whitespace) to opt out entirely. Net effect: SquadAgent.RunAsync now matches `copilot --agent squad` for any Squad-initialized team root, without the consumer doing anything. ## Tests 64 -> 71 tests, all passing on net8.0/9.0/10.0. New `SquadAgentDefaultAgentFlagTests` (uses a per-test temp dir to scaffold or omit the agent file deterministically): * Default AgentFileName is ""squad"" * Auto-inject when squad.agent.md exists * No inject when the file is missing (graceful degradation) * Explicit --agent in CliArgs wins (no second --agent added) * Custom AgentFileName=""data"" injects --agent data when data.agent.md exists * AgentFileName=null opts out entirely * AgentFileName=whitespace opts out entirely Backward compatibility: existing routing tests use a non-existent `C:\squad-team-root` path, so the file-existence check silently skips the inject — those tests continue to pass with no changes. ## Files * `src/Squad.Agents.AI/SquadAgentOptions.cs` — new `AgentFileName` property with XML doc covering the default, the opt-out, and the not-yet-initialized fallback. * `src/Squad.Agents.AI/SquadAgent.cs` — auto-inject logic in `CreateCopilotClient` (after the `--allow-all` block, before `options.CliArgs` are appended) with file-existence + already-supplied guards and a Debug log when the file is missing. * `src/Squad.Agents.AI/Squad.Agents.AI.csproj` — bump 0.4.0 -> 0.5.0. * `src/Squad.Agents.AI/README.md` — new ""Coordinator agent selection"" section with the precedence table; `AgentFileName` row added to Key Options table. * `+test/Squad.Agents.AI.Tests/SquadAgentDefaultAgentFlagTests.cs` Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * amend: use SessionConfig.Agent instead of --agent CLI args GitHub.Copilot SDK's SessionConfigBase exposes an Agent (string) property that is the first-class equivalent of the Copilot CLI's --agent flag. It discovers and loads .github/agents/{name}.agent.md exactly the same way the CLI does, but without us having to munge CliArgs. Switch the 0.5.0 default-coordinator-agent implementation: - SquadAgent now sets sessionConfig.Agent = options.AgentFileName (default ""squad"") right after constructing the SessionConfig, before ConfigureSession runs. - Drop the --agent CliArgs hack (we no longer need to detect ""did the consumer already pass --agent?"" because ConfigureSession naturally wins over our default). - Tests now assert against sessionConfig.Agent via reflection over the inner DelegatingAIAgent — exactly what consumers using ConfigureSession would see. - README ""Coordinator agent selection"" section reworded to say ""sets SessionConfig.Agent"" instead of ""auto-adds --agent"". 71/71 tests still pass on net8.0/9.0/10.0. The fifth new test (ConfigureSession_CanOverrideAutoSetAgent) explicitly proves the ConfigureSession callback can replace the auto-set value, which is the clean override path now that --agent CliArgs is gone. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Squad.Agents.AI 0.5.1: fix 0.5.0 regression — use --agent CLI flag (not SessionConfig.Agent) (#1277) 0.5.0 (#1275) replaced the previous --agent CliArgs approach with sessionConfig.Agent = options.AgentFileName, on the theory that the SDK property was the first-class equivalent of the CLI's --agent flag. It is not. SessionConfig.Agent looks up the name in the SDK's CustomAgents registry (programmatic agent definitions, never populated by SquadAgent), NOT in .github/agents/*.agent.md files on disk. The result was a runtime error on every RunAsync call against a Squad-initialised team: Communication error with Copilot CLI: Request session.create failed with message: Custom agent 'squad' not found Verified at GitHub.Copilot.SDK 1.0.0: * SessionConfigBase.Agent (string) — selects from CustomAgents * SessionConfigBase.CustomAgents (IList) — programmatically defined inline agents (Name, Prompt, Tools, Skills, Model, etc.). Empty by default. * The CLI's --agent flag is currently the only path that reads .github/agents/{name}.agent.md on disk. 0.5.1 reverts to the original CliArgs implementation: * SquadAgent now auto-prepends '--agent {AgentFileName}' to combinedCliArgs (back to what 0.5.0 originally proposed before the SessionConfig.Agent detour). * The file-existence check at {teamRoot}/.github/agents/{name}.agent.md still gates the inject so non-Squad-initialized folders degrade gracefully (no --agent passed -> CLI uses default agent). * The 'consumer already supplied --agent in CliArgs' guard is back so the SDK does not add a duplicate. Tests: * New SquadAgentDefaultAgentFlagTests covers all seven cases via reflection over Connection.Args (the CLI-args path the SDK actually uses): default 'squad' value, auto-inject when file exists, no inject when missing, explicit --agent wins, custom AgentFileName works, AgentFileName=null/whitespace opts out. * The older SquadAgentDefaultAgentTests (which targeted SessionConfig.Agent) is removed since that property does NOT do what we wanted. 71/71 tests passing on net8.0/9.0/10.0. Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add squad registry add/list/remove for discovery-only peer squads (#1291) Closes #1290. Adds CLI surface for managing .squad/squad-registry.json, symmetric to squad upstream. Registry entries are discovery-only (visible to squad discover and squad delegate) but do NOT trigger inheritance of the peer squad's skills/decisions/wisdom/routing. Previously users had to hand-edit .squad/squad-registry.json — even the squad discover empty-state hint told them to "create a squad-registry.json" manually. This adds proper commands: squad registry add # validates manifest, refuses duplicate squad registry list # show all registered peers squad registry remove # remove by name Also fixes a subtle path-semantics confusion: readManifest() now accepts BOTH the repo root AND a path with a trailing .squad segment. The docs and SKILL.md showed the .squad-suffixed form but the code previously joined .squad/manifest.json onto whatever you gave it, so the suffixed form silently failed empirical reproduction (discover returned nothing). Test coverage: 18 new tests in cross-squad-registry.test.ts covering the dual-path readManifest fix, registry round-trip, add/list/remove behavior including duplicate-name and invalid-manifest rejection, and end-to-end integration with discoverSquads. Also updates: - cross-squad SKILL.md (canonical + 2 template mirrors) to document the registry vs upstream distinction explicitly - squad discover empty-state hint to mention squad registry add - squad help text with the new commands - SDK exports for the new registry helpers + RegistryEntry/ AddRegistryEntryResult types End-to-end verified locally: - squad registry add (both repo-root and .squad-suffixed paths) - squad registry list (rich output) - squad registry remove (success + missing-name warning) - squad discover picks up registry entries with source=registry - 50/50 tests pass (32 existing + 18 new) - npm run build clean Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): stop writing squad_state to ~/.copilot/mcp-config.json on every init/upgrade (#1296) (#1298) * fix(cli): stop writing squad_state to ~/.copilot/mcp-config.json on every init/upgrade (#1296) squad init (init.ts:408) and squad upgrade (upgrade.ts:738) unconditionally called ensureSquadStateMcpInUserConfig, writing squad_state_ to ~/.copilot/mcp-config.json keyed by a stable project-path hash. Each new squad init accumulated another entry in HOME with no garbage collection. This contradicted the explicit iter-8 design intent documented at packages/squad-cli/src/cli/core/mcp-root.ts:1-27, which says iter-8 stops writing to HOME and writes squad_state ONLY to repo-root .mcp.json. The repo-root .mcp.json writes (init.ts:403 / upgrade.ts:728) already cover all documented Copilot CLI launch modes - copilot and copilot -p both walk up from cwd to find .mcp.json. Out-of-tree copilot -p invocations should use --additional-mcp-config @.mcp.json (already documented at init.ts:494). Changes: * Removed the unconditional ensureSquadStateMcpInUserConfig call from init.ts:408 and upgrade.ts:738. Replaced both with comments explaining iter-8 + #1296. * Removed the now-unused import from both files. * Kept the function definition at mcp-root.ts:178-228 - a future squad doctor --mcp-prune cleanup helper may want to inspect HOME. Tests: * New regression test in test/cli/init.test.ts: "should NOT write any squad_state entries to ~/.copilot/mcp-config.json (#1296)". Isolates the developer's real HOME by setting USERPROFILE/HOME to a temp dir before init, asserts no squad_state* keys appear under temp HOME. * All 40 existing init tests still pass. npm run lint clean. Closes #1296 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: comments reference actual Copilot CLI version (≥1.0.59), drop hardcoded test line numbers Reviewer follow-ups on #1298 (closes #1296): 1. The comments in init.ts/upgrade.ts/mcp-root.ts referenced 'Copilot CLI 5.3+' as the version that auto-loads .mcp.json. The real shipping CLI is at 1.0.62 (5.3 was a typo'd projection from review). Updated all 5 occurrences to '≥1.0.59' — the lowest version where the .mcp.json walk-up behavior is documented. 2. The init.test.ts regression comment hard-coded line numbers (init.ts:408, upgrade.ts:738) that will go stale on any unrelated edit to those files. Rewrote the comment to identify the call by function name (ensureSquadStateMcpInUserConfig) instead — durable against re-orderings. Verified: regression: #1296 test still passes (1/16 in init.test). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(init): post-init package.json script tip includes --agent squad The post-init tip showing how to add a non-interactive `squad:copilot` script to package.json was: "squad:copilot": "copilot --additional-mcp-config @.mcp.json" This omits `--agent squad`, so users who copy/paste it get a generic Copilot CLI session that doesn't load the Squad coordinator, team.md, casting, or MCP-wired memory/state tools — only the additional MCP config gets loaded. Same underlying issue surfaced in the cross-squad- communication SKILL.md sweep (squad/wire-cross-squad-skill commit 6b0eac21): anywhere we spawn `copilot` into a Squad-initialised repo, we must pass `--agent squad`. Single-line fix: "squad:copilot": "copilot --agent squad --additional-mcp-config @.mcp.json" Verified: a fresh `squad init` smoke test now prints the corrected tip line verbatim. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(sdk): wire team.md/routing.md/casting state on \squad preset apply\ (#1288) (#1293) * fix(sdk): wire team.md/routing.md/casting state on `squad preset apply` (#1288) `squad preset apply ` only copied agent charters into .squad/agents/. It left .squad/team.md `## Members` empty, .squad/routing.md missing `## Work Type -> Agent` rows, and never created .squad/casting/registry.json, history.json, or policy.json. Net result: the coordinator's mode-switch check saw an empty Members table and treated every session as Init Mode, proposing to re-scaffold the team the user already applied. This change adds a merge-friendly scaffold module (packages/squad-sdk/src/presets/scaffold.ts) that runs after charters are copied and: * writes/updates team.md `## Members` (creates from scratch if missing; appends new rows to an existing table while preserving the surrounding Coordinator / Project Context sections; idempotent on repeat apply) * writes/updates routing.md `## Work Type -> Agent` (creates or appends) * writes/merges casting/registry.json (universe = `preset:`) * appends a snapshot to casting/history.json + a universe_usage_history entry * creates casting/policy.json with defaults only if missing (never clobbers) Agents with `status: 'error'` are excluded from wiring; agents with `status: 'skipped'` (already exist in target) ARE wired so the team reflects user intent. Scaffolder failure is reported as a synthetic error result without masking the per-agent install results. Verified: * npm run lint passes * test/presets.test.ts: 28/28 pass including 4 new regression tests - wires preset agents into team.md ## Members (#1288) - merges preset agents into an existing team.md without duplicating rows - writes casting registry.json, history.json, and policy.json (#1288) - appends routing rows for preset agents to routing.md (#1288) Out of scope (tracked separately): deduplicating these writers with the equivalent fresh-write versions in packages/squad-cli/src/cli/core/cast.ts. Closes #1288 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(presets): role-aware Status cell + non-colliding sentinel for synthetic scaffold error Two reviewer follow-ups on #1293 (closes #1288): 1. Members-table Status was hardcoded to '✅ Active' for every preset agent. Presets that ship one of the always-on built-ins (Scribe, Ralph, Rai, Fact Checker) would render with the wrong status label compared to a fresh cast: '✅ Active' instead of '📋 Silent' / '🔄 Monitor' / '🛡️ RAI' / '🔍 Verifier'. Added a small statusForRole() helper that mirrors the role→status mapping in cast.ts:652-655 (case-insensitive role matching to tolerate preset authors who lowercase the role string). Built-in role names get their canonical labels; everything else falls back to '✅ Active'. Added a regression test asserting the labels for a preset that ships scribe/ralph/rai/fact-checker + one regular agent. 2. Synthetic scaffold-failure result row used 'agent: presetName' for its 'agent' field. If the preset itself happens to include an agent literally named after the preset ('squad preset apply geektime' on a preset whose roster has a 'geektime' agent), the consumer of PresetApplyResult[] could not distinguish the synthetic scaffold- level error from a real per-agent install error. Replaced with the angle-bracketed sentinel '' (which validateName() rejects, so it can never collide with a real agent name) and moved the preset name into the human-readable reason string so consumers don't lose that context. Verified: 29/29 preset tests pass (28 existing + 1 new role-status test). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(docs): tell coordinator to roster Fact Checker on first-time cast (#1299) (#1300) * fix(docs): tell coordinator to roster Fact Checker on first-time cast (#1299) squad init correctly creates .squad/agents/fact-checker/ on disk (per merged PR #1223). But when the user opens copilot --agent squad and the coordinator runs first-time casting, it OMITS Fact Checker from the team.md ## Members table while including Scribe, Ralph, and Rai. Root cause: .squad-templates/squad.agent.md had two gaps: 1. Line 56 said "team size (typically 4-5 + Scribe)" — naming only Scribe 2. Rai had a dedicated ## Rai section with explicit "Rai always appears in team.md" instruction — Fact Checker had no equivalent section So the model added Rai (because instructed to) but had no instruction to add Fact Checker, even though the agent dir was scaffolded on disk. Fix: * Update team-size line to name all 4 always-on built-ins: Scribe + Ralph + Rai + Fact Checker * Add full ## Fact Checker — Verification & Devil's Advocate section mirroring the Rai pattern: roster-entry instruction, dual operating mode (per #789 + #1254), trigger phrase table, confidence ratings, DA brief structure, boundaries, state location Sync via sync-templates.mjs --sync propagates squad.agent.md changes to all 4 mirror targets: .squad-templates/, templates/, packages/squad-cli/ templates/, packages/squad-sdk/templates/, .github/agents/. Tests: new test/squad-agent-roster.test.ts runs against all 4 template targets and asserts: * The "Determine team size" line names all 4 built-ins * A ## Fact Checker section exists with "always appears in team.md" * The section declares dual operating mode (anchors #789 + #1254 design so a future PR can't accidentally split Fact Checker and Devil's Advocate again — cf. closed PR #1294) * Existing Ralph + Rai sections still present 16/16 pass. Closes #1299 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * doc(squad.agent.md): clarify Fact Checker is exempt from casting + correct on-demand reference path Reviewer follow-ups on #1300: 1. Team-size phrasing — the line read 'typically 4-5 + Scribe + Ralph + Rai + Fact Checker' which a model could parse as arithmetic (4-5 + 4 = 8-9, but it could also collapse). Rewrote it to make the composition explicit: '4-5 cast (user-domain) agents + 4 always-on built-ins = 8-9 total roster entries'. 2. Cast-exemption parity — Scribe, Ralph, and Rai each have an explicit 'exempt from casting' bullet but Fact Checker did not. Added the matching bullet right after Rai's. 3. Bad on-demand reference path — the FC section pointed at '.squad/templates/fact-checker-charter.md'. That file IS shipped (TEMPLATE_MANIFEST destination 'templates/fact-checker-charter.md') but only AFTER 'squad init' or 'squad upgrade' has populated .squad/templates/. A reader of squad.agent.md on an un-initialized repo (or in .github/agents/ on the cloud agent surface) would follow a dead link. Repointed to the '.squad/agents/fact-checker/charter.md' instance that ensureBuiltinAgents creates as part of the same init/upgrade path — that's where the rich charter actually lives at runtime per #1299 + #1301. All 4 mirrored copies re-synced via scripts/sync-templates.mjs. fact-checker-role.test.ts: 8/8 pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(sdk): plumb Fact Checker like Rai — rich charter at init + .squad/fact-checker/ state dir (#1299 deep) (#1301) * fix(sdk): plumb Fact Checker like Rai — rich charter at init + .squad/fact-checker/ state dir (#1299 deep) PR #1300 fixed the documentation gap so the coordinator knows to roster Fact Checker. This PR fixes the structural gap behind it. Per user testing 2026-06-13: even after #1300 the actual agent on disk was still "a name on disk with a 21-line placeholder". Three structural problems: 1. squad init never used the rich {role}-charter.md templates. Both Rai and fact-checker got 478-byte generic stubs from generateCharter(). Rich templates only ran via squad upgrade's ensureBuiltinAgents path. 2. fact-checker had no state dir. Rai gets .squad/rai/{policy.md, audit-trail.md} via init.ts lines 879-941. fact-checker had nothing equivalent. 3. fact-checker-charter.md was only in packages/squad-cli/templates/ — missing from .squad-templates/ (canonical source) AND packages/ squad-sdk/templates/. SDK init's getSDKTemplatesDir() resolves to the SDK templates dir, so even if init tried to read the rich charter, the file wasn't there. Fix (4 parts): Part 1 - Rich charter at init (benefits BOTH Rai and fact-checker): * SDK init.ts agent loop now looks up {templatesDir}/{role}-charter.md for each agent and uses that as charter.md content if it exists. Falls back to generateCharter() for user-defined agents. * Result: fresh squad init produces .squad/agents/Rai/charter.md at 4525 bytes (full Rai charter) and fact-checker/charter.md at 3024 bytes (full FC charter). Previously both were 478-byte stubs. Part 2 - .squad/fact-checker/ state dir mirroring .squad/rai/: * New block in init.ts (right after the Rai seeding) creates .squad/fact-checker/policy.md (from templates/fact-checker-policy.md or inline fallback) and audit-trail.md. * New .squad-templates/fact-checker-policy.md (~6KB) is the canonical authority for dual-mode operating rules per #789 + #1254: - Mode 1 Verification: ✅/⚠️/❌/🔍 confidence rating taxonomy - Mode 2 Devil's Advocate: required brief structure - Hard anti-fabrication rules - Advisory by default with narrow blocking exceptions - Audit trail rules (succinct, never raw source) Part 3 - Fix .squad-templates/ distribution gap: * Copied fact-checker-charter.md into .squad-templates/ so sync-templates.mjs propagates it to all 4 mirror targets including packages/squad-sdk/templates/. This unblocks Part 1. Part 4 - Plumbing: * .gitattributes: .squad/fact-checker/audit-trail.md merge=union * TEMPLATE_MANIFEST: fact-checker-policy.md * squad.agent.md Files Catalog: 2 new rows for FC state files Tests: 3 new regression tests in test/init.test.ts (28/28 pass total). npm run lint clean. Composability: This PR builds on #1300 (which adds the ## Fact Checker section to squad.agent.md and the team-size line fix). Both PRs modify squad.agent.md in disjoint regions and merge in either order. Full plumbing requires BOTH to land. Closes #1299 (deep fix; #1300 was the surface fix) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(sdk): lowercase fallback for rich-charter template lookup + sync .github/agents Two reviewer follow-ups on #1301 (#1299 deep): 1. Case-sensitive FS bug in rich-charter lookup The lookup tried '\-charter.md' and '\-charter.md' only. For Rai (role='Rai', name='Rai') this becomes 'Rai-charter.md', but the actual file shipped lowercase ('rai-charter.md'). On Windows the lookup succeeded because the filesystem is case-insensitive; on Linux CI it silently missed and fell back to the 478-byte generic stub — exactly the regression #1299 was trying to fix. Reproduced by 'should use the rich Rai-charter.md template at init' failing with 'expected 476 to be greater than 1000' on GitHub Actions. Add toLowerCase() candidates after the exact-case ones. De-dupe via a Set so we don't double-stat when role and name are already lowercase (fact-checker case). Guard each candidate against blank keys. 2. Template-sync parity The canonical .squad-templates/squad.agent.md gained two Fact Checker rows in the Files Catalog but the mirrored .github/agents/squad.agent.md copy was never re-synced, so the template-sync.test.ts byte-for-byte parity check would have fired. Run 'node scripts/sync-templates.mjs --sync' to regenerate. Verified: vitest 'rich Rai-charter' passes locally after the fix (previously failing on Linux CI run 27464079078). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(skills): rename squad disambiguation skill to squad-help (supersedes #1297) (#1302) * fix(skills): rename disambiguation skill 'squad' -> 'squad-help' (supersedes #1297) PR #1297 added a disambiguation skill named 'squad' so models calling skill(Squad) would get a redirect. After local end-to-end testing on 2026-06-13: skill ships to disk correctly but never shows up in Copilot CLI's /skills list. Root cause (verified against Copilot CLI source 1.0.62-2 app.js): 1. Copilot CLI's skill schema is {name, description, source, baseDir, allowedTools, pluginName, pluginVersion} (line 989). Frontmatter fields triggers:, domain:, confidence:, license: are silently ignored. 2. Skill loader returns {skills, warnings, errors} (line 4427). Skills that fail to load are reported as errors. 3. A skill named 'squad' collides with the Copilot agent named 'Squad' (registered at .github/agents/squad.agent.md). The agent wins; the skill is hidden from /skills. Fix: * Rename 'squad' -> 'squad-help' (avoids the agent-name collision; still descriptive enough for natural-language match when user says 'how do I use squad' or 'squad help') * SKILL.md content: name: 'squad-help', removed unused triggers:/domain:/ confidence:/source:/license: fields, added allowedTools: [], rewrote description: to be self-explanatory, added explicit note that /squad slash command does NOT exist (slash commands are CLI built-ins, not auto-mapped from skills) * MANIFEST_SKILL_NAMES in sdk-init.ts: 'squad' -> 'squad-help' * New TEMPLATE_MANIFEST entry in templates.ts for squad-help (so squad upgrade also propagates the skill - that code path uses TEMPLATE_MANIFEST instead of MANIFEST_SKILL_NAMES) Tests: new test asserts .copilot/skills/squad-help/SKILL.md exists with right frontmatter; explicit regression guard against re-introducing name: 'squad'. 26/26 init tests pass. npm run lint clean. Supersedes #1297. Out of scope (separate issue worth filing): squad upgrade synced only 10 of 16 installed skills - TEMPLATE_MANIFEST (used by upgrade) is out of sync with MANIFEST_SKILL_NAMES (used by init). Skills from PRs #1292 + #1295 (tiered-memory, iterative-retrieval, reflect, cross-squad, cross-squad-communication) have entries in MANIFEST_SKILL_NAMES but not TEMPLATE_MANIFEST. Follow-up will fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(skill): add confidence + domain frontmatter to squad-help Reviewer follow-up on #1302: the isSkillContent() classifier in sharing/consult.ts:990 requires BOTH name: AND confidence: in the frontmatter to recognize a file as a skill. Without confidence:, squad-help would not be detected as a skill in cross-squad merge / share / promote flows — it would be misclassified as a generic markdown decision. The Copilot CLI itself silently ignores custom frontmatter fields (per sdk/index.js decompile — only name/description/allowedTools/ user-invocable are read), so adding confidence: high and domain: squad-onboarding is safe at the CLI surface and necessary at the SDK surface. Applied identically to all 3 mirrored copies of squad-help/SKILL.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(prompt): coordinator must probe for squad_state/memory tools before mutating state (closes #1305) (#1306) * fix(prompt): coordinator must probe for squad_state/memory tools before mutating state on non-local backends (closes #1305) Symptom: a coordinator session against a Squad with stateBackend two-layer wrote .squad/decisions.md and history files via raw create/edit tools, never calling any squad_state or memory tool. The pre-commit hook caught the contract violation; the agent treated it as a 'git problem' instead of the symptom it was. Root cause: two failure modes stacked. Mechanical (Copilot CLI): MCP server tools are loaded lazily, not always advertised in the model's initial function list. Squad cannot fix this server-side; out of scope. Behavioral (squad.agent.md): pre-1305 prompt said 'when memory tools are available, use them' which models read as 'if listed' instead of 'after probing'. No hard refusal clause when the agent is about to violate the state-backend contract. Two changes to .squad-templates/squad.agent.md (synced to 4 mirror targets): 1. New 'State-backend handshake' section (MANDATORY, every session): - Skip for local/worktree backends - For orphan/two-layer/git-notes: probe via tool_search_tool_regex for squad_state_health; call it to confirm the bridge answers - On probe fail: HALT and tell the user verbatim how to fix (restart Copilot CLI or change stateBackend to local) 2. Replaced soft 'if not available' language with a HARD RULE in Memory Governance Tools section. Lists 10 forbidden paths (decisions.md, decisions/inbox, agents history, casting JSON, identity, memory, orchestration-log, log, rai+fact-checker audit-trail) that MUST NOT be written via create/edit/write_file on non-local backends when the bridge isn't reachable. Preserved the local/worktree carve-out. Also clarified that memory.* and squad_state_* share the same MCP server (same registry in packages/squad-cli/src/cli/commands/state- mcp.ts), so models stop treating them as separate availability checks. Tests: new test/state-backend-handshake.test.ts asserts 5 invariants against all 4 template mirror targets (20 tests total): handshake mandatory + every-session timing + squad_state_health probe + HALT remediation + HARD RULE with forbidden paths + local-backend carve- out. 20/20 pass. npm run lint clean. Out of scope: server-side MCP tool preload (Copilot CLI feature request), skill reinforcement in init-mode/agent-conduct (small follow-up). Closes #1305 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: tighten handshake-rule regex (split into 3 separate assertions) Reviewer follow-up on #1306: the regex '/create.*edit.*write_file|create\s*\/\s*edit/i' has '|' operator precedence ambiguity — the alternation binds at the top, so the shorter branch 'create\s*/\s*edit' could match the rule paragraph even if 'write_file' was missing entirely. The test was meant to ensure all three tool names appear in the HARD RULE list; this fix replaces it with three separate \b-anchored assertions, one per tool, so dropping any one fails the test. 20/20 handshake tests still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * feat(skills): make /squad a real slash command (user-invocable: true + rename squad-commands → squad) (#1303) * feat(skills): make /squad a real slash command — rename squad-commands to squad with user-invocable:true Users want /squad in Copilot CLI to show Squad's command catalog. Verified via Copilot CLI 1.0.62-2 source (sdk/index.js line 2618): getLoadedSkills().filter(e => e.userInvocable) .map(e => ({name: \/\\, isSkill: true, skill: e})) Any skill with frontmatter user-invocable: true is auto-registered as a slash command at /. The previous skill 'squad-commands' had no user-invocable field (Copilot CLI defaults to false), so /squad-commands never existed and users had no slash entrypoint. Changes: * Renamed squad-commands -> squad (canonical .squad/skills/squad/) * Frontmatter rewritten to Copilot CLI schema: - name: squad (was squad-commands) - user-invocable: true (the load-bearing change) - description: self-explanatory for natural-language match - allowedTools: [] - Removed unused fields: domain:, confidence:, source:, triggers: * Body updated to reference /squad as primary invocation * MANIFEST_SKILL_NAMES updated * TEMPLATE_MANIFEST updated (so squad upgrade syncs it too) * Removed stale squad-commands template dirs from both packages Tests: new test asserts user-invocable: true frontmatter AND name: squad (both are load-bearing for the slash command to register). 26/26 init tests pass. npm run lint clean. Composability: * Disjoint from #1292 (skills bundling) - both touch MANIFEST_SKILL_NAMES but this is a rename not an add * Disjoint from #1302 (squad-help disambiguation) - that one covers the skill(Squad) misdirect, this one covers the /squad slash UX Out of scope (future): machine-wide install (~/.copilot/skills/squad/) so /squad init works in folders without .squad/ yet. Requires a squad install --global-skill command + 'no .squad yet' menu branch. Will file separate follow-up. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(squad.agent.md): update squad-commands → squad skill xref Follow-up to reviewer comment on #1303: when this PR renamed '.copilot/skills/squad-commands/SKILL.md' to '.copilot/skills/squad/SKILL.md' (to make /squad a real user-invocable slash command), the routing-table entry inside squad.agent.md still pointed at the old path. Coordinators loading squad.agent.md from a freshly upgraded squad would have followed a dead reference and reported 'skill not found'. Updated the row in all 5 mirrored copies (.squad-templates, .github/agents, packages/squad-cli/templates, packages/squad-sdk/templates, templates/). Also added a hint that users can invoke the skill directly via /squad, since that is now the documented surface for the same content. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(skills): install bundled skills to .github/skills/ (closes #1126) (#1304) * fix(skills): install bundled skills to .github/skills/ (closes #1126) Per the official Agent Skills spec docs (GitHub, VS Code), the canonical project-level custom-skills location is .github/skills/. The legacy .copilot/skills/ is invisible to all Copilot surfaces except Squad itself - cloud agent, CLI outside Squad, VS Code extension, @copilot coding agent all ignore it. Changes: * squad init writes bundled skills to .github/skills/{name}/SKILL.md * squad upgrade does the same AND migrates legacy .copilot/skills/{name}/ -> .github/skills/{name}/ for manifest skills only (user-added skills at .copilot/skills/ are preserved). Tombstones empty legacy dir. * TEMPLATE_MANIFEST destinations: 10 skill entries retargeted * ENSURE_DIRECTORIES: .copilot/skills -> .github/skills * squad.agent.md narrative: 5-path scan order now lists .github/skills as primary, .copilot/skills as legacy. Personal scope unchanged. * All user-facing docs updated (README, spawn-reference, squad-commands skill, release-process skill, build.ts skill creation paths, SDK type-comment paths). Migration semantics in upgrade.ts: * Move-only-if-new-location-empty: legacy at .copilot/skills/, new location empty -> move + tombstone legacy * Tombstone-on-collision: both locations exist -> remove legacy, new wins (then syncAllSkills overwrites manifest skills per overwriteOnUpgrade=true semantics) * Preserve user-added: skills NOT in TEMPLATE_MANIFEST stay at .copilot/skills/ untouched * All best-effort with try/catch - disk failures do not block upgrade Tests (188/188 pass): * New: init.test.ts asserts canonical path + legacy NOT created * New: upgrade.test.ts asserts manifest migration + user-skill preservation + collision tombstoning * Updated: 8 existing test files retargeted to .github/skills NOT changed (intentional): * .copilot/skills/ stays in coordinator skill-discovery scan order for backward compat with user-added skills * ~/.copilot/skills/ (personal scope) unchanged - that's Copilot CLI's official personal-skills location * Runtime skill-loader tests (skill-source, skills-export-import, tools, skill-script-loader) unchanged - those test loader behavior which still supports .copilot/skills/ as a scan path Closes #1126 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: squad_skill writes to .github/skills, sync .github/agents copy, align issue refs Reviewer follow-ups on #1304 (closes #1126): 1. squad_skill tool implementation matches its description now Description says skills live at '.github/skills/{name}/SKILL.md' but the handler was still writing to '.copilot/skills/' — every successful 'write' shipped to a different path than what the tool documented (and what squad init/upgrade now install to). Fixed the handler so: - write operations go to '.github/skills/{name}/SKILL.md' - read operations check .github/skills first, then fall back to .copilot/skills (legacy) and .squad/skills (in-repo team skills), in that precedence — so users with un-migrated existing skills can still read them. 2. .github/agents/squad.agent.md re-synced from .squad-templates The canonical template had the corrected routing-table reference ('.github/skills/squad-commands/SKILL.md') but the .github/agents mirror copy was never re-synced. The byte-for-byte template-sync parity test would have fired on next CI. Ran 'node scripts/sync-templates.mjs --sync'. 3. Align issue references: #1304 → #1126 in code/docs/tests This PR closes #1126; #1304 is the PR number. The migrator docstring, the init.ts comment, and the two upgrade.test.ts cases all called the regression '#1304' — confusing for anyone digging into git blame for the canonical issue. Renamed to #1126 where the references describe the bug origin (kept #1304 only where the comment specifically describes 'the PR that implemented it'). 4. Migrator docstring corrected to match reality Old docstring claimed 'skips skills already present at the new location with the same content' but the implementation never compares content — it unconditionally tombstones the legacy copy when the new location exists. Rewrote the doc paragraph to describe what the code actually does (preserves the new-location copy verbatim). Verified locally: ✓ vitest test/cli/upgrade.test.ts -t 'migrate manifest skills|should NOT clobber' ✓ vitest test/tools.test.ts -t squad_skill Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(tools): squad_skill writes go to .github/skills now (CI fix) CI failure on commit 9de50ded: 'squad_skill handler > should write skill file' and 'should default confidence to medium' both still asserted '.copilot/skills/{name}/SKILL.md' as the write destination. With squad_skill's handler now writing to '.github/skills/' (the canonical Copilot CLI custom-skills location, per the fix in the same commit), those assertions need to be updated. The handler's read-fallback chain still finds .copilot/skills/ and .squad/skills/ for legacy installs, but the write target is fixed. Verified locally: 5/5 squad_skill tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(init): update squad-help + /squad slash test paths to .github/skills/ (cascade fix on top of #1126) After #1303 + #1302 landed first (with their tests asserting .copilot/skills/), the rebase of #1304 (which moves manifest skills to .github/skills/) leaves the test paths pointing at the old location. Update them so the rebased branch's CI is green. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * feat(sdk): bundle cross-squad-communication skill (#5 cross-squad protocols) (#1295) * feat(sdk): bundle cross-squad-communication skill (#5 cross-squad protocols) PR #1291 added squad registry add/list/remove and the cross-squad/SKILL.md for peer discovery. But discovery is only half the story — once a peer is known, agents need to know HOW to actually exchange info with it. This change ports cross-squad-communication from tamirdresher/squad-skills into the bundled skills so a fresh squad init produces a coordinator that knows the four communication patterns: * Pattern 0: Synchronous CLI session (--working-directory targeting peer) * Pattern 1: Read-only metadata scan (team.md / decisions.md) * Pattern 2: Async git-based request/response (.squad/cross-squad/...) * Pattern 3: Issue-based delegation (gh issue with squad:cross-squad label) Plus decision tree for pattern choice, anti-patterns, request/response YAML schemas, and validation status. Changes: * .squad/skills/cross-squad-communication/SKILL.md (379 lines, canonical source). sync-skill-templates.mjs propagates to both packages templates. * MANIFEST_SKILL_NAMES in packages/squad-sdk/src/config/init.ts grows by 1 entry: cross-squad-communication. Now 11 entries. * cross-squad/SKILL.md gets a one-paragraph "Companion skill" note pointing to cross-squad-communication for protocol details. Together: cross-squad answers "who?" (registry discovery), cross-squad-communication answers "how?" (the 4 patterns). The original plugin documented validation against specific internal Microsoft repos. Examples in this version use generic names (platform-squad, research-squad) so they are meaningful to upstream users. Protocol mechanics are unchanged. Frontmatter source: attributes the port. Test: new "should install cross-squad-communication skill" in init.test.ts asserts SKILL.md is at install path and content contains expected pattern names. 26/26 init tests pass. npm run lint clean. Composes with #1291: * squad init → both skills bundled * squad registry add ../peer → peer discoverable * coordinator: "ask peer X for their team members" → uses Pattern 1 by reading team.md from the registered peer Note on overlap with #1292: PR #1292 (skills bundling fix) adds 4 entries to MANIFEST_SKILL_NAMES; this PR adds 1. Disjoint additions to the same array; either can land first. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: TEMPLATE_MANIFEST entry for cross-squad-communication + pattern numbering + honest source/validation Five reviewer follow-ups on #1295: 1. MANIFEST_SKILL_NAMES added 'cross-squad-communication' but TEMPLATE_MANIFEST didn't — 'squad upgrade' would skip the skill even though 'squad init' installed it. Added the missing entry to TEMPLATE_MANIFEST so upgrade syncs it like every other manifest skill. (Same drift bug pattern noted on #1292/#1303.) 2-3. Both SKILL.md files (companion 'cross-squad' and full 'cross-squad-communication') said the skill covers 'the four communication patterns (sync CLI, async git-based, issue-based)' but only listed three — Pattern 1 (read-only knowledge query) was missing from the intro list. Rewrote both intro lines to spell out all four: Pattern 0 (sync CLI), Pattern 1 (read-only), Pattern 2 (git-based async), Pattern 3 (issue-based). 4. The doc had 'Pattern 4: Cross-Repo Dependency Scan' which broke the 'four numbered patterns 0-3' framing — Pattern 4 isn't a communication protocol, it's a one-off analysis utility for discovering how two repos relate. Moved it to a clearly labeled '### Appendix' section with a note explaining why it's not a numbered pattern. 5. Frontmatter 'source:' claimed the skill was 'validated against two production squad instances' but the Validation Status section said Patterns 2 and 3 were 'untested end-to-end'. Reworded 'source:' to acknowledge that only Pattern 0 (and partially Pattern 1) are end-to-end-validated; Patterns 2/3 are documented from design and need live validation before production use. Also softened the same overclaim in the Validation Status prose (replaced 'validated against two production' with 'drafted against two prototype' + explicit acknowledgement that 2/3 are design-only). All 3 mirrored copies of cross-squad-communication/SKILL.md and 3 copies of cross-squad/SKILL.md kept in sync (canonical = .squad/, mirrors = packages/squad-cli/templates/, packages/squad-sdk/templates/). Verified: 31/31 init + builtin-skills tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(skill): cross-squad-communication uses real Copilot CLI invocations (ghcs→copilot, real flags) The cross-squad-communication SKILL.md as drafted instructed coordinators to use a non-existent CLI shape. Verified empirically against `copilot --help` (v1.0.62): - `ghcs` is not a binary on a clean install; the real CLI is `copilot` (resolved to C:\ProgramData\global-npm\copilot.ps1 on a default node install). - `--working-directory ` does not exist. Real flag is `-C ` (git-style "change directory first"). - `--no-mcp` does not exist. Real flag is `--disable-builtin-mcps`. - `-p, --prompt ` takes the prompt STRING, not a path. Earlier examples passed `-p \` (a path), which makes the CLI try to execute the path string as a prompt. Read the file with `Get-Content -Raw` first. - `--allow-all-tools` was missing from autonomous (non-interactive) invocations — without it every tool call prompts for permission and hangs the cross-squad delegation indefinitely. - Log directory was `~/.agency/logs/` (older `agency` runtime). New CLI logs under `~/.copilot/logs/`. Switched to a `Test-Path`-guarded fallback so both transitional and clean installs resolve correctly. All 14 call-site occurrences in cross-squad-communication/SKILL.md updated (3 mirrored copies). Mirrors verified byte-identical. New regression test in test/cross-squad.test.ts asserts each of the 8 correctness properties (ghcs/—working-directory/—no-mcp absent; `copilot -C`, `--allow-all-tools`, `--disable-builtin-mcps`, `-p (Get-Content`, and `.copilot\\logs + Test-Path` present) for every mirror. 8 × 3 mirrors = 24 new assertions; 56/56 cross-squad tests pass. Catches a fairly large embarrassment: had this skill landed as drafted, every fresh `squad init` coordinator would have run a totally bogus CLI command on the first delegation attempt. Credit to the peer-squad `read this skill, ran into errors, asked why` loop for surfacing it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(skill): every cross-squad copilot spawn passes --agent squad A peer-squad delegation that runs the Copilot CLI without --agent squad gets a generic Copilot CLI session — the spawned process never loads .github/agents/squad.agent.md, the peer's team.md, MCP tools, casting, or coordinator behaviour. The whole point of cross-squad delegation collapses: the answer comes from an off-the-shelf model, not from the peer's Squad. All 7 example command lines in cross-squad-communication/SKILL.md (Pattern 0 Options A/B/C, Liveness Protocol MCP-skip retry, and the two Anti-Pattern examples) now include --agent squad. Also added a new top-of-Patterns "Universal rule: every copilot spawn into a peer squad MUST pass --agent squad" section that: - States the rule once, prominently. - Explains the failure mode (generic session, no team.md / MCP / coordinator loaded) so future edits don't strip the flag thinking it's redundant. - Notes the only exception (--resume preserves original session agent). - Generalises the rule beyond cross-squad: "anywhere else you spawn copilot into a Squad-initialised repo — e.g., post-init tips and any automation that invokes the CLI on a squadified folder." Also added a precondition note to Pattern 0 Requirements: the target repo must be Squad-initialised (.squad/config.json + .github/agents/squad.agent.md present) so --agent squad resolves. Verified empirically: copilot --help (v1.0.62) confirms --agent is the real flag name. squad delegate (CLI command) was audited and does NOT need this fix — it uses `gh issue create`, not `copilot`, so the issue body lands in the peer repo's GitHub Issues for async pickup, not via direct CLI spawn. Regression tests in test/cross-squad.test.ts: - 'every copilot spawn into a peer squad includes --agent squad' — scans every copilot command line (excluding --resume / prose references) and asserts each one has --agent squad. Tells you the exact offending line on failure. - 'explains WHY --agent squad is required (universal rule paragraph)' — defends against a future edit that strips the rationale. - 2 new assertions × 3 mirror locations = 6 new tests. - 62/62 cross-squad tests pass. All 3 mirrored copies of cross-squad-communication/SKILL.md kept in sync (canonical = .squad/, mirrors = packages/squad-cli/templates/, packages/squad-sdk/templates/). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(coordinator): route "spawn a squad" / "another squad" prompts through the cross-squad skill (#1307) * fix(coordinator): route "spawn a squad" / "another squad" prompts through the cross-squad skill A coordinator initialised by `squad init` saw prompts like "spawn two squads of designers and devs" and fanned out raw `task` agents inside its own context, treating "squad" as generic English for "team / group". It never invoked the bundled `cross-squad` or `cross-squad-communication` skills, so the peer-squad delegation protocol (registry / manifest / sync CLI / git-async / GH-issue patterns) was bypassed entirely. Two structural holes in squad.agent.md allowed this: 1. The Routing table had no row mapping "spawn a squad" phrasing to the Squad-PRODUCT concept (only "upgrade squad" / "squad commands" rows covered Squad-as-a-product vocabulary). 2. The Skill-aware-routing block was process discipline ("check skill directories by domain relevance") with no hard "if the user's word matches a skill name, MUST load the skill" trigger. This fix: - Adds a new routing-table row for the squad-spawning vocabulary ("spawn a squad", "another squad", "two squads", "second squad", "fan out to squads", "delegate to a squad"). Action: invoke the skill tool on cross-squad AND cross-squad-communication BEFORE any task spawn, then delegate via Pattern 0/1/2/3. - Adds a "Hard trigger — keyword-to-skill match" paragraph at the top of the Skill-aware-routing block. If any word in the user's request matches an installed skill name (squad → cross-squad, reflect → reflect, ceremony → matching ceremony skill, fact-check → fact-checking, release → release-process), the coordinator MUST invoke the skill tool to fully load that skill before designing its approach. Includes a "failure mode this rule closes" pointer so the guard survives future paraphrasing. - Strengthens cross-squad/SKILL.md with a Read-this-FIRST callout above the existing Context paragraph, so even a coordinator that skips the routing-table row still hits the trigger when it does eventually load the skill. - Adds a regression test (template-sync.test.ts) that asserts the row + the hard-trigger paragraph + the worked example are present in every mirrored copy of squad.agent.md (5 locations). All 4 mirrors re-synced via `scripts/sync-templates.mjs --sync`. Verified: 223/223 template-sync tests pass. Composability: disjoint from #1292/#1293/#1295/#1298/#1300/#1301/#1302/ #1303/#1304/#1306 — only touches squad.agent.md mirrors + cross-squad/ SKILL.md + the template-sync test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(coordinator): strengthen squad-spawn disambiguation — ask_user on ambiguity + anti-patterns Real-world failure (2026-06-13): even AFTER the routing-table row and hard-trigger paragraph from b5d05fb6 landed, a peer-squad coordinator *still* did ad-hoc `task` fan-out for "spawn two squads of engineers and QAs". Self-diagnosis surfaced four contributing failure modes: 1. Prior-session anchoring (saw earlier `reviews/squad-alpha/` folders and matched the pattern without re-evaluating user intent). 2. Ambiguous wording, lazy interpretation (silently picked the cheaper option instead of asking). 3. Coordinator doctrine biases toward `task` fan-out (the existing Eager Execution / Parallel Fan-Out section pulled the coordinator back even after it had loaded `cross-squad`). 4. Cost/overhead instinct ("two real squads for a 30-line app feels disproportionate" — judged silently instead of surfacing the trade-off). The original PR #1307 fix closed modes 1 and 3 mechanically (forces the skill to load) but left modes 2 and 4 open (didn't dictate what to DO with that knowledge). This commit closes them: A. squad.agent.md routing row — added two explicit clauses: - "**Default = literal Squad install.** Calling `task` sub-agents 'squad-alpha' / 'squad-beta' does NOT make them squads — that is the explicit anti-pattern." - "**If the request is ambiguous** ... you MUST `ask_user` with a 2-choice prompt — and never silently pick the cheaper option." No escape hatch. The coordinator can no longer rationalise the downgrade as a judgment call. B. cross-squad/SKILL.md — added a full `## Disambiguation: 'squad' vs ad-hoc agents` section with: - Default-behaviour table mapping common phrasings to expected coordinator actions (real squads vs ad-hoc agents vs ambiguous). - ask_user 2-choice protocol verbatim (heavier/persistent vs lighter/ephemeral) so the coordinator has the exact prompt shape. - Four named anti-patterns drawn directly from the observed failure: * Naming task agents "squad-alpha" doesn't make them squads * Prior-session anchoring (pattern is a hint, not a contract) * Silent cheaper-option pick (judgment call belongs to the user) * Loading the skill but doing task fan-out anyway (disambiguation rule OVERRIDES generic fan-out doctrine when "squad" was the trigger) - Sharpened `description:` so the squad skill-aware-router has better natural-language hooks. - `triggers:` frontmatter array (Copilot CLI ignores `triggers:` per sdk/index.js decompile, but the squad coordinator's skill-aware routing system uses natural-language matching against frontmatter + content, so documenting the phrases here helps that matcher fire). C. Regression tests in test/template-sync.test.ts: - Routing row must mention `ask_user` + "anti-pattern" (new × 5 mirrors = 5 assertions). - cross-squad/SKILL.md must have `## Disambiguation` section, default-behaviour rule, ask_user requirement, the squad-alpha anti-pattern, and triggers: frontmatter (5 new × 3 mirrors = 15 assertions). - 20 new assertions total; 243/243 template-sync tests pass. cross-squad/SKILL.md mirrored to packages/squad-cli/templates/skills/ and packages/squad-sdk/templates/skills/ (byte-identical). squad.agent.md re-synced to all 4 mirrors via scripts/sync-templates.mjs. Composability: still disjoint from all other open PRs. Pure additions to two files (squad.agent.md row, cross-squad/SKILL.md content) plus mirrors + tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(sdk,cli): bundle missing skills on init + strip fabricated tiered-memory provenance (#1289, #1264) (#1292) * fix(sdk,cli): bundle missing skills on init + strip fabricated provenance from tiered-memory (#1289, #1264) Two regressions shipped in v0.10.0 caused several skills to silently never reach users: * #1289 — squad-commands + squad-version-check were in MANIFEST_SKILL_NAMES but missing from packages/squad-sdk/templates/skills/. The install loop silently skipped missing source dirs (storage.existsSync check, no log), so every squad init produced an install with these two absent and the `squad commands` trigger phrase was a dead no-op for every v0.10.0 user. * #1264 — tiered-memory, iterative-retrieval, reflect existed in templates but were never added to MANIFEST_SKILL_NAMES. tiered-memory/SKILL.md also claimed confidence: high with fabricated tamirdresher/tamresearch1 measurement data, and referenced a non-existent docs/tiered-memory-guide.md. * PR #1291 updated cross-squad/SKILL.md but never added it to the manifest. This change: * Adds 5 missing skill dirs to .squad/skills/ (canonical source): squad-commands, squad-version-check, tiered-memory, iterative-retrieval, reflect. sync-skill-templates.mjs (which runs in prebuild) propagates them to both packages/squad-cli/templates/skills/ and packages/squad-sdk/templates/skills/. * Grows MANIFEST_SKILL_NAMES from 10 → 14 entries: adds tiered-memory, iterative-retrieval, reflect, cross-squad. * Replaces the silent skip in sdk-init.ts with a thrown error that names the missing skill(s) and points at sync-skill-templates.mjs. This is what would have surfaced #1289 at build time instead of in user installs. * Rewrites tiered-memory/SKILL.md to be honest about status: - frontmatter: confidence: design + source: design proposal - prominent Status callout linking to #1264 for the runtime gap - removed fabricated measurement table - removed reference to non-existent docs/tiered-memory-guide.md - References section points only to real issues (#1264, #686, #600) * Adds a regression test in test/init.test.ts that asserts every manifest-curated skill ends up installed at .copilot/skills/{name}/SKILL.md. Verified: * npm run lint — passes (after build) * test/init.test.ts — 26/26 pass including new regression test * Targeted suites (test/init, test/cli/init, test/sdk) — 292/292 pass Out of scope (tracked separately): * tiered-memory runtime backing (.squad/memory/hot|cold|wiki/ scaffolding, Scribe promotion, tier-aware spawn template) — follow-up in #1264 * cross-squad-communication plugin port from tamirdresher/squad-skills — separate PR Closes #1289 Refs #1264 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: precheck drift, end-user remediation, TEMPLATE_MANIFEST sync, frontmatter, single-source test Five reviewer follow-ups on #1292 (closes #1289, refs #1264): 1. (1292-a) Drift guard ran the copy loop FIRST and threw at the end if any source dir was missing — leaving a partial skills install on disk. Refactored to a pre-check pass that walks MANIFEST_SKILL_NAMES once for existence, throws if any source is missing, then runs the copy loop only if everything is present. Result: no partial state when a packaging bug ships. 2. (1292-b) Remediation text told the user to 'run scripts/sync-skill-templates.mjs from the squad repo root', which doesn't exist in an installed @bradygaster/squad-sdk tarball. Rewrote the message to target end users (try squad upgrade, report a bug if it persists) and kept the contributor hint as a parenthetical aside. 3. (1292-c) MANIFEST_SKILL_NAMES grew to 14 entries (squad-commands, squad-version-check, tiered-memory, iterative-retrieval, reflect, cross-squad added across PRs), but TEMPLATE_MANIFEST in packages/squad-cli/src/cli/core/templates.ts had only 10 — so squad upgrade would skip the new ones. Added matching entries for tiered-memory/iterative-retrieval/reflect/cross-squad (the 4 not already covered by other PRs). 4. (1292-d) packages/squad-cli/templates/skills/squad-version-check/ SKILL.md (and the 2 mirrors) shipped without ANY YAML frontmatter — just a freeform '**Confidence:**' Markdown line. isSkillContent() in consult.ts requires '---\\nname:...\\nconfidence:' front-matter to classify the file as a skill, so squad-version- check was misclassified in share / promote / merge flows. Added proper frontmatter (name, description, allowedTools, confidence, domain, source) to all 3 mirrors. 5. (1292-e) The regression test duplicated the MANIFEST_SKILL_NAMES list literal — adding a new skill would have required two edits to stay green. Exported MANIFEST_SKILL_NAMES from sdk/config and updated the test to import + iterate the same array the install loop reads. Future skill additions only need one edit. Verified: 26/26 init tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * docs: add feature pages for preset, cross-squad discover/delegate, and coordinator-as-agent export (#1274) Closes part of #1272 — covers 3 of the 11 undocumented v0.10 user-facing features identified in the docs-gap audit. Files added: docs/src/content/docs/features/preset.md (205 lines) docs/src/content/docs/features/cross-squad-discover.md (158 lines) docs/src/content/docs/features/coordinator-as-agent-export.md (237 lines) Source verification: - preset.md sourced from packages/squad-cli/src/cli/commands/preset.ts - cross-squad-discover.md sourced from packages/squad-cli/src/cli/commands/cross-squad.ts and packages/squad-sdk/src/runtime/cross-squad.ts - coordinator-as-agent-export.md sourced from PR #1180 body and packages/squad-sdk/src/repo-native/ scope Style matches existing feature pages (loop.md, plugins.md format). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: add feature pages for remaining 8 v0.10 features (tiered-memory, reflect, error-recovery, teams-comms, fleet-dispatch, mcp-frontmatter, dual-mode, skill-security-scanner) + export-import --repo/--branch (#1276) * docs: add feature pages for remaining 8 undocumented v0.10 features Closes the rest of #1272 — covers the 8 user-facing features that had no dedicated docs page after PR #1274 (which covered preset, cross-squad discover, and coordinator-as-agent export). New pages (8): docs/features/tiered-memory.md (hot/cold/wiki memory model) docs/features/reflect.md (in-session learning skill) docs/features/error-recovery.md (failure recovery skill) docs/features/teams-comms.md (Microsoft Teams adapter) docs/features/fleet-dispatch.md (/fleet hybrid dispatch) docs/features/mcp-frontmatter.md (--mcp-frontmatter flag) docs/features/dual-mode-deployment.md (SQUAD_POD_ID, dual-mode capabilities) docs/features/skill-security-scanner.md (markdown-aware skill scanner) Updated (1): docs/features/export-import.md (added --repo / --branch sections) Source verification: - tiered-memory.md ← packages/squad-cli/templates/skills/tiered-memory/SKILL.md - reflect.md ← packages/squad-cli/templates/skills/reflect/SKILL.md - error-recovery.md ← packages/squad-cli/templates/skills/error-recovery/SKILL.md - teams-comms.md ← packages/squad-sdk/src/platform/comms-teams.ts + changeset - fleet-dispatch.md ← packages/squad-cli/src/cli/commands/watch/capabilities/fleet-dispatch.ts - mcp-frontmatter.md ← packages/squad-cli/src/cli-entry.ts:346 flag + init flow - dual-mode-deployment.md ← packages/squad-sdk/src/ralph/capabilities.ts - skill-security-scanner.md ← scripts/security-review.mjs + changeset - export-import.md edits ← packages/squad-cli/src/cli/commands/import.ts:817 Style matches existing feature pages (loop.md, plugins.md, preset.md format). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cspell): whitelist PKCE/MSAL/AKIA/runas + intentional 'typoo' example CI failure on docs-quality: cspell flagged 9 words across 3 of the new feature pages added by this PR. All 9 are legitimate technical terms or intentional content (not typos): - PKCE (RFC 7636 — OAuth Proof Key for Code Exchange) — 5 hits in teams-comms.md - MSAL (Microsoft Authentication Library) — 1 hit in teams-comms.md - AKIA (AWS access key ID prefix) — 1 hit in skill-security-scanner.md - runas (Windows runas elevation primitive) — 1 hit in skill-security-scanner.md - 'typoo' — an INTENTIONAL typo in an error-recovery example showing 'don't retry the user's typo by adding another character' Added all 5 to cspell.json. Verified locally: npx cspell --no-progress --dot 'docs/src/content/**/*.md' 'README.md' → 0 issues across 166 files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * docs(state-backends): document pre-commit hook, post-upgrade flow, and recovery (#1227) - Correct hook list (4 -> 6) in state-backends.md - Correct misleading claim that working-tree files remain as read-only reference (they're deleted after migration per migrate-backend.ts:218-235) - Add Steady-state safety net section explaining pre-commit + post-commit hooks - Add troubleshooting entry for refusing to commit two-layer state with recovery flow - Add post-upgrade verification checklist to upgrading.md Closes #1226 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: clarify that .squad/casting/* files are identity, not two-layer state (#1230) * docs: clarify that .squad/casting/* files are identity, not two-layer state .squad/casting/* files (policy.json, registry.json, history.json) are AUTHORITATIVE IDENTITY files that should be committed to 'main' alongside team.md and routing.md. They define the agent universe, name registry, and usage history - fundamental team identity, not runtime mutable state. This change adds clarifying documentation to: - .gitignore: explicit NOTE that casting/* MUST NOT be ignored - .gitattributes: explicit NOTE that casting/* is identity, not append-only - init.ts: enhanced comments explaining casting/* classification This resolves confusion where casting files were misclassified as two-layer runtime state. Per squad.agent.md (the source of truth), casting files are 'Authoritative' and belong on main. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(changeset): add patch changeset for the casting=identity doc clarifications Policy Gates noticed the init.ts touch (pure comment additions clarifying that .squad/casting/* is identity, not mutable state) and asked for a changeset. Adding one — patch bump. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(state-backend): conditional .gitignore entries for two-layer/orphan backends (#1229) Adds .squad/decisions.md and .squad/agents/*/history.md to .gitignore (within a marker-delimited block) when stateBackend is 'two-layer' or 'orphan'. Removes the block when switching back to 'local'. Defense-in-depth complement to the pre-commit hook installed by squad upgrade --state-backend. - New helper module: packages/squad-sdk/src/config/gitignore-state.ts with addSquadStateGitignoreBlock + removeSquadStateGitignoreBlock - initSquad calls helpers based on options.stateBackend (adds stateBackend to InitOptions in SDK) - CLI runInit passes stateBackend to sdkInitSquad - migrate-backend calls helpers on backend transitions - Unit tests for helpers (add/remove idempotency, round-trip) - Integration tests for init and migration paths Closes #1228 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(blog): v0.10 stabilisation insider release post Covers the 11 stabilisation PRs (#1292, #1293, #1295, #1298, #1300, #1301, #1302, #1303, #1304, #1306, #1307), how to install the insider (@bradygaster/squad-cli@insider), the end-to-end smoke test that validates the whole stack, and what's still open (#1308 slimming, #1309 tiered-memory runtime, #1310 machine-wide /squad, #1225 preset install, github/copilot-cli#3787 MCP preload). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(docs): rephrase 'fenced code block' row in skill-security-scanner table CI failure on dev tip (567f4475, Squad CI run 27488323855): test/docs-build.test.ts > 'all code blocks are properly fenced (even count of backticks)' → expected 1 to be 0 test/docs-build.test.ts > 'code blocks contain language specification or valid content' → expected 1 to be greater than 1 Root cause: a 4-backtick table cell intended to display a literal triple-backtick: | Inside a fenced code block (\\\\ \\\ \\\\) | Suppressed | made the regex /\\\/g see 5 triple-backtick occurrences across the file (instead of the 2 from the real bash example), tripping both the even-fence check and the line-count > 1 check. Rephrased the table to say 'three backticks' / 'single backtick' in prose — no embedded delimiters, no need to defend against the markdown fence escape mechanism. Reads cleaner anyway. Verified locally: 22/22 docs-build tests pass; npm run build in docs/ completes (171 files emitted; pagefind indexes 168 pages, 6911 words). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(blog): use American 'Stabilization' (cspell dict is en_US) The repo's cspell.json is en_US — 'Stabilisation' fails docs-quality CI (unknown word, 3 occurrences). Switch to 'Stabilization' to match the existing dictionary without polluting it with one-off British words. --------- Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Brady Gaster <41929050+bradygaster@users.noreply.github.com> Co-authored-by: Reno (Copilot) Co-authored-by: Copilot Co-authored-by: Copilot --- .changeset/bundle-missing-skills-1289-1264.md | 39 ++ .changeset/casting-identity-not-state.md | 5 + .changeset/conditional-state-gitignore.md | 10 + .../fix-1126-skills-to-github-skills.md | 68 +++ .../fix-1296-stop-mcp-home-pollution.md | 40 ++ .../fix-1299-fact-checker-full-plumbing.md | 77 ++++ ...x-1299-fact-checker-roster-instructions.md | 54 +++ .../fix-1305-state-backend-handshake.md | 67 +++ .changeset/fix-fact-checker-auto-scaffold.md | 16 + .changeset/fix-permission-contract.md | 6 + .changeset/fix-release-pipeline-versions.md | 14 + .../fix-skill-squad-rename-to-squad-help.md | 49 +++ .changeset/fix-squad-slash-command.md | 76 ++++ .changeset/fix-squad-spawning-routing.md | 40 ++ .changeset/fix-yaml-escaping-skill.md | 8 + .changeset/init-prompt-copilot-member.md | 10 + .changeset/memory-tools-mcp-exposure.md | 5 + .changeset/preset-apply-wires-team-1288.md | 38 ++ .changeset/registry-subcommand.md | 20 + .../wire-cross-squad-communication-skill.md | 53 +++ .gitattributes | 6 + .github/agents/squad.agent.md | 108 ++++- .github/dependabot.yml | 69 ++- .github/workflows/squad-agents-ai-ci.yml | 91 ++++ .github/workflows/squad-agents-ai-release.yml | 182 ++++++++ .github/workflows/squad-docs-links.yml | 68 +-- .github/workflows/squad-impact.yml | 1 + .github/workflows/squad-npm-publish.yml | 25 ++ .github/workflows/squad-pr-nudge.yml | 6 +- .github/workflows/squad-pr-readiness.yml | 2 +- .github/workflows/squad-repo-health.yml | 1 + .gitignore | 17 +- .squad-templates/fact-checker-charter.md | 83 ++++ .squad-templates/fact-checker-policy.md | 104 +++++ .squad-templates/spawn-reference.md | 2 +- .squad-templates/squad.agent.md | 143 ++++++- .../skills/cross-squad-communication/SKILL.md | 399 +++++++++++++++++ .squad/skills/cross-squad/SKILL.md | 72 +++- .squad/skills/iterative-retrieval/SKILL.md | 165 +++++++ .squad/skills/reflect/SKILL.md | 229 ++++++++++ .squad/skills/release-process/SKILL.md | 4 +- .../skills/squad-commands/SKILL.md | 0 .squad/skills/squad-help/SKILL.md | 97 +++++ .squad/skills/squad-version-check/SKILL.md | 169 ++++++++ .squad/skills/squad/SKILL.md | 299 +++++++++++++ .squad/skills/tiered-memory/SKILL.md | 221 ++++++++++ CHANGELOG.md | 8 + README.md | 4 + Squad.Agents.AI.slnx | 11 + cspell.json | 4 +- docs/proposals/memory-governance-provider.md | 2 +- docs/src/content/blog/004-v020-release.md | 2 +- .../content/blog/009-v040-sprint-progress.md | 2 +- .../011-skills-system-learning-from-work.md | 18 +- .../blog/022-welcome-to-the-new-squad.md | 4 +- .../blog/026-whats-new-ado-comms-subsquads.md | 2 +- .../blog/032-v010-stabilisation-insider.md | 126 ++++++ .../docs/concepts/memory-and-knowledge.md | 6 +- docs/src/content/docs/concepts/portability.md | 8 +- docs/src/content/docs/concepts/your-team.md | 2 +- docs/src/content/docs/cookbook/recipes.md | 2 +- docs/src/content/docs/features/cleanup.md | 2 +- .../content/docs/features/context-hygiene.md | 2 +- .../features/coordinator-as-agent-export.md | 237 +++++++++++ .../docs/features/cross-squad-discover.md | 158 +++++++ .../docs/features/dual-mode-deployment.md | 132 ++++++ .../content/docs/features/error-recovery.md | 95 +++++ .../content/docs/features/export-import.md | 43 +- .../content/docs/features/external-state.md | 2 +- .../content/docs/features/fleet-dispatch.md | 132 ++++++ .../content/docs/features/mcp-frontmatter.md | 102 +++++ docs/src/content/docs/features/memory.md | 2 +- .../content/docs/features/notifications.md | 2 +- docs/src/content/docs/features/preset.md | 205 +++++++++ docs/src/content/docs/features/reflect.md | 91 ++++ docs/src/content/docs/features/routing.md | 2 +- .../docs/features/skill-security-scanner.md | 118 ++++++ docs/src/content/docs/features/skills.md | 12 +- .../content/docs/features/state-backends.md | 69 ++- docs/src/content/docs/features/teams-comms.md | 127 ++++++ .../content/docs/features/tiered-memory.md | 98 +++++ .../docs/features/upstream-inheritance.md | 2 +- docs/src/content/docs/guide.md | 2 +- docs/src/content/docs/reference/glossary.md | 2 +- .../content/docs/scenarios/cross-org-auth.md | 2 +- .../content/docs/scenarios/existing-repo.md | 2 +- docs/src/content/docs/scenarios/monorepo.md | 4 +- docs/src/content/docs/scenarios/solo-dev.md | 6 +- .../content/docs/scenarios/troubleshooting.md | 47 ++ docs/src/content/docs/scenarios/upgrading.md | 37 ++ docs/src/content/docs/sdk-first-mode.md | 2 +- package-lock.json | 6 +- packages/squad-cli/README.md | 10 +- packages/squad-cli/package.json | 5 +- packages/squad-cli/src/cli-entry.ts | 10 + packages/squad-cli/src/cli/commands/build.ts | 6 +- .../squad-cli/src/cli/commands/cross-squad.ts | 87 +++- .../src/cli/commands/migrate-backend.ts | 23 + packages/squad-cli/src/cli/commands/skill.ts | 7 +- .../squad-cli/src/cli/commands/state-mcp.ts | 6 + packages/squad-cli/src/cli/core/cast.ts | 86 ++++ packages/squad-cli/src/cli/core/init.ts | 66 ++- packages/squad-cli/src/cli/core/mcp-root.ts | 85 +++- packages/squad-cli/src/cli/core/mcp-spec.ts | 5 +- packages/squad-cli/src/cli/core/templates.ts | 66 ++- packages/squad-cli/src/cli/core/upgrade.ts | 205 ++++++++- .../templates/fact-checker-policy.md | 104 +++++ .../skills/cross-squad-communication/SKILL.md | 399 +++++++++++++++++ .../templates/skills/cross-squad/SKILL.md | 72 +++- .../templates/skills/release-process/SKILL.md | 4 +- .../templates/skills/squad-help/SKILL.md | 97 +++++ .../skills/squad-version-check/SKILL.md | 9 + .../squad-cli/templates/skills/squad/SKILL.md | 299 +++++++++++++ .../templates/skills/tiered-memory/SKILL.md | 75 ++-- .../squad-cli/templates/spawn-reference.md | 2 +- .../templates/squad.agent.md.template | 143 ++++++- packages/squad-sdk/package.json | 3 +- .../squad-sdk/src/config/gitignore-state.ts | 95 +++++ packages/squad-sdk/src/config/index.ts | 1 + packages/squad-sdk/src/config/init.ts | 201 ++++++++- packages/squad-sdk/src/index.ts | 6 + packages/squad-sdk/src/presets/index.ts | 36 +- packages/squad-sdk/src/presets/scaffold.ts | 401 ++++++++++++++++++ packages/squad-sdk/src/runtime/cross-squad.ts | 109 ++++- .../squad-sdk/src/skills/handler-types.ts | 2 +- packages/squad-sdk/src/tools/index.ts | 28 +- .../templates/fact-checker-charter.md | 83 ++++ .../templates/fact-checker-policy.md | 104 +++++ .../skills/cross-squad-communication/SKILL.md | 399 +++++++++++++++++ .../templates/skills/cross-squad/SKILL.md | 72 +++- .../templates/skills/release-process/SKILL.md | 4 +- .../templates/skills/squad-commands/SKILL.md | 303 +++++++++++++ .../templates/skills/squad-help/SKILL.md | 97 +++++ .../skills/squad-version-check/SKILL.md | 169 ++++++++ .../squad-sdk/templates/skills/squad/SKILL.md | 299 +++++++++++++ .../templates/skills/tiered-memory/SKILL.md | 75 ++-- .../squad-sdk/templates/spawn-reference.md | 2 +- .../templates/squad.agent.md.template | 143 ++++++- scripts/bump-build.mjs | 38 ++ scripts/check-bootstrap-deps.mjs | 5 +- scripts/pr-readiness.mjs | 15 +- src/Squad.Agents.AI/README.md | 323 ++++++++++++++ src/Squad.Agents.AI/Squad.Agents.AI.csproj | 47 ++ src/Squad.Agents.AI/SquadAgent.cs | 396 +++++++++++++++++ src/Squad.Agents.AI/SquadAgentDiagnostics.cs | 124 ++++++ src/Squad.Agents.AI/SquadAgentOptions.cs | 298 +++++++++++++ .../SquadAgentOptionsConfigurator.cs | 103 +++++ src/Squad.Agents.AI/SquadConnectionFactory.cs | 102 +++++ .../SquadServiceCollectionExtensions.cs | 307 ++++++++++++++ .../SquadSubagentTraceMapper.cs | 167 ++++++++ .../samples/Squad.Agents.AI.Sample/Program.cs | 343 +++++++++++++++ .../samples/Squad.Agents.AI.Sample/README.md | 3 + .../Squad.Agents.AI.Sample.csproj | 20 + templates/fact-checker-charter.md | 83 ++++ templates/fact-checker-policy.md | 104 +++++ templates/spawn-reference.md | 2 +- templates/squad.agent.md.template | 143 ++++++- .../Squad.Agents.AI.Tests.csproj | 25 ++ .../SquadAgentDefaultAgentFlagTests.cs | 220 ++++++++++ .../SquadAgentDefaultObservabilityTests.cs | 286 +++++++++++++ .../SquadAgentOptionsSecurityTests.cs | 145 +++++++ .../SquadAgentRoutingTests.cs | 212 +++++++++ .../SquadAgentSessionConfigTests.cs | 84 ++++ test/Squad.Agents.AI.Tests/SquadBYOKTests.cs | 179 ++++++++ .../SquadConnectionFactoryTests.cs | 77 ++++ .../SquadKeyedDITests.cs | 121 ++++++ .../SquadServiceCollectionExtensionsTests.cs | 190 +++++++++ .../SquadSubagentTraceTests.cs | 242 +++++++++++ test/builtin-skills.test.ts | 8 +- test/cli/init-upgrade-parity.test.ts | 2 +- test/cli/init.test.ts | 45 +- test/cli/upgrade.test.ts | 67 ++- test/cross-squad-registry.test.ts | 266 ++++++++++++ test/cross-squad.test.ts | 114 +++++ test/gitignore-state.test.ts | 178 ++++++++ test/human-journeys.test.ts | 2 +- test/init-scaffolding.test.ts | 70 +++ test/init-sdk.test.ts | 4 +- test/init.test.ts | 211 ++++++++- test/mcp-spec-init.test.ts | 43 ++ test/presets.test.ts | 200 +++++++++ test/repl-ux-fixes.test.ts | 4 +- test/squad-agent-roster.test.ts | 70 +++ test/state-backend-handshake.test.ts | 91 ++++ test/template-sync.test.ts | 141 ++++++ test/tools.test.ts | 4 +- test/upgrade-state-backend.test.ts | 107 +++++ 187 files changed, 15359 insertions(+), 377 deletions(-) create mode 100644 .changeset/bundle-missing-skills-1289-1264.md create mode 100644 .changeset/casting-identity-not-state.md create mode 100644 .changeset/conditional-state-gitignore.md create mode 100644 .changeset/fix-1126-skills-to-github-skills.md create mode 100644 .changeset/fix-1296-stop-mcp-home-pollution.md create mode 100644 .changeset/fix-1299-fact-checker-full-plumbing.md create mode 100644 .changeset/fix-1299-fact-checker-roster-instructions.md create mode 100644 .changeset/fix-1305-state-backend-handshake.md create mode 100644 .changeset/fix-fact-checker-auto-scaffold.md create mode 100644 .changeset/fix-permission-contract.md create mode 100644 .changeset/fix-release-pipeline-versions.md create mode 100644 .changeset/fix-skill-squad-rename-to-squad-help.md create mode 100644 .changeset/fix-squad-slash-command.md create mode 100644 .changeset/fix-squad-spawning-routing.md create mode 100644 .changeset/fix-yaml-escaping-skill.md create mode 100644 .changeset/init-prompt-copilot-member.md create mode 100644 .changeset/memory-tools-mcp-exposure.md create mode 100644 .changeset/preset-apply-wires-team-1288.md create mode 100644 .changeset/registry-subcommand.md create mode 100644 .changeset/wire-cross-squad-communication-skill.md create mode 100644 .github/workflows/squad-agents-ai-ci.yml create mode 100644 .github/workflows/squad-agents-ai-release.yml create mode 100644 .squad-templates/fact-checker-charter.md create mode 100644 .squad-templates/fact-checker-policy.md create mode 100644 .squad/skills/cross-squad-communication/SKILL.md create mode 100644 .squad/skills/iterative-retrieval/SKILL.md create mode 100644 .squad/skills/reflect/SKILL.md rename {packages/squad-cli/templates => .squad}/skills/squad-commands/SKILL.md (100%) create mode 100644 .squad/skills/squad-help/SKILL.md create mode 100644 .squad/skills/squad-version-check/SKILL.md create mode 100644 .squad/skills/squad/SKILL.md create mode 100644 .squad/skills/tiered-memory/SKILL.md create mode 100644 Squad.Agents.AI.slnx create mode 100644 docs/src/content/blog/032-v010-stabilisation-insider.md create mode 100644 docs/src/content/docs/features/coordinator-as-agent-export.md create mode 100644 docs/src/content/docs/features/cross-squad-discover.md create mode 100644 docs/src/content/docs/features/dual-mode-deployment.md create mode 100644 docs/src/content/docs/features/error-recovery.md create mode 100644 docs/src/content/docs/features/fleet-dispatch.md create mode 100644 docs/src/content/docs/features/mcp-frontmatter.md create mode 100644 docs/src/content/docs/features/preset.md create mode 100644 docs/src/content/docs/features/reflect.md create mode 100644 docs/src/content/docs/features/skill-security-scanner.md create mode 100644 docs/src/content/docs/features/teams-comms.md create mode 100644 docs/src/content/docs/features/tiered-memory.md create mode 100644 packages/squad-cli/templates/fact-checker-policy.md create mode 100644 packages/squad-cli/templates/skills/cross-squad-communication/SKILL.md create mode 100644 packages/squad-cli/templates/skills/squad-help/SKILL.md create mode 100644 packages/squad-cli/templates/skills/squad/SKILL.md create mode 100644 packages/squad-sdk/src/config/gitignore-state.ts create mode 100644 packages/squad-sdk/src/presets/scaffold.ts create mode 100644 packages/squad-sdk/templates/fact-checker-charter.md create mode 100644 packages/squad-sdk/templates/fact-checker-policy.md create mode 100644 packages/squad-sdk/templates/skills/cross-squad-communication/SKILL.md create mode 100644 packages/squad-sdk/templates/skills/squad-commands/SKILL.md create mode 100644 packages/squad-sdk/templates/skills/squad-help/SKILL.md create mode 100644 packages/squad-sdk/templates/skills/squad-version-check/SKILL.md create mode 100644 packages/squad-sdk/templates/skills/squad/SKILL.md create mode 100644 src/Squad.Agents.AI/README.md create mode 100644 src/Squad.Agents.AI/Squad.Agents.AI.csproj create mode 100644 src/Squad.Agents.AI/SquadAgent.cs create mode 100644 src/Squad.Agents.AI/SquadAgentDiagnostics.cs create mode 100644 src/Squad.Agents.AI/SquadAgentOptions.cs create mode 100644 src/Squad.Agents.AI/SquadAgentOptionsConfigurator.cs create mode 100644 src/Squad.Agents.AI/SquadConnectionFactory.cs create mode 100644 src/Squad.Agents.AI/SquadServiceCollectionExtensions.cs create mode 100644 src/Squad.Agents.AI/SquadSubagentTraceMapper.cs create mode 100644 src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/Program.cs create mode 100644 src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/README.md create mode 100644 src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/Squad.Agents.AI.Sample.csproj create mode 100644 templates/fact-checker-charter.md create mode 100644 templates/fact-checker-policy.md create mode 100644 test/Squad.Agents.AI.Tests/Squad.Agents.AI.Tests.csproj create mode 100644 test/Squad.Agents.AI.Tests/SquadAgentDefaultAgentFlagTests.cs create mode 100644 test/Squad.Agents.AI.Tests/SquadAgentDefaultObservabilityTests.cs create mode 100644 test/Squad.Agents.AI.Tests/SquadAgentOptionsSecurityTests.cs create mode 100644 test/Squad.Agents.AI.Tests/SquadAgentRoutingTests.cs create mode 100644 test/Squad.Agents.AI.Tests/SquadAgentSessionConfigTests.cs create mode 100644 test/Squad.Agents.AI.Tests/SquadBYOKTests.cs create mode 100644 test/Squad.Agents.AI.Tests/SquadConnectionFactoryTests.cs create mode 100644 test/Squad.Agents.AI.Tests/SquadKeyedDITests.cs create mode 100644 test/Squad.Agents.AI.Tests/SquadServiceCollectionExtensionsTests.cs create mode 100644 test/Squad.Agents.AI.Tests/SquadSubagentTraceTests.cs create mode 100644 test/cross-squad-registry.test.ts create mode 100644 test/gitignore-state.test.ts create mode 100644 test/squad-agent-roster.test.ts create mode 100644 test/state-backend-handshake.test.ts diff --git a/.changeset/bundle-missing-skills-1289-1264.md b/.changeset/bundle-missing-skills-1289-1264.md new file mode 100644 index 000000000..87d4ad480 --- /dev/null +++ b/.changeset/bundle-missing-skills-1289-1264.md @@ -0,0 +1,39 @@ +--- +"@bradygaster/squad-cli": patch +"@bradygaster/squad-sdk": patch +--- + +Fix #1289, #1264: Bundle missing skills on `squad init` and strip fabricated provenance from tiered-memory SKILL.md + +**Problem** + +Two regressions shipped in v0.10.0 caused several skills to silently never reach users: + +1. **#1289** — `squad-commands` and `squad-version-check` were in `MANIFEST_SKILL_NAMES` (sdk-init.ts) but missing from `packages/squad-sdk/templates/skills/`. The install loop silently skipped them with `if (storage.existsSync(srcSkill))`, so every `squad init` produced an install with these two skills absent. The trigger phrase `squad commands` was a dead no-op for every v0.10.0 user. + +2. **#1264** — `tiered-memory`, `iterative-retrieval`, and `reflect` skill files existed in both templates dirs but were never added to `MANIFEST_SKILL_NAMES`, so they never installed either. Additionally, `tiered-memory/SKILL.md` claimed `confidence: high` and `source: earned (production measurements in tamirdresher/tamresearch1, 34-74KB baseline payloads)` — the referenced repository does not exist and the measurement table at the bottom of the SKILL contained fabricated numbers. The SKILL also referenced `docs/tiered-memory-guide.md`, which does not exist. + +3. The previously-merged cross-squad fix (#1291) updated `cross-squad/SKILL.md` content but never added it to `MANIFEST_SKILL_NAMES`, so it also never installed. + +**Fix** + +- **Source of truth.** Added the 5 missing skill directories to `.squad/skills/` (`squad-commands`, `squad-version-check`, `tiered-memory`, `iterative-retrieval`, `reflect`). The pre-existing `sync-skill-templates.mjs` runs in `prebuild` and propagates them to both `packages/squad-cli/templates/skills/` and `packages/squad-sdk/templates/skills/`. + +- **`MANIFEST_SKILL_NAMES`** grew from 10 → 14 entries: added `tiered-memory`, `iterative-retrieval`, `reflect`, `cross-squad`. + +- **Anti-regression guard.** The install loop in `sdk-init.ts` now collects missing skill source dirs and `throw`s with a clear remediation message (`Run \`node scripts/sync-skill-templates.mjs\``) instead of silently skipping. This is what would have surfaced #1289 at build time instead of in user installs. + +- **Provenance honesty.** `tiered-memory/SKILL.md` rewritten to: + - Frontmatter: `confidence: design (runtime not yet implemented)` + `source: design proposal` + - Added prominent "Status (v0.10.0)" callout linking to #1264 for the runtime gap + - Removed the fabricated `tamirdresher/tamresearch1` measurement table + - Removed reference to non-existent `docs/tiered-memory-guide.md` + - References section now points to real issues (#1264, #686, #600) + +- **Test guard.** New regression test in `test/init.test.ts` (`should install every manifest-curated skill`) asserts every entry in the expected manifest list ends up at `.copilot/skills/{name}/SKILL.md` after `initSquad()`. + +**Out of scope (tracked separately)** + +The tiered-memory runtime (storage scaffolding under `.squad/memory/hot|cold|wiki/`, Scribe promotion logic, spawn-template tier-aware reads) remains tracked in #1264. This change lands the install-time fixes; runtime work follows in a separate PR. + +The comprehensive `cross-squad-communication` plugin from `tamirdresher/squad-skills` is also separate; this change just adds the existing upstream `cross-squad` skill to the manifest so users get the registry-aware version that landed with #1291. diff --git a/.changeset/casting-identity-not-state.md b/.changeset/casting-identity-not-state.md new file mode 100644 index 000000000..569abea80 --- /dev/null +++ b/.changeset/casting-identity-not-state.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-sdk": patch +--- + +docs: clarify in `init.ts` and `.gitattributes` / `.gitignore` generators that `.squad/casting/*` files are authoritative identity (registry + history), NOT mutable two-layer state. They belong on `main`, must be committed, and should NOT receive a `merge=union` driver or be added to `.gitignore`. Pure comment additions — no runtime behavior change. diff --git a/.changeset/conditional-state-gitignore.md b/.changeset/conditional-state-gitignore.md new file mode 100644 index 000000000..48b5f0db6 --- /dev/null +++ b/.changeset/conditional-state-gitignore.md @@ -0,0 +1,10 @@ +--- +"@bradygaster/squad-sdk": patch +"@bradygaster/squad-cli": patch +--- + +fix: conditional .gitignore entries for two-layer/orphan state backends + +When the state backend is `two-layer` or `orphan`, `squad init` and `squad upgrade` now add `.squad/decisions.md` and `.squad/agents/*/history.md` to `.gitignore` (delimited by `# Squad: state owned by squad-state branch` marker comments). When the backend is switched back to `local`, the marker block is removed so those files become committable again. + +Defense-in-depth complement to the existing pre-commit hook — prevents `git add .`, IDE "stage all", and `git add -A` from silently staging two-layer state into the working tree. diff --git a/.changeset/fix-1126-skills-to-github-skills.md b/.changeset/fix-1126-skills-to-github-skills.md new file mode 100644 index 000000000..e44599011 --- /dev/null +++ b/.changeset/fix-1126-skills-to-github-skills.md @@ -0,0 +1,68 @@ +--- +"@bradygaster/squad-sdk": minor +"@bradygaster/squad-cli": minor +--- + +Move bundled skills from `.copilot/skills/` to `.github/skills/` so they're visible to all Copilot surfaces (closes #1126) + +## Symptom (per #1126) + +Squad-bundled skills installed at `.copilot/skills/` are **invisible to every Copilot surface except Squad itself**: +- ❌ GitHub Copilot cloud agent +- ❌ Copilot CLI (outside Squad sessions) +- ❌ VS Code Copilot extension (agent mode) +- ❌ `@copilot` coding agent on issues +- ❌ Any future Copilot surface + +Per the [official Agent Skills docs](https://docs.github.com/en/copilot/concepts/agents/about-agent-skills), [add-skills docs](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/cloud-agent/add-skills), and [VS Code docs](https://code.visualstudio.com/docs/copilot/customization/agent-skills), the canonical **project-level** custom-skills location is `.github/skills/`. `.copilot/skills/` at repo root is not recognized by any Copilot surface — the home-directory equivalent `~/.copilot/skills/` IS recognized for **personal** skills, which is the source of the original mistake. + +## Fix + +1. **`squad init`** writes bundled skills to `.github/skills/{name}/SKILL.md` (was `.copilot/skills/`). +2. **`squad upgrade`** does the same AND auto-migrates legacy `.copilot/skills/{manifest-skill}/` into `.github/skills/{manifest-skill}/` (best-effort, preserves user-added non-manifest skills at `.copilot/skills/`, tombstones the legacy dir when empty). +3. **`TEMPLATE_MANIFEST`** destinations rewritten: all 10 skill entries now target `../.github/skills/` instead of `../.copilot/skills/`. +4. **`ENSURE_DIRECTORIES`** (upgrade.ts) updated so existing squads get `.github/skills/` created on upgrade. +5. **squad.agent.md** narrative updated: 5-path scan order now lists `.github/skills/` as path #2 (Copilot CLI's canonical custom-skills location) and `.copilot/skills/` as path #3 (Legacy install path; `squad upgrade` migrates). Personal scope (`~/.copilot/skills/`) preserved as-is. +6. **All other docs** (`spawn-reference.md`, `README.md`, `squad-commands` skill, `release-process` skill, `build.ts`, SDK type comments) updated to reference `.github/skills/` as the install destination. + +## Migration semantics (`squad upgrade`) + +`migrateLegacyCopilotSkills()` runs **before** `syncAllSkills`: + +| Scenario | Migration action | User-added skills at `.copilot/skills/` | +|---|---|---| +| `.copilot/skills/{manifest-skill}/` exists, `.github/skills/{manifest-skill}/` does NOT | Move legacy → new; remove legacy | Untouched | +| `.copilot/skills/{manifest-skill}/` exists AND `.github/skills/{manifest-skill}/` exists | Tombstone legacy (new wins) | Untouched | +| `.copilot/skills/my-custom-skill/` (NOT in TEMPLATE_MANIFEST) | Left alone | Preserved | +| `.copilot/skills/` becomes empty after migration | Directory removed | n/a | + +All migration steps are best-effort with try/catch — disk-write failures don't block upgrade. + +## Tests + +New regression tests: +- `test/init.test.ts > should install Squad-bundled skills at .github/skills/...` — asserts canonical path, asserts legacy path is NOT created +- `test/cli/upgrade.test.ts > should migrate manifest skills from .copilot/skills/ to .github/skills/` — asserts manifest skill moves, user-added skill preserved +- `test/cli/upgrade.test.ts > should NOT clobber a customized .github/skills/{name} if the legacy copy exists` — asserts both-locations case tombstones legacy without losing the new + +Updated existing tests: +- `test/builtin-skills.test.ts` regex now matches `.github/skills/` +- `test/cli/init.test.ts`, `test/init.test.ts`, `test/init-sdk.test.ts`, `test/cli/upgrade.test.ts`, `test/human-journeys.test.ts`, `test/repl-ux-fixes.test.ts`, `test/cli/init-upgrade-parity.test.ts` — install-path assertions updated to `.github/skills/` + +**188/188 init/upgrade/builtin tests pass; `npm run lint` clean.** + +## What's NOT changed (intentional) + +- **`.copilot/skills/` scan path stays in `squad.agent.md`'s 5-path skill discovery** — the coordinator still discovers user-added skills at the legacy location for backward compat; only Squad-installed (manifest) skills migrate. +- **`~/.copilot/skills/` (personal scope) is unchanged** — that's Copilot CLI's official personal-skills location and remains valid. +- **`test/skill-source.test.ts`, `test/skills-export-import.test.cjs`, `test/tools.test.ts`, `test/skill-script-loader.test.ts`** are NOT touched — they test the runtime skill loader and tool behavior, which still supports `.copilot/skills/` as a valid scan path. + +## Composability + +- **Disjoint from #1292, #1293, #1295, #1297, #1298, #1300, #1301, #1302, #1303** — touches the same `TEMPLATE_MANIFEST` array as #1292/#1295/#1303 but only modifies the destination path, not the source or entry list. Conflicts on `MANIFEST_SKILL_NAMES` are trivial: take the union of skill names with the `../.github/skills/` destination from this PR. +- This is the **canonical fix** for #1126; closes that issue. + +## Out of scope (separate follow-ups) + +- Backward-compat shim that adds a `.copilot/skills -> .github/skills` symlink. Not needed because users won't be looking at `.copilot/skills/` anymore once their tools find skills at `.github/skills/`. Filed as a follow-up if anyone reports broken muscle memory. +- A `squad doctor --skills` check that warns when `.copilot/skills/` still has manifest skills after upgrade (suggesting the migration silently failed). Worth adding to the next maintenance pass. diff --git a/.changeset/fix-1296-stop-mcp-home-pollution.md b/.changeset/fix-1296-stop-mcp-home-pollution.md new file mode 100644 index 000000000..cf8f38fec --- /dev/null +++ b/.changeset/fix-1296-stop-mcp-home-pollution.md @@ -0,0 +1,40 @@ +--- +"@bradygaster/squad-cli": patch +--- + +Fix #1296: stop writing `squad_state_` to `~/.copilot/mcp-config.json` on every `squad init` / `squad upgrade` + +`squad init` (line 408 of `packages/squad-cli/src/cli/core/init.ts`) and `squad upgrade` (line 738 of `upgrade.ts`) unconditionally called `ensureSquadStateMcpInUserConfig`, which wrote a `squad_state_` entry to `~/.copilot/mcp-config.json` keyed by a stable hash of the project path. Every new `squad init` accumulated another entry in HOME with no garbage collection. + +This contradicted the explicit iter-8 design intent documented at `packages/squad-cli/src/cli/core/mcp-root.ts:1-27`: + +> *iter-7: wrote `squad_state_` into the user's HOME `~/.copilot/mcp-config.json`. That polluted HOME with one entry per Squad project and required a stale-entry GC that we never built. It also touched a file outside the project, which is surprising for `squad init` / `squad upgrade`.* +> +> *iter-8 flips it back inside the project: we write `squad_state` to a repo-root `.mcp.json` ... **No HOME modifications.*** + +The repo-root `.mcp.json` writes (init.ts:403 / upgrade.ts:728) already cover all documented Copilot CLI launch modes: + +- ``copilot`` from the project root → walks up from cwd to git root, auto-loads `.mcp.json` +- ``copilot -p`` from the project root → same `.mcp.json` is found + +For ``copilot -p`` invocations launched from **outside** the project root, the right pattern is `--additional-mcp-config @.mcp.json` (already documented as the recommended pattern at init.ts:494). + +**Changes** + +- Removed the unconditional `ensureSquadStateMcpInUserConfig` call from `init.ts:408` and `upgrade.ts:738`. Replaced both with comments explaining the iter-8 design and pointing at #1296. +- Removed the now-unused import from both files. +- The function definition itself (`mcp-root.ts:178-228`) is **kept** — a future `squad doctor --mcp-prune` cleanup helper may want to inspect HOME for orphan entries. It's just no longer called from the init/upgrade auto-flow. + +**Tests** + +New regression test in `test/cli/init.test.ts`: + +> `should NOT write any squad_state entries to ~/.copilot/mcp-config.json (regression: #1296)` + +Isolates the developer's real HOME by setting `USERPROFILE`/`HOME` to a temp dir before init, then asserts no `squad_state*` keys appear under that temp HOME after init. The test passes only because the unconditional write is gone — previously this assertion would have failed. + +All 40 existing init tests still pass; `npm run lint` clean. + +**Cleanup** + +A follow-up could add `squad doctor --mcp-prune` to walk `~/.copilot/mcp-config.json`, find `squad_state_` entries whose target directory no longer exists, and remove them. Out of scope for this fix. diff --git a/.changeset/fix-1299-fact-checker-full-plumbing.md b/.changeset/fix-1299-fact-checker-full-plumbing.md new file mode 100644 index 000000000..bbe9ff5c2 --- /dev/null +++ b/.changeset/fix-1299-fact-checker-full-plumbing.md @@ -0,0 +1,77 @@ +--- +"@bradygaster/squad-cli": minor +"@bradygaster/squad-sdk": minor +--- + +Fix #1299 (deep): Fact Checker gets the same plumbing as Rai — rich charter at init + state directory + policy template + +PR #1300 (also #1299) fixed the documentation gap so the coordinator knows to roster Fact Checker. This PR fixes the **structural** gap behind it. Per user testing on 2026-06-13, even after #1300 the actual agent on disk is still a "name on disk with a 21-line placeholder" — three concrete problems: + +| Piece | Rai before this PR | Fact Checker before this PR | +|-------|--------------------|------------------------------| +| `charter.md` at init | Generic 478-byte stub from `generateCharter()` | Generic 523-byte stub from `generateCharter()` | +| Rich charter template usage | Only used by `squad upgrade` (never by `squad init`) | Same — never used by `squad init` | +| `.squad/{name}/policy.md` | ✅ Seeded from `rai-policy.md` (4160 bytes) | ❌ Directory does not exist | +| `.squad/{name}/audit-trail.md` | ✅ Seeded with append-only header | ❌ Directory does not exist | +| `merge=union` in `.gitattributes` | ✅ `.squad/rai/audit-trail.md` | ❌ No entry for fact-checker | +| `fact-checker-charter.md` distribution | n/a | Only in `packages/squad-cli/templates/` — missing from `.squad-templates/` (canonical source) AND `packages/squad-sdk/templates/`, so the SDK init path could never find it | + +## Fix (4 parts) + +### Part 1 — Rich charter at init (benefits BOTH Rai and Fact Checker) + +`packages/squad-sdk/src/config/init.ts` agent loop now looks up `{templatesDir}/{role}-charter.md` for each agent and uses that as `charter.md` content if it exists. Falls back to `generateCharter()` for user-defined agents that have no rich template (everyone except the built-ins). + +Result: a fresh `squad init` produces `.squad/agents/Rai/charter.md` with the full Rai charter (4525 bytes) and `.squad/agents/fact-checker/charter.md` with the full Fact Checker charter (3024 bytes). Previously both were 478-byte stubs. + +### Part 2 — `.squad/fact-checker/` state dir, mirroring `.squad/rai/` + +Added a new block in `init.ts` (right after the Rai seeding at lines 879–941) that creates: + +- `.squad/fact-checker/policy.md` — seeded from `templates/fact-checker-policy.md` (or a minimal inline fallback if the template is stripped) +- `.squad/fact-checker/audit-trail.md` — seeded with an append-only header + +The policy template (`.squad-templates/fact-checker-policy.md`, ~6 KB) is the canonical authority for the dual-mode operating rules per #789 + #1254: + +- **Mode 1 Verification:** confidence rating taxonomy (✅/⚠️/❌/🔍), what gets checked (URLs, packages, APIs, file paths, signatures, quotes, statistics, cross-references) +- **Mode 2 Devil's Advocate:** required brief structure (steelman → assumptions → pre-mortem → alternatives → risk acceptance) +- **Hard rules:** anti-fabrication guarantees — never cite unverified URL/package/API, never invent measurement data, never fabricate counter-hypotheses, never block on opinion +- **Advisory by default** with two narrow blocking exceptions (❌ at Pre-Ship; coordinator-escalated DA risk) +- **Opt-out model** mirroring Rai's +- **Audit trail rules** — succinct (verdict + citation, never raw source material) +- **Reviewer Rejection Protocol integration** for ❌ Contradicted verdicts + +### Part 3 — Fix the `.squad-templates/` distribution gap + +The existing `fact-checker-charter.md` had been added directly to `packages/squad-cli/templates/` only, bypassing the canonical `.squad-templates/` source. That meant `sync-templates.mjs` couldn't propagate it to `packages/squad-sdk/templates/` (which `getSDKTemplatesDir()` resolves at runtime), so the SDK init code path could never find the rich charter even if it tried. + +Fix: copied `fact-checker-charter.md` to `.squad-templates/` and re-synced. Now all 4 mirror targets have it. This unblocks Part 1. + +### Part 4 — Plumbing updates + +- `.gitattributes` block: added `.squad/fact-checker/audit-trail.md merge=union` alongside Rai's existing entry +- `packages/squad-cli/src/cli/core/templates.ts`: new `TEMPLATE_MANIFEST` entry for `fact-checker-policy.md → templates/fact-checker-policy.md` so `squad upgrade` propagates it +- `.squad-templates/squad.agent.md` Files Catalog table: 2 new rows for `.squad/fact-checker/policy.md` (authoritative) and `.squad/fact-checker/audit-trail.md` (derived/append-only) + +## Tests + +`test/init.test.ts` gains 3 regression tests (28/28 pass total): + +1. `should seed .squad/fact-checker/{policy,audit-trail}.md (regression: bradygaster/squad#1299)` — asserts policy declares both modes + anti-fabrication rules + confidence ratings; audit trail is append-only. +2. `should use the rich fact-checker-charter.md template for built-in agents at init (#1299)` — asserts the rendered charter is > 1 KB (not the 478-byte stub) and contains Verification Methodology + Confidence Ratings. +3. `should use the rich Rai-charter.md template at init (companion to fact-checker fix, #1299)` — asserts Rai gets the same treatment; charter references `.squad/rai/policy.md` and `.squad/rai/audit-trail.md`. + +`npm run lint` clean. + +## Composability + +This PR builds on #1300 (which adds the `## Fact Checker` section to `squad.agent.md` and the "team size" line fix). Both modify `.squad-templates/squad.agent.md` but in **disjoint regions**: + +- #1300 touches the team-size line at L56 and inserts a new `## Fact Checker` section before `## PRD Mode` +- #1301 touches the Files Catalog table at L710-711 + +They will merge cleanly in either order. The user-facing experience requires BOTH to land for full plumbing. + +## Closes / refs + +Closes #1299 (deep fix; #1300 was the surface fix). diff --git a/.changeset/fix-1299-fact-checker-roster-instructions.md b/.changeset/fix-1299-fact-checker-roster-instructions.md new file mode 100644 index 000000000..46ca9339e --- /dev/null +++ b/.changeset/fix-1299-fact-checker-roster-instructions.md @@ -0,0 +1,54 @@ +--- +"@bradygaster/squad-cli": patch +"@bradygaster/squad-sdk": patch +--- + +Fix #1299: tell the coordinator to roster Fact Checker (and Ralph + Rai explicitly) in the casting flow + +When a user runs `squad init` followed by `copilot --agent squad`, the coordinator's first-time casting flow correctly creates `.squad/agents/fact-checker/` on disk (per merged PR #1223) but **omits Fact Checker from the `## Members` table** in `.squad/team.md`. Rai is included, Ralph is included, Scribe is included — Fact Checker is silently dropped. + +## Root cause + +`.squad-templates/squad.agent.md` (the canonical coordinator instructions, mirrored to all package templates) had two gaps: + +1. **Line 56** said *"Determine team size (typically 4–5 + Scribe)"* — naming only Scribe among the always-on built-ins. +2. The Rai section (line 884) explicitly told the coordinator *"Rai always appears in team.md: `| Rai | RAI Reviewer | ...`"*, but **no equivalent section existed for Fact Checker**. + +So the model added Rai correctly (saw the roster-entry instruction) but had no instruction to add Fact Checker, even though the agent directory was scaffolded. + +## Fix + +1. Updated the team-size line to *"typically 4–5 + Scribe + Ralph + Rai + Fact Checker — the 4 always-on built-ins, see their dedicated sections below"*. +2. Added a full `## Fact Checker — Verification & Devil's Advocate` section that mirrors the Rai structure: + - Declares the single-agent dual-mode design (per #789 + #1254) — *"single agent, two modes"* + - Explicit **Roster Entry** line: *"Fact Checker always appears in `team.md`: `| Fact Checker | Fact Checker | .squad/agents/fact-checker/charter.md | 🔍 Verifier |`"* + - Trigger phrase table for both Verification mode and Devil's Advocate mode + - Confidence rating taxonomy (✅/⚠️/❌/🔍) + - DA brief structure (steelman, assumptions, pre-mortem, alternatives, risk acceptance) + - Boundaries, background-mode default, state location + +## Changes + +- `.squad-templates/squad.agent.md` (canonical source) updated. +- `sync-templates.mjs --sync` propagates to all 4 mirror targets: + - `templates/squad.agent.md.template` + - `packages/squad-cli/templates/squad.agent.md.template` + - `packages/squad-sdk/templates/squad.agent.md.template` + - `.github/agents/squad.agent.md` (this repo's own coordinator file) + +## Test coverage + +New `test/squad-agent-roster.test.ts` runs against **all 4 template targets** and asserts: + +- The "Determine team size" line mentions Scribe, Ralph, Rai, **and** Fact Checker +- A `## Fact Checker` section exists with an explicit "always appears in team.md" roster-entry line +- The Fact Checker section declares dual operating mode (Verification + Devil's Advocate) — anchors the design from #789 + #1254 so a future PR can't accidentally split them again (cf. closed PR #1294) +- The Ralph + Rai sections are still present and unmodified + +All 40 existing init/cli/init tests still pass; `npm run lint` clean. + +## Verification + +After this PR ships, a fresh `squad init` + `copilot --agent squad` produces a `team.md ## Members` table that includes Fact Checker alongside Scribe, Ralph, and Rai. + +Closes #1299 diff --git a/.changeset/fix-1305-state-backend-handshake.md b/.changeset/fix-1305-state-backend-handshake.md new file mode 100644 index 000000000..40af92ac4 --- /dev/null +++ b/.changeset/fix-1305-state-backend-handshake.md @@ -0,0 +1,67 @@ +--- +"@bradygaster/squad-cli": minor +"@bradygaster/squad-sdk": minor +--- + +Fix #1305: coordinator must probe for `squad_state` / `memory.*` tools before writing state, and hard-refuse writes when the bridge isn't reachable on non-local backends + +## Symptom + +A coordinator session against a Squad with `stateBackend: "two-layer"` wrote `.squad/decisions.md` + four `.squad/agents/{name}/history.md` files + a `.squad/decisions/inbox/copilot-directive-…md` via raw `create`/`edit` tools — never calling any `squad_state_*` or `memory.*` tool. The pre-commit hook caught the contract violation; the agent treated it as a "git problem" instead of the symptom it was. + +## Root cause (two failure modes stacked) + +**Mechanical (Copilot CLI):** Copilot CLI loads MCP server tools lazily — they're registered (via `.mcp.json`) but not always advertised in the model's initial function list. Models have to use `tool_search_tool_regex` to find them or guess they exist. The `squad_state` MCP server is correctly registered with `tools: ["*"]` and responds to `initialize`/`tools/list` over stdio — but the model's tool block at session start may not include them. This is a Copilot CLI architectural choice (lazy discovery vs. preload), not something Squad can fix server-side. + +**Behavioral (squad.agent.md):** The pre-1305 prompt said *"When memory tools are available, use them before writing durable memory by hand"* and *"If memory tools are not available, fall back to squad_decide or squad_state_write"*. Models read `"available"` as `"listed in my tool prompt"` instead of `"after probing to find out"`. There was no hard refusal clause for the case where the agent is about to violate the state-backend contract. + +## Fix + +Two changes to `.squad-templates/squad.agent.md` (synced to all 4 mirror targets): + +### 1. New "State-backend handshake" section (MANDATORY, runs once per session) + +Inserted right after the `stateBackend` resolution at L131. Steps: + +1. If `STATE_BACKEND ∈ {"local", "worktree"}`: file ops on `.squad/` are valid; skip the probe. +2. Otherwise: probe for `squad_state_health` via `tool_search_tool_regex` (or equivalent tool-discovery mechanism). On success, call `squad_state_health` once to confirm the bridge answers. +3. **If the probe fails**: HALT before any state write. Output a precise error to the user (verbatim text in the template): *"Squad's runtime state bridge is missing for backend `{STATE_BACKEND}`. The `squad_state` MCP server in `.mcp.json` is not reachable in this Copilot session. Restart Copilot CLI so `.mcp.json` is loaded, or change `stateBackend` to `local` in `.squad/config.json`."* + +### 2. Replaced soft "if not available" language with a HARD RULE in `### Memory Governance Tools` + +Lists the runtime-owned paths that are FORBIDDEN to write via `create`/`edit`/`write_file` on non-local backends when the bridge isn't reachable: + +- `.squad/decisions.md` +- `.squad/decisions/inbox/**` +- `.squad/agents/*/history.md` +- `.squad/casting/*.json` +- `.squad/identity/*.md` +- `.squad/memory/**` +- `.squad/orchestration-log/**` +- `.squad/log/**` +- `.squad/rai/audit-trail.md` +- `.squad/fact-checker/audit-trail.md` + +Clarified the local-backend carve-out so `STATE_BACKEND ∈ {"local", "worktree"}` users still freely use `create`/`edit`/`write_file` on `.squad/`. + +Also clarified that `memory.*` and `squad_state_*` share the same MCP server (they're aliases in the same registry — see `packages/squad-cli/src/cli/commands/state-mcp.ts`) so models stop treating them as separate availability checks. + +## Tests + +New `test/state-backend-handshake.test.ts` runs against all 4 template mirror targets and asserts (20 tests = 5 assertions × 4 files): + +- Mandatory handshake section exists with "every session" + "before any state mutation" timing +- Probe step exists with `squad_state_health` + tool-discovery mechanism +- HALT step exists with restart-CLI + change-stateBackend remediation +- HARD RULE exists listing the forbidden paths + `create`/`edit`/`write_file` tools by name +- Local/worktree carve-out preserved + +20/20 pass; `npm run lint` clean. + +## What's NOT in scope (filed/tracked separately) + +**Server-side fix (auto-preload MCP tools):** The mechanical root cause is a Copilot CLI feature — preload MCP server tools into the model's function list at session start instead of behind lazy discovery. Out of Squad's control. If maintainers agree it's worth pursuing, file as a feature request against `github/copilot-cli`. With this PR's prompt-level enforcement in place, the server-side fix becomes a nice-to-have rather than load-bearing. + +**Skill reinforcement (init-mode, agent-conduct):** Could repeat the rules in `.squad/skills/init-mode/SKILL.md` and `.squad/skills/agent-conduct/SKILL.md` as defense-in-depth for users who skip `squad upgrade`. Worth a small follow-up PR; not blocking. + +Closes #1305 diff --git a/.changeset/fix-fact-checker-auto-scaffold.md b/.changeset/fix-fact-checker-auto-scaffold.md new file mode 100644 index 000000000..899c60d98 --- /dev/null +++ b/.changeset/fix-fact-checker-auto-scaffold.md @@ -0,0 +1,16 @@ +--- +"@bradygaster/squad-cli": patch +--- + +Fix #1222: Auto-scaffold Fact Checker agent during `squad init`, `squad cast`, and `squad upgrade` + +The Fact Checker role was added in v0.10.0 (#789) with its catalog entry, charter template, skill, AGENT_TEMPLATES map entry, and template manifest entry — but it was never wired into the user-facing onboarding flow. Users running `squad init` got Scribe/Ralph/Rai but never saw Fact Checker. Users running `squad upgrade` from older versions never got Rai or Fact Checker scaffolded either (upgrade was intentionally silent on agents). + +This change wires Fact Checker (and Rai, as a defensive backfill) into three code paths: + +- **`init.ts`** — adds `fact-checker` to the default `agents:` array passed to `sdkInitSquad()`. Fresh `squad init` now produces `.squad/agents/fact-checker/`. +- **`cast.ts`** — adds `factCheckerMember()`, `factCheckerCharter()`, `hasFactChecker` branches in `castTeam()`, and the roster banner line. Interactive `squad cast` now offers Fact Checker as an always-on background agent. +- **`upgrade.ts`** — new `ensureBuiltinAgents()` runs in `runEnsureChecks()`. Idempotently scaffolds `.squad/agents/Rai/` and `.squad/agents/fact-checker/` from shipped charter templates if missing. Never overwrites existing charters or history files. Scribe and Ralph are intentionally NOT scaffolded by upgrade (they predate this fix in all squads, and their charters are inlined in cast.ts). + +Result: any squad — fresh init, interactive cast, or upgrade from any prior version — now ends up with Fact Checker available. + diff --git a/.changeset/fix-permission-contract.md b/.changeset/fix-permission-contract.md new file mode 100644 index 000000000..c40727410 --- /dev/null +++ b/.changeset/fix-permission-contract.md @@ -0,0 +1,6 @@ +--- +"@bradygaster/squad-sdk": patch +"@bradygaster/squad-cli": patch +--- + +Fix permission handler to use `approve-once` instead of deprecated `approved` kind, aligning with Copilot CLI v1.0.54+ permission contract diff --git a/.changeset/fix-release-pipeline-versions.md b/.changeset/fix-release-pipeline-versions.md new file mode 100644 index 000000000..cc918ff55 --- /dev/null +++ b/.changeset/fix-release-pipeline-versions.md @@ -0,0 +1,14 @@ +--- +"@bradygaster/squad-cli": patch +--- + +fix: release pipeline version pinning (#1203, #1204) + +- Lower SDK dependency floor from `>=0.10.0` to `>=0.9.0` so the CLI tarball + resolves against the last published SDK when the current version isn't yet on + the registry. +- Add `isLocalOrUnpublishedVersion` guard to `buildMcpServerSpecs` so local dev + builds and versions with build metadata (`+`) fall back to `@insider` instead + of writing an unresolvable version string into MCP config. +- Extend `resolveSquadStateMcpSpec` to short-circuit for build-metadata versions. +- Add CI step to verify SDK dependency is resolvable before publishing the CLI. diff --git a/.changeset/fix-skill-squad-rename-to-squad-help.md b/.changeset/fix-skill-squad-rename-to-squad-help.md new file mode 100644 index 000000000..29907db3c --- /dev/null +++ b/.changeset/fix-skill-squad-rename-to-squad-help.md @@ -0,0 +1,49 @@ +--- +"@bradygaster/squad-sdk": patch +"@bradygaster/squad-cli": patch +--- + +Fix `skill(Squad)` discovery — rename disambiguation skill to `squad-help` (supersedes #1297) + +PR #1297 added a disambiguation skill named `squad` so models calling `skill(Squad)` would get a redirect instead of "Skill not found". After local end-to-end testing on 2026-06-13: **the skill ships to disk correctly but never shows up in Copilot CLI's `/skills` list**. + +## Root cause (verified against Copilot CLI source 1.0.62-2) + +Decompiling `~/.copilot/pkg/win32-x64/1.0.62-2/app.js`: + +1. **Copilot CLI's skill schema is `{name, description, source, baseDir, allowedTools, pluginName, pluginVersion}`** (line 989). Frontmatter fields like `triggers:`, `domain:`, `confidence:`, `license:` are silently ignored. +2. **Skill loader returns `{skills, warnings, errors}`** (line 4427). Skills that fail to load are reported as errors. +3. **A skill named `squad` collides with the Copilot agent named `Squad`** (registered at `.github/agents/squad.agent.md`). The agent wins; the skill is hidden from `/skills`. + +The original `triggers:` frontmatter from #1297 was based on a wrong assumption — Copilot CLI doesn't read that field. Triggering happens via natural-language match against `description:`, and the skill name is what `skill(X)` looks up. + +## Fix + +1. **Rename** the disambiguation skill `squad` → `squad-help`. Avoids the agent-name collision, descriptive enough that the model can find it via description match when a user says *"how do I use squad"* or *"squad help"*. +2. **Update SKILL.md content:** + - `name: "squad-help"` (was `"squad"`) + - Removed `triggers:`, `domain:`, `confidence:`, `source:`, `license:` (all ignored by Copilot CLI) + - Added `allowedTools: []` (matches Copilot CLI's schema) + - `description:` rewritten to be self-explanatory so natural-language match works + - Body still explains the agent-vs-skill distinction and routes to `task(agent_type="Squad", …)` for misdirected `skill(Squad)` attempts + - Added explicit note about `/squad` slash command: it does not exist (slash commands are built-in CLI keywords, not auto-mapped from skills) and there's no way to create one without a Copilot CLI feature change +3. **Update** `MANIFEST_SKILL_NAMES` in `packages/squad-sdk/src/config/init.ts`: `'squad'` → `'squad-help'`. +4. **Add** `TEMPLATE_MANIFEST` entry in `packages/squad-cli/src/cli/core/templates.ts` for `squad-help` so `squad upgrade` also propagates the skill (independent of #1297 which only updated MANIFEST_SKILL_NAMES — `squad upgrade` uses a different code path that reads TEMPLATE_MANIFEST). + +## Test coverage + +New `test/init.test.ts > should install the squad-help disambiguation skill`: +- Asserts `.copilot/skills/squad-help/SKILL.md` exists after `initSquad()` +- Asserts the frontmatter says `name: "squad-help"` (not `"squad"`) — regression guard against re-introducing the collision +- Asserts content references `agent_type="Squad"` (the correct invocation path) +- Asserts content references `squad-commands` (the right next-step skill) + +26/26 init tests pass; `npm run lint` clean. + +## Supersedes #1297 + +PR #1297 added a colliding-name skill. This PR is the correct version. Close #1297 in favor of this one; the consolidated changeset will be picked up here. + +## Out of scope (separate issue worth filing) + +`squad upgrade` synced only 10 of 16 installed skills in local testing — `TEMPLATE_MANIFEST` (used by upgrade) is out of sync with `MANIFEST_SKILL_NAMES` (used by init). Specifically, `tiered-memory`, `iterative-retrieval`, `reflect`, `cross-squad`, `cross-squad-communication` (added in PRs #1292 + #1295) have entries in `MANIFEST_SKILL_NAMES` but not in `TEMPLATE_MANIFEST`. Will file a follow-up to add those to `TEMPLATE_MANIFEST` so both code paths agree. diff --git a/.changeset/fix-squad-slash-command.md b/.changeset/fix-squad-slash-command.md new file mode 100644 index 000000000..dc4e364d6 --- /dev/null +++ b/.changeset/fix-squad-slash-command.md @@ -0,0 +1,76 @@ +--- +"@bradygaster/squad-sdk": minor +"@bradygaster/squad-cli": minor +--- + +Make `/squad` a real slash command — rename `squad-commands` skill to `squad` with `user-invocable: true` + +## What this enables + +Users can type `/squad` in any Copilot CLI session in a squad-initialized project and get the categorized command catalog (Install & Upgrade, Team Management, Issues & PRs, Plugins & Skills, Model & Cost, Sessions & State). This is what users naturally expect — Squad ships an in-chat command surface, and the slash key is the muscle memory. + +## Verified mechanism (Copilot CLI 1.0.62-2) + +Decompiling `~/.copilot/pkg/win32-x64/1.0.62-2/sdk/index.js` line 2618: + +```js +getLoadedSkills().filter(e => e.userInvocable) + .map(e => ({name: `/${eF(e)}`, isSkill: true, skill: e})) +``` + +Any skill with frontmatter `user-invocable: true` is auto-registered by Copilot CLI as a slash command at `/`. The built-in `customize-cloud-agent` skill shipped with Copilot CLI uses this exact pattern with `user-invocable: false`; setting it to `true` makes the skill appear in `/skills` AND as a slash keystroke. + +The previous skill `squad-commands` had no `user-invocable` field (Copilot CLI defaults to false), so `/squad-commands` did not exist. Users either typed natural language ("squad commands", "what can squad do") or never discovered the menu. + +## Changes + +1. **Renamed** the `squad-commands` skill to `squad` (canonical source in `.squad/skills/squad/`). +2. **Frontmatter rewritten** to match Copilot CLI's actual schema (verified against decompiled source): + - `name: squad` (was `squad-commands` — `/squad` is shorter, intuitive, and matches the agent name without colliding because slash commands and skill lookups are separate namespaces) + - `user-invocable: true` ← **the load-bearing change** + - `description:` rewritten to be self-explanatory so natural-language match also still works + - `allowedTools: []` (matches Copilot CLI's schema) + - Removed unused fields: `domain:`, `confidence:`, `source:`, `triggers:` (all silently ignored by Copilot CLI) +3. **Body text** updated to reference `/squad` as the primary invocation path. +4. **`MANIFEST_SKILL_NAMES`** in `packages/squad-sdk/src/config/init.ts`: `'squad-commands'` → `'squad'`. +5. **`TEMPLATE_MANIFEST`** in `packages/squad-cli/src/cli/core/templates.ts`: updated `source` + `destination` + `description` to reflect the rename. +6. **Removed** stale `packages/{squad-cli,squad-sdk}/templates/skills/squad-commands/` directories. + +## Test coverage + +New `test/init.test.ts > should install the squad slash-command skill with user-invocable: true`: +- Asserts `.copilot/skills/squad/SKILL.md` exists after `initSquad()` +- Asserts frontmatter contains `user-invocable: true` (load-bearing — without this, no slash command) +- Asserts frontmatter `name: squad` (load-bearing — slash command is `/`) +- Asserts the menu presentation rules are still in the body + +26/26 init tests pass; `npm run lint` clean. + +## How users will experience this + +After `squad init` + opening Copilot CLI in the project: + +``` +> /squad +📋 Squad Commands — pick a category: + 1. Install & Upgrade + 2. Team Management + 3. Issues & PRs + 4. Plugins & Skills + 5. Model & Cost + 6. Sessions & State +``` + +## Composability with other open PRs + +- **Disjoint from #1292** (skills bundling, 4 new entries). Both modify `MANIFEST_SKILL_NAMES` but `squad-commands → squad` is a rename, not an add. Merge order doesn't matter — the renamer wins. +- **Disjoint from #1302** (`squad-help` disambiguation skill). `squad-help` covers the `skill(Squad)` misdirect case; this PR covers the `/squad` slash command UX. They're complementary. + +## Out of scope (future, mentioned in user request) + +The user wants to eventually install the `squad` skill **machine-wide** (e.g., `~/.copilot/skills/squad/`) so `/squad init` works in any folder even before `squad` is initialized in that project. That's a separate piece of work because: +1. It requires a `squad install --global-skill` command or similar (none exists today) +2. The `/squad` menu currently calls into `squad` CLI subcommands that assume `.squad/` exists in cwd — the menu would need a "Init Mode" branch for the "no `.squad/` yet" case +3. Copilot CLI's `~/.copilot/skills/` is a personal scope, so it'd need to be opt-in per-user + +Will file a follow-up issue tracking that work after this lands. diff --git a/.changeset/fix-squad-spawning-routing.md b/.changeset/fix-squad-spawning-routing.md new file mode 100644 index 000000000..5e86ee042 --- /dev/null +++ b/.changeset/fix-squad-spawning-routing.md @@ -0,0 +1,40 @@ +--- +"@bradygaster/squad-cli": patch +"@bradygaster/squad-sdk": patch +--- + +Coordinator now recognises "spawn a squad" / "another squad" / "two squads" as Squad-PRODUCT vocabulary + +## Symptom + +A coordinator initialised by `squad init` saw prompts like *"spawn two squads of designers and devs"* and fanned out raw `task` agents inside its own context, treating "squad" as generic English for "team / group". It never invoked the bundled `cross-squad` or `cross-squad-communication` skills, so the peer-squad delegation protocol (registry / manifest / sync CLI / git-async / GitHub-issue patterns) was bypassed entirely. + +Two structural holes in `squad.agent.md` allowed this: + +1. The Routing table had no row that mapped "spawn a squad" phrasing to the Squad-PRODUCT concept. +2. The Skill-aware-routing block was process discipline ("check skill directories by domain relevance") with no hard "if the user's word matches a skill name, MUST load the skill" trigger. + +## Fix + +Two surgical edits to the canonical `.squad-templates/squad.agent.md` (synced to all 4 mirror copies via `scripts/sync-templates.mjs --sync`): + +1. **New routing-table row** — matches "spawn a squad", "another squad", "two squads", "second squad", "fan out to squads", "delegate to a squad", or any phrasing that treats "squad" as a unit to spawn or address. Action: invoke the `skill` tool on `cross-squad` AND `cross-squad-communication` BEFORE any `task` spawn, then delegate via Pattern 0/1/2/3 — never fan out raw `task` agents in the coordinator's own context. + +2. **New "Hard trigger — keyword-to-skill match" paragraph** at the top of the Skill-aware-routing block — if any word in the request matches an installed skill name (e.g., "squad" → `cross-squad`, "reflect" → `reflect`, "ceremony" → matching ceremony skill, "fact-check" → `fact-checking`, "release" → `release-process`), the coordinator MUST invoke the `skill` tool to fully load that skill BEFORE designing its approach. Includes a one-line "failure mode this closes" pointer so the rule survives future paraphrasing. + +3. **Companion `cross-squad/SKILL.md` opener strengthened** — added a one-line "Read this FIRST any time the user says 'squad' as a thing to spawn / delegate to / address" callout above the existing `## Context` paragraph, so even a coordinator that skips the routing-table row still hits the trigger when it does eventually load the skill. + +## Regression test + +`test/template-sync.test.ts` now asserts, for every mirrored copy of `squad.agent.md` (5 locations — canonical + 3 template mirrors + `.github/agents/`), that: + +* The routing-table row exists and contains all three trigger phrases ("spawn a squad", "another squad", "two squads"). +* The row's action cell references the `cross-squad` skill AND the `skill` tool. +* The "Hard trigger" + "keyword-to-skill match" markers are both present in the Skill-aware-routing block. +* The hard-trigger paragraph names the worked example `"squad" → cross-squad` so future edits can't drop the concrete mapping. + +223/223 template-sync tests pass. + +## Composability + +Disjoint from all other open PRs in the v0.10 stabilisation set (#1292, #1293, #1295, #1298, #1300, #1301, #1302, #1303, #1304, #1306). Only modifies `.squad-templates/squad.agent.md` + its 4 mirrors + `cross-squad/SKILL.md` + the template-sync test. Existing squads can pick up the fix two ways: (a) `squad upgrade` once this lands, or (b) hand-patch `.squad/squad.agent.md` against the upstream diff and restart the Copilot CLI session. diff --git a/.changeset/fix-yaml-escaping-skill.md b/.changeset/fix-yaml-escaping-skill.md new file mode 100644 index 000000000..3c2623fd9 --- /dev/null +++ b/.changeset/fix-yaml-escaping-skill.md @@ -0,0 +1,8 @@ +--- +"@bradygaster/squad-cli": patch +--- + +Fix YAML escaping in skill command apm.yml generation + +Use `JSON.stringify()` for skill descriptions in generated apm.yml files +to properly escape quotes and newlines, preventing invalid YAML output. diff --git a/.changeset/init-prompt-copilot-member.md b/.changeset/init-prompt-copilot-member.md new file mode 100644 index 000000000..eb3dc7a95 --- /dev/null +++ b/.changeset/init-prompt-copilot-member.md @@ -0,0 +1,10 @@ +--- +"@bradygaster/squad-cli": minor +--- + +Add @copilot team member prompt during `squad init` + +During interactive `squad init`, users are now prompted to add @copilot +as an autonomous team member. Answering yes adds the Coding Agent section +to team.md and copies copilot-instructions.md into the project. Non-interactive +mode skips silently with a hint to run `squad copilot enable` later. diff --git a/.changeset/memory-tools-mcp-exposure.md b/.changeset/memory-tools-mcp-exposure.md new file mode 100644 index 000000000..7a6a30528 --- /dev/null +++ b/.changeset/memory-tools-mcp-exposure.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-cli": patch +--- + +Expose memory.* tools (classify, write, search, promote, delete, audit) through the squad_state MCP server so agents can discover and call them. Also pin squad_state to user-level ~/.copilot/mcp-config.json for external `copilot -p` mode compatibility. diff --git a/.changeset/preset-apply-wires-team-1288.md b/.changeset/preset-apply-wires-team-1288.md new file mode 100644 index 000000000..0f2df1982 --- /dev/null +++ b/.changeset/preset-apply-wires-team-1288.md @@ -0,0 +1,38 @@ +--- +"@bradygaster/squad-sdk": patch +"@bradygaster/squad-cli": patch +--- + +Fix #1288: `squad preset apply` now wires team.md, routing.md, and casting state + +`squad preset apply ` used to copy only the preset's agent charters into `.squad/agents/`. It left: + +- `.squad/team.md` `## Members` table empty +- `.squad/routing.md` with no Work Type rows for the preset agents +- `.squad/casting/registry.json`, `history.json`, and `policy.json` not created + +Net result: the coordinator's mode-switch check saw an empty `## Members` table and treated every session as **Init Mode**, proposing to re-scaffold a team the user already applied — defeating the entire purpose of presets. + +This change adds a new merge-friendly scaffold module (`packages/squad-sdk/src/presets/scaffold.ts`) that, after `applyPreset` copies the charters, wires the preset agents into: + +- **team.md** `## Members` — creates the file from scratch if missing, or merges new rows into an existing `## Members` table while preserving the surrounding content (Coordinator section, Project Context, etc.). Idempotent: a second apply does not duplicate rows. +- **routing.md** `## Work Type → Agent` — creates from scratch or appends new rows after the existing routing table. Each preset agent becomes `| | | — |`. +- **casting/registry.json** — merges new agents into an existing registry without clobbering pre-existing entries. Universe = `preset:` so future casts can distinguish preset-provided agents. +- **casting/history.json** — appends a preset-application snapshot and a universe_usage_history entry. +- **casting/policy.json** — created with sensible defaults only if missing; never overwrites an existing policy. + +Failure modes: + +- Agents with `status: 'error'` in the per-agent results (e.g., source dir missing) are excluded from the wiring step. +- Agents with `status: 'skipped'` (already exist in target) ARE wired into team.md/registry so the team reflects user intent. +- If the scaffolding itself throws (e.g., disk error), a synthetic error result is appended to the return value so the CLI can surface it; per-agent install results are preserved. + +Out of scope (tracked separately): deduplicating these writers with the equivalent fresh-write versions in `packages/squad-cli/src/cli/core/cast.ts`. A future refactor can move both call sites to the shared SDK module. + +Test coverage in `test/presets.test.ts`: +- `wires preset agents into team.md ## Members (#1288)` +- `merges preset agents into an existing team.md without duplicating rows (#1288)` — includes idempotency assertion +- `writes casting registry.json, history.json, and policy.json (#1288)` +- `appends routing rows for preset agents to routing.md (#1288)` + +Closes #1288 diff --git a/.changeset/registry-subcommand.md b/.changeset/registry-subcommand.md new file mode 100644 index 000000000..08be83809 --- /dev/null +++ b/.changeset/registry-subcommand.md @@ -0,0 +1,20 @@ +--- +"@bradygaster/squad-cli": minor +"@bradygaster/squad-sdk": minor +--- + +Add `squad registry add/list/remove` for discovery-only peer squads (no inheritance) + +The existing `squad upstream add` triggers inheritance (skills/decisions/wisdom flow from the other squad into yours at session start). For peer relationships where you want discovery and delegation but NOT inheritance, you previously had to hand-edit `.squad/squad-registry.json` — the `squad discover` error message even told you to "create a squad-registry.json" manually. + +This adds a real CLI surface for the registry, symmetric to `squad upstream`: + +- `squad registry add ` — registers a peer, validates its manifest, refuses on duplicate name +- `squad registry list` — shows all registered peers +- `squad registry remove ` — removes by name + +It also fixes a subtle path-semantics confusion: `readManifest()` now accepts BOTH `repo-root` and `repo-root/.squad` paths (the docs/SKILL showed the `.squad`-suffixed form, but the code previously joined `.squad/manifest.json` onto whatever you gave it, so the suffixed form silently failed). The SDK helpers (`readSquadRegistry`, `writeSquadRegistry`, `addRegistryEntry`, `removeRegistryEntry`) are exported for tooling. + +The `cross-squad` SKILL and the `squad discover` empty-state hint are updated to reflect the new command. + +Closes #1290. diff --git a/.changeset/wire-cross-squad-communication-skill.md b/.changeset/wire-cross-squad-communication-skill.md new file mode 100644 index 000000000..898af4c20 --- /dev/null +++ b/.changeset/wire-cross-squad-communication-skill.md @@ -0,0 +1,53 @@ +--- +"@bradygaster/squad-sdk": minor +"@bradygaster/squad-cli": minor +--- + +Add **cross-squad-communication** as a built-in skill (companion to cross-squad) + +The merged registry work (#1291) added `squad registry add/list/remove` and the `cross-squad/SKILL.md` for discovery. But discovery is only half the story — once a peer squad is known, agents need to know **how** to actually exchange information with it (sync CLI sessions, async git-based requests, issue-based delegation). + +This change ports `cross-squad-communication` from [tamirdresher/squad-skills](https://github.com/tamirdresher/squad-skills/tree/main/plugins/cross-squad-communication) into Squad's bundled skills so a fresh `squad init` produces a coordinator that already knows the four communication patterns. The plugin was validated against two production squad instances (one GitHub-hosted, one Azure DevOps-hosted) before being ported. + +**What this skill teaches** + +| Pattern | When to use | +|---|---| +| Pattern 0: Synchronous CLI session | Quick knowledge queries — spawn `copilot -C ` with the prompt text via `-p (Get-Content $promptFile -Raw)` | +| Pattern 1: Read-only metadata scan | "What's the architecture of squad X?" — read their `team.md` / `decisions.md` directly | +| Pattern 2: Async git-based request/response | Long-running work, PR reviews, multi-cycle tasks. Request files in `.squad/cross-squad/requests/`, response files in `.squad/cross-squad/responses/`. | +| Pattern 3: Issue-based delegation | GitHub-hosted repos — `gh issue create` with `squad:cross-squad` label as the message bus | + +Plus: decision tree for choosing the right pattern, anti-patterns, request/response YAML format, and validation status. + +**Changes** + +- New `.squad/skills/cross-squad-communication/SKILL.md` (canonical source). `sync-skill-templates.mjs` (prebuild) propagates to both `packages/squad-cli/templates/skills/` and `packages/squad-sdk/templates/skills/`. +- `MANIFEST_SKILL_NAMES` in `packages/squad-sdk/src/config/init.ts` grows by 1 entry: `cross-squad-communication`. Now 11 entries. +- `cross-squad/SKILL.md` (the registry-aware skill from #1291) gets a one-paragraph "Companion skill" note at the top pointing to `cross-squad-communication` for protocol details. The two skills are designed to be used together: `cross-squad` answers "who?" (discovery via registry), `cross-squad-communication` answers "how?" (the 4 communication patterns). + +**Genericization** + +The original plugin documented validation against specific internal Microsoft repositories. Examples in this version use generic names (`platform-squad`, `research-squad`, etc.) so they're meaningful to all upstream users. The protocol mechanics are unchanged. The frontmatter `source:` attributes the port to `tamirdresher/squad-skills`. + +**Test coverage** + +New `test/init.test.ts > should install cross-squad-communication skill (companion to cross-squad — #5)`: asserts the SKILL.md ends up at `.copilot/skills/cross-squad-communication/SKILL.md` after `initSquad()` and that the content contains the expected pattern names. 26/26 init tests pass; `npm run lint` clean. + +**Composition with #1291** + +```bash +# 1. Init produces a squad that already knows both skills +squad init + +# 2. Register a peer squad (#1291) +squad registry add ../peer-squad-repo + +# 3. Ask the coordinator: "what are the team members of the peer squad?" +# → The coordinator now has cross-squad-communication's Pattern 1 in scope +# and knows to read team.md from the registered peer +``` + +**Note on overlap with #1292** + +PR #1292 (skills bundling fix) adds `squad-commands`, `squad-version-check`, `tiered-memory`, `iterative-retrieval`, `reflect`, and `cross-squad` to `MANIFEST_SKILL_NAMES`. That PR and this one both modify the same array but add disjoint entries. When both merge, the manifest grows to 15 entries (10 base + 4 from #1292 + 1 from here). Either PR can land first. diff --git a/.gitattributes b/.gitattributes index 7673b120e..4654fe2e0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,6 +3,12 @@ .squad/agents/*/history.md merge=union .squad/log/** merge=union .squad/orchestration-log/** merge=union + +# NOTE: .squad/casting/* files are AUTHORITATIVE IDENTITY (team-defined agent +# universe/roles, persistent name registry, universe assignment history). +# They are committed to 'main' like team.md and routing.md, not to squad-state. +# They do NOT need union merge and are intentionally NOT listed above. + # Squad: union merge for append-only team state files .squad/decisions/decisions.md merge=union # Squad: union merge for append-only team state files diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 9c71cacce..0ec9b9448 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -53,13 +53,14 @@ No team exists yet. Propose one — but **DO NOT create any files until the user 1. **Identify the user.** Run `git config user.name` to learn who you're working with. Use their name in conversation (e.g., *"Hey {user}, what are you building?"*). Store their name (NOT email) in `team.md` under Project Context. **Never read or store `git config user.email` — email addresses are PII and must not be written to committed files.** 2. Ask: *"What are you building? (language, stack, what it does)"* 3. **Cast the team.** Before proposing names, run the Casting & Persistent Naming algorithm (see that section): - - Determine team size (typically 4–5 + Scribe). + - Determine team size: pick **4–5 cast (user-domain) agents**, then add the **4 always-on built-ins** (Scribe + Ralph + Rai + Fact Checker — see their dedicated sections below). So a typical fresh squad has **8–9 total roster entries**, not 4–5. - Determine assignment shape from the user's project description. - Derive resonance signals from the session and repo context. - Select a universe. Allocate character names from that universe. - Scribe is always "Scribe" — exempt from casting. - Ralph is always "Ralph" — exempt from casting. - Rai is always "Rai" — exempt from casting. + - Fact Checker is always "Fact Checker" — exempt from casting (same pattern as Scribe / Ralph / Rai). 4. Propose the team with their cast names. Example (names will vary per cast): ``` @@ -256,14 +257,16 @@ The `name` parameter generates the human-readable agent ID shown in the tasks pa **When you detect a directive:** -1. Capture the directive with the runtime state tools when available: - - Prefer `squad_state_write` to write `decisions/inbox/copilot-directive-{timestamp}.md` using this format: +1. Capture the directive with governed memory tools when available: + - Prefer `memory.write` with class `decision` to persist the directive through the governed pipeline: ``` - ### {timestamp}: User directive - **By:** {user name} (via Copilot) - **What:** {the directive, verbatim or lightly paraphrased} - **Why:** User request — captured for team memory + memory.write({ + class: "decision", + key: "copilot-directive-{timestamp}", + content: "### {timestamp}: User directive\n**By:** {user name} (via Copilot)\n**What:** {the directive, verbatim or lightly paraphrased}\n**Why:** User request — captured for team memory" + }) ``` + - If `memory.write` is not available, fall back to `squad_decide` or `squad_state_write` to `decisions/inbox/copilot-directive-{timestamp}.md`. - Do **not** run `git notes`, checkout `squad-state`, or manually commit mutable `.squad/` state. The runtime owns state persistence. 2. Acknowledge briefly: `"📌 Captured. {one-line summary of the directive}."` 3. If the message ALSO contains a work request, route that work normally after capturing. If it's directive-only, you're done — no agent spawn needed. @@ -295,19 +298,23 @@ The routing table determines **WHO** handles work. After routing, use Response M | PRD intake ("here's the PRD", "read the PRD at X", pastes spec) | Follow PRD Mode (see that section) | | Human member management ("add {name} as PM", routes to human) | Follow Human Team Members (see that section) | | Ralph commands ("Ralph, go", "keep working", "Ralph, status", "Ralph, idle") | Follow Ralph — Work Monitor (see that section) | -| "squad commands", "what can squad do", "show me squad options", "slash commands", "what commands are available" | Read `.copilot/skills/squad-commands/SKILL.md`, present categorized menu (see squad-commands skill) | +| "squad commands", "what can squad do", "show me squad options", "slash commands", "what commands are available" | Read `.github/skills/squad/SKILL.md`, present categorized menu (see squad skill). Users can also invoke this directly via `/squad`. | | "upgrade squad", "update squad", "what's new in squad", "install the update" | Run upgrade flow per `.squad/templates/session-init-reference.md` | +| User says "spawn a squad", "another squad", "two squads", "second squad", "fan out to squads", "delegate to a squad", or any phrasing that treats "squad" as a unit to spawn or address | This is the Squad-PRODUCT concept (a peer with its own `.squad/`), NOT generic English "team" or "group". **Before any `task` spawn**, invoke the `skill` tool on `cross-squad` (discovery via registry/upstream) AND `cross-squad-communication` (sync CLI / git-async / GH-issue protocols) to load the full peer-squad workflow. Then delegate via Pattern 0/1/2/3 — NOT by fanning out raw `task` agents inside your own coordinator context. **Default = literal Squad install.** Calling `task` sub-agents "squad-alpha" / "squad-beta" does NOT make them squads — that is the explicit anti-pattern. **If the request is ambiguous** (could be either "two real `.squad/` installs" or "two ad-hoc groups of agents"), you MUST `ask_user` with a 2-choice prompt — `["Real squads — separate .squad/ per squad (heavier, persistent)", "Ad-hoc agents — one-shot task dispatch (lighter, ephemeral)"]` — and never silently pick the cheaper option. If the peer doesn't exist yet, walk the user through `squad init` in a separate directory or `squad registry add` first. | | Rai commands ("Rai, review this", "RAI check", "content safety review") | Follow Rai — RAI Reviewer (see that section) | | General work request | Check routing.md, spawn best match + any anticipatory agents | | Quick factual question | Answer directly (no spawn) | | Ambiguous | Pick the most likely agent; say who you chose | | Multi-agent task (auto) | Check `ceremonies.md` for `when: "before"` ceremonies whose condition matches; run before spawning work | - + **Skill-aware routing:** Before spawning, check ALL project skill directories in precedence order for skills relevant to the task domain: + +**Hard trigger — keyword-to-skill match (do this FIRST, before any spawn or task call):** If any word in the user's request matches the name of an installed skill (e.g., "squad" → `cross-squad` and/or `cross-squad-communication`, "reflect" → `reflect`, "ceremony" → the matching ceremony skill, "fact-check" → `fact-checking`, "release" → `release-process`), you MUST invoke the `skill` tool to fully load that skill BEFORE designing your approach or selecting agents. The one-line description in the discovery list is for discovery only — it is NOT sufficient to act on. Read the full SKILL.md, then route. This rule applies whether or not the request also matches a routing-table row above; when both apply, load the skill first, then execute the routing-table action. Failure mode this rule closes: a coordinator that sees "squad" in the prompt, treats it as generic English, and fans out raw `task` agents instead of invoking the `cross-squad-communication` peer-delegation protocol. + 1. `.squad/skills/` — **Team-earned skills** (highest precedence). Patterns captured by agents during work; a team-written override beats any generic version. -2. `.copilot/skills/` — **Project playbook.** Human-curated process knowledge: release workflows, git conventions, reviewer protocols. -3. `.github/skills/` — **Generic project skills.** Sits alongside `.github/workflows/` and `.github/copilot-instructions.md`; common location for shared-repo skills. +2. `.github/skills/` — **Project playbook** (Copilot CLI's canonical custom-skills location). Human-curated process knowledge: release workflows, git conventions, reviewer protocols. Sits alongside `.github/workflows/` and `.github/copilot-instructions.md`. `squad init` and `squad upgrade` install Squad's bundled skills here. +3. `.copilot/skills/` — **Legacy install path** (pre-1304). Older squads may have skills here; `squad upgrade` migrates them to `.github/skills/`. Still scanned for any user-added or unmigrated skills. 4. `.claude/skills/` — **Claude-ecosystem skills.** Vendor-specific path; less common in multi-tool projects. 5. `.agents/skills/` — **Generic agents path** (lowest project precedence). Least-specific convention. @@ -403,7 +410,7 @@ prompt: | TARGET FILE(S): {exact file path(s)} Do the work. Keep it focused. - If you made a meaningful decision, persist it with `squad_decide` when available, or `squad_state_write` to `decisions/inbox/{name}-{brief-slug}.md`. Do not run git notes, switch branches, or write mutable `.squad/` state by hand. + If you made a meaningful decision, persist it with `memory.write` (class: `decision`) when available, or fall back to `squad_decide` / `squad_state_write` to `decisions/inbox/{name}-{brief-slug}.md`. Do not run git notes, switch branches, or write mutable `.squad/` state by hand. ⚠️ OUTPUT: Report outcomes in human terms. Never expose tool internals or SQL. ⚠️ RESPONSE ORDER: After ALL tool calls, write a plain text summary as FINAL output. @@ -528,7 +535,7 @@ When the user gives any task, the Coordinator MUST: To enable full parallelism, shared writes use a drop-box pattern that eliminates file conflicts: **decisions.md** — Agents do NOT write directly to `decisions.md`. Instead: -- Agents record decisions with `squad_decide` or `squad_state_write` to `decisions/inbox/{agent-name}-{brief-slug}.md`. +- Agents record decisions with `memory.write` (class: `decision`) when available, or fall back to `squad_decide` / `squad_state_write` to `decisions/inbox/{agent-name}-{brief-slug}.md`. - The runtime routes that write to the configured state backend. Agents must not run `git notes`, switch to `squad-state`, or hand-roll backend commits. - Scribe merges into the canonical `.squad/decisions.md` and clears the inbox - All agents READ from `.squad/decisions.md` at spawn time (last-merged snapshot) @@ -707,6 +714,8 @@ If the user wants to remove someone: | `.squad/templates/` | **Reference.** Format guides for runtime files. Not authoritative for enforcement. | Squad (Coordinator) at init | Squad (Coordinator) | | `.squad/rai/policy.md` | **Authoritative RAI policy.** Check categories, terminology standards, and opt-out rules. | Squad (Coordinator) at init; Rai may propose updates via decisions inbox | Rai, All agents (read-only) | | `.squad/rai/audit-trail.md` | **Derived / append-only.** RAI review evidence log. Redacted — never contains raw secrets or harmful content. | Rai (append only) | Rai, Squad (Coordinator) | +| `.squad/fact-checker/policy.md` | **Authoritative verification + Devil's Advocate policy.** Confidence rating taxonomy, hard anti-fabrication rules, mode triggers, opt-out model. | Squad (Coordinator) at init; Fact Checker may propose updates via decisions inbox | Fact Checker, All agents (read-only) | +| `.squad/fact-checker/audit-trail.md` | **Derived / append-only.** Verification verdicts + DA brief evidence log. Succinct — verdict + citation, never raw source material. | Fact Checker (append only) | Fact Checker, Squad (Coordinator) | | `.squad/plugins/marketplaces.json` | **Authoritative plugin config.** Registered marketplace sources. | Squad CLI (`squad plugin marketplace`) | Squad (Coordinator) | **Rules:** @@ -962,6 +971,79 @@ Rai participates as a specialized Reviewer. When Rai rejects: --- +## Fact Checker — Verification & Devil's Advocate + +Fact Checker is a built-in squad member whose job is **claim verification + Devil's Advocate analysis**. **Fact Checker ensures every team has a quality challenge from day one.** Always on the roster, dual operating mode: verifies factual claims AND challenges design assumptions before they ship. + +**Single agent, two modes:** + +| Mode | Question asked | When triggered | +|------|---------------|----------------| +| **Verification** | *"Is this claim true? Do these URLs / packages / API endpoints actually exist?"* | Pre-publish review of research output, external references, version claims | +| **Devil's Advocate** | *"Is this plan wise? What's the strongest counter-argument? What would we do if X was forbidden?"* | Before significant design decisions, pre-mortem on risky launches, when the team is converging too fast | + +**Philosophy: "Trust, but verify. Then steelman the opposition."** Fact Checker is rigorous but constructive — never gotcha-driven. Every challenge or finding includes WHAT (the issue or counter-argument), WHY (evidence or failure scenario), and HOW (the fix or alternative). + +**On-demand reference:** Read `.squad/agents/fact-checker/charter.md` (created by `squad init` / `squad upgrade` from the rich `fact-checker-charter.md` template, per #1299) for the full charter, verification methodology, confidence rating taxonomy, and pre-ship ceremony format. + +### Roster Entry + +Fact Checker always appears in `team.md`: `| Fact Checker | Fact Checker | .squad/agents/fact-checker/charter.md | 🔍 Verifier |` + +### Triggers + +| User says | Action | +|-----------|--------| +| "fact-check this" / "verify these claims" / "double-check" | Spawn Fact Checker in Verification mode | +| "play devil's advocate" / "what's wrong with this plan?" / "steelman the opposite" | Spawn Fact Checker in Devil's Advocate mode | +| "is this true?" / "does this URL/package exist?" | Spawn Fact Checker for empirical verification | +| "pre-mortem this" / "what could go wrong?" | Spawn Fact Checker for pre-mortem analysis | +| Pre-Ship ceremony (auto) | Fact Checker spawned automatically before user-facing artifacts finalize | +| Post-research (auto, optional) | After any agent produces research output or external references | + +These are intent signals, not exact strings — match meaning, not words. + +### Confidence Ratings (Verification Mode) + +Every verified item gets one of: + +| Rating | Meaning | +|--------|---------| +| ✅ **Verified** | Confirmed via source, test, or direct observation | +| ⚠️ **Unverified** | Plausible but could not confirm — needs human review | +| ❌ **Contradicted** | Found evidence that contradicts the claim | +| 🔍 **Needs Investigation** | Requires deeper analysis beyond current scope | + +### Devil's Advocate Output (DA Mode) + +Every DA brief includes: + +1. **Steelman of the opposition** — the strongest version of the counter-argument +2. **Load-bearing assumptions** — what would invalidate the plan if untrue +3. **Pre-mortem** — concrete failure scenario in 30 days +4. **Alternative approach** — at least one sketch so the chosen direction is a chosen direction +5. **Risk acceptance** — flag remaining risks for the team to consciously accept or mitigate + +### Boundaries + +**Fact Checker handles:** Claim verification, hallucination detection, counter-argument construction, pre-mortem analysis, assumption surfacing. + +**Fact Checker does not handle:** Implementation or code writing (reviews not creates), final decisions (advisory only — the team or coordinator decides), tone-policing. + +**Advisory by default.** Findings are advisory unless the coordinator or another reviewer escalates a specific risk to a gate. Never blocks on opinion, only on provably false claims or unaccepted risks. + +### Background Mode (Default) + +Fact Checker runs in background by default (like Scribe and Rai) — non-blocking. Spawns on-demand or via Pre-Ship ceremony auto-trigger. + +### Fact Checker State + +- **History** (`.squad/agents/fact-checker/history.md`) — verification + DA briefs across sessions +- **Charter** (`.squad/agents/fact-checker/charter.md`) — methodology + dual-mode operating rules +- **Decisions** — significant verification verdicts or DA briefs go to `.squad/decisions/inbox/fact-checker-{slug}.md` + +--- + ## PRD Mode Squad can ingest a PRD and use it as the source of truth for work decomposition and prioritization. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e3e4ee1de..da2410e7e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,7 +2,6 @@ version: 2 updates: - package-ecosystem: "npm" directory: "/" - target-branch: "main" schedule: interval: "weekly" day: "monday" @@ -22,14 +21,76 @@ updates: - "minor" - "patch" + - package-ecosystem: "nuget" + directory: "/src/Squad.Agents.AI" + schedule: + interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Etc/UTC" + open-pull-requests-limit: 5 + labels: ["dependencies", "squad:belanna"] + commit-message: + prefix: "deps" + groups: + microsoft-ai-majors: + patterns: + - "Microsoft.Agents.AI*" + - "Microsoft.Extensions.AI" + update-types: + - "major" + microsoft-packages: + patterns: + - "Microsoft.*" + update-types: + - "minor" + - "patch" + ignore: + - dependency-name: "OpenTelemetry*" + update-types: + - "version-update:semver-major" + + - package-ecosystem: "nuget" + directory: "/test/Squad.Agents.AI.Tests" + schedule: + interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Etc/UTC" + open-pull-requests-limit: 5 + labels: ["dependencies", "squad:belanna"] + commit-message: + prefix: "deps" + groups: + microsoft-ai-majors: + patterns: + - "Microsoft.Agents.AI*" + - "Microsoft.Extensions.AI" + update-types: + - "major" + microsoft-packages: + patterns: + - "Microsoft.*" + update-types: + - "minor" + - "patch" + ignore: + - dependency-name: "OpenTelemetry*" + update-types: + - "version-update:semver-major" + - package-ecosystem: "github-actions" directory: "/" - target-branch: "main" schedule: interval: "weekly" day: "monday" time: "08:00" timezone: "Etc/UTC" open-pull-requests-limit: 5 - labels: - - "dependencies" + labels: ["dependencies", "squad:belanna"] + commit-message: + prefix: "ci" + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/squad-agents-ai-ci.yml b/.github/workflows/squad-agents-ai-ci.yml new file mode 100644 index 000000000..cdd8b5081 --- /dev/null +++ b/.github/workflows/squad-agents-ai-ci.yml @@ -0,0 +1,91 @@ +name: Squad.Agents.AI CI + +on: + pull_request: + paths: + - 'src/Squad.Agents.AI/**' + - 'test/Squad.Agents.AI.Tests/**' + - 'src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/**' + - '.github/workflows/squad-agents-ai-ci.yml' + - 'Directory.*.props' + - 'Directory.*.json' + - 'global.json' + - 'nuget.config' + push: + branches: [main] + paths: + - 'src/Squad.Agents.AI/**' + - 'test/Squad.Agents.AI.Tests/**' + - 'src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/**' + - '.github/workflows/squad-agents-ai-ci.yml' + - 'Directory.*.props' + - 'Directory.*.json' + - 'global.json' + - 'nuget.config' + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + dotnet: + name: .NET ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + + env: + DOTNET_CLI_TELEMETRY_OPTOUT: '1' + DOTNET_NOLOGO: '1' + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Restore package + run: dotnet restore src/Squad.Agents.AI/Squad.Agents.AI.csproj -p:NuGetAudit=true + + - name: Restore tests + run: dotnet restore test/Squad.Agents.AI.Tests/Squad.Agents.AI.Tests.csproj -p:NuGetAudit=true + + - name: Restore sample + run: dotnet restore src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/Squad.Agents.AI.Sample.csproj -p:NuGetAudit=true + + - name: Build package + run: dotnet build src/Squad.Agents.AI/Squad.Agents.AI.csproj --no-restore -c Release + + - name: Build tests + run: dotnet build test/Squad.Agents.AI.Tests/Squad.Agents.AI.Tests.csproj --no-restore -c Release + + - name: Build sample + run: dotnet build src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/Squad.Agents.AI.Sample.csproj --no-restore -c Release + + - name: Test + run: dotnet test test/Squad.Agents.AI.Tests/Squad.Agents.AI.Tests.csproj --no-build -c Release --logger trx --results-directory TestResults + + - name: Pack + run: dotnet pack src/Squad.Agents.AI/Squad.Agents.AI.csproj --no-build -c Release -o nupkgs + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: squad-agents-ai-testresults-${{ matrix.os }} + path: TestResults + if-no-files-found: ignore + + - name: Upload NuGet packages + uses: actions/upload-artifact@v4 + with: + name: squad-agents-ai-nupkgs-${{ matrix.os }} + path: nupkgs + if-no-files-found: error diff --git a/.github/workflows/squad-agents-ai-release.yml b/.github/workflows/squad-agents-ai-release.yml new file mode 100644 index 000000000..cf84a2d89 --- /dev/null +++ b/.github/workflows/squad-agents-ai-release.yml @@ -0,0 +1,182 @@ +# Publishes Squad.Agents.AI to NuGet.org via Trusted Publishing (OIDC). +# +# ## Authentication +# No long-lived API key is stored. The job uses GitHub Actions OIDC to obtain a +# short-lived (1 hour) NuGet API key via the NuGet/login@v1 action. A Trusted +# Publishing policy must be configured at https://www.nuget.org/account/trusted-publishing +# under the package owner (Squad organization) with: +# Repository Owner: bradygaster +# Repository: squad +# Workflow File: squad-agents-ai-release.yml +# Environment: (leave empty) +# Set repository variable NUGET_USER to the nuget.org profile name that should +# perform the token exchange (a member of the Squad org). Use a variable, not a +# secret — the username is non-sensitive. +# +# ## Failure recovery +# Publishing is branch-driven: dev creates prerelease NuGet packages, main creates stable NuGet +# packages, and workflow_dispatch remains an escape hatch. PACKAGE_VERSION is resolved once +# before build/test/pack/push and the nupkgs/* artifacts are uploaded before the registry write. +# If a run is interrupted after NuGet accepted the package but before the workflow completes, +# rerun the same workflow/run: NuGet package versions are immutable and `--skip-duplicate` +# turns already-published packages into no-ops while retrying anything not yet published. +# `cancel-in-progress: false` prevents a recovery rerun from canceling an in-flight publish. +# Dev prereleases use github.run_number in the prerelease field because it is monotonically +# increasing per workflow and participates in NuGet/SemVer ordering; reruns keep the same +# version intentionally, and `--skip-duplicate` makes that safe. + +name: Squad.Agents.AI NuGet Release + +on: + workflow_dispatch: + inputs: + version: + description: 'Optional NuGet package version override, for example 0.1.0-preview.123' + required: false + type: string + push: + branches: + - dev + - main + paths: + - 'src/Squad.Agents.AI/**' + - 'test/Squad.Agents.AI.Tests/**' + - 'Directory.*.props' + - 'global.json' + +permissions: + contents: read + +jobs: + publish: + name: Pack and publish NuGet package + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # Required for OIDC token issuance to NuGet/login + concurrency: + group: squad-agents-ai-release-${{ github.ref }} + cancel-in-progress: false + env: + DOTNET_CLI_TELEMETRY_OPTOUT: '1' + DOTNET_NOLOGO: '1' + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Resolve package version + id: version + shell: bash + run: | + set -euo pipefail + + project_file="src/Squad.Agents.AI/Squad.Agents.AI.csproj" + csproj_version="$(sed -nE 's/^[[:space:]]*([^<]+)<\/Version>[[:space:]]*$/\1/p' "${project_file}" | head -n 1)" + manual_version="${{ inputs.version }}" + version_source="" + + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" && -n "${manual_version}" ]]; then + package_version="${manual_version}" + version_source="workflow_dispatch input" + else + if [[ -z "${csproj_version}" ]]; then + echo "::error::Could not find in ${project_file}." + exit 1 + fi + + case "${GITHUB_REF_NAME}" in + main) + # Stable releases mirror the Squad CLI's release workflow: the maintainer bumps + # the source version before merge, and the workflow publishes that exact version. + package_version="${csproj_version}" + version_source="${project_file}" + if [[ "${package_version}" == *-* ]]; then + echo "::error::main publishes stable packages; ${project_file} has prerelease version '${package_version}'. Bump it before merging." + exit 1 + fi + ;; + dev) + # The Squad CLI derives prereleases from the source version plus commit identity. + # For NuGet, build metadata does not give consumers monotonic prerelease ordering, + # so use github.run_number in the prerelease field instead. + stable_base="${csproj_version%%+*}" + stable_base="${stable_base%%-*}" + package_version="${stable_base}-preview.${GITHUB_RUN_NUMBER}" + version_source="${project_file} + github.run_number" + ;; + *) + echo "::error::workflow_dispatch without a version input must run on dev or main; current ref is '${GITHUB_REF_NAME}'." + exit 1 + ;; + esac + fi + + if [[ -z "${package_version}" ]]; then + echo "::error::PACKAGE_VERSION resolved to empty." + exit 1 + fi + + if [[ ! "${package_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then + echo "::error::PACKAGE_VERSION '${package_version}' from ${version_source} is not a SemVer 2.0 version." + exit 1 + fi + + echo "PACKAGE_VERSION=${package_version}" >> "${GITHUB_OUTPUT}" + echo "Resolved PACKAGE_VERSION=${package_version} (${version_source})" + + - name: Verify NUGET_USER variable + shell: bash + env: + NUGET_USER: ${{ vars.NUGET_USER }} + run: | + set -euo pipefail + if [[ -z "${NUGET_USER}" ]]; then + echo "::error::Missing repository variable NUGET_USER. Set it under Settings → Secrets and variables → Actions → Variables to the nuget.org profile name (NOT email) of a Squad org member that will perform the OIDC token exchange. Configure the matching Trusted Publishing policy at https://www.nuget.org/account/trusted-publishing." + exit 1 + fi + + - name: Restore package + run: dotnet restore src/Squad.Agents.AI/Squad.Agents.AI.csproj + + - name: Restore tests + run: dotnet restore test/Squad.Agents.AI.Tests/Squad.Agents.AI.Tests.csproj + + - name: Build package + run: dotnet build src/Squad.Agents.AI/Squad.Agents.AI.csproj -c Release --no-restore -p:Version=${{ steps.version.outputs.PACKAGE_VERSION }} + + - name: Build tests + run: dotnet build test/Squad.Agents.AI.Tests/Squad.Agents.AI.Tests.csproj -c Release --no-restore -p:Version=${{ steps.version.outputs.PACKAGE_VERSION }} + + - name: Test + run: dotnet test test/Squad.Agents.AI.Tests/Squad.Agents.AI.Tests.csproj -c Release --no-build + + - name: Pack + run: dotnet pack src/Squad.Agents.AI/Squad.Agents.AI.csproj -c Release --no-build -o nupkgs -p:Version=${{ steps.version.outputs.PACKAGE_VERSION }} + + - name: Upload NuGet package artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: squad-agents-ai-nupkgs-${{ steps.version.outputs.PACKAGE_VERSION }} + path: nupkgs/* + if-no-files-found: ignore + + - name: NuGet login (OIDC → temp API key) + id: nuget-login + uses: NuGet/login@v1 + with: + user: ${{ vars.NUGET_USER }} + + - name: Push to NuGet.org + shell: bash + env: + NUGET_TEMP_KEY: ${{ steps.nuget-login.outputs.NUGET_API_KEY }} + run: dotnet nuget push nupkgs/*.nupkg --source https://api.nuget.org/v3/index.json --api-key "${NUGET_TEMP_KEY}" --skip-duplicate diff --git a/.github/workflows/squad-docs-links.yml b/.github/workflows/squad-docs-links.yml index db3380bfa..046c49640 100644 --- a/.github/workflows/squad-docs-links.yml +++ b/.github/workflows/squad-docs-links.yml @@ -1,34 +1,34 @@ -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 +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-impact.yml b/.github/workflows/squad-impact.yml index f7fd7c59a..a17c5abcf 100644 --- a/.github/workflows/squad-impact.yml +++ b/.github/workflows/squad-impact.yml @@ -11,6 +11,7 @@ on: permissions: contents: read pull-requests: write + issues: write concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} diff --git a/.github/workflows/squad-npm-publish.yml b/.github/workflows/squad-npm-publish.yml index 779bdf4af..5dc14adf5 100644 --- a/.github/workflows/squad-npm-publish.yml +++ b/.github/workflows/squad-npm-publish.yml @@ -316,6 +316,31 @@ jobs: exit 1 fi echo "SDK dependency is safe: $SDK_DEP" + - name: "Verify SDK version is resolvable on npm (#1203)" + run: | + # Extract the minimum version floor from the SDK dependency range + SDK_DEP=$(node -p "require('./packages/squad-cli/package.json').dependencies['@bradygaster/squad-sdk']") + echo "SDK dependency declared: $SDK_DEP" + # Verify that at least one version satisfying the range exists on the registry + if ! npm view "@bradygaster/squad-sdk" versions --json 2>/dev/null | node -e " + const semver = require('semver'); + const range = process.argv[1]; + let data = ''; + process.stdin.on('data', c => data += c); + process.stdin.on('end', () => { + const versions = JSON.parse(data); + const arr = Array.isArray(versions) ? versions : [versions]; + const match = arr.find(v => semver.satisfies(v, range)); + if (!match) { + console.error('::error::No published SDK version satisfies ' + range); + process.exit(1); + } + console.log('✅ SDK dependency resolves to: ' + match); + }); + " "$SDK_DEP"; then + echo "::error::Cannot verify SDK dependency is resolvable — aborting CLI publish to prevent #1203" + exit 1 + fi - name: Build squad-sdk (required for CLI project references) run: npm -w packages/squad-sdk run build - name: Build squad-cli diff --git a/.github/workflows/squad-pr-nudge.yml b/.github/workflows/squad-pr-nudge.yml index 12f901e3e..1536d6d4d 100644 --- a/.github/workflows/squad-pr-nudge.yml +++ b/.github/workflows/squad-pr-nudge.yml @@ -8,7 +8,7 @@ permissions: contents: read pull-requests: write checks: read - issues: read + issues: write jobs: nudge-stale-prs: @@ -146,8 +146,8 @@ jobs: base: pr.base.ref, head: pr.head.sha }); - if (comparison.data.ahead_by > 10) { - actions.push(`⬇️ **${comparison.data.ahead_by} commits behind ${pr.base.ref}.** Rebase to pick up latest changes.`); + if (comparison.data.behind_by > 10) { + actions.push(`⬇️ **${comparison.data.behind_by} commits behind ${pr.base.ref}.** Rebase to pick up latest changes.`); } // 6. If everything looks good and approved — it's ready to merge diff --git a/.github/workflows/squad-pr-readiness.yml b/.github/workflows/squad-pr-readiness.yml index 7809ceda5..5218b5261 100644 --- a/.github/workflows/squad-pr-readiness.yml +++ b/.github/workflows/squad-pr-readiness.yml @@ -45,7 +45,7 @@ jobs: PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.workflow_run.head_sha }} PR_BASE_REF: ${{ github.event.pull_request.base.ref || github.event.workflow_run.pull_requests[0].base.ref }} PR_DRAFT: ${{ github.event.pull_request.draft || 'false' }} - PR_LABELS: ${{ toJson(github.event.pull_request.labels || '[]') }} + PR_LABELS: ${{ toJson(github.event.pull_request.labels) }} REPO_OWNER: ${{ github.repository_owner }} REPO_NAME: ${{ github.event.repository.name }} RUN_NAME: ${{ github.workflow }} diff --git a/.github/workflows/squad-repo-health.yml b/.github/workflows/squad-repo-health.yml index 4fd722927..4b57a50ac 100644 --- a/.github/workflows/squad-repo-health.yml +++ b/.github/workflows/squad-repo-health.yml @@ -11,6 +11,7 @@ on: permissions: contents: read pull-requests: write + issues: write concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} diff --git a/.gitignore b/.gitignore index 8cfd30156..c7b2e48e9 100644 --- a/.gitignore +++ b/.gitignore @@ -27,13 +27,26 @@ docs/tests/screenshots/ .squad-workstream .squad/.first-run .squad/.watch-pids -.squad/.cache/ +.squad/.cache/ # Images folder (root only — don't ignore docs/public/images/) -/images/ +/images/ # Squad: ignore runtime state (logs, inbox, sessions) .squad/.scratch/ # Squad: PR-1200 picard local artifacts (do not commit) .pr-body-new.md .followup-issue-body.md + +# .NET build output +bin/ +obj/ +artifacts/ + +# Visual Studio IDE artifacts +.vs/ +*.user +*.userprefs + +# NOTE: .squad/casting/* is AUTHORITATIVE IDENTITY and MUST be committed to main. +# Do not add .squad/casting/ to this ignore list. diff --git a/.squad-templates/fact-checker-charter.md b/.squad-templates/fact-checker-charter.md new file mode 100644 index 000000000..1d03e0b4e --- /dev/null +++ b/.squad-templates/fact-checker-charter.md @@ -0,0 +1,83 @@ +# Fact Checker + +> Trust, but verify. Every claim gets a source check. + +## Identity + +- **Name:** Fact Checker +- **Role:** Devil's Advocate & Verification Agent +- **Style:** Rigorous but constructive. Flags issues clearly without being abrasive. +- **Casting:** Gets a universe name like any other agent (not exempt like Scribe/Ralph). + +## What I Do + +Validate claims, detect hallucinations, and run counter-hypotheses on team output before it ships. + +## Verification Methodology + +For every claim or assertion I review: + +1. **Source Check:** What evidence supports this? Can I verify it? +2. **Counter-Hypothesis:** What would disprove this? Is there an alternative explanation? +3. **Existence Check:** Do the URLs, package names, API endpoints, file paths, and version numbers actually exist? +4. **Consistency Check:** Does this contradict anything in `.squad/decisions.md` or prior team output? + +## Confidence Ratings + +Every verified item gets one of: + +| Rating | Meaning | +|--------|---------| +| ✅ Verified | Confirmed via source, test, or direct observation | +| ⚠️ Unverified | Plausible but could not confirm — needs human review | +| ❌ Contradicted | Found evidence that contradicts the claim | +| 🔍 Needs Investigation | Requires deeper analysis beyond current scope | + +## When I'm Triggered + +- **Auto-trigger (via routing):** Tasks tagged with `review`, `verify`, `fact-check`, `audit` +- **Pre-publish gate:** Before any artifact is delivered to the user, if configured +- **Manual:** User says "fact-check this", "verify these claims", "double-check" +- **Post-research:** After any agent produces research output or external references + +## How I Work + +1. **Read the artifact** — understand what's being claimed +2. **Extract claims** — list every factual assertion (package versions, API behavior, file existence, etc.) +3. **Verify each claim** — use available tools (grep, glob, web search, gh CLI) to check +4. **Run counter-hypotheses** — for key assumptions, ask "what if this is wrong?" +5. **Produce a verification report:** + +```markdown +## Verification Report — {artifact name} + +### Claims Verified +- ✅ {claim} — confirmed via {source} +- ⚠️ {claim} — could not verify, {reason} +- ❌ {claim} — contradicted by {evidence} + +### Counter-Hypotheses +- {assumption} → Alternative: {counter} + +### Recommendation +{proceed / revise / block with reasons} +``` + +6. **Write decision** if I found issues: `.squad/decisions/inbox/fact-checker-{slug}.md` + +## Boundaries + +**I handle:** Verification, fact-checking, counter-hypotheses, hallucination detection. + +**I don't handle:** Implementation, design, testing, or docs. I review, not create. + +**I am not a blocker by default.** My verification report is advisory unless the coordinator or a reviewer escalates it to a gate. + +## Project Context + +**Project:** {project_name} +{project_description} + +## Learnings + +Initial setup complete. Ready for verification work. diff --git a/.squad-templates/fact-checker-policy.md b/.squad-templates/fact-checker-policy.md new file mode 100644 index 000000000..5cf43ee7c --- /dev/null +++ b/.squad-templates/fact-checker-policy.md @@ -0,0 +1,104 @@ +# Fact Checker Policy + +> Authoritative verification & devil's-advocate methodology for this project. Fact Checker enforces these standards. + +The Fact Checker is **one agent with two operating modes** — Verification (empirical claim checks) and Devil's Advocate (design challenge / pre-mortem). This policy defines what each mode does, what gets flagged at each confidence level, and which findings are advisory vs. blocking. + +--- + +## Mode 1: Verification + +Empirical check of claims against sources. Triggered by `"fact-check this"`, `"verify these claims"`, `"is this true?"`, Pre-Ship ceremony, or after any agent produces external references. + +### What gets checked + +| Claim type | What to verify | +|------------|----------------| +| **URLs** | Does the URL actually resolve? (200, not 404 or 5xx) | +| **Package names + versions** | Does the package exist on the registry at that version? | +| **API endpoints** | Does the documented endpoint exist on the vendor's current docs? | +| **File paths** | Does the file exist in the repo at the claimed path? | +| **Function / type signatures** | Do they match the actual source? | +| **Quoted text** | Does the source actually contain the quoted text verbatim? | +| **Statistics / measurements** | Is the cited source authoritative and recent? | +| **Cross-references to team decisions** | Does `.squad/decisions.md` actually say what was claimed? | + +### Confidence rating (every verified item gets one) + +| Rating | Meaning | Required next step | +|--------|---------|--------------------| +| ✅ **Verified** | Confirmed via source, test, or direct observation | None — proceed | +| ⚠️ **Unverified** | Plausible but could not confirm (no source, source ambiguous) | Flag in the verification report; team decides whether to ship | +| ❌ **Contradicted** | Found evidence that contradicts the claim | **Blocking** — must be revised before ship | +| 🔍 **Needs Investigation** | Requires deeper analysis beyond current scope | Flag + recommend a follow-up | + +--- + +## Mode 2: Devil's Advocate + +Design challenge + pre-mortem. Triggered by `"play devil's advocate"`, `"what's wrong with this plan?"`, `"steelman the opposite"`, `"pre-mortem this"`, or before any major architectural decision. + +### What gets produced (every DA brief) + +1. **Steelman of the opposition** — the strongest version of the counter-argument (not the weakest version that's easy to defeat). +2. **Load-bearing assumptions** — list the things the team is treating as fixed that are actually choices. *"We assumed we had to use Postgres — what if we couldn't?"* +3. **Pre-mortem** — concrete failure scenario in 30 days. *"Imagine this shipped and failed. Write the post-mortem now."* +4. **Alternative approach** — at least one concrete alternative sketch, even if worse, so the chosen direction is a chosen direction. +5. **Risk acceptance** — flag remaining risks for the team to consciously accept or mitigate. Never a veto. + +--- + +## Hard Rules (Anti-Fabrication) + +These are violations Fact Checker will catch and flag — even in its own output: + +- **Never cite a URL, package, or API without verifying it exists.** If the verification tool isn't available in the session, mark as ⚠️ Unverified — never as ✅ Verified. +- **Never invent measurement data, benchmarks, or "production results"** to support a claim. Cited measurements must link to a real source (`bradygaster/squad#1264` is the canonical example of this anti-pattern being caught). +- **Never fabricate a counter-hypothesis** for Devil's Advocate mode. The steelman must be a real opposing argument that the team could reasonably encounter from a senior engineer. +- **Never block on opinion.** Devil's Advocate flags risks; it does not veto. Only ❌ Contradicted findings in Verification mode are blocking by default. + +--- + +## Advisory by Default + +Fact Checker is **advisory** by default — like Rai's 🟡 Yellow. Findings are surfaced; the team or coordinator decides whether to act. + +Two exceptions where Fact Checker becomes a **blocking gate**: + +1. **❌ Contradicted finding in Verification mode** during a Pre-Ship ceremony — the user-facing artifact must be revised. +2. **Coordinator-escalated DA risk** — when the coordinator marks a Devil's Advocate finding as "must address before ship", standard Reviewer Rejection Protocol applies. + +--- + +## Opt-Out Model + +- **Cannot disable** the anti-fabrication hard rules above. They are framework-level guarantees. +- **Can disable** automatic Pre-Ship Fact Check triggering with justification logged to audit trail. +- **Cannot disable** Devil's Advocate on architectural decisions if the user explicitly asks for it (`"play devil's advocate"`). +- **Temporary opt-down** supported (auto re-enables after 30 days, same model as Rai). + +--- + +## Audit Trail + +All Fact Checker findings (verification verdicts + DA briefs) are logged to `.squad/fact-checker/audit-trail.md` (append-only). Entries are **succinct** — never paste raw verification source material, only the verdict + citation. The audit trail is the team's evidence ledger: + +- What was checked +- Which sources were consulted +- Which verdict was issued (or which DA brief was produced) +- Whether the team accepted the finding + +Decisions that affect other agents go to `.squad/decisions/inbox/fact-checker-{slug}.md` for Scribe to merge into `.squad/decisions.md`. + +--- + +## Integration with Reviewer Rejection Protocol + +When Fact Checker issues a ❌ Contradicted verdict on a user-facing artifact at Pre-Ship time: + +1. **Reviewer Rejection Protocol activates** — the original author is locked out +2. **Fact Checker names the fix agent** — usually the agent that produced the unverified claim +3. **Pair mode** — Fact Checker provides the citations / counter-evidence so the fix agent can revise with grounding +4. **Re-verification required** — Fact Checker must issue ✅ or ⚠️ before the artifact can ship + +This mirrors Rai's RAI Reviewer Rejection Protocol. The two are complementary: Rai blocks on safety/ethics/RAI violations, Fact Checker blocks on factual contradictions. diff --git a/.squad-templates/spawn-reference.md b/.squad-templates/spawn-reference.md index 94c2509d9..a2571c820 100644 --- a/.squad-templates/spawn-reference.md +++ b/.squad-templates/spawn-reference.md @@ -81,7 +81,7 @@ prompt: | Read `decisions.md` with `squad_state_read` when state tools are available; otherwise fall back to `.squad/decisions.md`. If .squad/identity/wisdom.md exists, read it before starting work. If .squad/identity/now.md exists, read it at spawn time. - Check project skill directories (.squad/skills/, .copilot/skills/, .github/skills/, .claude/skills/, .agents/skills/) for any SKILL.md the coordinator attached to your prompt. + Check project skill directories (.squad/skills/, .github/skills/, .copilot/skills/, .claude/skills/, .agents/skills/) for any SKILL.md the coordinator attached to your prompt. Read any relevant SKILL.md files before working. ⚠️ WORK FRESHNESS: When determining what to work on: diff --git a/.squad-templates/squad.agent.md b/.squad-templates/squad.agent.md index 9c71cacce..49a15598b 100644 --- a/.squad-templates/squad.agent.md +++ b/.squad-templates/squad.agent.md @@ -53,13 +53,14 @@ No team exists yet. Propose one — but **DO NOT create any files until the user 1. **Identify the user.** Run `git config user.name` to learn who you're working with. Use their name in conversation (e.g., *"Hey {user}, what are you building?"*). Store their name (NOT email) in `team.md` under Project Context. **Never read or store `git config user.email` — email addresses are PII and must not be written to committed files.** 2. Ask: *"What are you building? (language, stack, what it does)"* 3. **Cast the team.** Before proposing names, run the Casting & Persistent Naming algorithm (see that section): - - Determine team size (typically 4–5 + Scribe). + - Determine team size: pick **4–5 cast (user-domain) agents**, then add the **4 always-on built-ins** (Scribe + Ralph + Rai + Fact Checker — see their dedicated sections below). So a typical fresh squad has **8–9 total roster entries**, not 4–5. - Determine assignment shape from the user's project description. - Derive resonance signals from the session and repo context. - Select a universe. Allocate character names from that universe. - Scribe is always "Scribe" — exempt from casting. - Ralph is always "Ralph" — exempt from casting. - Rai is always "Rai" — exempt from casting. + - Fact Checker is always "Fact Checker" — exempt from casting (same pattern as Scribe / Ralph / Rai). 4. Propose the team with their cast names. Example (names will vary per cast): ``` @@ -130,6 +131,18 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"local"` (default), `"orphan"`, `"two-layer"`. Legacy alias: `"worktree"` maps to `"local"`. Deprecated: `"git-notes"` maps to `"two-layer"` with a deprecation warning. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. +**State-backend handshake — MANDATORY on every session before any state mutation (bradygaster/squad#1305):** + +For all backends EXCEPT `"local"` / `"worktree"`, the runtime owns persistence and you MUST NOT touch `.squad/decisions.md`, `.squad/decisions/inbox/`, `.squad/agents/*/history.md`, `.squad/casting/*.json`, `.squad/identity/*.md`, or `.squad/memory/*` paths via `create` / `edit` / `write_file` tools. Those writes either fail at the pre-commit hook or create phantom state the runtime overwrites at next read — a contract violation that produces silent data loss. + +The `squad_state_*` and `memory.*` tools that own persistence are exposed via the `squad_state` MCP server (declared in `.mcp.json`). Copilot CLI may load MCP tools **lazily** — they are not always advertised in your initial function list at session start. You MUST proactively confirm they are reachable: + +1. If `STATE_BACKEND ∈ {"local", "worktree"}`: file ops on `.squad/` are valid; skip the probe. +2. Otherwise (backend is `orphan`, `two-layer`, or `git-notes`): probe for `squad_state_health` (or any `squad_state_*` / `memory.*` tool) using whatever tool-discovery mechanism your runtime exposes (e.g. `tool_search_tool_regex` in Copilot CLI). If you can locate the tool, call `squad_state_health` once to confirm it answers; on success, treat the bridge as available for the rest of the session. +3. **If the probe fails** (tool not found, or `squad_state_health` errors): **HALT** before any state write. Tell the user verbatim: *"Squad's runtime state bridge is missing for backend `{STATE_BACKEND}`. The `squad_state` MCP server in `.mcp.json` is not reachable in this Copilot session. Restart Copilot CLI so `.mcp.json` is loaded, or change `stateBackend` to `local` in `.squad/config.json`."* — and stop until the user acknowledges. Do not silently fall back to raw file ops. + +This handshake runs **once per session**, not per spawn. Cache the result. + **⚡ 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). **Session catch-up (lazy — not on every start):** Do NOT scan logs on every session start. Only provide a catch-up summary when: @@ -256,28 +269,49 @@ The `name` parameter generates the human-readable agent ID shown in the tasks pa **When you detect a directive:** -1. Capture the directive with the runtime state tools when available: - - Prefer `squad_state_write` to write `decisions/inbox/copilot-directive-{timestamp}.md` using this format: +1. Capture the directive with governed memory tools when available: + - Prefer `memory.write` with class `decision` to persist the directive through the governed pipeline: ``` - ### {timestamp}: User directive - **By:** {user name} (via Copilot) - **What:** {the directive, verbatim or lightly paraphrased} - **Why:** User request — captured for team memory + memory.write({ + class: "decision", + key: "copilot-directive-{timestamp}", + content: "### {timestamp}: User directive\n**By:** {user name} (via Copilot)\n**What:** {the directive, verbatim or lightly paraphrased}\n**Why:** User request — captured for team memory" + }) ``` + - If `memory.write` is not available, fall back to `squad_decide` or `squad_state_write` to `decisions/inbox/copilot-directive-{timestamp}.md`. - Do **not** run `git notes`, checkout `squad-state`, or manually commit mutable `.squad/` state. The runtime owns state persistence. 2. Acknowledge briefly: `"📌 Captured. {one-line summary of the directive}."` 3. If the message ALSO contains a work request, route that work normally after capturing. If it's directive-only, you're done — no agent spawn needed. ### Memory Governance Tools -When memory tools are available, use them before writing durable memory by hand: +The `memory.*` tools share the same `squad_state` MCP server as `squad_state_*` (they're aliases in the same registry — see `packages/squad-cli/src/cli/commands/state-mcp.ts`). After the state-backend handshake above confirms the bridge is reachable, prefer governed memory tools for durable writes: - Classify candidate memories with `memory.classify`. - Persist approved durable facts, decisions, and policies with `memory.write`. - Search governed memory with `memory.search` before relying only on raw file search. - Promote, delete, and audit governed entries with `memory.promote`, `memory.delete`, and `memory.audit`. -If memory tools are not available, use runtime state tools for durable Squad state when present. In MCP sessions these are exposed as `squad_state_read`, `squad_state_write`, `squad_state_append`, `squad_state_delete`, `squad_state_list`, and `squad_state_health` aliases. Only fall back to local `.squad/` file writes when `STATE_BACKEND` is `worktree`/`local` and no runtime state tool exists. For `git-notes`, `orphan`, or `two-layer`, do not hand-write mutable state; report that the `squad_state` MCP/runtime state bridge is missing. Never claim provider-backed Copilot Memory, semantic indexing, or remote deletion unless a configured tool or CLI bridge performed the operation. External semantic memory is opt-in; forbidden or transient content must not be persisted. +If `memory.*` is not present in the bridge (older Squad versions before the bridge landed) but `squad_state_*` is, use `squad_state_*` directly. Both are governed paths. + +**HARD RULE — Backend contract enforcement:** If `STATE_BACKEND ∈ {"orphan", "two-layer", "git-notes"}` AND the state-backend handshake (above) did NOT confirm reachable tools, you MUST NOT write to ANY of these paths via `create` / `edit` / `write_file`: + +- `.squad/decisions.md` +- `.squad/decisions/inbox/**` +- `.squad/agents/*/history.md` +- `.squad/casting/*.json` +- `.squad/identity/*.md` +- `.squad/memory/**` +- `.squad/orchestration-log/**` +- `.squad/log/**` +- `.squad/rai/audit-trail.md` +- `.squad/fact-checker/audit-trail.md` + +These are runtime-managed paths under non-local backends. Hand-writing creates phantom state. The pre-commit hook will catch it and fail the user; even if it didn't, the runtime overwrites the file at next read. Report the missing bridge and halt instead. + +For `STATE_BACKEND ∈ {"local", "worktree"}`, file writes to `.squad/` are valid because the local backend IS the filesystem. + +**External memory:** Never claim provider-backed Copilot Memory, semantic indexing, or remote deletion unless a configured tool or CLI bridge performed the operation. External semantic memory is opt-in; forbidden or transient content must not be persisted. ### Routing @@ -295,19 +329,23 @@ The routing table determines **WHO** handles work. After routing, use Response M | PRD intake ("here's the PRD", "read the PRD at X", pastes spec) | Follow PRD Mode (see that section) | | Human member management ("add {name} as PM", routes to human) | Follow Human Team Members (see that section) | | Ralph commands ("Ralph, go", "keep working", "Ralph, status", "Ralph, idle") | Follow Ralph — Work Monitor (see that section) | -| "squad commands", "what can squad do", "show me squad options", "slash commands", "what commands are available" | Read `.copilot/skills/squad-commands/SKILL.md`, present categorized menu (see squad-commands skill) | +| "squad commands", "what can squad do", "show me squad options", "slash commands", "what commands are available" | Read `.github/skills/squad/SKILL.md`, present categorized menu (see squad skill). Users can also invoke this directly via `/squad`. | | "upgrade squad", "update squad", "what's new in squad", "install the update" | Run upgrade flow per `.squad/templates/session-init-reference.md` | +| User says "spawn a squad", "another squad", "two squads", "second squad", "fan out to squads", "delegate to a squad", or any phrasing that treats "squad" as a unit to spawn or address | This is the Squad-PRODUCT concept (a peer with its own `.squad/`), NOT generic English "team" or "group". **Before any `task` spawn**, invoke the `skill` tool on `cross-squad` (discovery via registry/upstream) AND `cross-squad-communication` (sync CLI / git-async / GH-issue protocols) to load the full peer-squad workflow. Then delegate via Pattern 0/1/2/3 — NOT by fanning out raw `task` agents inside your own coordinator context. **Default = literal Squad install.** Calling `task` sub-agents "squad-alpha" / "squad-beta" does NOT make them squads — that is the explicit anti-pattern. **If the request is ambiguous** (could be either "two real `.squad/` installs" or "two ad-hoc groups of agents"), you MUST `ask_user` with a 2-choice prompt — `["Real squads — separate .squad/ per squad (heavier, persistent)", "Ad-hoc agents — one-shot task dispatch (lighter, ephemeral)"]` — and never silently pick the cheaper option. If the peer doesn't exist yet, walk the user through `squad init` in a separate directory or `squad registry add` first. | | Rai commands ("Rai, review this", "RAI check", "content safety review") | Follow Rai — RAI Reviewer (see that section) | | General work request | Check routing.md, spawn best match + any anticipatory agents | | Quick factual question | Answer directly (no spawn) | | Ambiguous | Pick the most likely agent; say who you chose | | Multi-agent task (auto) | Check `ceremonies.md` for `when: "before"` ceremonies whose condition matches; run before spawning work | - + **Skill-aware routing:** Before spawning, check ALL project skill directories in precedence order for skills relevant to the task domain: + +**Hard trigger — keyword-to-skill match (do this FIRST, before any spawn or task call):** If any word in the user's request matches the name of an installed skill (e.g., "squad" → `cross-squad` and/or `cross-squad-communication`, "reflect" → `reflect`, "ceremony" → the matching ceremony skill, "fact-check" → `fact-checking`, "release" → `release-process`), you MUST invoke the `skill` tool to fully load that skill BEFORE designing your approach or selecting agents. The one-line description in the discovery list is for discovery only — it is NOT sufficient to act on. Read the full SKILL.md, then route. This rule applies whether or not the request also matches a routing-table row above; when both apply, load the skill first, then execute the routing-table action. Failure mode this rule closes: a coordinator that sees "squad" in the prompt, treats it as generic English, and fans out raw `task` agents instead of invoking the `cross-squad-communication` peer-delegation protocol. + 1. `.squad/skills/` — **Team-earned skills** (highest precedence). Patterns captured by agents during work; a team-written override beats any generic version. -2. `.copilot/skills/` — **Project playbook.** Human-curated process knowledge: release workflows, git conventions, reviewer protocols. -3. `.github/skills/` — **Generic project skills.** Sits alongside `.github/workflows/` and `.github/copilot-instructions.md`; common location for shared-repo skills. +2. `.github/skills/` — **Project playbook** (Copilot CLI's canonical custom-skills location). Human-curated process knowledge: release workflows, git conventions, reviewer protocols. Sits alongside `.github/workflows/` and `.github/copilot-instructions.md`. `squad init` and `squad upgrade` install Squad's bundled skills here. +3. `.copilot/skills/` — **Legacy install path** (pre-1304). Older squads may have skills here; `squad upgrade` migrates them to `.github/skills/`. Still scanned for any user-added or unmigrated skills. 4. `.claude/skills/` — **Claude-ecosystem skills.** Vendor-specific path; less common in multi-tool projects. 5. `.agents/skills/` — **Generic agents path** (lowest project precedence). Least-specific convention. @@ -403,7 +441,7 @@ prompt: | TARGET FILE(S): {exact file path(s)} Do the work. Keep it focused. - If you made a meaningful decision, persist it with `squad_decide` when available, or `squad_state_write` to `decisions/inbox/{name}-{brief-slug}.md`. Do not run git notes, switch branches, or write mutable `.squad/` state by hand. + If you made a meaningful decision, persist it with `memory.write` (class: `decision`) when available, or fall back to `squad_decide` / `squad_state_write` to `decisions/inbox/{name}-{brief-slug}.md`. Do not run git notes, switch branches, or write mutable `.squad/` state by hand. ⚠️ OUTPUT: Report outcomes in human terms. Never expose tool internals or SQL. ⚠️ RESPONSE ORDER: After ALL tool calls, write a plain text summary as FINAL output. @@ -528,7 +566,7 @@ When the user gives any task, the Coordinator MUST: To enable full parallelism, shared writes use a drop-box pattern that eliminates file conflicts: **decisions.md** — Agents do NOT write directly to `decisions.md`. Instead: -- Agents record decisions with `squad_decide` or `squad_state_write` to `decisions/inbox/{agent-name}-{brief-slug}.md`. +- Agents record decisions with `memory.write` (class: `decision`) when available, or fall back to `squad_decide` / `squad_state_write` to `decisions/inbox/{agent-name}-{brief-slug}.md`. - The runtime routes that write to the configured state backend. Agents must not run `git notes`, switch to `squad-state`, or hand-roll backend commits. - Scribe merges into the canonical `.squad/decisions.md` and clears the inbox - All agents READ from `.squad/decisions.md` at spawn time (last-merged snapshot) @@ -707,6 +745,8 @@ If the user wants to remove someone: | `.squad/templates/` | **Reference.** Format guides for runtime files. Not authoritative for enforcement. | Squad (Coordinator) at init | Squad (Coordinator) | | `.squad/rai/policy.md` | **Authoritative RAI policy.** Check categories, terminology standards, and opt-out rules. | Squad (Coordinator) at init; Rai may propose updates via decisions inbox | Rai, All agents (read-only) | | `.squad/rai/audit-trail.md` | **Derived / append-only.** RAI review evidence log. Redacted — never contains raw secrets or harmful content. | Rai (append only) | Rai, Squad (Coordinator) | +| `.squad/fact-checker/policy.md` | **Authoritative verification + Devil's Advocate policy.** Confidence rating taxonomy, hard anti-fabrication rules, mode triggers, opt-out model. | Squad (Coordinator) at init; Fact Checker may propose updates via decisions inbox | Fact Checker, All agents (read-only) | +| `.squad/fact-checker/audit-trail.md` | **Derived / append-only.** Verification verdicts + DA brief evidence log. Succinct — verdict + citation, never raw source material. | Fact Checker (append only) | Fact Checker, Squad (Coordinator) | | `.squad/plugins/marketplaces.json` | **Authoritative plugin config.** Registered marketplace sources. | Squad CLI (`squad plugin marketplace`) | Squad (Coordinator) | **Rules:** @@ -962,6 +1002,79 @@ Rai participates as a specialized Reviewer. When Rai rejects: --- +## Fact Checker — Verification & Devil's Advocate + +Fact Checker is a built-in squad member whose job is **claim verification + Devil's Advocate analysis**. **Fact Checker ensures every team has a quality challenge from day one.** Always on the roster, dual operating mode: verifies factual claims AND challenges design assumptions before they ship. + +**Single agent, two modes:** + +| Mode | Question asked | When triggered | +|------|---------------|----------------| +| **Verification** | *"Is this claim true? Do these URLs / packages / API endpoints actually exist?"* | Pre-publish review of research output, external references, version claims | +| **Devil's Advocate** | *"Is this plan wise? What's the strongest counter-argument? What would we do if X was forbidden?"* | Before significant design decisions, pre-mortem on risky launches, when the team is converging too fast | + +**Philosophy: "Trust, but verify. Then steelman the opposition."** Fact Checker is rigorous but constructive — never gotcha-driven. Every challenge or finding includes WHAT (the issue or counter-argument), WHY (evidence or failure scenario), and HOW (the fix or alternative). + +**On-demand reference:** Read `.squad/agents/fact-checker/charter.md` (created by `squad init` / `squad upgrade` from the rich `fact-checker-charter.md` template, per #1299) for the full charter, verification methodology, confidence rating taxonomy, and pre-ship ceremony format. + +### Roster Entry + +Fact Checker always appears in `team.md`: `| Fact Checker | Fact Checker | .squad/agents/fact-checker/charter.md | 🔍 Verifier |` + +### Triggers + +| User says | Action | +|-----------|--------| +| "fact-check this" / "verify these claims" / "double-check" | Spawn Fact Checker in Verification mode | +| "play devil's advocate" / "what's wrong with this plan?" / "steelman the opposite" | Spawn Fact Checker in Devil's Advocate mode | +| "is this true?" / "does this URL/package exist?" | Spawn Fact Checker for empirical verification | +| "pre-mortem this" / "what could go wrong?" | Spawn Fact Checker for pre-mortem analysis | +| Pre-Ship ceremony (auto) | Fact Checker spawned automatically before user-facing artifacts finalize | +| Post-research (auto, optional) | After any agent produces research output or external references | + +These are intent signals, not exact strings — match meaning, not words. + +### Confidence Ratings (Verification Mode) + +Every verified item gets one of: + +| Rating | Meaning | +|--------|---------| +| ✅ **Verified** | Confirmed via source, test, or direct observation | +| ⚠️ **Unverified** | Plausible but could not confirm — needs human review | +| ❌ **Contradicted** | Found evidence that contradicts the claim | +| 🔍 **Needs Investigation** | Requires deeper analysis beyond current scope | + +### Devil's Advocate Output (DA Mode) + +Every DA brief includes: + +1. **Steelman of the opposition** — the strongest version of the counter-argument +2. **Load-bearing assumptions** — what would invalidate the plan if untrue +3. **Pre-mortem** — concrete failure scenario in 30 days +4. **Alternative approach** — at least one sketch so the chosen direction is a chosen direction +5. **Risk acceptance** — flag remaining risks for the team to consciously accept or mitigate + +### Boundaries + +**Fact Checker handles:** Claim verification, hallucination detection, counter-argument construction, pre-mortem analysis, assumption surfacing. + +**Fact Checker does not handle:** Implementation or code writing (reviews not creates), final decisions (advisory only — the team or coordinator decides), tone-policing. + +**Advisory by default.** Findings are advisory unless the coordinator or another reviewer escalates a specific risk to a gate. Never blocks on opinion, only on provably false claims or unaccepted risks. + +### Background Mode (Default) + +Fact Checker runs in background by default (like Scribe and Rai) — non-blocking. Spawns on-demand or via Pre-Ship ceremony auto-trigger. + +### Fact Checker State + +- **History** (`.squad/agents/fact-checker/history.md`) — verification + DA briefs across sessions +- **Charter** (`.squad/agents/fact-checker/charter.md`) — methodology + dual-mode operating rules +- **Decisions** — significant verification verdicts or DA briefs go to `.squad/decisions/inbox/fact-checker-{slug}.md` + +--- + ## PRD Mode Squad can ingest a PRD and use it as the source of truth for work decomposition and prioritization. diff --git a/.squad/skills/cross-squad-communication/SKILL.md b/.squad/skills/cross-squad-communication/SKILL.md new file mode 100644 index 000000000..9286d5fa2 --- /dev/null +++ b/.squad/skills/cross-squad-communication/SKILL.md @@ -0,0 +1,399 @@ +--- +name: "cross-squad-communication" +description: "Protocol for sending queries, delegating tasks, and sharing context between independent Squad instances across different repositories" +domain: "multi-repo coordination" +confidence: "medium" +source: "Ported from tamirdresher/squad-skills (plugins/cross-squad-communication). Companion to the registry-aware cross-squad skill — this one teaches the actual communication protocols once a peer is discovered. Pattern 0 (synchronous CLI) is the only end-to-end-validated pattern; Patterns 1, 2, 3 are documented from design but require live validation against your own setup before relying on them in production. See the Validation Status section at the bottom of this skill." +--- + +## Context + +When multiple repositories each have their own Squad (AI team), they need to exchange information: knowledge queries, PR reviews, task delegation, and dependency analysis. Each squad has its own agents, MCP tools, and issue tracker — there is no shared runtime. + +> **Companion skill — read first:** `cross-squad/SKILL.md` covers **discovery** of peer squads via `squad registry add/list/remove`. This skill picks up after a peer is known and covers the **communication protocols** themselves — the four numbered patterns below: Pattern 0 (synchronous CLI), Pattern 1 (read-only knowledge query), Pattern 2 (git-based async), and Pattern 3 (GitHub-issue-based delegation). A separate non-numbered appendix (Cross-Repo Dependency Scan) is provided as a related analysis tool, not a communication pattern. The two skills are designed to be used together. + +**When this skill applies:** +- A squad agent needs information from another squad-enabled repo +- A task needs to be delegated to another squad +- Cross-repo dependency analysis is needed +- PR review requests span repo boundaries + +**Key constraint:** Each squad has its own runtime, MCP tools, and issue tracker. Cross-squad communication can be **synchronous** (via CLI session targeting the other repo) or **asynchronous** (file-based or issue-based). The coordinator decides which approach fits. + +--- + +## Patterns + +### Decision Tree: Choosing the Right Pattern + +``` +Is the target repo cloned locally? +├─ NO → Use Pattern 3 (Issue-Based) or Pattern 2 (Git-Based Async) +└─ YES + ├─ Is this a quick query / knowledge lookup? + │ └─ YES → Use Pattern 0 (Synchronous CLI) — fastest + ├─ Does the work need to persist as artifacts? + │ └─ YES → Use Pattern 2 (Git-Based Async) + ├─ Is it a long-running analysis or multi-cycle task? + │ └─ YES → Use Pattern 2 (Git-Based Async) + └─ Is the target squad's Ralph running? + ├─ YES → Pattern 2 or 3 (async processing available) + └─ NO → Pattern 0 (Synchronous CLI) or Pattern 1 (Read-Only) +``` + +--- + +## Universal rule: every `copilot` spawn into a peer squad MUST pass `--agent squad` + +The `copilot` CLI accepts `--agent ` to select a custom agent (see `copilot --help`). Squad installs ship `.github/agents/squad.agent.md`, which is loaded **only when `--agent squad` is specified**. Without it the spawned session runs as a generic Copilot CLI session that does NOT load the peer's `team.md`, routing, MCP tools, casting, or coordinator behaviour — so you get an off-the-shelf model answering, not the peer's Squad. **Every command example in this skill that spawns `copilot` into a peer repo includes `--agent squad`; do not strip it.** + +This rule also applies anywhere else you spawn `copilot` into a Squad-initialised repo (not just cross-squad protocols) — e.g., `squad init`'s post-init tip and any automation that invokes the CLI on a squadified folder. The only case where you may omit `--agent` is when resuming an existing session (`copilot --resume `) — the resumed session preserves its original agent context. + +--- + +### Pattern 0: Synchronous CLI Session (Fastest for Interactive Queries) + +For quick knowledge queries, decision lookups, or short analyses — spawn a Copilot CLI session with the working directory set to the target squad's repo. This lets you send a prompt and get a response within the same session, using the target repo's full context. + +This is the same technique used by `ralph-watch.ps1`: write the prompt to a temp file, then invoke the CLI with that file as input. The key insight is that setting the working directory to the target repo gives the CLI session access to that squad's `.squad/` metadata, codebase, and conventions. + +**Protocol:** +1. Write prompt to a temp file (avoids argument-splitting issues, as learned in `ralph-watch.ps1`) +2. Read the file into a string and invoke `copilot -p ` with `-C ` set to the target repo (`-p` takes prompt text, NOT a file path) AND `--agent squad` so the spawned session uses the peer squad's coordinator (without `--agent` you get a generic Copilot CLI session that doesn't load the peer's `team.md`, MCP tools, or skills) +3. Receive response in the same session + +**Invocation:** +```powershell +# Spawn a Copilot CLI session targeting another squad's repo +$targetRepo = "C:\repos\platform-squad-repo" +$promptFile = New-TemporaryFile +@" +You are working in a Squad-enabled repository. +Read .squad/team.md and .squad/decisions.md first. + +[CROSS-SQUAD REQUEST] +From: research-squad +Request Type: knowledge_query +Query: What is the current architecture of the platform? What services does it expose? +Response Format: Brief structured summary +"@ | Out-File $promptFile -Encoding utf8 + +# Option A: copilot with prompt file (read file into string; -p takes text, not a path) +# --agent squad is REQUIRED: the target is another Squad install, so the spawned +# session must use that squad's coordinator (not a generic Copilot CLI session). +copilot -C $targetRepo --agent squad -p (Get-Content $promptFile -Raw) --allow-all-tools + +# Option B: Start-Process for non-blocking (ralph-watch.ps1 style) +Start-Process pwsh -ArgumentList "-NoProfile -Command `"copilot -C '$targetRepo' --agent squad -p (Get-Content '$promptFile' -Raw) --allow-all-tools`"" -Wait + +# Option C: Pipe directly (stdin is the prompt text) +"What is the platform architecture?" | copilot -C $targetRepo --agent squad --allow-all-tools +``` + +**When to use synchronous vs async:** + +| Scenario | Pattern | Why | +|----------|---------|-----| +| Quick knowledge query | Synchronous CLI (Pattern 0) | Fast answer, no overhead | +| "What did you decide about X?" | Synchronous CLI (Pattern 0) | Read decisions.md via the target squad's context | +| PR review request | Either (Pattern 0 or 2/3) | Sync for quick feedback, async for thorough review | +| Task delegation (do work in their repo) | Async (Pattern 2 or 3) | Work needs to persist beyond the session | +| Long-running analysis | Async (Pattern 2) | May take multiple cycles | +| Target repo not locally cloned | Async (Pattern 3) | Can't set working directory to a remote repo | + +**The coordinator decides which pattern to use based on:** +1. Is the target repo cloned locally? → If yes, sync CLI is available +2. Is this a quick query or a long task? → Quick = sync, long = async +3. Does the work need to persist? → If yes, use async (creates artifacts) +4. Is the target squad's Ralph running? → Needed for async processing + +**Requirements:** +- Target repo must be cloned locally (for `copilot -C `) +- Target repo must be Squad-initialised (`.squad/config.json` + `.github/agents/squad.agent.md` present), so `--agent squad` resolves to the peer's coordinator +- Prompt file avoids argument-splitting bugs (see `ralph-watch.ps1` lines 2166-2184) + +**Response quality:** ⭐⭐⭐⭐⭐ — the CLI session has full context of the target repo, including code, squad metadata, and MCP tools. + +### Liveness Protocol for Pattern 0 + +The synchronous CLI session requires monitoring to avoid false timeouts. With 7+ MCP servers initializing and `.squad/` metadata being read, startup can take 30-60 seconds. A hard timeout kills valid sessions before they complete. Instead, monitor the agency session's activity log directory. + +**Health Check Approach:** + +Instead of a fixed wall-clock timeout, monitor the agency session log directory for activity: + +```powershell +# The Copilot CLI creates a session log directory at ~/.copilot/logs/. +# Older `agency` runtimes wrote to ~/.agency/logs/; fall back to that +# location if the new path doesn't exist yet on the user's machine. +# e.g., ~/.copilot/logs/session_20260325_071211_57824 +$copilotLogs = "$env:USERPROFILE\.copilot\logs" +$agencyLogs = "$env:USERPROFILE\.agency\logs" +$logRoot = if (Test-Path $copilotLogs) { $copilotLogs } elseif (Test-Path $agencyLogs) { $agencyLogs } else { $null } +if ($logRoot) { + $logDir = Get-ChildItem $logRoot -Directory | Sort-Object LastWriteTime -Descending | Select-Object -First 1 +} +$lastSize = 0 +$stallCount = 0 + +while ($proc -and -not $proc.HasExited) { + Start-Sleep -Seconds 15 + $currentSize = (Get-ChildItem $logDir -Recurse -File | Measure-Object -Property Length -Sum).Sum + + if ($currentSize -eq $lastSize) { + $stallCount++ + if ($stallCount -ge 4) { # 60s with no progress + Write-Warning "Session stalled — no log activity for 60s" + break + } + } else { + $stallCount = 0 + $lastSize = $currentSize + } +} +``` + +**Progress Indicators (What Counts as "Alive"):** + +- New files appearing in the session log directory (e.g., `transcript.log`, `mcp-server-logs/`) +- Log file size increasing (indicates active processing) +- New or modified `.squad/` files in the target repo (e.g., `decisions/inbox.md`, `identity/history.md`) +- Process still running and consuming non-idle CPU time + +**Stall Detection (When to Intervene):** + +- **No log activity for 60s** → Issue a warning; session may be slow but not hung +- **No log activity for 120s** → Likely stuck; consider terminating and checking logs +- **Process exited with non-zero exit code** → Failed; examine `transcript.log` and `stderr` for errors +- **MCP server connection timeout** → Session blocked waiting for an MCP server response + +**Recovery Actions When Stalled:** + +1. **Check for user input waiting:** Inspect logs for prompts or dialogs (shouldn't happen with `--autopilot`) +2. **Check MCP server health:** Review `mcp-server-logs/` for connection errors or timeouts +3. **Retry with `--disable-builtin-mcps` flag:** For lightweight queries that don't require MCP tools + ```powershell + # Retry without MCP servers — faster startup, limited capability + copilot -C $targetRepo --agent squad -p (Get-Content $promptFile -Raw) --disable-builtin-mcps --allow-all-tools + ``` +4. **Increase timeout threshold:** If MCP server initialization is consistently slow (>90s), raise threshold before declaring stall + +--- + +### Pattern 1: Read-Only Knowledge Query (No CLI Needed) + +For questions about another squad's architecture, decisions, or current state — read their `.squad/` metadata directly. + +**Protocol:** +1. Read target repo's `.squad/team.md` → get stack, members, issue source +2. Read `.squad/decisions.md` → get architectural decisions +3. Read `.squad/routing.md` → understand who handles what +4. Read `.squad/identity/now.md` → get current focus +5. Scan code structure if needed (csproj files, directory layout) + +**Requirements:** +- Target repo must be cloned locally or accessible via git +- No authentication needed beyond git read access + +**Example:** +```powershell +# Query another squad's architecture +$targetRepo = "C:\repos\platform-squad-repo" +Get-Content "$targetRepo\.squad\team.md" +Get-Content "$targetRepo\.squad\decisions.md" +Get-Content "$targetRepo\.squad\identity\now.md" +``` + +**Response quality:** ⭐⭐⭐⭐ — excellent for structural/architectural questions. + +--- + +### Pattern 2: Async Task Request (Git-Based) + +For work that needs the target squad to execute (PR reviews, issue analysis, code changes). + +**Protocol:** +1. Create request file in YOUR repo: `.squad/cross-squad/requests/{timestamp}-{target}-{id}.yaml` +2. Commit and push +3. Target squad's Ralph detects on next cycle +4. Target squad processes and writes response to their `.squad/cross-squad/responses/` +5. Your Ralph picks up the response + +**Request File Format:** +```yaml +id: req-2026-06-13-001 +source_squad: research-squad +source_repo: your-org/research-squad-repo +target_squad: platform-squad +target_repo: your-org/platform-squad-repo +request_type: knowledge_query | pr_review | task_delegation | dependency_check +priority: high | normal | low +created_at: 2026-06-13T10:00:00Z +query: "What is the current architecture of the platform?" +routing_hint: "lead" # optional — which agent should handle this +status: pending +``` + +**Response File Format:** +```yaml +id: req-2026-06-13-001 +responding_squad: platform-squad +responding_agent: lead +responded_at: 2026-06-13T10:15:00Z +status: completed | partial | rejected +response: | + The platform architecture consists of... +artifacts: [] # optional file paths +``` + +--- + +### Pattern 3: Issue-Based Delegation (For GitHub-Hosted Repos) + +For repos on GitHub, use issues with labels as the message bus. + +**Protocol:** +1. Create issue in target repo with label `squad:cross-squad` +2. Include source squad identifier and routing hint in issue body +3. Target squad's Ralph picks up and routes to appropriate agent +4. Response posted as issue comment +5. Issue closed when complete + +**Example:** +```bash +gh issue create \ + --repo your-org/platform-squad-repo \ + --title "[Cross-Squad] Architecture query from research-squad" \ + --body "Source: research-squad\nQuery: What services does the platform expose?\nRouting: lead" \ + --label "squad:cross-squad" +``` + +**Limitation:** Only works for repos on GitHub. Other platforms (Azure DevOps, GitLab, etc.) need different approach. + +--- + +### Appendix: Cross-Repo Dependency Scan (Related Analysis Tool — Not a Communication Pattern) + +> This section is intentionally listed as an appendix rather than "Pattern 4" — it is a one-off analysis utility for discovering how two repos relate, not a protocol the coordinator picks from the decision tree above. The four numbered communication patterns are 0–3. + +For discovering how two repos relate to each other. + +**Protocol:** +1. Search both repos for mutual references: + ```powershell + Select-String -Path (Get-ChildItem $repoA -Recurse -Include "*.md","*.cs","*.json","*.csproj") ` + -Pattern $repoB_name + Select-String -Path (Get-ChildItem $repoB -Recurse -Include "*.md","*.cs","*.json","*.csproj") ` + -Pattern $repoA_name + ``` +2. Check shared NuGet packages / npm packages +3. Check shared ADO project or GitHub org +4. Document relationship type: code dependency, operational coupling, shared infra + +--- + +## Discovery Protocol + +Before sending any cross-squad request, verify the target: + +``` +1. Does .squad/team.md exist? → Squad is installed +2. What is the issue_source? → GitHub Issues | ADO | Planner +3. What agents are active? → Check member status column +4. What is the routing table? → Read routing.md +5. What is the current focus? → Read identity/now.md +6. Is Ralph running? → Check for recent commits by Ralph +``` + +If `.squad/team.md` doesn't exist, the repo is not squad-enabled. Fall back to standard human communication. + +--- + +## Platform Compatibility Matrix + +| Source Issue Tracker | Target Issue Tracker | Mechanism | +|---------------------|---------------------|-----------| +| GitHub Issues | GitHub Issues | Issue-based (Pattern 3) | +| GitHub Issues | ADO Work Items | Git-based (Pattern 2) | +| GitHub Issues | Planner | Git-based (Pattern 2) | +| ADO Work Items | GitHub Issues | Issue-based (Pattern 3) via `gh` CLI | +| ADO Work Items | ADO Work Items | ADO cross-project work items | +| Any | Any | Git-based (Pattern 2) — universal fallback | + +--- + +## Examples + +### Example 1: research-squad queries platform-squad architecture + +```powershell +# Step 1: Read metadata (Pattern 1) +$target = "C:\repos\platform-squad-repo" +$team = Get-Content "$target\.squad\team.md" -Raw +$decisions = Get-Content "$target\.squad\decisions.md" -Raw + +# Step 2: Extract answer from metadata +# team.md reveals tech stack and member roles +# decisions.md reveals architectural choices + +# Step 3: If deeper analysis needed, create async request (Pattern 2) +``` + +### Example 2: Request PR review from another squad + +```yaml +# .squad/cross-squad/requests/2026-06-13-platform-squad-pr-review.yaml +id: pr-review-001 +source_squad: research-squad +target_squad: platform-squad +request_type: pr_review +priority: normal +query: "Review PR #54 — package version fix. Check for correctness." +routing_hint: "lead" +status: pending +``` + +--- + +## Anti-Patterns + +### ⚠️ Know when synchronous CLI is NOT the right choice +```powershell +# WRONG — don't use sync CLI for long-running tasks that need artifacts +copilot -C $targetRepo --agent squad -p (Get-Content $promptFile -Raw) --allow-all-tools +# If the task creates files, PRs, or takes multiple cycles → use async (Pattern 2 or 3) + +# WRONG — don't use sync CLI when the target repo isn't cloned locally +copilot -C "C:\not\cloned\yet" --agent squad --allow-all-tools +# If the repo isn't available locally → use issue-based delegation (Pattern 3) +``` +Synchronous CLI sessions (Pattern 0) are valid for quick queries and knowledge lookups. Use async patterns for work that needs to persist or where the target repo isn't available locally. + +### ❌ Don't assume shared MCP tools +Each squad has its own MCP server instances. You cannot invoke another squad's ADO tools or GitHub tools from your session. + +### ❌ Don't skip the discovery step +Always read `team.md` first. The target squad may use a different issue tracker, have different agents, or be in a different state than expected. + +### ❌ Don't send requests to squads without Ralph +If the target squad doesn't have Ralph (Work Monitor) running, async requests will never be processed. Check for recent Ralph activity first. + +### ❌ Don't mix up repo platforms +Different repos may use GitHub Issues vs Azure DevOps Work Items vs Jira. Check `team.md` / repository metadata for the right tooling before sending requests. + +--- + +## Validation Status + +This skill was originally drafted against two prototype squad setups (a GitHub-hosted platform squad with ~10 agents and an Azure DevOps-hosted automation squad with ~4 agents). The protocols are platform-agnostic; the examples in this document use generic names so you can substitute your own repos. Patterns 0 and 1 have been exercised end-to-end in those prototypes; Patterns 2 and 3 are documented from design but have not been end-to-end-validated against a live target repo. + +| Scenario | Result | +|----------|--------| +| Knowledge query (read-only) | ✅ Works via Pattern 1 | +| Step handler discovery | ✅ Works via file scan | +| PR review (basic) | ⚠️ Partial — git log only, no API | +| Backlog enumeration | ⚠️ Partial — depends on issue platform | +| Dependency analysis | ✅ Works via cross-reference scan | +| CLI invocation (sync) + Liveness Protocol | ✅ Works — session launches successfully; log monitoring prevents false timeouts | + +**Confidence: MEDIUM** — Synchronous CLI pattern (Pattern 0) validated end-to-end. Liveness protocol provides operational robustness against slow MCP initialization. Git-based async (Pattern 2) and issue-based (Pattern 3) untested end-to-end. Production readiness requires Ralph integration on both sides. diff --git a/.squad/skills/cross-squad/SKILL.md b/.squad/skills/cross-squad/SKILL.md index 1d4e3a251..d008e85b8 100644 --- a/.squad/skills/cross-squad/SKILL.md +++ b/.squad/skills/cross-squad/SKILL.md @@ -1,9 +1,20 @@ --- name: "cross-squad" -description: "Coordinating work across multiple Squad instances" +description: "Coordinating work across multiple Squad instances — discovery, delegation, and disambiguation when the user says 'squad' (the product) vs casual English 'group of agents'." domain: "orchestration" confidence: "medium" source: "manual" +triggers: + - "spawn N squads" + - "spawn a squad" + - "another squad" + - "two squads of" + - "second squad" + - "fan out to squads" + - "delegate to a squad" + - "set up squads for" + - "create a squad to review" + - "ask the other squad" tools: - name: "squad-discover" description: "List known squads and their capabilities" @@ -14,21 +25,60 @@ tools: --- ## Context + +> **Read this FIRST any time the user says "squad" as a thing to spawn, delegate to, address, or fan out to** — e.g., *"spawn two squads of designers and devs"*, *"ask the other squad"*, *"delegate to a squad"*. In Squad-PRODUCT vocabulary, "squad" is a **peer** (an independent installation with its own `.squad/`, `team.md`, MCP server, and agents) — NOT a generic English synonym for "team" or "group". Do not fan out raw `task` agents inside your own coordinator context when the user means "another squad". Use the discovery and communication patterns below (and the companion `cross-squad-communication` skill for the actual protocols). + When an organization runs multiple Squad instances (e.g., platform-squad, frontend-squad, data-squad), those squads need to discover each other, share context, and hand off work across repository boundaries. This skill teaches agents how to coordinate across squads without creating tight coupling. +> **Companion skill — for protocol details:** `cross-squad-communication/SKILL.md` covers the four communication patterns (synchronous CLI, read-only knowledge query, git-based async, and GitHub-issue-based delegation) once a peer squad is discovered via the registry below. This skill answers "who?" — the companion answers "how?". + Cross-squad orchestration applies when: - A task requires capabilities owned by another squad - An architectural decision affects multiple squads - A feature spans multiple repositories with different squads - A squad needs to request infrastructure, tooling, or support from another squad +## Disambiguation: "squad" vs ad-hoc agents + +When the user uses the word **"squad" / "squads"** or asks to **"spawn a team"**, the coordinator MUST treat it as a literal reference to a Squad install (a `.squad/` directory with its own roster, casting, and coordinator) — NOT as a casual synonym for "a group of sub-agents". + +### Default behaviour (apply unless explicitly told otherwise) + +| User says | Coordinator does | +|---|---| +| *"spawn two squads of X and Y"* / *"set up squads for X, Y, Z"* | Bootstrap N **real** Squad installs — separate folder + `git init` + `squad init` per squad — then use the cross-squad patterns below (`.squad/manifest.json`, `squad registry add`, `squad delegate`) and the protocols in the `cross-squad-communication` skill | +| *"ask the other squad about X"* / *"delegate to the data squad"* | Discover the peer via `squad registry list` (or by reading a known `.squad/manifest.json`), then use `cross-squad-communication` Pattern 0 / 1 / 2 / 3 — never re-implement the protocol with `task` | +| *"spawn a few agents to do X"* / *"have some agents review X"* / *"in parallel, get sub-agents to..."* | Ad-hoc `task` fan-out is fine — no `.squad/` bootstrap needed. This is the only path where raw `task` is appropriate when the user mentioned a multi-agent activity | + +### Ambiguous? `ask_user`, never silently downgrade + +If the request **could** be either interpretation AND bootstrapping real squads is non-trivial (more than one or two `squad init` runs), you MUST use the `ask_user` tool with a 2-choice prompt before proceeding: + +``` +question: "Should I create separate Squad installs or just dispatch ad-hoc agents?" +choices: + - "Real squads — separate .squad/ per squad (heavier, persistent, can be re-engaged later)" + - "Ad-hoc agents — one-shot `task` dispatch (lighter, ephemeral, no .squad/ created)" +``` + +The cost of asking is one `ask_user`. The cost of getting it wrong is the user has to redo the work. **Never silently pick the cheaper option just because it feels disproportionate for the task size — surface the trade-off and let the user pick.** + +### Anti-patterns (every one of these is a real failure mode observed in production) + +- **Calling `task` sub-agents "squad-alpha" / "squad-beta"** and treating them as squads. Naming something a squad doesn't make it one — a squad has its own `.squad/`, `team.md`, MCP server, and coordinator. If those aren't there, it's not a squad. +- **Matching a prior session's ad-hoc pattern without re-checking current intent.** If you see existing `reviews/squad-alpha/` folders from a previous run, that's a hint, NOT a contract — the user may have meant something different this time. Re-evaluate from scratch. +- **Silently choosing the cheaper interpretation because "bootstrapping two real squads for a 30-line app feels disproportionate".** That's a judgment call for the USER to make, not the coordinator. Use `ask_user`. +- **Loading the `cross-squad` skill, reading it, then doing `task` fan-out anyway** because the eager-execution / parallel-fan-out doctrine pulled you back. The disambiguation rule on this page OVERRIDES the generic fan-out doctrine when "squad" was the trigger. + ## Patterns ### Discovery via Manifest -Each squad publishes a `.squad/manifest.json` declaring its name, capabilities, and contact information. Squads discover each other through: -1. **Well-known paths**: Check `.squad/manifest.json` in known org repos -2. **Upstream config**: Squads already listed in `.squad/upstream.json` are checked for manifests -3. **Explicit registry**: A central `squad-registry.json` can list all squads in an org +Each squad publishes a `.squad/manifest.json` declaring its name, capabilities, and contact information. Squads discover each other through two mechanisms: + +1. **`.squad/squad-registry.json`** — **discovery-only.** Peer squads are findable via `squad discover` and addressable via `squad delegate`, but their skills/decisions/wisdom are NOT loaded into your coordinator. Manage with `squad registry add/list/remove`. +2. **`.squad/upstream.json`** — **discovery + inheritance.** Squads listed here are also discoverable, AND your coordinator inherits their skills/decisions/wisdom/routing at session start. Manage with `squad upstream add/list/remove/sync`. + +Both forms read the peer's manifest via the same code path. The `path` field is the **repository root** (e.g. `../friend-repo`), and Squad appends `.squad/manifest.json` internally. Pointing at the `.squad/` directory works too — Squad accepts both forms (`readManifest` strips a trailing `.squad` if present). ```json { @@ -73,9 +123,19 @@ Track delegated work completion: ## Examples +### Registering a peer squad (no inheritance) +```bash +# Friend's repo is checked out at ../friend-platform/ +squad registry add platform-squad ../friend-platform + +# Verify +squad registry list +squad discover +``` + ### Discovering squads ```bash -# List all squads discoverable from upstreams and known repos +# List all squads discoverable from registry + upstreams squad discover # Output: diff --git a/.squad/skills/iterative-retrieval/SKILL.md b/.squad/skills/iterative-retrieval/SKILL.md new file mode 100644 index 000000000..4d8eea993 --- /dev/null +++ b/.squad/skills/iterative-retrieval/SKILL.md @@ -0,0 +1,165 @@ +--- +name: "iterative-retrieval" +description: "Max-3-cycle protocol for agent sub-tasks with WHY context and coordinator validation. Use when spawning sub-agents to complete scoped work." +domain: "agent-coordination" +confidence: "high" +license: MIT +--- + +# Iterative Retrieval Skill + +Squad agents frequently spawn sub-agents to complete scoped work. Without structure, these +handoffs become vague, cycles multiply, and outputs land without being checked. The +**Iterative Retrieval Pattern** caps cycles at 3, mandates WHY context in every spawn, and +requires the coordinator to validate agent output before closing an issue. + +--- + +## Spawn Prompt Template + +Every agent spawn must include the following four sections. Copy and fill in the template: + +``` +## Task +{What you need done — concrete and bounded} + +## WHY this matters +{The motivation and context. What system or user goal does this serve? What breaks if skipped?} + +## Success criteria +{How you will know the output is correct. Be explicit — list acceptance criteria, not vibes.} +Example: +- [ ] File X exists and contains Y +- [ ] No regressions in existing tests +- [ ] PR is open targeting main with description matching the issue + +## Escalation path +{What the agent should do if uncertain or stuck. "Stop and ask me" is valid.} +Example: +- If requirements are ambiguous → stop, comment on the issue, set label status:needs-decision +- If blocked by a dependency → label status:blocked, explain in a comment +- If 3 cycles exhausted without resolution → write a summary to inbox and surface to coordinator +``` + +--- + +## 3-Cycle Protocol + +| Cycle | Description | Exit condition | +|-------|-------------|----------------| +| **1** | Initial attempt | Done → coordinator validates. Incomplete → surface delta. | +| **2** | Targeted retry with specific corrections | Done → coordinator validates. Incomplete → one more. | +| **3** | Final attempt with all context from cycles 1–2 | Done or escalate — no cycle 4. | + +### Rules + +1. **After each cycle**, the coordinator evaluates the output against the success criteria + before accepting it or spawning the next cycle. +2. **Objective context forward**: each subsequent spawn includes a summary of what was tried + and what is still missing — not just a repeat of the original task. +3. **Cycle 3 exhausted** → escalate: write a summary to `.squad/decisions/inbox/`, label the + issue `status:needs-decision`, and notify the user. + +--- + +## Coordinator Validation Checklist + +Before accepting agent output and closing an issue, the coordinator must check: + +- [ ] All success criteria from the spawn prompt are met +- [ ] PR exists and description matches the issue (if code work) +- [ ] No obvious regressions (grep for TODO/FIXME introduced, build passes) +- [ ] Agent did not silently skip parts of the task +- [ ] If the agent reported uncertainty — was it resolved or escalated? + +If any item fails → do **not** accept. Spawn cycle N+1 (up to cycle 3) with specific deltas. + +--- + +## When to Escalate vs Retry + +**Retry (cycle N+1)** when: +- Output is structurally correct but missing specific items +- Agent misunderstood scope (provide more context and re-run) +- Partial success — clearly identified remaining delta + +**Escalate** when: +- Requirements are fundamentally unclear (decision needed) +- 3 cycles complete without convergence +- Agent returned conflicting results across cycles +- Task requires elevated permissions or external action +- The work depends on another issue that isn't done yet + +--- + +## Issue Dedup Check (Mandatory) + +Before any agent creates a GitHub issue, it **must** search for existing open issues to avoid +duplicates. + +```bash +# Check for existing open issues before creating a new one +gh issue list --search "" --state open +``` + +- If an open issue already covers the same problem → **comment on it** instead of creating a new one. +- If no duplicate → proceed to create the issue. +- Use 2–3 representative keywords from the planned issue title as the search query. + +--- + +## Mandatory Output Requirement (Research-Then-Execute) + +Every research or analysis task completed under this protocol **MUST** end with at least one +concrete action before the cycle is closed. Acceptable follow-up actions: + +- GitHub issue created documenting the findings and next steps +- PR opened implementing a recommendation +- Decision recorded in `.squad/decisions/inbox/` +- Documented recommendation with a named assignee and due date + +**Pure analysis reports without actionable follow-up will be rejected during triage.** +If no action is warranted, the agent must explicitly state why and get coordinator sign-off. + +--- + +## Anti-Patterns + +- **Spawning without WHY** — agents can't prioritise trade-offs without motivation context. +- **Accepting output without validating** — one failed check avoids merging broken work. +- **Cycle 4+** — if 3 cycles haven't converged, the problem is in the requirements, not the agent. +- **Vague success criteria** — "looks good" is not a criterion. Use checkboxes. +- **Forwarding WHAT without delta** — cycle 2+ prompts must include what cycle 1 got wrong. +- **Creating issues without dedup check** — always search before creating. +- **Research without action** — delivering analysis with no issue, PR, decision, or assignee is incomplete work. + +--- + +## Examples + +### Good spawn prompt +``` +## Task +Add an "Iterative Retrieval Protocol" section to `.squad/agents/coordinator/charter.md` explaining +the 3-cycle rule, WHY format, and validation checklist. + +## WHY this matters +The coordinator spawns sub-agents on every round. Without a documented protocol, agents run unbounded +cycles and outputs go unvalidated — leading to stale issues and silent failures. + +## Success criteria +- [ ] Section "Iterative Retrieval Protocol" exists in charter.md +- [ ] Section documents max-3-cycles rule +- [ ] Section documents WHY format requirement +- [ ] Section contains validation checklist (at least 4 items) +- [ ] No other sections of charter.md are modified + +## Escalation path +If the charter.md format is unclear, check another agent charter as a reference. +If uncertain about content, stop and surface to coordinator. +``` + +### Bad spawn prompt (don't do this) +``` +Update the coordinator charter with the iterative retrieval stuff. +``` diff --git a/.squad/skills/reflect/SKILL.md b/.squad/skills/reflect/SKILL.md new file mode 100644 index 000000000..6a85b5190 --- /dev/null +++ b/.squad/skills/reflect/SKILL.md @@ -0,0 +1,229 @@ +--- +name: reflect +description: Learning capture system that extracts HIGH/MED/LOW confidence patterns from conversations to prevent repeating mistakes. Use after user corrections ("no", "wrong"), praise ("perfect", "exactly"), or when discovering edge cases. Complements .squad/agents/{agent}/history.md and .squad/decisions.md. +license: MIT +version: 1.0.0-squad +domain: team-memory, learning +confidence: high +--- + +# Reflect Skill + +**Critical learning capture system** for Squad. Prevents repeating mistakes and preserves successful patterns across sessions. + +Analyze conversations and propose improvements to squad knowledge based on what worked, what didn't, and edge cases discovered. **Every correction is a learning opportunity.** + +--- + +## Integration with Squad Architecture + +**Reflect complements existing Squad knowledge systems:** + +1. **`.squad/agents/{agent}/history.md`** — Permanent learnings from completed work (append-only; each agent updates their own file; Scribe propagates cross-agent updates) +2. **`.squad/decisions.md`** — Team-wide decisions that all agents respect +3. **`reflect` skill** — Captures in-flight learnings from conversations that may graduate to history.md or decisions.md + +**Workflow:** +- Use `reflect` during work to capture learnings +- At session end, review captured learnings +- Promote HIGH confidence patterns → lead agent for decision.md review +- Promote agent-specific patterns → `{agent}/history.md` updates + +--- + +## Triggers + +### 🔴 HIGH Priority (Invoke Immediately) + +| Trigger | Example | Why Critical | +|---------|---------|--------------| +| User correction | "no", "wrong", "not like that", "never do" | Captures mistakes to prevent repetition | +| Architectural insight | "you removed that without understanding why" | Documents design decisions (Chesterton's Fence) | +| Immediate fixes | "debug", "root cause", "fix all" | Learns from errors in real-time | + +### 🟡 MEDIUM Priority (Invoke After Multiple) + +| Trigger | Example | Why Important | +|---------|---------|---------------| +| User praise | "perfect", "exactly", "great" | Reinforces successful patterns | +| Tool preferences | "use X instead of Y", "prefer" | Builds workflow preferences | +| Edge cases | "what if X happens?", "don't forget", "ensure" | Captures scenarios to handle | + +### 🟢 LOW Priority (Invoke at Session End) + +| Trigger | Example | Why Useful | +|---------|---------|------------| +| Repeated patterns | Frequent use of specific commands/tools | Identifies workflow preferences | +| Session end | After complex work | Consolidates all session learnings | + +--- + +## Process + +### Phase 1: Identify Learning Target + +Determine what knowledge system should be updated: + +1. **Agent-specific learning** → `.squad/agents/{agent}/history.md` +2. **Team-wide decision** → `.squad/decisions/inbox/{agent}-{topic}.md` +3. **Skill-specific improvement** → Document in session, recommend to skill owner + +### Phase 2: Analyze Conversation + +Scan for learning signals with confidence levels: + +#### HIGH Confidence: Corrections + +User actively steered or corrected output. + +**Detection patterns:** +- Explicit rejection: "no", "not like that", "that's wrong" +- Strong directives: "never do", "always do", "don't ever" +- User provided alternative implementation + +**Example:** +```text +User: "No, use the azure-devops MCP tool instead of raw API calls" +→ [HIGH] + Add constraint: "Prefer azure-devops MCP tools over REST API" +``` + +#### MEDIUM Confidence: Success Patterns + +Output was accepted or praised. + +**Detection patterns:** +- Explicit praise: "perfect", "great", "yes", "exactly" +- User built on output without modification +- Output was committed without changes + +**Example:** +```text +User: "Perfect, that's exactly what I needed" +→ [MED] + Add preference: "Include usage examples in documentation" +``` + +#### MEDIUM Confidence: Edge Cases + +Scenarios not anticipated. + +**Detection patterns:** +- Questions not answered +- Workarounds user had to apply +- Error handling gaps discovered + +#### LOW Confidence: Preferences + +Accumulated patterns over time. + +--- + +### Phase 3: Propose Learnings + +Present findings: + +```text +┌─────────────────────────────────────────────────────────────┐ +│ REFLECTION: {target (agent/decision/skill)} │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ [HIGH] + Add constraint: "{specific constraint}" │ +│ Source: "{quoted user correction}" │ +│ Target: .squad/decisions/inbox/{agent}-{topic}.md │ +│ │ +│ [MED] + Add preference: "{specific preference}" │ +│ Source: "{evidence from conversation}" │ +│ Target: .squad/agents/{agent}/history.md │ +│ │ +│ [LOW] ~ Note for review: "{observation}" │ +│ Source: "{pattern observed}" │ +│ Target: Session notes only │ +│ │ +├─────────────────────────────────────────────────────────────┤ +│ Apply changes? [Y/n/edit] │ +└─────────────────────────────────────────────────────────────┘ +``` + +**Confidence Threshold:** + +| Threshold | Action | +|-----------|--------| +| ≥1 HIGH signal | Always propose (user explicitly corrected) | +| ≥2 MED signals | Propose (sufficient pattern) | +| ≥3 LOW signals | Propose (accumulated evidence) | +| 1-2 LOW only | Skip (insufficient evidence) | + +### Phase 4: Persist Learnings + +**ALWAYS show changes before applying.** + +After user approval: + +1. **For Agent History:** + - Append to `.squad/agents/{agent}/history.md` under `## Learnings` section + - Format: Date, assignment context, key learning + +2. **For Team Decisions:** + - Create `.squad/decisions/inbox/{agent}-{topic}.md` + - Lead agent reviews and merges to `decisions.md` if appropriate + +3. **For Skills:** + - Document recommendation in session notes + - Squad lead reviews and routes to skill owner + +--- + +## Usage Examples + +### Example 1: User Correction + +**Conversation:** +``` +Agent: "I'll use grep to search the repository" +User: "No, use the code search tools first, grep is too slow" +``` + +**Reflection Output:** +``` +[HIGH] + Add constraint: "Use code intelligence tools before grep" + Source: "No, use the code search tools first, grep is too slow" + Target: .squad/agents/{agent}/history.md +``` + +### Example 2: Success Pattern + +**Conversation:** +``` +Agent: [Creates PR with detailed description and test plan] +User: "Perfect! This is exactly the format I want for all PRs" +``` + +**Reflection Output:** +``` +[MED] + Add preference: "Include test plan in PR descriptions" + Source: User praised detailed PR format + Target: .squad/decisions/inbox/pr-format.md (for team adoption) +``` + +--- + +## When to Use + +✅ **Use reflect when:** +- User says "no", "wrong", "not like that" (HIGH priority) +- User says "perfect", "exactly", "great" (MED priority) +- You discover edge cases or gaps +- Complex work session with multiple learnings +- At end of sprint/milestone to consolidate patterns + +❌ **Don't use reflect when:** +- Simple one-off questions with no pattern +- User is just exploring ideas (no concrete decisions) +- Learning is already captured in history.md/decisions.md + +--- + +## See Also + +- `.squad/decisions.md` — Team-wide decisions +- `.squad/agents/*/history.md` — Agent-specific learnings +- `.squad/routing.md` — Work assignment patterns diff --git a/.squad/skills/release-process/SKILL.md b/.squad/skills/release-process/SKILL.md index c80603986..18e0e84b1 100644 --- a/.squad/skills/release-process/SKILL.md +++ b/.squad/skills/release-process/SKILL.md @@ -9,7 +9,7 @@ source: "earned" # Release Process > Earned knowledge from the v0.9.0→v0.9.1 and v0.9.4 incidents. Every agent involved in releases MUST read this before starting release work. -> See also: `.copilot/skills/release-process/SKILL.md` for the Copilot-facing runbook. +> See also: `.github/skills/release-process/SKILL.md` for the Copilot-facing runbook. ## SCOPE @@ -213,5 +213,5 @@ See `.github/workflows/squad-ci.yml` → `publish-policy` job for implementation - v0.9.4 fixes: PR #1042 (CHANGELOG), PR #1043 (root package.json), PR #1044 (lockfile integrity) - Retro: `.squad/decisions/inbox/surgeon-v091-retrospective.md` - CI audit: `.squad/decisions/inbox/booster-ci-audit.md` -- Copilot-level skill: `.copilot/skills/release-process/SKILL.md` +- Copilot-level skill: `.github/skills/release-process/SKILL.md` - Playbook: `PUBLISH-README.md` (repo root) diff --git a/packages/squad-cli/templates/skills/squad-commands/SKILL.md b/.squad/skills/squad-commands/SKILL.md similarity index 100% rename from packages/squad-cli/templates/skills/squad-commands/SKILL.md rename to .squad/skills/squad-commands/SKILL.md diff --git a/.squad/skills/squad-help/SKILL.md b/.squad/skills/squad-help/SKILL.md new file mode 100644 index 000000000..81bc4ae2a --- /dev/null +++ b/.squad/skills/squad-help/SKILL.md @@ -0,0 +1,97 @@ +--- +name: "squad-help" +description: "How to actually use Squad — Squad is a custom Copilot agent (invoked via the task tool with agent_type='Squad'), not a skill. This file explains the right invocation paths for setting up a team, listing squad commands, and initializing Squad in a new project." +allowedTools: [] +confidence: high +domain: squad-onboarding +--- + +# Skill: squad-help + +> **Quick reference.** If you're reading this because a user said "use squad" or "squad" or "set up a squad", you're in the right place — read on for the correct invocation paths. + +--- + +## Squad is a custom agent, not a skill + +The Squad framework registers a **custom Copilot CLI agent** at `.github/agents/squad.agent.md`. The agent is named **`Squad`** and its description is *"Your AI team. Describe what you're building, get a team of specialists that live in your repo."* + +Copilot CLI agents and skills are different things: + +| Thing | How to invoke | Example | +|---|---|---| +| **Skill** | `skill(name)` tool call or natural-language match | `skill(squad-commands)` | +| **Agent** | `task` tool with `agent_type=` | `task(name="...", agent_type="Squad", prompt="...")` | +| **Slash command** | Built-in CLI keyword | `/agent`, `/skills`, `/mcp` | + +Calling `skill(Squad)` will fail with *"Skill not found: Squad"* because Squad is the agent, not a skill. (`/squad` as a slash command also does not exist — only built-in CLI keywords like `/agent`, `/skills`, `/mcp` are slash commands. There's no way to map a skill name to a slash command without a Copilot CLI feature change.) + +--- + +## How to actually use Squad + +Pick the path that matches the user's intent: + +### A) Invoke the Squad coordinator agent (most common) + +The Squad coordinator orchestrates a team of specialists. It routes work to the right agent, scaffolds a team if none exists, and enforces handoffs. + +```text +task( + name="", + agent_type="Squad", + prompt="" +) +``` + +Use this when the user says things like: +- *"Use Squad to build X"* +- *"Set up an AI team for this project"* +- *"Have the Squad coordinator design Y"* +- *"Spawn Squad"* / *"Squad, help me with ..."* + +### B) See what Squad commands exist + +The `squad-commands` skill is a categorized catalog of common Squad operations. The coordinator presents it as an interactive menu. + +Trigger by natural-language match: `"squad commands"`, `"what can squad do"`, `"show me squad options"`, `"slash commands"`, `"what commands are available"`. + +Use this when the user says things like: +- *"What can Squad do?"* +- *"Show me the squad commands"* +- *"squad help"* + +### C) Initialize Squad in a fresh project + +`squad init` is a **shell command**, not a tool call. The user runs it in their terminal in a project that has no `.squad/` directory yet. + +```bash +squad init +``` + +Do **not** try to invoke this from inside an existing Copilot session — `.squad/` is already initialized if you're reading this file. + +--- + +## What NOT to do + +- ❌ Do not call `skill(Squad)`, `skill(squad)`, or `skill(squad-coordinator)` — Squad is not a skill. +- ❌ Do not type `/squad` expecting a slash command — slash commands are CLI keywords, not skill names. Use `/agent` (browse) or invoke the `Squad` agent via the `task` tool. +- ❌ Do not call `task(agent_type="Squad", …)` for tiny tasks the current agent can handle directly. Squad is for work that needs orchestration; trivial edits do not. + +--- + +## How this skill was discovered + +This skill ships from the Squad SDK templates and is wired into `MANIFEST_SKILL_NAMES`. It lives at `.copilot/skills/squad-help/SKILL.md` so the Copilot CLI's `/skills` loader picks it up alongside the other bundled Squad skills. + +If you removed this skill on purpose, the model will fall back to its own reasoning and may make the lookup mistakes described above. + +--- + +## See also + +- `.github/agents/squad.agent.md` — the actual Squad coordinator agent +- `.copilot/skills/squad-commands/SKILL.md` — the command catalog +- `.copilot/skills/squad-conventions/SKILL.md` — conventions for working on the Squad codebase itself +- `.copilot/skills/squad-version-check/SKILL.md` — version-stamping mechanics diff --git a/.squad/skills/squad-version-check/SKILL.md b/.squad/skills/squad-version-check/SKILL.md new file mode 100644 index 000000000..3f3aebb0a --- /dev/null +++ b/.squad/skills/squad-version-check/SKILL.md @@ -0,0 +1,169 @@ +--- +name: "squad-version-check" +description: "Internals of how @bradygaster/squad-cli stamps its version, how `squad upgrade` works (what it preserves vs overwrites), and how to probe the npm registry for the latest version from a coordinator prompt." +allowedTools: [] +confidence: medium +domain: squad-internals +source: "Discovered by Data; validated in bradygaster/squad#1173 recon (2026-05-26)." +--- + +# SKILL: Squad CLI Internals — Version Stamping & Upgrade Mechanics + +**Confidence:** medium +**Discovered by:** Data +**Date:** 2026-05-26 +**Validated in:** Issue #1173 recon (bradygaster/squad) + +--- + +## What This Skill Covers + +Reusable knowledge about how `@bradygaster/squad-cli` stamps its version into `squad.agent.md`, how `squad upgrade` works, what it preserves vs. overwrites, and how to probe the npm registry for the latest version from a coordinator prompt. + +--- + +## Package & Registry Facts + +- **Package name:** `@bradygaster/squad-cli` +- **Registry:** npm (public) +- **CLI binary:** `squad` (registered via `package.json#bin.squad`) +- **Node version requirement:** Node ≥22.5.0 (ESM-only codebase) + +--- + +## Version Stamping Mechanism + +**Source file:** `dist/cli/core/version.js` + +Three functions: + +### `getPackageVersion()` +Walks up from the compiled JS file to find `package.json`. Returns `pkg.version`. Works from both `dist/cli/core/version.js` and a bundled root `cli.js`. Returns `'0.0.0'` as fallback if not found. + +### `stampVersion(filePath, version)` +Mutates `squad.agent.md` in three places: +1. HTML comment: `` (must be on the line immediately after frontmatter `---`) +2. Identity line: `- **Version:** {version}` +3. Greeting instruction: backtick-quoted `` `Squad v{version}` `` + +**Called by:** both `init` and `upgrade` — after copying the template to the destination. + +### `readInstalledVersion(filePath)` +Reads the stamped version back from `squad.agent.md`: +1. First tries HTML comment format: `//` +2. Falls back to old frontmatter format: `/^version:\s*"([^"]+)"/m` +3. Returns `'0.0.0'` on any error + +--- + +## `squad upgrade` Behavior + +**Source file:** `dist/cli/core/upgrade.js` + +### What gets overwritten: +- `squad.agent.md` — full overwrite from template, then `stampVersion()` +- Files with `overwriteOnUpgrade: true` in `TEMPLATE_MANIFEST`: casting JSON files, template .md files, `copilot-instructions.md` (if @copilot enabled) +- GitHub Actions workflows — from `templates/workflows/`; non-npm projects get type-aware stubs +- Runs `runMigrations()` after file copy + +### What is PRESERVED: +- `team.md`, `routing.md`, `decisions.md`, `ceremonies.md` (user-owned) +- `agents/*/history.md` (individual agent memory) +- `.squad/config.json` — **never touched**; `stateBackend` survives intact +- User-added files not in TEMPLATE_MANIFEST + +### Self-upgrade path (`selfUpgradeCli()`): +Detects npm/pnpm/yarn via `npm_execpath` and `npm_config_user_agent`. Runs: +- npm: `npm install -g @bradygaster/squad-cli@latest` +- pnpm: `pnpm add -g @bradygaster/squad-cli@latest` +- yarn: `yarn global add @bradygaster/squad-cli@latest` +Use `@insider` tag for insider builds. + +### `compareSemver(a, b)` utility (in upgrade.js): +Returns -1/0/1. Handles pre-release: strips pre-release for base comparison, then treats pre-release as less than release (e.g., `0.9.5-insider.1` < `0.9.5`). Can be ported directly if needed in prompt logic. + +--- + +## `.squad/config.json` — What It Holds + +```json +{ + "version": 1, + "stateBackend": "worktree" +} +``` + +Other optional fields added by the coordinator at runtime: +- `defaultModel` — global model override for all agent spawns +- `agentModelOverrides.{agentName}` — per-agent model override + +The file is read-only from the upgrade path's perspective. Only the coordinator writes to it (for model preferences). + +--- + +## Version-Check Probe (npm Registry) + +Use this one-liner from inside a coordinator prompt to fetch dist-tags: + +``` +npm view @bradygaster/squad-cli dist-tags --json +``` + +- Timeout: **5 seconds.** If no response within 5 seconds, abandon and show normal greeting. +- On success: extract `dist-tags[channel]` (e.g., `dist-tags["insider"]`). +- On any error (network failure, registry unreachable, parse error): show normal greeting. + +--- + +## Upstream OS-Specific Cache + +The CLI (`self-update.ts`) writes `latest` version info to an OS-specific path with a 24h TTL. + +**One-liner to read the upstream cache:** +``` +node -e "const p=require('path'),o=require('os');const b=process.env.APPDATA||(process.platform==='darwin'?p.join(o.homedir(),'Library','Application Support'):p.join(o.homedir(),'.config'));const f=p.join(b,'squad-cli','update-check.json');try{const d=JSON.parse(require('fs').readFileSync(f,'utf8'));const age=Date.now()-d.checkedAt;if(age<86400000)console.log(JSON.stringify(d));else console.log('STALE')}catch{console.log('MISS')}" +``` + +Output semantics: +- Valid JSON `{"latestVersion":"X.Y.Z","checkedAt":N}` → cache hit; use `latestVersion` +- `STALE` → cache expired (older than 24h); treat as no data +- `MISS` → cache missing or corrupt; treat as no data + +**OS-specific cache path:** +- Windows: `%APPDATA%\squad-cli\update-check.json` +- Linux: `~/.config/squad-cli/update-check.json` +- macOS: `~/Library/Application Support/squad-cli/update-check.json` + +--- + +## Repo-Local Cache Convention: `.squad/.cache/version-check.json` + +Used by coordinator for `insider`/`preview` channels (the upstream cache only stores `latest`). + +**Schema:** +```json +{ + "checkedAt": "2026-05-26T14:13:28.492Z", + "currentVersion": "0.9.6-insider.2", + "channel": "insider", + "channelVersion": "0.9.7-insider.1" +} +``` + +**TTL:** 24 hours from `checkedAt`. +**Gitignore:** `.squad/.cache/` is listed in `.gitignore` — cache files are never committed. + +--- + +## Key File Paths (installed CLI) + +| Purpose | Path | +|---|---| +| Version utilities | `dist/cli/core/version.js` | +| Upgrade logic | `dist/cli/core/upgrade.js` | +| Init logic | `dist/cli/core/init.js` | +| Template manifest | `dist/cli/core/templates.js` | +| Copilot install helper | `dist/cli/copilot-install.js` | +| squad.agent.md template | `templates/squad.agent.md.template` | +| Session init reference | `templates/session-init-reference.md` | +| All templates | `templates/` | diff --git a/.squad/skills/squad/SKILL.md b/.squad/skills/squad/SKILL.md new file mode 100644 index 000000000..e67cd9710 --- /dev/null +++ b/.squad/skills/squad/SKILL.md @@ -0,0 +1,299 @@ +--- +name: squad +description: >- + Squad's command catalog and interactive menu. Invoke via /squad (slash command) or natural language ("squad commands", "what can squad do", "show me squad options"). Presents categorized operations (Install & Upgrade, Team Management, Issues & PRs, Plugins & Skills, Model & Cost, Sessions & State) as an interactive picker. Routes to the right squad CLI command or the Squad coordinator agent. +user-invocable: true +allowedTools: [] +--- + +## Menu Presentation Rules + +When the user triggers this skill (via `/squad` slash command, "squad commands", "help", "what can squad do", etc.): + +1. **Category-level menu first.** Present category names as an `ask_user` choice list: + ``` + 📋 Squad Commands — pick a category: + 1. Install & Upgrade + 2. Team Management + 3. Issues & PRs + 4. Plugins & Skills + 5. Model & Cost + 6. Sessions & State + ``` +2. **Drill-down.** After selection, show operation titles in that category as a second `ask_user` list. +3. **Direct match skips the menu.** If the user says "how do I upgrade with state backend," match to the specific entry and go straight to argument collection. +4. **Compact fallback.** If `ask_user` is unavailable, render as a markdown table instead. +5. **Back / Cancel.** Include "← Back to categories" in sub-menus. Include "Cancel" in confirmation prompts. Respect "never mind" / "cancel" at any point. + +**Argument collection:** For entries with `args`, iterate the list sequentially. Use `ask_user` with choices when `choices` is provided; free-text prompt otherwise. If the user says "just do it" or "defaults are fine," skip remaining args and use their defaults. + +**Confirmation template:** +``` +⚠️ This will {action-description}. +{what will change} +Proceed? (yes / no) +``` + +--- + +## Install & Upgrade + +### Upgrade Squad CLI + +- **intent:** upgrade squad, update squad, install latest version, get new version +- **summary:** Upgrade Squad CLI to the latest version for your channel +- **action:** shell +- **command:** squad upgrade +- **args:** + - `state-backend`: Which state backend? | choices: {worktree, git-notes, orphan, two-layer} | default: (keep current) +- **confirm:** false +- **platform_caveats:** Requires terminal. In VS Code, open the integrated terminal and run the command directly. + +### Initialize Squad + +- **intent:** set up squad, initialize squad, create team, start squad in this project +- **summary:** Scaffold Squad in the current directory (idempotent) +- **action:** shell +- **command:** squad init +- **args:** (none) +- **confirm:** false +- **platform_caveats:** Requires terminal. Recommend a standalone terminal for best results. + +### Switch State Backend + +- **intent:** switch state backend, change state storage, use git-notes, use orphan branch +- **summary:** Change where Squad stores mutable state (config.json) +- **action:** file-edit +- **command:** .squad/config.json → stateBackend +- **args:** + - `stateBackend`: Which state backend? | choices: {worktree, git-notes, orphan, two-layer} | default: (keep current) +- **confirm:** true +- **platform_caveats:** May require migration if switching away from worktree. Show current value and new value before confirming. + +--- + +## Team Management + +### Add Team Member + +- **intent:** add team member, hire agent, add agent, add developer, recruit +- **summary:** Add a new agent to the team roster +- **action:** coordinator +- **command:** Add Team Member flow (Init Mode / Team Mode) +- **args:** + - `role`: What role should this agent fill? (e.g., Frontend Dev, Backend Dev, QA Engineer) + - `name`: Preferred name or casting universe? | default: (auto-cast from active universe) +- **confirm:** false + +### Remove Team Member + +- **intent:** remove team member, fire agent, delete agent, remove developer +- **summary:** Remove an agent and delete their charter and history files +- **action:** coordinator +- **command:** Remove Team Member flow +- **args:** + - `member`: Which team member to remove? (name or role) +- **confirm:** true + +### Reassign Roles + +- **intent:** reassign role, change role, swap roles, update team member role +- **summary:** Update a team member's role in team.md and their charter +- **action:** coordinator +- **command:** Update team.md roster + charter.md +- **args:** + - `member`: Which team member? + - `newRole`: New role? +- **confirm:** false + +### Show Roster + +- **intent:** show roster, who is on the team, list team members, show team, capability profile +- **summary:** Display the current team roster and capability profile +- **action:** coordinator +- **command:** Direct Mode — read team.md, answer +- **args:** (none) +- **confirm:** false + +--- + +## Issues & PRs + +### Connect GitHub Repo + +- **intent:** connect github, enable issues, set up issues, link repository, github issues mode +- **summary:** Connect this project to GitHub Issues via gh auth +- **action:** coordinator +- **command:** GitHub Issues Mode (connection flow) +- **args:** (none) +- **confirm:** false +- **platform_caveats:** Requires `gh auth login` to have been run in the terminal. + +### Triage Issues + +- **intent:** triage issues, review issues, assign issues, label issues +- **summary:** Run the Lead triage flow on open GitHub issues +- **action:** coordinator +- **command:** GitHub Issues Mode → Lead triage +- **args:** (none) +- **confirm:** false + +### Activate Ralph + +- **intent:** activate ralph, start ralph, ralph go, start work monitor, start auto-work +- **summary:** Activate Ralph — Work Monitor — to pick up and run queued issues +- **action:** coordinator +- **command:** Ralph — Work Monitor triggers +- **args:** (none) +- **confirm:** false + +### Set Ralph Polling Interval + +- **intent:** set ralph interval, change ralph timing, how often does ralph check, ralph every N minutes +- **summary:** Tell Ralph how frequently to poll for new work +- **action:** coordinator +- **command:** Ralph trigger: "Ralph, check every N minutes" +- **args:** + - `interval`: How often should Ralph poll? (in minutes) | default: 10 +- **confirm:** false + +### Start Squad Watch + +- **intent:** start watch, squad watch, monitor issues, watch for issues, auto-triage +- **summary:** Start squad watch to continuously poll and triage issues +- **action:** shell +- **command:** squad watch +- **args:** + - `interval`: Poll interval in minutes | default: 10 +- **confirm:** false +- **platform_caveats:** CLI-only — long-running foreground process. Not viable in VS Code without an integrated terminal. Run: `squad watch --interval {n}` in your terminal. + +--- + +## Plugins & Skills + +### Browse Plugin Marketplace + +- **intent:** browse plugins, explore plugins, what plugins are available, plugin marketplace +- **summary:** Browse available plugins in the Squad marketplace +- **action:** shell +- **command:** squad plugin marketplace browse +- **args:** + - `name`: Plugin name to search for | default: (browse all) +- **confirm:** false + +### Add Marketplace Plugin + +- **intent:** add plugin, install plugin, get plugin from marketplace +- **summary:** Add a plugin from the marketplace to this Squad +- **action:** shell +- **command:** squad plugin marketplace add +- **args:** + - `plugin`: Plugin owner/repo (e.g., owner/plugin-name) +- **confirm:** false + +### Remove Marketplace Plugin + +- **intent:** remove plugin, uninstall plugin, delete plugin +- **summary:** Remove an installed marketplace plugin +- **action:** shell +- **command:** squad plugin marketplace remove +- **args:** + - `name`: Plugin name to remove +- **confirm:** true + +### List Marketplace Plugins + +- **intent:** list plugins, show installed plugins, what plugins do I have +- **summary:** List all plugins registered in this Squad +- **action:** shell +- **command:** squad plugin marketplace list +- **args:** (none) +- **confirm:** false + +### List Installed Skills + +- **intent:** list skills, show skills, what skills are installed, skill catalog +- **summary:** List all skills installed in .squad/skills/ and .github/skills/ +- **action:** coordinator +- **command:** Direct Mode — list .squad/skills/ and .github/skills/ directories +- **args:** (none) +- **confirm:** false + +--- + +## Model & Cost + +### Set Default Model + +- **intent:** set default model, change model, use gpt-4, use claude, switch model +- **summary:** Set the default model for all agents in config.json +- **action:** file-edit +- **command:** .squad/config.json → defaultModel +- **args:** + - `model`: Model name (e.g., gpt-4o, claude-sonnet-4.5, o3) +- **confirm:** false + +### Override Per-Agent Model + +- **intent:** set model for agent, agent model override, use different model for one agent +- **summary:** Set a model override for a specific agent in config.json +- **action:** file-edit +- **command:** .squad/config.json → agentModelOverrides.{agentName} +- **args:** + - `agent`: Agent name (must match name in team.md) + - `model`: Model name (e.g., gpt-4o, claude-sonnet-4.5) +- **confirm:** false + +### Clear Model Preference + +- **intent:** clear model, reset model, remove model preference, use default model +- **summary:** Remove a model override from config.json (reverts to system default) +- **action:** file-edit +- **command:** .squad/config.json → remove defaultModel or agentModelOverrides.{agentName} +- **args:** + - `scope`: Clear default or a specific agent? | choices: {default model, specific agent} | default: default model + - `agent`: Agent name (only if scope = specific agent) +- **confirm:** false + +--- + +## Sessions & State + +### Catch-Up Summary + +- **intent:** catch me up, what happened, status, what did the team do, session summary +- **summary:** Summarize recent agent activity and key decisions +- **action:** coordinator +- **command:** Session catch-up flow (lazy scan) +- **args:** (none) +- **confirm:** false + +### Show Recent Decisions + +- **intent:** show decisions, recent decisions, what decisions were made, decision log +- **summary:** Display recent entries from .squad/decisions.md +- **action:** coordinator +- **command:** Direct Mode — read decisions.md, answer +- **args:** (none) +- **confirm:** false + +### Archive Old Decisions + +- **intent:** archive decisions, clean up decisions, move old decisions, compact decisions +- **summary:** Move old decisions from decisions.md to decisions-archive.md +- **action:** coordinator +- **command:** Move entries older than threshold from .squad/decisions.md → .squad/decisions-archive.md +- **args:** + - `olderThan`: Archive decisions older than how many days? | default: 30 +- **confirm:** true + +### Summarize Agent History + +- **intent:** summarize history, what did agent do, agent history, compress history +- **summary:** Spawn an agent to summarize and compress a team member's history file +- **action:** coordinator +- **command:** Spawn agent with history.md summarization task +- **args:** + - `member`: Which team member's history to summarize? +- **confirm:** false diff --git a/.squad/skills/tiered-memory/SKILL.md b/.squad/skills/tiered-memory/SKILL.md new file mode 100644 index 000000000..5921eb80a --- /dev/null +++ b/.squad/skills/tiered-memory/SKILL.md @@ -0,0 +1,221 @@ +--- +name: tiered-memory +description: Three-tier agent memory model (hot/cold/wiki) for context reduction per spawn +domain: memory-management, performance +confidence: design (runtime not yet implemented) +source: design proposal +--- + +# Skill: Tiered Agent Memory + +> **Status (v0.10.0):** This skill describes a **design proposal**, not a shipped runtime. Skill files install via `squad init`/`upgrade`, but the underlying tier scaffolding (`.squad/memory/hot/`, `cold/`, `wiki/`), Scribe promotion logic, and spawn-template tier-aware reads are tracked in [bradygaster/squad#1264](https://github.com/bradygaster/squad/issues/1264). Until those land, agents continue to load full `history.md` + `decisions.md` on every spawn. + +## Overview + +Squad agents today load their full context history on every spawn, which grows unboundedly across sessions. The Tiered Agent Memory model proposes a three-tier separation so agents only load the bytes that are actually relevant to the current task, with older context kept available on demand. + +--- + +## Memory Tiers + +### 🔥 Hot Tier — Current Session Context +- **Size target:** keep small (~2–4KB typical) +- **Load policy:** Always loaded. Every spawn includes hot memory by default. +- **Contents:** Current task description, active decisions made this session, immediate blockers, last 3–5 actions taken, who you are talking to right now. +- **Lifetime:** Current session only. Discarded after session ends (Scribe promotes relevant parts to Cold). +- **Purpose:** Provide immediate task context without any latency or load decision. + +### ❄️ Cold Tier — Summarized Cross-Session History +- **Size target:** larger summary, not full transcript (~8–12KB typical) +- **Load policy:** Load on demand. Include only when the task explicitly needs history. +- **Contents:** Summarized past sessions (compressed by Scribe), cross-session decisions, recurring patterns, unresolved issues from prior work. +- **Lifetime:** Rolling window (default proposal: 30 days). Eligible entries are then promoted to Wiki. +- **Purpose:** Answer "what have we tried before?" and "what was decided?" without replaying full transcripts. +- **How to include:** Pass `--include-cold` in spawn template or add `## Cold Memory` section. + +### 📚 Wiki Tier — Durable Structured Knowledge +- **Size target:** variable, structured reference docs +- **Load policy:** Async write, selective read. Load only when task requires domain knowledge. +- **Contents:** Architecture decisions (ADRs), agent charters, routing rules, stable conventions, external API contracts, known platform constraints. +- **Lifetime:** Permanent until explicitly deprecated. +- **Purpose:** Authoritative reference. Not history — structured facts. +- **How to include:** Pass `--include-wiki` or reference specific wiki doc paths in spawn template. + +--- + +## When to Load Each Tier + +| Situation | Hot | Cold | Wiki | +|-----------|-----|------|------| +| New task, no prior context needed | ✅ | ❌ | ❌ | +| Resuming interrupted work | ✅ | ✅ | ❌ | +| Debugging a recurring issue | ✅ | ✅ | ❌ | +| Implementing against a spec/ADR | ✅ | ❌ | ✅ | +| Onboarding to unfamiliar subsystem | ✅ | ❌ | ✅ | +| Post-incident review | ✅ | ✅ | ✅ | + +--- + +## Spawn Template Pattern + +The default spawn prompt should include **Hot tier only**: + +``` +## Memory Context + +### Hot (current session) +{hot_context} +``` + +Add `--include-cold` when the task needs history: +``` +## Memory Context + +### Hot (current session) +{hot_context} + +### Cold (summarized history — load on demand) +See: .squad/memory/cold/{agent-name}.md +``` + +Add `--include-wiki` when the task needs domain knowledge: +``` +## Memory Context + +### Hot (current session) +{hot_context} + +### Wiki (durable reference) +See: .squad/memory/wiki/{topic}.md +``` + +--- + +## Integration with Scribe Agent (design — not yet implemented) + +Scribe is the proposed memory coordinator for this system. Once the runtime lands, Scribe will: + +1. **End of session:** Compress Hot → Cold summary (target: ~10% of session verbosity) +2. **Aged cold entries:** Promote Cold → Wiki for decisions/facts that aged into stable knowledge +3. **On-demand wiki writes:** Any agent can request Scribe to write a wiki entry mid-session + +Until then, see the Scribe charter for current behavior: `.squad/agents/scribe/charter.md` + +--- + +## Implementation Checklist (tracked in #1264) + +- [ ] Scribe writes Hot context file at session start (`.squad/memory/hot/{agent}.md`) +- [ ] Scribe compresses and writes Cold summary at session end +- [ ] Spawn templates default to Hot-only +- [ ] Coordinators add `--include-cold` / `--include-wiki` flags as needed +- [ ] Wiki entries stored in `.squad/memory/wiki/` +- [ ] Cold entries stored in `.squad/memory/cold/` with rolling TTL + +--- + +## References + +- Tracking issue: [bradygaster/squad#1264](https://github.com/bradygaster/squad/issues/1264) — installation gap + runtime status +- Original design spike: [bradygaster/squad#686](https://github.com/bradygaster/squad/issues/686) — tiered memory implementation plan +- Related: [bradygaster/squad#600](https://github.com/bradygaster/squad/issues/600) — context payload growth + +--- + +## Spawn Template + +# Spawn Template: Agent with Tiered Memory + +Use this template when spawning any Squad agent. By default it loads **Hot tier only**. Add optional sections as needed. + +--- + +## Task + +{task_description} + +## WHY + +{why_this_matters} + +## Success Criteria + +- [ ] {criterion_1} +- [ ] {criterion_2} + +--- + +## Memory Context + +### 🔥 Hot (always included) + +> Paste current session context here (~2–4KB target): + +``` +Current task: {task_description} +Active decisions: {decisions_this_session} +Last actions: {last_3_to_5_actions} +Blockers: {current_blockers_or_none} +Talking to: {current_interlocutor} +``` + +--- + +### ❄️ Cold (include when task needs history — add `--include-cold`) + +> Load on demand. Do not inline unless specifically needed. + +Summarized cross-session history is at: +`.squad/memory/cold/{agent-name}.md` + +Include when: +- Resuming interrupted work +- Debugging a recurring issue +- "What have we tried before?" + +**To load cold memory, add this section and fetch the file before spawning:** + +``` +## Cold Memory Summary +{contents_of_.squad/memory/cold/{agent-name}.md} +``` + +--- + +### 📚 Wiki (include when task needs domain knowledge — add `--include-wiki`) + +> Load on demand. Reference specific wiki docs by path. + +Wiki entries are at: `.squad/memory/wiki/` + +Include when: +- Implementing against an ADR or spec +- Onboarding to unfamiliar subsystem +- Need stable conventions or API contracts + +**To load wiki, add this section and reference the specific doc:** + +``` +## Wiki Reference +{contents_of_.squad/memory/wiki/{topic}.md} +``` + +--- + +## Escalation + +If blocked or uncertain: +- Architecture questions → @picard +- Security concerns → @worf +- Infrastructure/deployment → @belanna +- Memory/history questions → @scribe + +--- + +## Notes + +- Hot tier is always included; keep it focused +- Cold adds a summary; only include when history is relevant +- Wiki adds variable size; only include specific relevant docs +- Runtime backing is tracked in [bradygaster/squad#1264](https://github.com/bradygaster/squad/issues/1264) — until those changes land, this skill is design-only and agents continue to load full history.md + decisions.md on every spawn + diff --git a/CHANGELOG.md b/CHANGELOG.md index dbb2fbc6a..81f25a9e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,14 @@ First stable release since v0.9.4 (April 25). Consumes 97 changesets (sdk: 50, c ### Internal / Tests / Docs - (cli) docs: point README migration guide links to the published docs site +## [0.1.0-preview] - 2026-06-02 + +### Added — Squad.Agents.AI NuGet preview +- Added `Squad.Agents.AI`, a `net10.0` Microsoft Agent Framework package that exposes a Squad team as an `AIAgent`. +- Public surface: `SquadAgent`, `SquadAgentOptions`, `SquadConnectionFactory`, and `SquadServiceCollectionExtensions`. +- Added DI registration, PATH/URI connection-string parsing, GitHub token/provider options, and XML documentation for the public preview surface. +- Added README documentation for the `Squad.Agents.AI` package in `src/Squad.Agents.AI/README.md`. + ## [0.9.0] - 2026-03-23 ### Added — Personal Squad Governance Layer diff --git a/README.md b/README.md index 5ee2e61ec..b2ab79a3d 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,10 @@ Squad proposes a team — each member named from a persistent thematic cast. You --- +## .NET package preview + +Building a .NET app that should call a Squad team as a Microsoft Agent Framework agent? `Squad.Agents.AI` is a preview NuGet package under [`src/Squad.Agents.AI`](src/Squad.Agents.AI/README.md). It registers a Squad-backed `AIAgent` in DI and targets early `0.1.0-preview` consumers. + ## Upgrading Upgrading Squad is a two-step process. diff --git a/Squad.Agents.AI.slnx b/Squad.Agents.AI.slnx new file mode 100644 index 000000000..907725a98 --- /dev/null +++ b/Squad.Agents.AI.slnx @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/cspell.json b/cspell.json index 1030c0872..2f1fc0a0c 100644 --- a/cspell.json +++ b/cspell.json @@ -39,7 +39,9 @@ "benleane", "TELMU", "Automator", "kedacore", "DEVBOX", "myaccount", "graphify", "Graphify", "graphifyy", "safishamsi", "jagilber", "benchmarkfn", "filterissuesbystream", "filterissuesbyworkstream", "Recognised", "recognised", - "squadified", "TOCTOU", "unflushed", "unparseable", "pluggability" + "squadified", "TOCTOU", "unflushed", "unparseable", "pluggability", + "PKCE", "MSAL", "AKIA", "runas", + "typoo" ], "dictionaries": ["en_US", "typescript", "node", "npm", "bash"], "allowCompoundWords": true diff --git a/docs/proposals/memory-governance-provider.md b/docs/proposals/memory-governance-provider.md index 4408d00b2..8089703cc 100644 --- a/docs/proposals/memory-governance-provider.md +++ b/docs/proposals/memory-governance-provider.md @@ -93,7 +93,7 @@ For `squad init` and Copilot custom agents, local worktree memory remains the de - `.squad/decisions.md` - `.squad/decisions/inbox/` - `.squad/agents/{name}/history.md` -- `.squad/skills/{name}/SKILL.md` +- `.copilot/skills/{name}/SKILL.md` This keeps Squad usable offline, reviewable in Git, and compatible with existing teams. It also preserves the current prompt-only behavior for Copilot custom agents: when no diff --git a/docs/src/content/blog/004-v020-release.md b/docs/src/content/blog/004-v020-release.md index 2eb3bf9b1..74d8ea616 100644 --- a/docs/src/content/blog/004-v020-release.md +++ b/docs/src/content/blog/004-v020-release.md @@ -18,7 +18,7 @@ hero: "Export your squad. Import it somewhere else. It remembers you — your pr ## What Shipped - **Export / Import CLI** — `npx @bradygaster/squad-cli export` serializes your squad's identity, history, skills, and decisions into a portable `.squad` package. `npx @bradygaster/squad-cli import` reconstitutes it in a new project. Your squad remembers YOU, not the repo it came from. _(Built by Fenster)_ -- **Skills Phase 1: Template + Read** — Agents read `SKILL.md` files from `.squad/skills/` before working. Skills are structured knowledge — domain conventions, patterns, anti-patterns — that agents reference during every spawn. _(Built by Verbal)_ +- **Skills Phase 1: Template + Read** — Agents read `SKILL.md` files from `.copilot/skills/` before working. Skills are structured knowledge — domain conventions, patterns, anti-patterns — that agents reference during every spawn. _(Built by Verbal)_ - **Skills Phase 2: Earned Skills** — Agents write `SKILL.md` files from real work. A skill starts at `low` confidence when first observed, moves to `medium` with repetition, and reaches `high` when proven across sessions. Your squad gets better because it worked with you, not because someone configured it. _(Built by Verbal)_ - **Tiered Response Modes** — Direct, Lightweight, Standard, Full. A one-line question no longer pays the same spawn overhead as a multi-file refactor. The coordinator picks the tier based on complexity. _(Built by Verbal)_ - **Smart Upgrade with Migrations** — `npx @bradygaster/squad-cli upgrade` now runs version-keyed migrations. Upgrading from v0.1.0 to v0.2.0 applies only the migrations for versions you haven't seen. Your team state is never touched. _(Built by Fenster)_ diff --git a/docs/src/content/blog/009-v040-sprint-progress.md b/docs/src/content/blog/009-v040-sprint-progress.md index a2488a7f7..7f8da90f1 100644 --- a/docs/src/content/blog/009-v040-sprint-progress.md +++ b/docs/src/content/blog/009-v040-sprint-progress.md @@ -73,7 +73,7 @@ Coordinator: 3. Extracts new milestones, relays to user in real-time 4. Falls back to "still working" if no milestones (graceful degradation) -**For v0.4.0:** Coordinator polling loop + `.squad/skills/progress-signals/SKILL.md` documentation. +**For v0.4.0:** Coordinator polling loop + `.copilot/skills/progress-signals/SKILL.md` documentation. **For v0.5.0+:** Customizable polling cadence, emoji matching to agent persona, milestone filtering for quiet mode. diff --git a/docs/src/content/blog/011-skills-system-learning-from-work.md b/docs/src/content/blog/011-skills-system-learning-from-work.md index 41ad5f56d..e9bc3d77b 100644 --- a/docs/src/content/blog/011-skills-system-learning-from-work.md +++ b/docs/src/content/blog/011-skills-system-learning-from-work.md @@ -25,7 +25,7 @@ That changed in v0.2.0 with the skills system. ## How It Works -Skills are **earned domain knowledge** that changes how agents approach work. After completing a task, agents extract reusable patterns and write them to `.squad/skills/{skill-name}/SKILL.md` using the Anthropic SKILL.md open standard. +Skills are **earned domain knowledge** that changes how agents approach work. After completing a task, agents extract reusable patterns and write them to `.copilot/skills/{skill-name}/SKILL.md` using the Anthropic SKILL.md open standard. Three categories exist: @@ -71,15 +71,15 @@ Verbal designed the skill lifecycle (acquisition → reinforcement → correctio Kujan validated that: - Skills stored separately from history enable clean export (history is project-specific, skills are portable) - The `store_memory` tool (Anthropic's skill persistence API) was the wrong model for Squad — filesystem persistence is Squad's architecture -- File paths in agent charters are frozen API contracts (changing `.squad/agents/{name}/skills.md` to `.squad/skills/` requires migration) +- File paths in agent charters are frozen API contracts (changing `.squad/agents/{name}/skills.md` to `.copilot/skills/` requires migration) ### Open Standard Adoption (2026-02-09) -Squad adopted the Agent Skills Open Standard (agentskills.io) and the SKILL.md YAML frontmatter format. Directory structure changed from per-agent files to a flat `.squad/skills/` directory. Skills are **team knowledge**, not agent-specific. +Squad adopted the Agent Skills Open Standard (agentskills.io) and the SKILL.md YAML frontmatter format. Directory structure changed from per-agent files to a flat `.copilot/skills/` directory. Skills are **team knowledge**, not agent-specific. The final decision (Verbal, 2026-02-09): -> _"Skills in `.squad/skills/{skill-name}/SKILL.md`. Coordinator injects `` XML for progressive disclosure (~50 tokens per skill at discovery). Skills portable beyond Squad — works in Claude Code, Copilot, any compliant tool."_ +> _"Skills in `.copilot/skills/{skill-name}/SKILL.md`. Coordinator injects `` XML for progressive disclosure (~50 tokens per skill at discovery). Skills portable beyond Squad — works in Claude Code, Copilot, any compliant tool."_ ## Technical Details @@ -113,9 +113,9 @@ What to avoid ### Discovery and Application -1. **Coordinator reads** `.squad/skills/` directory at session start +1. **Coordinator reads** `.copilot/skills/` directory at session start 2. **Progressive disclosure**: Only skill names and descriptions are loaded initially (~50 tokens per skill) -3. **Agent spawns with context**: Spawn template says "check `.squad/skills/{skill-name}/SKILL.md` if relevant" +3. **Agent spawns with context**: Spawn template says "check `.copilot/skills/{skill-name}/SKILL.md` if relevant" 4. **Agent reads full skill** when applicable to the task 5. **Agent applies pattern** from the skill 6. **Agent updates or extracts**: Bump confidence if validated, extract new skill if pattern discovered @@ -136,7 +136,7 @@ Skills travel via the `squad-export.json` manifest: ``` When imported into a new squad: -- Skill files are written to `.squad/skills/{skill-name}/SKILL.md` +- Skill files are written to `.copilot/skills/{skill-name}/SKILL.md` - Agents read them before first spawn - Team arrives at the new project already competent @@ -161,7 +161,7 @@ squad plugin marketplace add github:squad-plugins/official squad plugin install aws-deployment-patterns ``` -The skill appears at `.squad/skills/aws-deployment-patterns/SKILL.md` and agents apply it on their next spawn. +The skill appears at `.copilot/skills/aws-deployment-patterns/SKILL.md` and agents apply it on their next spawn. ### Cross-Tool Compatibility @@ -179,7 +179,7 @@ Users aren't locked into Squad. The knowledge is portable. As of v0.2.0: - **2 built-in skills** shipped with Squad (`squad-conventions`, `label-driven-workflow`) -- **15+ learned skills** in Squad's own `.squad/skills/` directory earned during dogfooding (GitHub Actions automation, Jekyll site deployment, Jest testing patterns, MCP tool discovery) +- **15+ learned skills** in Squad's own `.copilot/skills/` directory earned during dogfooding (GitHub Actions automation, Jekyll site deployment, Jest testing patterns, MCP tool discovery) - **0 npm dependencies** — pure markdown with YAML frontmatter - **~50 tokens per skill** at discovery (name + description only) - **Full content (~500-2000 tokens)** loaded only when agent needs it diff --git a/docs/src/content/blog/022-welcome-to-the-new-squad.md b/docs/src/content/blog/022-welcome-to-the-new-squad.md index ee2ff28ec..a4b98af07 100644 --- a/docs/src/content/blog/022-welcome-to-the-new-squad.md +++ b/docs/src/content/blog/022-welcome-to-the-new-squad.md @@ -238,7 +238,7 @@ squad upgrade This updates Squad-owned files (templates, CLI scripts, SDK code) but never touches: - `.squad/agents/**` (your agent charters) - `.squad/decisions.md` (architectural decisions) -- `.squad/skills/**` (learned patterns) +- `.copilot/skills/**` (learned patterns) - `.squad/history/**` (session logs) Your team's memory is sacred. Upgrades respect that. @@ -486,7 +486,7 @@ npm install && npm start **What you'll see:** ``` -📚 Loading skills from .squad/skills/ +📚 Loading skills from .copilot/skills/ ✅ Loaded: TypeScript Patterns (confidence: high 🟢) Triggers: typescript, types, generics diff --git a/docs/src/content/blog/026-whats-new-ado-comms-subsquads.md b/docs/src/content/blog/026-whats-new-ado-comms-subsquads.md index 30cf45d3d..8c0e22323 100644 --- a/docs/src/content/blog/026-whats-new-ado-comms-subsquads.md +++ b/docs/src/content/blog/026-whats-new-ado-comms-subsquads.md @@ -111,7 +111,7 @@ Every platform adapter went through a community-driven 5-model security review ( - Runtime `Module._resolveFilename` intercept for Node 24+ ESM compatibility - 5-layer secret defense architecture -- `.squad/skills/secret-handling/SKILL.md` team reference +- `.copilot/skills/secret-handling/SKILL.md` team reference - 59 TDD security hook tests - Charter hardening for Trejo (Git & Release) and Drucker (CI/CD) diff --git a/docs/src/content/blog/032-v010-stabilisation-insider.md b/docs/src/content/blog/032-v010-stabilisation-insider.md new file mode 100644 index 000000000..107d82f93 --- /dev/null +++ b/docs/src/content/blog/032-v010-stabilisation-insider.md @@ -0,0 +1,126 @@ +--- +title: "Squad v0.10 Stabilization: Eleven PRs and an Insider Release" +date: 2026-06-14 +author: "Squad (Copilot)" +wave: null +tags: [release, stabilization, insider, memory, presets, cross-squad, fact-checker] +status: published +hero: "v0.10 shipped six big features. v0.10.0-insider.1 makes them work end-to-end: memory tools that survive a fresh Copilot session, presets that wire up the team on apply, skills that land in the right folder, and a Fact-Checker that's on every team by default." +--- + +# Squad v0.10 Stabilization: Eleven PRs and an Insider Release + +> _"Done means a fresh squad init, in a fresh repo, with a two-layer state backend, can build a working app without anyone holding its hand."_ + +## The Problem v0.10 Left Open + +v0.10 introduced six big things: a two-layer state backend, presets, the `squad commands` skill, cross-squad discovery, cross-squad communication, and the Fact-Checker / Devil's Advocate agent. Each one shipped behind feature work; each one had at least one rough edge that surfaced the first time someone tried to use it in anger. + +A weekend of dogfooding found and fixed eleven of those edges. The result is `@bradygaster/squad-cli@0.10.0-insider.1` — install today, get the polished v0.10 experience without waiting for v0.11. + +## Install the Insider + +```bash +# One-off use +npx -y @bradygaster/squad-cli@insider init --state-backend two-layer + +# Or pin it in .copilot/mcp-config.json +{ + "squad_state_": { + "command": "npx", + "args": ["-y", "@bradygaster/squad-cli@0.10.0-insider.1", "state-mcp"] + } +} +``` + +The insider track follows the `-insider.N` pattern. Today: `0.10.0-insider.1`. Each new insider bumps N; the public `latest` tag stays on `0.10.0` until the next stable release. + +## What Got Fixed + +### 1. Memory tools survive a fresh Copilot session + +The two-layer state backend writes squad decisions to an orphan `squad-state` branch and exposes them through the MCP server as `squad_state.*` plus six `memory.*` aliases. Before: a freshly opened Copilot session would show seven tools, sometimes silently. After: + +- A handshake at session start surfaces all thirteen tools as the first function-list call returns (#1306) +- The MCP config no longer accumulates one `squad_state_` entry per project in `~/.copilot/mcp-config.json` (#1298) +- A drift guard in the manifest catches missing skill sources at build time instead of silently dropping them (#1292) +- A HARD RULE in `squad.agent.md` prevents the coordinator from silently degrading when a state-backend probe fails (#1306, closes #1305) + +### 2. Presets wire the team on apply + +`squad preset apply ` used to install the preset files but stop short of wiring the roster. The team table stayed empty, the casting registry was unpopulated, and the next Copilot session would mode-switch into **Init Mode** instead of **Established Mode**. PR #1293 added a merge-friendly `presets/scaffold.ts` that populates: + +- `.squad/team.md` `## Members` table from the preset roster +- `.squad/casting/registry.json` and `history.json` initial entries +- `.squad/routing.md` placeholder substitution + +After: opening a Copilot session on a freshly applied preset shows mode-switch detect **Established Mode** and `squad team list` shows all members. + +### 3. The `squad commands` skill lands in the right folder + +The `squad commands` skill (and `squad-version-check`, `squad-help`) lives in the CLI templates dir but was missing from the SDK manifest, so `squad init` silently skipped it on a fresh install. PR #1292 ports it across plus three other previously-missing skills (`tiered-memory`, `iterative-retrieval`, `reflect`). PR #1304 makes the install target `.github/skills/` (the canonical Copilot CLI skill discovery path) and migrates older squads from `.squad/skills/` on `squad upgrade`. PR #1303 adds a slash-style `/squad` command surface. + +Bonus: a regression test (#1292) asserts `MANIFEST_SKILL_NAMES.length === installed_skills.length`. No more silent skips. + +### 4. Cross-squad communication ships with every squad + +The `cross-squad-communication` skill — which lets one squad call another with `copilot --agent squad --allow-all-tools` — is now bundled by `squad init` and `squad upgrade`. PR #1295 audited the CLI invocations to make sure they use the real flags (`--agent squad`, capital `-C`, `-p` for text not path, `--allow-all-tools`) and added a universal rule: every `copilot` spawn into a peer squad passes `--agent squad`. + +### 5. Fact-Checker / Devil's Advocate is on every team + +The Fact-Checker agent (also operates as Devil's Advocate in its second mode; one agent, two modes per #1254) used to be a manual cast. PR #1300 added it to the always-on roster scaffolded by `squad init` and `squad upgrade`, alongside RAI. PR #1301 gave it a rich charter, its own state dir, and a Linux-CI case-fix for the file path. + +### 6. The coordinator routes "spawn a squad" correctly + +When a user typed "spawn a squad to build X", the coordinator sometimes silently fanned out individual `task` calls instead of using the `task(agent_type="Squad", …)` path. PR #1307 fixed the routing row in `squad.agent.md`, added an `ask_user` clarification step for ambiguous requests, and documented the anti-pattern explicitly. PR #1297 added a `squad` disambiguation skill so `skill(Squad)` lookups succeed and redirect to the right path. + +## End-to-End Smoke Test + +The acceptance criterion this whole weekend was driving toward: + +```powershell +# Fresh dir, fresh git repo, no prior squad state +mkdir C:\Temp\smoke-test; cd C:\Temp\smoke-test; git init + +# Init with two-layer backend +npx -y @bradygaster/squad-cli@insider init --state-backend two-layer + +# Ask it to build something +copilot --agent squad --allow-all-tools ` + -p "build hello-squad — a Node CLI that prints the squad team members" +``` + +Result: the coordinator built three files (`package.json`, `index.js`, `test/index.test.js`), ran the tests, self-corrected one path bug, and finished green in 1m 58s with `1 pass / 0 fail`. The squad-state orphan branch picked up four commits (decisions, history, charters, routing). A fresh Copilot session on the same repo loaded all thirteen MCP tools on the first function-list call. + +## What's Still Open + +- **Preset install from a URL** (PR #1225) — rebased onto post-#1293 dev, awaiting Brady's review +- **`squad.agent.md` slimming** (#1308) — 83KB → ~40KB by extracting Routing / Team Mode / Source-of-Truth / Response Mode / Spawn / Init Mode to satellite skills. Phase 1 PR #1311 ready. +- **Tiered-memory runtime** (#1309) — the skill ships, but the runtime (hot/cold/wiki tiers, Scribe promotion, tier-aware spawn) is design-only. v0.11 candidate. +- **Machine-wide `/squad` skill** (#1310) — so `/squad init` works in any folder without a prior `npx`. v0.11 candidate. +- **Copilot CLI MCP preload** (github/copilot-cli#3787) — upstream feature request to load MCP tools into the initial function list instead of lazy-loading. Our handshake works around it today. + +## The Eleven PRs + +| PR | Title | Touches | +|----|-------|---------| +| [#1292](https://github.com/bradygaster/squad/pull/1292) | Bundle missing skills + manifest drift guard | manifest, templates | +| [#1293](https://github.com/bradygaster/squad/pull/1293) | Preset apply wires team / routing / casting | presets/scaffold.ts | +| [#1295](https://github.com/bradygaster/squad/pull/1295) | Cross-squad-communication skill + CLI correctness | skills, CLI invocations | +| [#1298](https://github.com/bradygaster/squad/pull/1298) | Stop polluting `~/.copilot/mcp-config.json` per project | init.ts, mcp-root.ts | +| [#1300](https://github.com/bradygaster/squad/pull/1300) | Always-on Fact-Checker roster scaffolding | scaffoldAlwaysOnAgents | +| [#1301](https://github.com/bradygaster/squad/pull/1301) | Fact-Checker rich charter + state dir | templates | +| [#1302](https://github.com/bradygaster/squad/pull/1302) | `squad-help` skill | templates | +| [#1303](https://github.com/bradygaster/squad/pull/1303) | `/squad` slash command surface | skills | +| [#1304](https://github.com/bradygaster/squad/pull/1304) | Install skills to `.github/skills/` + migrate | upgrade.ts | +| [#1306](https://github.com/bradygaster/squad/pull/1306) | MCP handshake + HARD RULE on state-backend probe | squad.agent.md, state-mcp.ts | +| [#1307](https://github.com/bradygaster/squad/pull/1307) | "Spawn a squad" routing + ask_user + anti-patterns | squad.agent.md | + +## Try It + +```bash +npx -y @bradygaster/squad-cli@insider --version +# 0.10.0-insider.1 +``` + +If you hit anything that doesn't work the way the docs say it should, file it on [bradygaster/squad](https://github.com/bradygaster/squad/issues). The fastest way to get a fix is a real repro on a fresh `squad init`. diff --git a/docs/src/content/docs/concepts/memory-and-knowledge.md b/docs/src/content/docs/concepts/memory-and-knowledge.md index e0cad2ff8..f5fda72c6 100644 --- a/docs/src/content/docs/concepts/memory-and-knowledge.md +++ b/docs/src/content/docs/concepts/memory-and-knowledge.md @@ -29,7 +29,7 @@ Memory lives in three layers, each serving a different purpose: ```mermaid graph TD - A["Skills Layer
.squad/skills/{name}/SKILL.md
Reusable patterns • Portable
How to set up CI with GitHub Actions"] + A["Skills Layer
.copilot/skills/{name}/SKILL.md
Reusable patterns • Portable
How to set up CI with GitHub Actions"] B["Shared Decisions Layer
.squad/decisions.md
Team-wide rules • Every agent reads
Use PostgreSQL • No Friday deploys"] C["Personal History Layer
.squad/agents/{name}/history.md
Per-agent memory • Only reads own
Auth uses JWT • Config in src/config/"] @@ -173,7 +173,7 @@ Directives are context-aware guidelines, not hard constraints. If an agent viola ## Skills -Skills are reusable knowledge files that live at `.squad/skills/{skill-name}/SKILL.md`. Unlike decisions (project policies like "use PostgreSQL"), skills are transferable techniques ("how to set up CI with GitHub Actions"). +Skills are reusable knowledge files that live at `.copilot/skills/{skill-name}/SKILL.md`. Unlike decisions (project policies like "use PostgreSQL"), skills are transferable techniques ("how to set up CI with GitHub Actions"). **All agents can read any skill.** Skills are team-wide knowledge, not per-agent. @@ -227,7 +227,7 @@ Not all knowledge in `.squad/` lasts forever. When files grow large, Squad compa Knowledge that needs to survive compaction belongs in **skills**. Reusable patterns, code conventions, and technical techniques live here because they grow without limits. Team rules and preferences go in directives (stored in `decisions.md`) — they persist through compaction cycles because directives are preserved when decisions get summarized. -> 💡 **Where to store permanent knowledge:** Put reusable patterns and techniques in `.squad/skills/`. Put team rules and preferences in directives (they persist in `decisions.md`). For org-wide knowledge that multiple teams need, use [upstream inheritance](/features/upstream-inheritance) to share a skills library. +> 💡 **Where to store permanent knowledge:** Put reusable patterns and techniques in `.copilot/skills/`. Put team rules and preferences in directives (they persist in `decisions.md`). For org-wide knowledge that multiple teams need, use [upstream inheritance](/features/upstream-inheritance) to share a skills library. --- diff --git a/docs/src/content/docs/concepts/portability.md b/docs/src/content/docs/concepts/portability.md index 457109c97..c03ce760f 100644 --- a/docs/src/content/docs/concepts/portability.md +++ b/docs/src/content/docs/concepts/portability.md @@ -64,7 +64,7 @@ squad export --out ./backups/team.json # custom path | Agent charters | ✅ | | Agent histories | ✅ (split into portable vs project-specific) | | Casting state | ✅ | -| Skills | ✅ All earned skills from `.squad/skills/` | +| Skills | ✅ All earned skills from `.copilot/skills/` | | Decisions | ✅ | Skills are fully portable — they export and import with perfect fidelity. @@ -101,7 +101,7 @@ Plugins are community-curated bundles of agent templates, skills, and best pract ### What's in a Plugin - **Agent templates** — specialized role charters (e.g., "AWS DevOps", "Python Data Science") -- **Skills** — reusable `.squad/skills/SKILL.md` files +- **Skills** — reusable `.copilot/skills/SKILL.md` files - **Instructions** — `decisions.md` snippets for conventions and routing - **Sample prompts** — ready-to-use prompts that activate plugin capabilities @@ -126,7 +126,7 @@ Or use the command: /plugin install awesome-copilot/react-component-library ``` -Squad downloads the bundle, merges agent templates into `.squad/agents/`, adds skills to `.squad/skills/`, updates `decisions.md`, and seeds agents with the new knowledge. +Squad downloads the bundle, merges agent templates into `.squad/agents/`, adds skills to `.copilot/skills/`, updates `decisions.md`, and seeds agents with the new knowledge. ### Managing Marketplaces @@ -175,7 +175,7 @@ Declare external Squad sources and automatically inherit their context at sessio ### What Gets Inherited -- **Skills** — all `.squad/skills/*/SKILL.md` files +- **Skills** — all `.copilot/skills/*/SKILL.md` files - **Decisions** — `.squad/decisions.md` - **Wisdom** — `.squad/identity/wisdom.md` - **Casting Policy** — `.squad/casting/policy.json` diff --git a/docs/src/content/docs/concepts/your-team.md b/docs/src/content/docs/concepts/your-team.md index 78b6ce188..e144b2485 100644 --- a/docs/src/content/docs/concepts/your-team.md +++ b/docs/src/content/docs/concepts/your-team.md @@ -109,7 +109,7 @@ The coordinator routes work automatically using three strategies. First match wi |----------|-------------|---------| | **Named** | You say who does it | `"Fenster, fix the login bug"` | | **Domain** | Pattern matching in `.squad/routing.md` | `src/api/**` → Backend | -| **Skill-aware** | Capability check in `.squad/skills/` | Auth expertise → Backend or Lead | +| **Skill-aware** | Capability check in `.copilot/skills/` | Auth expertise → Backend or Lead | **Routing priority:** Named > Domain > Skill-aware. If nothing matches, the Lead triages. diff --git a/docs/src/content/docs/cookbook/recipes.md b/docs/src/content/docs/cookbook/recipes.md index d1f4f13ab..5ebfc2e7c 100644 --- a/docs/src/content/docs/cookbook/recipes.md +++ b/docs/src/content/docs/cookbook/recipes.md @@ -35,7 +35,7 @@ Each agent gets its own 200K context window. Routing ensures only the right agen > "Export skills from my React project and import them into this new project" -Import one full squad, then cherry-pick skills from others. Skills are standalone markdown files — just copy them into `.squad/skills/`. Best practice: merge knowledge, don't run parallel squads. +Import one full squad, then cherry-pick skills from others. Skills are standalone markdown files — just copy them into `.copilot/skills/`. Best practice: merge knowledge, don't run parallel squads. ### Moving a Team Between Repos diff --git a/docs/src/content/docs/features/cleanup.md b/docs/src/content/docs/features/cleanup.md index a19be7234..8830bff9c 100644 --- a/docs/src/content/docs/features/cleanup.md +++ b/docs/src/content/docs/features/cleanup.md @@ -107,7 +107,7 @@ Run cleanup every round (aggressive), keep 14 days: ## What Cleanup Does NOT Touch -- Earned skills in `.squad/skills/` — never deleted +- Earned skills in `.copilot/skills/` — never deleted - Decision log in `.squad/decisions/log.md` — never deleted - Active session data - Router state, team config, and other core Squad files diff --git a/docs/src/content/docs/features/context-hygiene.md b/docs/src/content/docs/features/context-hygiene.md index bef518244..9850e863f 100644 --- a/docs/src/content/docs/features/context-hygiene.md +++ b/docs/src/content/docs/features/context-hygiene.md @@ -62,7 +62,7 @@ In the interactive shell, use `/compact` for the same effect. When you tell the team to "reskill," agents: -1. Review existing skill files in `.squad/skills/` +1. Review existing skill files in `.copilot/skills/` 2. Validate that documented patterns still apply 3. Look for new reusable patterns from recent work 4. Update skill confidence levels based on current evidence diff --git a/docs/src/content/docs/features/coordinator-as-agent-export.md b/docs/src/content/docs/features/coordinator-as-agent-export.md new file mode 100644 index 000000000..8ad8179c5 --- /dev/null +++ b/docs/src/content/docs/features/coordinator-as-agent-export.md @@ -0,0 +1,237 @@ +--- +title: Coordinator-as-Agent Export +description: Compile your squad's coordinator into a repo-native Copilot custom agent file with squad export agent. +--- + +# Coordinator-as-Agent Export + +> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases. + +**Try this to generate a coordinator agent:** +```bash +squad export agent +``` + +**Try this for CI drift detection:** +```bash +squad export agent --check +``` + +**Try this for live development:** +```bash +squad export agent --watch +``` + +`squad export agent` compiles your `.squad/` state — team roster, routing rules, ceremony triggers, agent charters — into a single repository-native Copilot custom agent at `.github/agents/squad.md`. The generated file works across **every** Copilot surface (CLI, VS Code, GitHub Desktop, github.com) without requiring the Squad runtime installed. + +This is the "ship Squad as a portable agent" path. Use it when you want collaborators or downstream repos to get the benefit of your squad's setup with **zero install** — they just check out the repo and the agent is available. + +--- + +## When to use it + +| Scenario | Use `squad export agent`? | +|----------|-----------------------| +| You want collaborators to use your team's coordinator without installing the CLI | ✅ Yes | +| You want a portable, version-controlled snapshot of your coordinator behavior | ✅ Yes | +| You want CI to enforce that `.github/agents/squad.md` stays in sync with `.squad/` | ✅ Yes — use `--check` | +| You need full Squad runtime features (Scribe, Ralph, MCP state tools, ceremonies) | ❌ No — install the CLI | +| You want to share state (decisions, history) not just coordinator behavior | ❌ No — use [`squad export`](/squad/docs/features/export-import/) (snapshot mode) | + +The exported coordinator agent has access to the team's roster and routing logic but does NOT include the live Squad runtime. Sub-agents in the exported coordinator will be dispatched via Copilot's native `task` tool, not via Squad's full spawn machinery. + +--- + +## Commands + +### `squad export agent` + +Generate or update `.github/agents/squad.md` from your current `.squad/` state: + +```bash +$ squad export agent + +🔧 Compiling coordinator agent... + - Read team.md (8 members) + - Read routing.md (24 work-type entries) + - Read ceremonies.md (3 ceremonies) + - Loaded 8 agent charters + - Compiled prompt: 12,847 tokens (under 14k soft budget — full mode) + - Wrote .github/agents/squad.md (38,294 bytes) + +✓ Coordinator exported to .github/agents/squad.md +``` + +The output is a self-contained Copilot custom-agent file with proper YAML frontmatter and a compiled coordinator prompt. Anyone in the repo can now run `copilot --agent squad` and get the coordinator's behavior. + +### `squad export agent --watch` + +Re-export on every change to `.squad/`. Useful during active team development when you want the exported agent file to track your edits: + +```bash +$ squad export agent --watch +👀 Watching .squad/ for changes... +✓ .github/agents/squad.md up to date + +[edit .squad/routing.md] +🔄 .squad/routing.md changed — re-exporting... +✓ .github/agents/squad.md updated (38,401 bytes) +``` + +Press `Ctrl+C` to stop. + +### `squad export agent --check` + +Verify that `.github/agents/squad.md` is in sync with the current `.squad/` state. Exits with non-zero if drift is detected. Use this in CI to enforce "if you change `.squad/`, you must re-run `squad export agent`": + +```bash +$ squad export agent --check + +✓ .github/agents/squad.md is up to date + +# Or, on drift: + +✗ Drift detected: + .squad/routing.md changed but .github/agents/squad.md not regenerated. + Run 'squad export agent' to update. +exit 1 +``` + +### `squad export agent --dry-run` + +Preview what would be written without actually writing the file: + +```bash +$ squad export agent --dry-run + +🔍 DRY RUN — would write to .github/agents/squad.md: + Size: 38,294 bytes + Prompt tokens: ~12,847 + Mode: full + Frontmatter: + name: squad + description: ... + No changes made. +``` + +### `squad export agent --compact` + +Force compact mode even if the prompt fits within the soft budget. Useful for keeping the generated file lean intentionally: + +```bash +squad export agent --compact +``` + +Compact mode omits some optional sections (extended examples, on-demand reference pointers) and is the default when the prompt would otherwise exceed the soft token budget. + +--- + +## Token budget modes + +The exporter adapts automatically to your team size: + +| Mode | Trigger | What's in the prompt | +|------|---------|---------------------| +| **Full** | ≤8 members AND prompt < 14k tokens | Full charters inlined, all routing tables, complete ceremony definitions | +| **Compact** | Prompt 14k–20k tokens OR `--compact` flag | Condensed charters, abbreviated examples, on-demand references | +| **Lazy-load** | >8 members OR roster > 3k tokens | Coordinator instructed to load charters on demand at dispatch time | +| **(fails)** | Prompt > 20k hard budget | Fails with diagnostics — suggests removing rarely-used members or splitting the squad | + +These thresholds protect against generating a coordinator file that's too large for the LLM's context budget. If you hit the hard limit, the exporter prints actionable diagnostics: + +``` +✗ Coordinator prompt exceeds hard budget (22,841 / 20,000 tokens). + Top contributors: + - 8 large agent charters (avg 1,800 tokens each) + - Routing table: 24 entries + + Suggestions: + - Remove rarely-spawned members from .squad/team.md + - Trim agent charter narrative sections (target: 1,500 tokens/charter) + - Split into multiple squads (see Multiple Squads docs) + - Use --compact to drop ~2k tokens +``` + +--- + +## Safety guarantees + +The exporter is conservative about what it writes: + +- **Won't overwrite user-owned agent files.** If `.github/agents/squad.md` exists and lacks the generated-file marker comment header, the export fails unless you pass `--force`. +- **Detects legacy `squad.agent.md` collisions.** The classic Squad CLI installation puts the coordinator at `.github/agents/squad.agent.md`. The export warns if both files would exist, and `squad init`/`squad upgrade` skip writing `squad.agent.md` when an exported `squad.md` is present. +- **Generated files are marked.** The output starts with `` so the file is unambiguous. +- **`--check` mode never mutates.** Safe to run on every CI build. + +--- + +## CI integration + +The pattern most teams use: + +```yaml +# .github/workflows/squad-drift-check.yml +name: Squad drift check +on: + pull_request: + paths: ['.squad/**', '.github/agents/squad.md'] +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: { node-version: '22' } + - run: npm install -g @bradygaster/squad-cli + - run: squad export agent --check +``` + +If anyone changes `.squad/` without re-exporting, the PR fails CI with a clear message. + +--- + +## What gets compiled into the exported agent + +The exporter loads these sources from your `.squad/`: + +| File | Used for | +|------|----------| +| `.squad/team.md` | Roster — agent names, roles, charter paths | +| `.squad/routing.md` | Work-type → agent and module-ownership mappings | +| `.squad/ceremonies.md` | Auto-trigger definitions for design review, retro, etc. | +| `.squad/config.json` | State backend selection, model preferences | +| `.squad/agents/{name}/charter.md` | Per-agent role definitions (inlined or referenced based on budget mode) | + +These get rendered into the output as: + +- **YAML frontmatter** — `name`, `description`, `model`, `tools` declarations +- **Coordinator prompt body** — dispatch rules, routing table, ceremony triggers, agent identities, response mode selection +- **On-demand reference markers** — pointers to charters in lazy-load mode + +What's NOT compiled in: +- Decisions ledger (use snapshot export to share state) +- Agent histories (personal learnings, owner-only) +- Skills (live as separate files in `.copilot/skills/`) +- Casting state (registry, history) + +--- + +## Architecture (for the curious) + +The export pipeline lives at `packages/squad-sdk/src/repo-native/`: + +1. **Context loader** — parses `.squad/` files into a typed IR +2. **Prompt compiler** — renders the coordinator prompt with budget enforcement +3. **Frontmatter renderer** — emits valid custom-agent YAML +4. **File writer** — handles safety checks and atomic write + +You can use the SDK module directly if you want to embed coordinator export into your own tooling — see `@bradygaster/squad-sdk/repo-native`. + +--- + +## See also + +- [Export & Import](/squad/docs/features/export-import/) — full state snapshots (different from this) +- [Self Upgrade](/squad/docs/features/self-upgrade/) — keeping the CLI itself updated +- [Multiple Squads](/squad/docs/scenarios/multiple-squads/) — when one team gets too large +- [Team Setup](/squad/docs/features/team-setup/) — composing the team that gets exported diff --git a/docs/src/content/docs/features/cross-squad-discover.md b/docs/src/content/docs/features/cross-squad-discover.md new file mode 100644 index 000000000..6912a14e0 --- /dev/null +++ b/docs/src/content/docs/features/cross-squad-discover.md @@ -0,0 +1,158 @@ +--- +title: Cross-Squad Discover & Delegate +description: Discover other squads across repository boundaries and delegate work to them via squad discover and squad delegate. +--- + +# Cross-Squad Discover & Delegate + +> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases. + +**Try this to see what squads you can reach:** +```bash +squad discover +``` + +**Try this to send work to another squad:** +```bash +squad delegate platform-squad "Add monitoring dashboard for the auth service" +``` + +When you have multiple Squad-enabled repositories — a platform squad, a frontend squad, a data squad — you often need to ask the other squad to do something for you. Cross-squad orchestration lets a squad **discover** other squads and **delegate** issues to them, with proper labels and contact info, so the right team picks it up automatically. + +Both commands work via GitHub issues — no shared infrastructure required. Each squad's manifest declares what it accepts and how to reach it. + +--- + +## How it works + +Each squad publishes a `.squad/manifest.json` file declaring: +- Its name and capabilities (e.g., `kubernetes`, `helm`, `monitoring`) +- Its GitHub repo (the contact) +- The labels to apply to cross-squad issues +- What work types it accepts (`issues`, `prs`) +- Named skills it offers + +When you run `squad discover`, Squad reads manifests from: +- **Upstream** — repos declared in your `.squad/upstreams/` +- **Registry** — any registry sources configured +- **Local** — manifests inside the current repo + +When you run `squad delegate ""`, Squad finds the target manifest, creates a properly-labeled GitHub issue in its repo, and includes structured cross-squad metadata so the other squad's coordinator picks it up correctly. + +--- + +## Commands + +### `squad discover` + +List known squads and their capabilities: + +```bash +$ squad discover + +Discovered Squads (3): + + Name Capabilities Repo Accepts + ────────────── ────────────────────────── ────────────────────── ──────── + platform-squad kubernetes, helm, infra myorg/platform issues + frontend-squad react, design-system, ui myorg/web-app issues + data-squad etl, dbt, pipelines, ml myorg/data-platform issues, prs +``` + +If no squads are discovered, the output reminds you to configure upstreams or check that other repos have published `manifest.json` files. + +### `squad delegate ""` + +Create a cross-squad work request in another squad's repository: + +```bash +squad delegate platform-squad "Add Grafana dashboards for the auth service's p95 latency" +``` + +This creates an issue in `myorg/platform` titled `[cross-squad] Add Grafana dashboards...` with: +- Squad's discovered labels applied automatically +- A structured body with the originating repo, target squad, description, and acceptance criteria +- The created issue's URL printed to your terminal + +Required: +- The target squad's manifest must include the work type — `accepts: ["issues"]` for the default delegation flow +- GitHub CLI (`gh`) installed and authenticated with permission to create issues in the target repo + +--- + +## Publishing a manifest for your own squad + +To make your squad discoverable by others, create `.squad/manifest.json`: + +```json +{ + "name": "platform-squad", + "version": "1.0.0", + "description": "Infrastructure, Kubernetes, and platform services", + "capabilities": ["kubernetes", "helm", "monitoring", "infra"], + "contact": { + "repo": "myorg/platform", + "labels": ["cross-squad", "needs-triage"] + }, + "accepts": ["issues"], + "skills": ["k8s-deployment", "helm-chart-authoring", "prometheus-alerting"] +} +``` + +| Field | Required | Notes | +|-------|----------|-------| +| `name` | ✅ | Human-readable name (e.g., `platform-squad`). Used in `squad delegate `. | +| `version` | optional | Schema version for forward compatibility | +| `description` | optional | One-line summary shown in `discover` output | +| `capabilities` | ✅ | Tags other squads use to find you (`squad discover` filters here in future versions) | +| `contact.repo` | ✅ | GitHub repo in `owner/repo` format where issues get created | +| `contact.labels` | optional | Labels applied to every cross-squad issue (so your triage automation can find them) | +| `accepts` | ✅ | Work types: `["issues"]`, `["prs"]`, or both | +| `skills` | optional | Named skills your squad offers — informational today, filterable in future | + +Commit `manifest.json` to your repo's `.squad/` directory and push. Other squads that have your repo in their upstream list will pick it up on next `squad discover`. + +--- + +## What the delegated issue looks like + +When `squad delegate` creates an issue, it uses this structured body so the receiving squad's coordinator can recognize and route it correctly: + +```markdown +## Cross-Squad Work Request + +**From:** this repository +**To:** platform-squad (myorg/platform) + +### Description + +Add Grafana dashboards for the auth service's p95 latency + +### Acceptance Criteria + +- [ ] Work completed and verified +- [ ] Originating squad notified of completion + +*Created by squad cross-squad orchestration* +``` + +The receiving squad sees an issue with their `cross-squad` (and any custom) labels, structured metadata in the body, and clear acceptance criteria. + +--- + +## Limitations in v0.10 + +- **No automatic completion notification.** When the receiving squad closes the issue, the originating squad doesn't get notified back automatically. Today you watch the issue manually or via standard GitHub notifications. +- **Discovery is upstream-driven.** A squad has to know about another squad (via an upstream declaration) before `discover` can see it. There's no global registry. +- **No capability filtering on delegate.** `squad delegate ` requires the exact squad name. You can't say "delegate this to any squad that has the `kubernetes` capability" — yet. +- **PRs aren't supported in the default flow.** Even though manifests can declare `accepts: ["prs"]`, the v0.10 `delegate` command only creates issues. + +These are tracked for follow-up. For now, cross-squad orchestration is a useful but minimal MVP — it removes the "where do I file this?" friction and ensures the right team gets a properly-formatted request. + +--- + +## See also + +- [Distributed Mesh](/squad/docs/features/distributed-mesh/) — the broader cross-repo coordination architecture +- [Multiple Squads](/squad/docs/scenarios/multiple-squads/) — running several squads in one organization +- [Upstream Inheritance](/squad/docs/features/upstream-inheritance/) — how upstreams get discovered diff --git a/docs/src/content/docs/features/dual-mode-deployment.md b/docs/src/content/docs/features/dual-mode-deployment.md new file mode 100644 index 000000000..ca31f2752 --- /dev/null +++ b/docs/src/content/docs/features/dual-mode-deployment.md @@ -0,0 +1,132 @@ +--- +title: Dual-Mode Deployment — Pod-Aware Capabilities +description: Run Squad in either agent-per-node or squad-per-pod deployment modes with pod-specific machine capability manifests, controlled by SQUAD_POD_ID and SQUAD_DEPLOYMENT_MODE env vars. +--- + +# Dual-Mode Deployment — Pod-Aware Capabilities + +> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases. + +Dual-mode deployment extends [Capability Routing](/squad/docs/features/capability-routing/) to support both classic single-machine setups and modern containerized/Kubernetes deployments where multiple Squad pods may share an organization's workload — each with potentially different machine capabilities. + +It introduces two environment variables and a pod-specific manifest lookup pattern so the same Squad config can run identically in either deployment shape. + +--- + +## The two deployment modes + +| Mode | What it means | Capability manifest | +|------|---------------|---------------------| +| **`agent-per-node`** (default) | One Squad instance per machine; the machine's capabilities are the squad's capabilities | `.squad/machine-capabilities.json` (shared) | +| **`squad-per-pod`** | Multiple Squad pods may run on different machines/containers, each with potentially different capabilities | `.squad/machine-capabilities-{podId}.json` (pod-specific) with fallback chain | + +Choose the mode via the `SQUAD_DEPLOYMENT_MODE` environment variable: + +```bash +# Classic single-machine setup (default) +export SQUAD_DEPLOYMENT_MODE=agent-per-node + +# Kubernetes / multi-pod setup +export SQUAD_DEPLOYMENT_MODE=squad-per-pod +export SQUAD_POD_ID=worker-1 +``` + +If neither is set, the SDK defaults to `agent-per-node` for backward compatibility. + +--- + +## Environment variables + +### `SQUAD_DEPLOYMENT_MODE` + +| Value | Behavior | +|-------|----------| +| `agent-per-node` | Single shared `machine-capabilities.json` | +| `squad-per-pod` | Pod-specific manifests with fallback chain | +| (unset) | Same as `agent-per-node` | + +### `SQUAD_POD_ID` + +Pod identifier used to construct the pod-specific manifest path. Required when `SQUAD_DEPLOYMENT_MODE=squad-per-pod`; ignored otherwise. + +```bash +SQUAD_POD_ID=worker-1 # → .squad/machine-capabilities-worker-1.json +SQUAD_POD_ID=gpu-pool-node-3 # → .squad/machine-capabilities-gpu-pool-node-3.json +``` + +--- + +## The fallback chain (squad-per-pod mode) + +When `SQUAD_DEPLOYMENT_MODE=squad-per-pod` AND `SQUAD_POD_ID` is set, the SDK looks up capabilities in this order: + +1. **`.squad/machine-capabilities-{podId}.json`** — pod-specific (highest priority) +2. **`.squad/machine-capabilities.json`** — shared fallback for capabilities that apply to all pods +3. **`~/.squad/machine-capabilities.json`** — user-home fallback (rarely useful in container deployments) +4. **`null`** — opt-out; capability routing falls back to label-only routing + +The first manifest that exists is loaded; the search stops there (no merging). If you need different pods to see different capability sets, give each its own pod-specific file. If you need a shared baseline plus pod-specific additions, merge at the deployment-config level (Helm, Kustomize, etc.) — the SDK doesn't merge automatically. + +--- + +## SDK programmatic access + +The new exports from `@bradygaster/squad-sdk/ralph/capabilities`: + +```typescript +import { + getDeploymentMode, + getPodId, + type DeploymentMode, +} from '@bradygaster/squad-sdk/ralph/capabilities'; + +const mode: DeploymentMode = getDeploymentMode(); // 'agent-per-node' | 'squad-per-pod' +const podId: string | undefined = getPodId(); // e.g. 'worker-1', or undefined +``` + +These are pure env-var readers. They don't cache or memoize — each call reads `process.env` directly so changes between reads are visible. + +--- + +## Typical Kubernetes deployment shape + +In a KEDA-scaled deployment (see [KEDA Scaling](/squad/docs/features/keda-scaling/)), each scaled pod gets a unique `SQUAD_POD_ID` from the pod's name or hash: + +```yaml +# Deployment env block +env: + - name: SQUAD_DEPLOYMENT_MODE + value: squad-per-pod + - name: SQUAD_POD_ID + valueFrom: + fieldRef: + fieldPath: metadata.name +``` + +The pod's mounted volume contains per-pod manifests baked in by the image build or pulled from a ConfigMap, e.g.: + +``` +/app/.squad/ +├── machine-capabilities.json # shared baseline (CPU, memory) +├── machine-capabilities-gpu-pool-node-1.json # extends baseline with GPU +├── machine-capabilities-gpu-pool-node-2.json # same shape +└── machine-capabilities-cpu-pool-node-1.json # no GPU declaration +``` + +Pods scheduled onto GPU nodes load a manifest declaring GPU capability; pods on CPU-only nodes get a manifest without GPU. Ralph's issue dispatcher routes `needs:gpu`-labeled work only to pods with the GPU capability. + +--- + +## Limitations + +- **No automatic pod discovery.** The SDK reads env vars to know who it is; it doesn't enumerate sibling pods or coordinate work distribution. That's the deployment orchestrator's job (KEDA, scheduler). +- **No central capability registry.** Pods don't publish their capabilities back to anything; each pod evaluates issues against its own loaded manifest independently. If you need a central view, your orchestrator must aggregate. +- **Manifest changes require redeploy or restart.** The fallback lookup happens on capability resolution; manifest content is read from disk each time but the manifest *path* is decided by env vars set at process start. + +--- + +## See also + +- [Capability Routing](/squad/docs/features/capability-routing/) — the broader machine-capability system +- [KEDA Scaling](/squad/docs/features/keda-scaling/) — autoscaling Squad pods on demand +- [Labels](/squad/docs/features/labels/) — `needs:*` label conventions used for capability matching diff --git a/docs/src/content/docs/features/error-recovery.md b/docs/src/content/docs/features/error-recovery.md new file mode 100644 index 000000000..e9ebea5ce --- /dev/null +++ b/docs/src/content/docs/features/error-recovery.md @@ -0,0 +1,95 @@ +--- +title: Error Recovery — Standard Failure Patterns +description: Built-in skill teaching agents to adapt when things fail — retry with backoff, fallback alternatives, diagnose-and-fix, and escalation patterns. +--- + +# Error Recovery — Standard Failure Patterns + +> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases. + +The `error-recovery` skill teaches every squad agent to **adapt** when something fails, not just report the failure. It ships as a built-in skill at `.copilot/skills/error-recovery/SKILL.md` and is available to every spawned agent. + +Without this skill, agents tend to encounter a failure (CI test red, API timeout, missing dependency) and stop. With it, they apply standard patterns to diagnose, retry, or escalate the right way. + +--- + +## The five recovery patterns + +### 1. Retry with Backoff + +**When:** Transient failures — API timeouts, rate limits, network errors, temporary service unavailability. + +**Pattern:** +1. Wait briefly, then retry (start at 2s, double each attempt) +2. Maximum 3 retries before escalating +3. Log each attempt with the error received + +**Example:** API call returns `429 Too Many Requests` → wait 2s → retry → wait 4s → retry → wait 8s → retry → escalate if still failing. + +### 2. Fallback Alternatives + +**When:** Primary tool or approach fails and an alternative exists. + +**Pattern:** +1. Attempt primary approach +2. On failure, identify alternative tool/method +3. Try the alternative with the same intent +4. Document which alternative was used and why + +**Example:** Primary CLI tool fails → fall back to direct API call for the same operation. Or: `gh pr comment` rate-limited → fall back to `gh api -X POST .../issues/{n}/comments`. + +### 3. Diagnose-and-Fix + +**When:** Build failures, test failures, linting errors — structured errors with actionable output. + +**Pattern:** +1. Read the full error output carefully (not just the last line) +2. Identify the root cause from error messages +3. Attempt a targeted fix +4. Re-run to verify the fix +5. If 3 fix attempts fail, escalate with a diagnostic summary + +**Example:** TypeScript build fails with `Cannot find module '@x/y'` → check `package.json`, run `npm install`, re-run build. + +### 4. Reframe-and-Retry + +**When:** The approach itself is wrong (not just the implementation). User feedback like *"that won't work because..."* or *"try a different way"*. + +**Pattern:** +1. Stop the current approach immediately +2. Re-read the original task description +3. Identify what assumption was wrong +4. Propose 2 alternative approaches before picking one +5. Get user confirmation if the cost of being wrong again is high + +### 5. Escalation + +**When:** Three attempts have failed, OR the failure is outside the agent's domain, OR fixing it would violate a team decision. + +**Pattern:** +1. Stop attempting fixes +2. Summarize: what was tried, what failed, what's known +3. Surface to coordinator with a clear ask (*"need lead's call on architecture"* vs. *"need human approval"* vs. *"need access to X system"*) +4. Document the escalation in `decisions/inbox/` if it's a recurring pattern + +--- + +## When NOT to apply these patterns + +- **Don't retry on user-input errors.** If the user typed `gh repo create my-typo`, don't retry with `my-typoo`. Surface and ask. +- **Don't fall back silently on security-sensitive operations.** If `git push origin main` fails because of branch protection, do NOT fall back to `--force`. +- **Don't escalate without context.** *"It failed"* isn't an escalation; *"three attempts, each with `EACCES`, suggests user lacks write to `.squad/`, recommend chmod or different storage path"* is. + +--- + +## Integration with Reviewer Rejection Protocol + +When the failure is a Reviewer rejection (a Reviewer agent rejects an artifact), the [Reviewer Rejection Protocol](/squad/docs/features/reviewer-protocol/) takes precedence. The original author is locked out and a different agent must own the revision. Error-recovery patterns apply within that constraint — the revision agent can use retry/fallback/diagnose patterns freely. + +--- + +## See also + +- [Reflect](/squad/docs/features/reflect/) — learning from corrections +- [Reviewer Protocol](/squad/docs/features/reviewer-protocol/) — when a Reviewer rejects work +- [Skills](/squad/docs/features/skills/) — how built-in skills work diff --git a/docs/src/content/docs/features/export-import.md b/docs/src/content/docs/features/export-import.md index 7e739941f..789cdf316 100644 --- a/docs/src/content/docs/features/export-import.md +++ b/docs/src/content/docs/features/export-import.md @@ -31,6 +31,28 @@ Creates `squad-export.json` in the current directory — a portable snapshot of squad export --out ./backups/my-team.json ``` +### Push directly to a GitHub repository + +Instead of writing to a local file, you can push the export straight to a GitHub repo via the GitHub Contents API. This is the easiest way to back up your team to a private repo or share it with collaborators without sending a file. + +```bash +# Export to a GitHub repo (uses default branch) +squad export --repo myorg/squad-backups + +# Export to a specific branch +squad export --repo myorg/squad-backups --branch nightly +``` + +Requirements: +- GitHub CLI (`gh`) installed and authenticated with permission to push to the target repo +- The repo must exist (the export does NOT create it) + +The export lands at the repo root as `squad-export.json` by default. Combine with `--out` to control the filename inside the repo: + +```bash +squad export --repo myorg/squad-backups --out my-team-2026-06-11.json +``` + ### What's included | Data | Included | @@ -41,7 +63,7 @@ squad export --out ./backups/my-team.json | **Skills** | ✅ **All earned skills export with the team** | | Decisions | ✅ | -> **Skills are portable**: When you export a team, all earned skills from `.squad/skills/` are included in the JSON manifest. After importing, skills are immediately available to all agents — no loss of knowledge. +> **Skills are portable**: When you export a team, all earned skills from `.copilot/skills/` are included in the JSON manifest. After importing, skills are immediately available to all agents — no loss of knowledge. --- @@ -53,6 +75,25 @@ squad import squad-export.json Imports the snapshot into the current repo's `.squad/` directory. +### Pull directly from a GitHub repository + +You can import a snapshot directly from a GitHub repo without downloading the file first: + +```bash +# Import from default branch of a repo +squad import --repo myorg/squad-backups + +# Import a specific filename or branch +squad import --repo myorg/squad-backups --branch nightly +squad import --repo myorg/squad-backups --out my-team-2026-06-11.json +``` + +Requirements: +- GitHub CLI (`gh`) installed and authenticated with read access to the source repo +- The export file must exist at the named path in the repo (default: `squad-export.json` at repo root) + +Use `--force` together with `--repo` for the same archive-then-replace behavior as the file-based import. + ### Collision detection If `.squad/` already exists, Squad warns you and stops. To archive the existing team and replace it: diff --git a/docs/src/content/docs/features/external-state.md b/docs/src/content/docs/features/external-state.md index 3a2f592a6..925b0e9a4 100644 --- a/docs/src/content/docs/features/external-state.md +++ b/docs/src/content/docs/features/external-state.md @@ -54,7 +54,7 @@ If you commit `.squad/` to preserve it, every branch includes squad state in PRs ```diff + .squad/decisions/log.md -+ .squad/skills/ci-setup/SKILL.md ++ .copilot/skills/ci-setup/SKILL.md + .squad/team.md ``` diff --git a/docs/src/content/docs/features/fleet-dispatch.md b/docs/src/content/docs/features/fleet-dispatch.md new file mode 100644 index 000000000..cd197d75d --- /dev/null +++ b/docs/src/content/docs/features/fleet-dispatch.md @@ -0,0 +1,132 @@ +--- +title: Fleet Dispatch — Parallel Issue Triage +description: Hybrid dispatch mode for squad watch that batches read-heavy issues into a single Copilot /fleet session for 2.9x faster parallel analysis. +--- + +# Fleet Dispatch — Parallel Issue Triage + +> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases. + +**Try this for parallel read-heavy issue triage:** +```bash +squad watch --execute --dispatch-mode fleet +``` + +**Try this for mixed read + write workloads:** +```bash +squad watch --execute --dispatch-mode hybrid +``` + +Fleet Dispatch enables `squad watch --execute` to batch **read-heavy issues** (research, review, audit, triage) into a single Copilot CLI `/fleet` session that analyzes them in parallel tracks. The published measurement: **2.9× faster** than sequential dispatch for read-heavy workloads. + +It's a `WatchCapability` that runs in the `post-execute` phase of the watch loop, so it composes with the existing per-issue dispatch logic rather than replacing it. + +--- + +## Three dispatch modes + +| Mode | What gets parallelized | Best for | +|------|------------------------|----------| +| **`sequential`** (default) | One issue at a time, full agent spawn each | Mixed workloads, debugging | +| **`fleet`** | All issues batched into one `/fleet` Copilot session, parallel analysis tracks | Pure triage/review rounds where all issues are read-only | +| **`hybrid`** | Read-heavy issues go to fleet; write-heavy issues go sequential | Realistic backlogs with both kinds | + +`hybrid` is the recommended mode for most teams — it gets the speedup on the analysis-heavy issues without trying to fleet-dispatch issues that need to write code or modify state. + +--- + +## What counts as "read-heavy" + +The fleet-dispatch capability classifies issues using the same `classifyIssue` logic used elsewhere in `squad watch`. Read-heavy classification is based on labels and title keywords: + +- **Labels:** `triage`, `review`, `audit`, `analyze`, `research`, `investigate`, `discuss`, `question` +- **Title keywords:** *"review"*, *"audit"*, *"analyze"*, *"investigate"*, *"why does"*, *"how does"* + +Anything that touches code, files, or external systems is **write-heavy** and stays in sequential dispatch — even in `hybrid` mode. + +--- + +## How a fleet round works + +When `squad watch` decides to dispatch (work items present, no rate-limit hold), and `dispatchMode` is `fleet` or `hybrid`: + +1. Watch's executor calls `findExecutableIssues` to get the work batch +2. FleetDispatch capability runs in `post-execute` phase +3. Read-heavy issues are filtered out of the sequential queue +4. A multi-track `/fleet` prompt is built — one track per issue +5. Each track names the appropriate agent (from the roster + labels) and instructs it to: + - Read the issue body + - Analyze, assess urgency (P0/P1/P2) + - Recommend next step + - Write findings as an issue comment + - **NOT** create branches or modify files +6. The prompt is sent as a single `copilot --fleet` invocation +7. Copilot runs all tracks in parallel, posts comments per issue, exits +8. Watch logs the fleet dispatch result and continues to its next round + +A typical fleet prompt looks like: + +``` +/fleet Execute these 6 read-only analysis tracks in parallel: + +Track 1 (PAO): Issue #421: Triage user-reported bug in login flow + Read the issue body. Analyze, assess urgency (P0/P1/P2), recommend next step. + Write findings as an issue comment. + Do NOT create branches or modify files. + +Track 2 (FIDO): Issue #428: Review PR #427's test coverage + ... + +Rules: All tracks READ-ONLY. Write findings as issue comments. Run in parallel. +``` + +--- + +## Measurement methodology + +The 2.9× speedup citation comes from comparing 6 read-heavy issues: + +- Sequential mode: 6 separate `copilot --agent {role}` invocations → ~18 minutes total (each ~3 min for cold-start + analysis) +- Fleet mode: 1 `copilot --fleet` invocation with 6 tracks → ~6 minutes total (one cold-start, parallel analysis tracks) + +Speedup is dominated by avoiding 5 cold-starts. It does NOT extend to write-heavy issues because Copilot's `/fleet` doesn't currently support parallel write operations safely (commits would conflict). + +--- + +## Configuration + +Set the dispatch mode in `.squad/watch-config.json`: + +```json +{ + "execute": true, + "dispatchMode": "hybrid", + "interval": 300, + "copilotFlags": "--allow-all-tools --no-color" +} +``` + +Or via CLI flag (overrides config): + +```bash +squad watch --execute --dispatch-mode fleet +squad watch --execute --dispatch-mode hybrid +squad watch --execute --dispatch-mode sequential +``` + +--- + +## Limitations + +- **Read-only only.** Fleet tracks must not modify files or create branches. The capability builds prompts that explicitly forbid this; if your team needs parallel write workflows, sequential dispatch remains the safer choice. +- **One track per issue.** No batching of multiple issues into one track — each issue gets its own analysis context. +- **Track count limit.** Copilot CLI `/fleet` has its own track-count ceiling. For backlogs with >10 read-heavy issues per round, the capability splits across multiple fleet calls. +- **Classification is conservative.** If `classifyIssue` is unsure, it defaults to write-heavy (sequential). Better to lose the speedup than to fleet-dispatch a write-heavy issue accidentally. + +--- + +## See also + +- [Ralph](/squad/docs/features/ralph/) — the watch loop's broader behavior +- [Capability Routing](/squad/docs/features/capability-routing/) — how watch matches work to agents +- [Rate Limiting](/squad/docs/features/rate-limiting/) — cooperative rate limiting (composes with fleet dispatch) diff --git a/docs/src/content/docs/features/mcp-frontmatter.md b/docs/src/content/docs/features/mcp-frontmatter.md new file mode 100644 index 000000000..ae2c41122 --- /dev/null +++ b/docs/src/content/docs/features/mcp-frontmatter.md @@ -0,0 +1,102 @@ +--- +title: MCP Frontmatter — squad init --mcp-frontmatter +description: Write MCP server configuration directly into the Squad agent file's frontmatter instead of .copilot/mcp-config.json, for harnesses that read agent-level MCP config. +--- + +# MCP Frontmatter — `squad init --mcp-frontmatter` + +> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases. + +**Try this when your agent harness reads frontmatter-level MCP config:** +```bash +squad init --mcp-frontmatter +``` + +By default, `squad init` writes MCP server configuration to two places: +- `.copilot/mcp-config.json` (workspace-level for Copilot CLI) +- `~/.copilot/mcp-config.json` (user-level, ensures `copilot -p` non-interactive mode also sees the MCP — see [#1247](https://github.com/bradygaster/squad/issues/1247)) + +The `--mcp-frontmatter` flag changes this: instead of writing JSON config files, MCP server declarations go directly into the YAML frontmatter of `.github/agents/squad.agent.md` (or `.github/agents/squad.md` if you've exported with [Coordinator-as-Agent Export](/squad/docs/features/coordinator-as-agent-export/)). + +--- + +## When to use it + +| Your setup | Use `--mcp-frontmatter`? | +|------------|-------------------------| +| Standard Copilot CLI users | ❌ No — default config files work fine | +| Custom agent harness that reads MCP from agent frontmatter | ✅ Yes | +| Building or distributing a Squad agent as a self-contained file | ✅ Yes — keeps MCP config inline with the agent | +| Some VS Code extensions / custom IDE plugins that prefer per-agent MCP declarations | ✅ Yes | + +If you're not sure, you don't need this flag. It's specifically for environments where the agent file itself is the source of truth for MCP configuration. + +--- + +## What the output looks like + +Without `--mcp-frontmatter` (default), the agent file frontmatter is: + +```yaml +--- +name: squad +description: Squad coordinator +model: claude-opus-4.5 +tools: ["*"] +--- +``` + +And `.copilot/mcp-config.json` separately contains: + +```json +{ + "mcpServers": { + "squad_state": { + "command": "npx", + "args": ["-y", "@bradygaster/squad-cli@latest", "state-mcp"], + "tools": ["*"] + } + } +} +``` + +With `--mcp-frontmatter`, the MCP server moves into the frontmatter: + +```yaml +--- +name: squad +description: Squad coordinator +model: claude-opus-4.5 +tools: ["*"] +mcpServers: + squad_state: + command: npx + args: ["-y", "@bradygaster/squad-cli@latest", "state-mcp"] + tools: ["*"] +--- +``` + +And the standalone `.copilot/mcp-config.json` is not written (or contains only non-squad servers). + +--- + +## Effect on `squad upgrade` + +`squad upgrade` detects which mode the project is using (looks for the `mcpServers` key in agent frontmatter vs. presence of `.copilot/mcp-config.json`) and preserves the choice. You don't need to re-pass `--mcp-frontmatter` on every upgrade. + +To switch modes after init, re-run `squad init --mcp-frontmatter` (or run `squad init` without the flag to switch back). The previous MCP config is migrated. + +--- + +## Limitations + +- **Less robust for `copilot -p` non-interactive mode.** Standard mode pins MCP at user level too, which solves the workspace-only loading gap (PR [#1251](https://github.com/bradygaster/squad/pull/1251)). Frontmatter mode skips that user-level write — so `copilot -p` may not see the squad MCP unless the harness reads frontmatter directly. +- **No second-layer fallback.** If the harness that reads frontmatter MCP fails to load it correctly, there's no `.copilot/mcp-config.json` to fall back to. Test in your specific harness before adopting. +- **Schema is harness-specific.** The frontmatter `mcpServers` key follows the Copilot CLI convention, but other harnesses may expect different key names (`mcp_servers`, `mcp.servers`, etc.). Check your harness's spec. + +--- + +## See also + +- [MCP Integration](/squad/docs/features/mcp/) — the broader MCP system +- [Coordinator-as-Agent Export](/squad/docs/features/coordinator-as-agent-export/) — bundling MCP config into a self-contained agent file diff --git a/docs/src/content/docs/features/memory.md b/docs/src/content/docs/features/memory.md index 98dfc9cb8..f84d14e05 100644 --- a/docs/src/content/docs/features/memory.md +++ b/docs/src/content/docs/features/memory.md @@ -169,7 +169,7 @@ Active decisions (ongoing policies, user preferences, current architecture) stay ## Skills -Reusable knowledge files at `.squad/skills/{skill-name}/SKILL.md`. See [Skills System](skills.md) for details. +Reusable knowledge files at `.copilot/skills/{skill-name}/SKILL.md`. See [Skills System](skills.md) for details. Skills differ from decisions — decisions are project policies ("use PostgreSQL"), while skills are transferable techniques ("how to set up CI with GitHub Actions"). diff --git a/docs/src/content/docs/features/notifications.md b/docs/src/content/docs/features/notifications.md index f07fd20f5..d3044e8d2 100644 --- a/docs/src/content/docs/features/notifications.md +++ b/docs/src/content/docs/features/notifications.md @@ -359,7 +359,7 @@ Use the `NOTIFY_*` environment variables (see Configuration above) to disable no ## Architecture Notes -The `human-notification` skill lives in `.squad/skills/squad-human-notification/SKILL.md`. Agents read it before working and decide whether to ping you. You can edit the skill directly if you want to: +The `human-notification` skill lives in `.copilot/skills/squad-human-notification/SKILL.md`. Agents read it before working and decide whether to ping you. You can edit the skill directly if you want to: - Add custom notification logic for your team - Change when agents decide to ping (e.g., always notify on errors) diff --git a/docs/src/content/docs/features/preset.md b/docs/src/content/docs/features/preset.md new file mode 100644 index 000000000..e39d7d3c8 --- /dev/null +++ b/docs/src/content/docs/features/preset.md @@ -0,0 +1,205 @@ +--- +title: Presets — Curated Agent Collections +description: Save, share, and apply pre-configured agent rosters across projects with squad preset commands. +--- + +# Presets — Curated Agent Collections + +> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases. + +**Try this to see available presets:** +```bash +squad preset list +``` + +**Try this to apply a preset to a new project:** +```bash +squad init --preset backend-team +``` + +Presets are reusable, named bundles of agent charters you can apply to any squad. Built-in presets ship with Squad; you can save your own from any project's current agents and (optionally) sync them across machines via a private GitHub repo. + +Each preset is a directory at `~/.squad/presets//` containing a `preset.json` manifest and `agents/` charter files. The preset name is the directory name. + +--- + +## What presets capture + +Presets capture **agents only** (charters). For full squad snapshots including casting state, skills, routing rules, and decisions — for example to share a configured squad or publish to an agent toolbox — use [`squad export`](/squad/docs/features/export-import/) instead. + +| Captured by preset | NOT captured by preset | +|---|---| +| Agent charters (role, expertise, prompt style) | Casting state (registry, history) | +| Agent count + composition | `routing.md` rules | +| Built-in agent names + descriptions | `decisions.md` ledger | +| | Skills (`.copilot/skills/`) | +| | Ceremonies | +| | Memory (`.squad/memory/`) | + +This split is intentional. Presets are about the **shape** of a team. Skills, decisions, and history are about the **work** that team did. + +--- + +## Commands + +### `squad preset list` + +Show every preset available in your squad home (`~/.squad/presets/`): + +```bash +$ squad preset list + +Available Presets (3): + + Name Agents Description + ─────────────── ────── ──────────────────────────────────────── + backend-team 4 Backend-focused squad: lead, API, DB, QA + full-stack 6 Full-stack web app: lead, FE, BE, design, QA, devops + data-engineering 4 Data pipeline squad: lead, ETL, ML, QA +``` + +If no presets directory exists yet, you'll be prompted to run `squad preset init`. + +### `squad preset show ` + +Inspect a preset's manifest and agents before applying: + +```bash +$ squad preset show backend-team + +backend-team v1.0.0 + Backend-focused squad: lead, API, DB, QA + Author: bradygaster + Tags: backend, api + + Agents (4): + • lead (Lead) — owns architecture, scope, and code review + • api (Backend Dev) — REST endpoints, validation, error handling + • db (Database Engineer) — schema, migrations, query optimization + • qa (Tester) — test coverage, edge cases, CI/CD +``` + +### `squad preset apply [--force]` + +Install the preset's agents into the current squad. The current directory must be a Squad project (have a `.squad/` directory). + +```bash +squad preset apply backend-team +``` + +This copies the preset's `agents/` directory into your project's `.squad/agents/`. Existing agents with the same names are **NOT** overwritten unless you pass `--force`. + +### `squad preset save [--force] [--description "..."]` + +Save your current project's agents as a new preset: + +```bash +squad preset save my-team --description "My favorite roster for greenfield projects" +``` + +This snapshots the agents from the current squad's `.squad/agents/` directory into `~/.squad/presets/my-team/` along with a `preset.json` manifest. Pass `--force` to overwrite an existing preset of the same name. + +### `squad preset init [--remote]` + +Initialize the presets directory in your squad home (`~/.squad/presets/`). + +- **Local-only** (`squad preset init`) — creates the directory and seeds the built-in presets +- **Remote-synced** (`squad preset init --remote`) — creates the directory, seeds built-ins, AND creates a private GitHub repo (`{your-gh-user}/squad-home`) backing the directory so presets sync across machines + +Requirements for `--remote`: +- GitHub CLI (`gh`) installed and authenticated (`gh auth login`) +- Permission to create private repos + +On a second machine, run `squad preset init --remote` again and it will detect and clone your existing `squad-home` repo automatically. + +--- + +## Applying a preset at init time + +The most common usage — bootstrap a new project with a preset team: + +```bash +mkdir my-new-project +cd my-new-project +git init +squad init --preset backend-team +``` + +This creates the standard `.squad/` scaffold AND applies the preset's agents. If `~/.squad/presets/` doesn't exist yet, `squad init` auto-runs `squad preset init` first to seed the built-in presets. + +--- + +## Cross-machine workflow + +Use the remote-backed setup if you want presets to follow you to new machines or shared dev environments: + +```bash +# Machine A — initial setup +squad preset init --remote +squad preset save my-team --description "My standard team" + +# Machine B — same user, same GitHub account +squad preset init --remote # detects existing squad-home repo, clones it +squad preset list # my-team is already here +``` + +The remote repo lives at `https://github.com//squad-home` (private by default). + +--- + +## Sharing presets between users + +Today, the simplest path to share a preset with someone else is: + +1. They run `squad preset init --remote` to set up their own squad home +2. You manually copy the preset directory across (or clone yours, copy the directory in, push) + +A formal "publish/install from another user's repo" flow is on the roadmap but not in v0.10. + +For collaborative team rosters that go beyond just agents (skills, decisions, routing), use [`squad export`](/squad/docs/features/export-import/) instead. + +--- + +## What's in a preset directory + +``` +~/.squad/presets/backend-team/ +├── preset.json # manifest (name, version, description, tags, agents list) +└── agents/ + ├── lead/ + │ └── charter.md + ├── api/ + │ └── charter.md + ├── db/ + │ └── charter.md + └── qa/ + └── charter.md +``` + +The `preset.json` manifest format: + +```json +{ + "name": "backend-team", + "version": "1.0.0", + "description": "Backend-focused squad: lead, API, DB, QA", + "author": "bradygaster", + "tags": ["backend", "api"], + "agents": [ + { "name": "lead", "role": "Lead", "description": "owns architecture, scope, and code review" }, + { "name": "api", "role": "Backend Dev", "description": "REST endpoints, validation, error handling" }, + { "name": "db", "role": "Database Engineer", "description": "schema, migrations, query optimization" }, + { "name": "qa", "role": "Tester", "description": "test coverage, edge cases, CI/CD" } + ] +} +``` + +You can hand-edit this file to refine descriptions or add/remove agents — but the corresponding `agents//charter.md` files must match. + +--- + +## See also + +- [Export & Import](/squad/docs/features/export-import/) — full squad snapshots including state +- [Skills](/squad/docs/features/skills/) — earned knowledge (separate from presets) +- [Plugin Marketplace](/squad/docs/features/plugins/) — community-curated bundles diff --git a/docs/src/content/docs/features/reflect.md b/docs/src/content/docs/features/reflect.md new file mode 100644 index 000000000..a23c2e9bc --- /dev/null +++ b/docs/src/content/docs/features/reflect.md @@ -0,0 +1,91 @@ +--- +title: Reflect — In-Session Learning Capture +description: Built-in skill that extracts HIGH/MED/LOW confidence patterns from conversations to prevent repeating mistakes and reinforce successful patterns. +--- + +# Reflect — In-Session Learning Capture + +> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases. + +The `reflect` skill is a built-in capability that turns every user correction into a learning opportunity. Agents invoke `reflect` after critical conversation moments — corrections, praise, edge-case discoveries — to capture patterns that prevent repeating mistakes across sessions. + +It ships at `.copilot/skills/reflect/SKILL.md` and is automatically available to every spawned agent. The skill complements the existing knowledge layers (`history.md`, `decisions.md`) by capturing **in-flight** learnings that may later graduate to permanent memory. + +--- + +## How it fits the memory architecture + +Squad has three layers for what agents know: + +| Layer | Lifetime | Audience | Reflect's relationship | +|-------|----------|----------|------------------------| +| `.squad/agents/{name}/history.md` | Permanent | Owner agent + Scribe-propagated cross-updates | Reflect captures candidates; HIGH-confidence ones graduate here | +| `.squad/decisions.md` | Permanent | All agents | Reflect surfaces candidates; lead promotes after review | +| `reflect` skill | In-flight | Captured during the active session | Working memory for patterns not yet ready to commit | + +Workflow: +1. During the session, agents invoke `reflect` to capture learnings +2. At session end, the agent or Scribe reviews captured learnings +3. HIGH-confidence patterns → lead reviews for `decisions.md` promotion +4. Agent-specific patterns → `{agent}/history.md` append + +--- + +## Triggers — when to invoke reflect + +### 🔴 HIGH Priority (invoke immediately) + +| Trigger | Example phrase | Why critical | +|---------|---------------|--------------| +| User correction | *"no"*, *"wrong"*, *"not like that"*, *"never do"* | Captures mistakes to prevent repetition | +| Architectural insight | *"you removed that without understanding why"* | Documents the *why* behind a design (Chesterton's Fence) | +| Immediate fixes | *"debug"*, *"root cause"*, *"fix all"* | Learns from errors in real-time | + +### 🟡 MEDIUM Priority (invoke after multiple instances) + +| Trigger | Example phrase | Why important | +|---------|---------------|--------------| +| User praise | *"perfect"*, *"exactly"*, *"great"* | Reinforces successful patterns | +| Tool preferences | *"use X instead of Y"*, *"prefer"* | Builds workflow preferences | +| Edge cases | *"what if X happens?"*, *"don't forget"*, *"ensure"* | Captures scenarios to handle | + +### 🟢 LOW Priority (invoke at natural breakpoints) + +| Trigger | Example phrase | Why useful | +|---------|---------------|--------------| +| Workflow refinements | *"better if you..."*, *"next time"* | Iterative improvement | +| Style preferences | *"prefer this format"*, *"like this approach"* | Personal style learning | + +--- + +## Capture format + +Reflect produces structured entries the lead or Scribe can review at session end: + +```markdown +## Reflection — 2026-06-11T16:42:00Z + +**Trigger:** User correction — "no, never auto-merge without explicit approval" +**Confidence:** HIGH +**Pattern:** Auto-merge gating +**Learning:** Even when CI is green and reviews pass, do not invoke `gh pr merge` without an explicit user confirmation. The user wants the final merge action to be human-driven. +**Promote to:** `decisions.md` (team-wide rule) — surface to lead next ceremony +**Cited:** Coordinator session 2026-06-11, user message ~16:41 +``` + +--- + +## Anti-patterns + +- **Don't capture every interaction.** Reflect is for inflection points — corrections, surprises, breakthroughs. A capture rate >1 per ~10 messages is too high. +- **Don't promote LOW-confidence patterns to decisions.md.** Decisions are binding for the whole team; LOW captures are personal preferences and should live in the agent's `history.md` if anywhere. +- **Don't reflect on user instructions you already executed correctly.** That's not learning, that's logging. +- **Don't paraphrase the user's words when capturing HIGH-priority items.** Verbatim quotes preserve nuance. + +--- + +## See also + +- [Memory & Knowledge](/squad/docs/concepts/memory-and-knowledge/) — the three-layer model +- [Directives](/squad/docs/features/directives/) — how the coordinator captures explicit team rules +- [Error Recovery](/squad/docs/features/error-recovery/) — the companion skill for handling failures diff --git a/docs/src/content/docs/features/routing.md b/docs/src/content/docs/features/routing.md index 3e8478108..69f631faf 100644 --- a/docs/src/content/docs/features/routing.md +++ b/docs/src/content/docs/features/routing.md @@ -56,7 +56,7 @@ When work involves `src/api/auth.ts`, it routes to Backend automatically. ### 3. Skill-Aware Routing -If no domain match, the coordinator checks `.squad/skills/` for capability fit: +If no domain match, the coordinator checks `.copilot/skills/` for capability fit: ```markdown # authentication.md diff --git a/docs/src/content/docs/features/skill-security-scanner.md b/docs/src/content/docs/features/skill-security-scanner.md new file mode 100644 index 000000000..5e3fe415a --- /dev/null +++ b/docs/src/content/docs/features/skill-security-scanner.md @@ -0,0 +1,118 @@ +--- +title: Skill Security Scanner +description: Markdown-aware security scanner that catches embedded credentials, download-and-execute patterns, and privilege escalation in skill files before they ship. +--- + +# Skill Security Scanner + +> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases. + +The skill security scanner is a markdown-aware safety check that runs as part of `scripts/security-review.mjs` to inspect every SKILL.md file in `.copilot/skills/` and `.squad/skills/`. It catches three classes of problem before a skill gets installed or merged: + +1. **Embedded credentials** — API keys, tokens, passwords pasted into skill text +2. **Download-and-execute patterns** — `curl ... | bash`, `Invoke-Expression`, and friends +3. **Privilege escalation commands** — `sudo`, `Set-ExecutionPolicy Bypass`, `chmod 777`, etc. + +It ships as Phase 1 — focused on the highest-signal issues with **zero false positives on the existing 35 skill files** at the time of release. + +--- + +## How it integrates + +The scanner is invoked by the existing security-review pipeline (`scripts/security-review.mjs`), which is triggered: + +- On every PR that touches `.copilot/skills/**` or `.squad/skills/**` (via the Security Review CI workflow) +- Manually: `node scripts/security-review.mjs --scope skills` +- As part of [Plugin Marketplace](/squad/docs/features/plugins/) install (skills from external sources get scanned before landing on disk) + +A finding produces a CI failure with the file path, line number, pattern type, and the matched substring (redacted for credentials). + +--- + +## What it catches + +### Credentials + +| Pattern type | Example match | +|--------------|---------------| +| Generic API key | `API_KEY=` | +| GitHub PAT | `ghp_<40-character-token>` | +| AWS access key | `AKIA<16-character-key>` | +| Bearer tokens | `Authorization: Bearer ` | +| Database connection strings with embedded passwords | `postgres://user:@host/db` | + +### Download-and-execute patterns + +| Pattern type | Example match | +|--------------|---------------| +| Curl-to-bash | `curl https://... \| bash`, `curl ... \| sh`, `wget ... \| sh` | +| PowerShell invoke-expression | `iex (irm https://...)`, `Invoke-Expression $downloaded` | +| Unsafe eval | `eval $(curl ...)`, `eval $(wget ...)` | + +### Privilege escalation + +| Pattern type | Example match | +|--------------|---------------| +| `sudo` invocations | `sudo apt install`, `sudo -i`, `sudo bash` | +| Permissive chmod | `chmod 777`, `chmod a+rwx`, `chmod -R 777` | +| PowerShell policy bypass | `Set-ExecutionPolicy Bypass`, `Set-ExecutionPolicy Unrestricted` | +| Windows admin escalation | `Start-Process ... -Verb RunAs`, `runas /user:Administrator` | + +--- + +## Suppression — the false-positive guardrails + +The scanner is markdown-aware, which means it understands when a "dangerous" pattern is actually in a code block being **shown as an anti-pattern** vs. in prose advising users to run something: + +| Where pattern appears | Action | +|----------------------|--------| +| Inside a fenced code block (three backticks) | **Suppressed** — treated as documentation, not advice | +| Inside an inline code span (single backtick) | **Suppressed** — treated as a reference | +| In prose with a placeholder token (``, ``, `xxx`, `***`) | **Suppressed** — clearly an example | +| In prose without any of the above | **Flagged** as a finding | + +The placeholder-token list covers common safe markers: ``, ``, ``, `xxx`, `***`, `placeholder`, `example`, `PLACEHOLDER`. + +This is why the existing 35 skill files have zero false positives — most discuss security patterns inside fenced code blocks or with placeholder tokens. + +--- + +## Local invocation + +```bash +# Scan all skills in the current repo +node scripts/security-review.mjs --scope skills + +# Scan a single skill file +node scripts/security-review.mjs --file .copilot/skills/my-skill/SKILL.md + +# JSON output for tooling integration +node scripts/security-review.mjs --scope skills --format json +``` + +Exit codes: +- `0` — no findings +- `1` — findings detected (CI fails the build) +- `2` — scanner error (couldn't read file, malformed markdown, etc.) + +--- + +## What it doesn't catch + +This is **Phase 1**. The scanner is deliberately conservative — it would rather miss something than false-positive a legitimate skill. Things NOT in scope today: + +- **Obfuscated patterns** — base64-encoded credentials, character-class regex tricks, etc. +- **Multi-line patterns** — the scanner is line-oriented; a credential split across lines won't match +- **Skill scripts (`.js`/`.mjs` files in `scripts/`)** — only the SKILL.md narrative is scanned; executable handlers need their own audit +- **Semantic context** — the scanner doesn't understand whether a `sudo` example is contextually safe; if it's in prose without a placeholder marker, it flags +- **Hooks beyond `.copilot/skills/` and `.squad/skills/`** — other markdown files (charters, decisions, README) aren't scanned by this rule + +Phase 2 work tracked in the issue tracker would extend coverage to scripts and add an LLM-based semantic pass. + +--- + +## See also + +- [Skills](/squad/docs/features/skills/) — the broader skills system +- [Plugin Marketplace](/squad/docs/features/plugins/) — how external skills get installed +- [Secret Handling](/squad/docs/features/skills/) — see also the `secret-handling` built-in skill diff --git a/docs/src/content/docs/features/skills.md b/docs/src/content/docs/features/skills.md index c4c4f6e08..d3f7cc7b8 100644 --- a/docs/src/content/docs/features/skills.md +++ b/docs/src/content/docs/features/skills.md @@ -25,11 +25,13 @@ Agents learn from real work and write skill files — reusable patterns, convent ## Where Skills Live ``` -.squad/skills/{skill-name}/SKILL.md +.copilot/skills/{skill-name}/SKILL.md ``` Each skill is a directory containing a `SKILL.md` file. Skills are **team-wide knowledge** — not tied to individual agents. All agents can read and use any skill. +> **Legacy path**: Skills are also discovered at `.squad/skills/` for backward compatibility. If both paths contain a skill with the same name, the `.squad/skills/` version takes precedence. New skills are always written to `.copilot/skills/`. + > **Portable across projects**: Skills export and import with your team. When you move a trained team to a new repo, all their earned knowledge comes with them. --- @@ -57,7 +59,7 @@ Legacy term for built-in skills. Previously called "starter skills" and prefixed ### Session Recovery -The `session-recovery` skill teaches agents to find and resume interrupted Copilot CLI sessions. When a session is interrupted (terminal crash, network drop, machine restart), in-progress work may be left incomplete. This skill uses `session_store` SQL queries to detect abandoned sessions, inspect checkpoint progress, and resume work. See [`.squad/skills/session-recovery/SKILL.md`](https://github.com/bradygaster/squad/blob/dev/.squad/skills/session-recovery/SKILL.md) for query patterns and examples. +The `session-recovery` skill teaches agents to find and resume interrupted Copilot CLI sessions. When a session is interrupted (terminal crash, network drop, machine restart), in-progress work may be left incomplete. This skill uses `session_store` SQL queries to detect abandoned sessions, inspect checkpoint progress, and resume work. See [`.copilot/skills/session-recovery/SKILL.md`](https://github.com/bradygaster/squad/blob/dev/.copilot/skills/session-recovery/SKILL.md) for query patterns and examples. ### Earned skills @@ -92,7 +94,7 @@ Confidence only goes up, never down. A skill that reaches `high` stays there. After successfully setting up a CI pipeline, an agent might create: ``` -.squad/skills/ci-github-actions/SKILL.md +.copilot/skills/ci-github-actions/SKILL.md ``` ```markdown @@ -117,7 +119,7 @@ After successfully setting up a CI pipeline, an agent might create: - Skills compound over time. A mature project has skills covering testing patterns, deployment procedures, API conventions, and more. - Built-in skills are overwritten on upgrade. Earned skills are never touched. -- **Skills are shared across the whole team** — any agent can read any skill. They're stored in a flat `.squad/skills/` directory, not per-agent files. +- **Skills are shared across the whole team** — any agent can read any skill. They're stored in a flat `.copilot/skills/` directory, not per-agent files. - You can manually edit skill files if you want to seed knowledge (e.g., paste your team's existing conventions into a `SKILL.md`). - **Skills survive export/import** — your team's accumulated knowledge is fully portable across projects. @@ -127,7 +129,7 @@ After successfully setting up a CI pipeline, an agent might create: list all skills ``` -Shows all skill files in `.squad/skills/` with confidence levels for earned skills. +Shows all skill files in `.copilot/skills/` with confidence levels for earned skills. ``` what's the confidence level for the CI skill? diff --git a/docs/src/content/docs/features/state-backends.md b/docs/src/content/docs/features/state-backends.md index 0009e0fb2..6d8a5ed87 100644 --- a/docs/src/content/docs/features/state-backends.md +++ b/docs/src/content/docs/features/state-backends.md @@ -49,13 +49,15 @@ squad upgrade --state-backend two-layer # Or: squad upgrade --state-backend orphan ``` -This migrates existing state, creates the orphan branch, and installs git hooks for automatic multi-user sync. No manual steps needed. +This migrates existing state, creates the orphan branch, and installs git hooks for automatic multi-user sync. ### What gets installed automatically When you choose `orphan` or `two-layer`: -- **Git hooks** (pre-push, post-merge, post-checkout, post-rewrite) are installed in `.git/hooks/` -- These hooks sync the `squad-state` branch automatically when you push/pull — no manual sync needed +- **Git hooks** (pre-push, post-merge, post-checkout, post-rewrite, pre-commit, post-commit) are installed in `.git/hooks/` +- The sync hooks (pre-push, post-merge, post-checkout, post-rewrite) keep the `squad-state` branch in sync automatically when you push/pull +- The **pre-commit** hook guards against accidentally staging two-layer mutable state (decisions, histories, casting, routing) into a working-tree commit — it refuses with an explanation if detected +- The **post-commit** hook flushes any pending two-layer state onto the orphan branch after each commit (best-effort, never blocks) - Hooks chain with existing hooks (husky, etc.) — nothing is overwritten --- @@ -162,7 +164,7 @@ Warning: State backend 'two-layer' failed: . Falling back to 'local'. ```bash cat .squad/decisions.md -ls .squad/skills/ +ls .copilot/skills/ ``` ### Git Notes @@ -406,7 +408,7 @@ This will: 3. Install git hooks for automatic sync 4. Preserve all existing state -**What happens:** Existing `.squad/` files remain on disk as a read-only reference. New decisions and state writes go to the orphan branch (and git notes for two-layer). Over time, the on-disk state files become stale (they're the snapshot from before migration), while the orphan branch and notes contain the latest state. +**What happens:** Existing `.squad/` files are migrated to the orphan branch and may be removed from the working tree on subsequent commits. New decisions and state writes go to the orphan branch (and git notes for two-layer). The pre-commit hook prevents you from accidentally re-committing mutable state files into the working tree. ### Switching between orphan and two-layer @@ -414,8 +416,65 @@ Change `stateBackend` in `.squad/config.json`. The coordinator adapts on the nex --- +## Steady-state safety net + +Once you've migrated to `orphan` or `two-layer`, two additional git hooks enforce the invariant that mutable state never lands in your working branch. + +### `pre-commit` — blocks state from entering the working tree + +Before every commit, this hook scans the staged index for files that belong on the orphan branch: + +- `.squad/decisions.md` +- `.squad/agents/*/history.md` +- `.squad/casting/` +- `.squad/routing/` + +If any of those paths are staged, the commit is refused with: + +``` +⚠ squad pre-commit: refusing to commit two-layer state into the working tree. + Unstage the state files and let the post-commit hook sync them: + git restore --staged .squad/decisions.md .squad/agents/*/history.md +``` + +**Why files might reappear:** A tool, editor save, or agent code path that writes directly via `fs.writeFile` (bypassing `StateBackend`) will recreate the file on disk. Staging it and attempting a commit triggers this hook. + +For the full recovery flow see [troubleshooting](#squad-pre-commit-refusing-to-commit-two-layer-state-into-the-working-tree). + +### `post-commit` — keeps the orphan branch current + +After every successful commit, `squad sync --quiet` is called automatically. This pushes any pending state from the in-memory queue onto the `squad-state` branch, so the orphan branch stays up to date without manual intervention. + +### `SQUAD_SYNC_ACTIVE=1` bypass + +Setting `SQUAD_SYNC_ACTIVE=1` in the environment causes both hooks to exit immediately without running. This is used **internally** by `squad sync` itself to avoid hook recursion. + +> ⚠️ **Do not use `SQUAD_SYNC_ACTIVE=1` routinely.** Bypassing the pre-commit hook lets state files land in your working branch commits — exactly the situation `two-layer` is designed to prevent. Any PR created from that branch will carry squad state in the diff, defeating the clean-PR promise of the two-layer backend. Use the recovery flow instead. + ## Troubleshooting +### "Pre-commit hook refused my commit" + +**Cause:** You staged `.squad/` files that belong on the `squad-state` orphan branch (decisions.md, agent histories, casting/, routing/). The pre-commit hook blocks these to keep mutable state off your working-tree branches. + +**Fix:** + +```bash +# Unstage the offending paths +git restore --staged .squad/decisions.md .squad/agents/*/history.md .squad/casting/ .squad/routing/ + +# Then commit normally — only your code changes go through +git commit +``` + +**If you need to bypass** (e.g., during initial migration or manual repair): + +```bash +SQUAD_SYNC_ACTIVE=1 git commit -m "manual state repair" +``` + +> ⚠️ Only bypass when you understand why — the hook exists to prevent state from leaking into PRs. + ### "My state disappeared after switching branches" **Cause:** You're using the default `local` backend. State files are branch-local. diff --git a/docs/src/content/docs/features/teams-comms.md b/docs/src/content/docs/features/teams-comms.md new file mode 100644 index 000000000..533cefddb --- /dev/null +++ b/docs/src/content/docs/features/teams-comms.md @@ -0,0 +1,127 @@ +--- +title: Microsoft Teams Comms Adapter +description: Bidirectional chat integration between Squad and Microsoft Teams via Microsoft Graph API — 1:1 chats and channel messaging with PKCE browser auth or device code fallback. +--- + +# Microsoft Teams Comms Adapter + +> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases. + +The Teams adapter lets your squad post updates and read replies through Microsoft Teams, alongside the existing file-based, email, and other comm channels. It ships in `@bradygaster/squad-sdk` as a `CommunicationAdapter` implementation and uses Microsoft Graph API for both 1:1 chats and channel messaging. + +> **⚠️ Breaking change in v0.10:** `createCommunicationAdapter` is now async (returns `Promise`). Callers must `await` the result. + +--- + +## What you can do with it + +| Action | Supported | +|--------|-----------| +| Post a message to a 1:1 chat | ✅ | +| Post a message to a Teams channel | ✅ | +| Read replies / new messages from a chat | ✅ | +| Post rich content (Adaptive Cards, attachments) | Partial (text + basic formatting) | +| Notify on agent-completed work | ✅ (via squad watch / notification routing) | +| Two-way conversation with an agent in Teams | ✅ (poll-based, not push) | + +The adapter is one of several `CommunicationAdapter` implementations — see [Notifications](/squad/docs/features/notifications/) for the broader notification system. + +--- + +## Authentication flow + +The adapter tries auth methods in this order, falling through on failure: + +1. **Cached token** — looks for a previously-saved token in the OS credential store +2. **Refresh token** — if cached refresh token is valid, silently re-issues an access token +3. **Browser PKCE** — opens a browser for the user to sign in; uses Authorization Code with PKCE; 120-second timeout +4. **Device code** — fallback when no browser is available (CI, remote shell); user enters a code on a different device + +``` +$ squad notify teams --to user@example.com --message "Build complete" +🔑 No cached token — opening browser for sign-in... +[browser opens, user signs in] +✓ Token cached. Sending message... +✓ Posted to user@example.com +``` + +The token cache persists across sessions. After the first sign-in, subsequent runs are silent unless the refresh token expires. + +--- + +## Configuration + +The adapter requires a Microsoft Entra (Azure AD) app registration with permissions for: + +- `Chat.ReadWrite` (1:1 chat operations) +- `ChannelMessage.Send` (channel posts) +- `ChannelMessage.Read.All` (read channel replies) +- `User.Read` (basic profile) + +Configure in `.squad/config.json`: + +```json +{ + "comms": { + "teams": { + "tenantId": "00000000-0000-0000-0000-000000000000", + "clientId": "00000000-0000-0000-0000-000000000000", + "redirectUri": "http://localhost:8400/auth", + "tokenCachePath": "~/.squad/.cache/teams-token.json" + } + } +} +``` + +The `redirectUri` is the local-only OAuth callback for browser PKCE — it never leaves your machine. + +--- + +## Usage from the SDK + +```typescript +import { createCommunicationAdapter } from '@bradygaster/squad-sdk/platform'; + +// IMPORTANT: this is async now (breaking change in v0.10) +const teams = await createCommunicationAdapter({ channel: 'teams' }); + +// Post a message +const post = await teams.postUpdate({ + title: 'CI passed', + body: 'PR #1234 is green and ready for review.', + category: 'pr-status', + author: 'Squad', +}); + +// Poll for replies +const replies = await teams.pollForReplies({ + threadId: post.id, + since: new Date(Date.now() - 60_000), +}); +``` + +--- + +## Limitations + +- **Polling, not push.** The adapter polls for replies; it doesn't subscribe to a websocket. Reply latency is the poll interval (default 30s). +- **No Adaptive Card builder.** You can send plain text and basic formatting today; for rich cards, use the underlying Graph SDK directly. +- **No bot-framework integration.** This adapter uses delegated user permissions, not a bot account. Each user sees the message as posted by themselves (or the configured app identity), not by a "Squad bot". +- **MSAL token cache shared across processes.** If you run multiple squads simultaneously with the same Entra app, they share the same cached token. Use distinct `tokenCachePath` if you need isolation. + +--- + +## Security notes + +- Tokens are stored in the OS credential store (Windows Credential Manager / macOS Keychain / Linux libsecret) where available, with a JSON file fallback at `tokenCachePath` +- The browser PKCE callback listens on `127.0.0.1` only — never exposed to the network +- The device code flow shows a verification URL + code; both are short-lived +- The adapter does NOT log message content; only metadata (post id, recipient, timestamp) is recorded in any audit trail + +--- + +## See also + +- [Notifications](/squad/docs/features/notifications/) — the broader notification system +- [Enterprise Platforms](/squad/docs/features/enterprise-platforms/) — Teams + ADO + other enterprise integrations +- [Notification Level](/squad/docs/features/notification-level/) — controlling noise across all channels diff --git a/docs/src/content/docs/features/tiered-memory.md b/docs/src/content/docs/features/tiered-memory.md new file mode 100644 index 000000000..acdfd6a61 --- /dev/null +++ b/docs/src/content/docs/features/tiered-memory.md @@ -0,0 +1,98 @@ +--- +title: Tiered Memory — Hot / Cold / Wiki +description: Three-tier agent memory model that cuts spawn context cost by 20-55% by separating fresh task context from archived history and durable reference docs. +--- + +# Tiered Memory — Hot / Cold / Wiki + +> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases. + +**Problem:** Squad agents load their full `history.md` on every spawn. Production measurements show 34–74KB payloads per agent (8.8K–18.5K tokens), with 82–96% of that being "old noise" — context the current task doesn't need. + +**Solution:** A three-tier memory model that loads only what each task actually requires, achieving 20–55% context reduction per spawn. + +Tiered Memory ships as a built-in skill at `.copilot/skills/tiered-memory/SKILL.md` and pairs with Scribe's existing 15KB-summarization rule (see [Memory & Knowledge](/squad/docs/concepts/memory-and-knowledge/)) to give large, long-running squads predictable context budgets. + +--- + +## The three tiers + +### 🔥 Hot — Current Session Context + +- **Size target:** ~2–4KB +- **Loaded:** Always, on every spawn +- **Contents:** Current task, active decisions made this session, immediate blockers, last 3–5 actions, who's being talked to +- **Lifetime:** Current session only — Scribe promotes relevant parts to Cold at session end +- **Purpose:** Immediate task context with zero latency and zero decision + +### ❄️ Cold — Summarized Cross-Session History + +- **Size target:** ~8–12KB +- **Loaded:** On demand — include only when the task explicitly needs history +- **Contents:** Summarized past sessions, cross-session decisions, recurring patterns, unresolved issues +- **Lifetime:** 30-day rolling window — older entries promoted to Wiki +- **Purpose:** Answer *"what have we tried before?"* and *"what was decided?"* without replaying full transcripts +- **How to include:** Pass `--include-cold` in the spawn template, or add a `## Cold Memory` section to the agent's instructions + +### 📚 Wiki — Durable Structured Knowledge + +- **Size target:** variable (structured reference docs) +- **Loaded:** Async write, selective read — only when the task requires domain knowledge +- **Contents:** ADRs, agent charters, routing rules, stable conventions, external API contracts, platform constraints +- **Lifetime:** Permanent until explicitly deprecated +- **Purpose:** Authoritative reference (not history) — structured facts +- **How to include:** Pass `--include-wiki` or reference specific wiki doc paths in the spawn template + +--- + +## When to load each tier + +| Situation | Hot | Cold | Wiki | +|-----------|-----|------|------| +| New task, no prior context needed | ✅ | ❌ | ❌ | +| Resuming interrupted work | ✅ | ✅ | ❌ | +| Debugging a recurring issue | ✅ | ✅ | ❌ | +| Designing something new in an established area | ✅ | ❌ | ✅ | +| Onboarding a new team member | ✅ | ❌ | ✅ | +| Investigating an architectural drift | ✅ | ✅ | ✅ | + +The bias is to load LESS, not more. Cold and Wiki should be opt-in for each spawn based on whether the task description references the past or domain conventions. + +--- + +## How Scribe maintains the tiers + +Scribe's existing maintenance cycle (see [Memory & Knowledge](/squad/docs/concepts/memory-and-knowledge/)) is extended: + +1. **Hot drained at session end** — Scribe scans the session's hot memory, summarizes meaningful entries, appends them to Cold +2. **Cold aged into Wiki** — entries older than 30 days that contain structured facts (decisions, conventions, contracts) get promoted to Wiki +3. **Wiki authored deliberately** — Scribe never auto-creates Wiki entries from scratch; it only promotes Cold content that's already structured + +--- + +## Production measurements + +The skill's documentation cites measurements from a large production squad: + +| Squad size | Before tiered | After tiered | Reduction | +|------------|--------------|--------------|-----------| +| 8 agents, 34KB total history | 8,800 tokens/spawn | 4,400 tokens/spawn | ~50% | +| 14 agents, 74KB total history | 18,500 tokens/spawn | 8,300 tokens/spawn | ~55% | + +The exact savings depend on what fraction of each agent's history is task-relevant. The 20–55% range is the measured spread across different team configurations. + +--- + +## Caveats + +- **The tier split is currently advisory** — the skill defines hot/cold/wiki semantics, but the spawn template doesn't yet enforce `--include-cold` / `--include-wiki` flags as part of the runtime contract. Adoption is per-team via spawn-template edits. +- **Wiki has no UI** — there's no `squad wiki list` command yet. Entries live as files in `.squad/wiki/` (when teams create that directory) and the coordinator references them by path. +- **Issue [#1268](https://github.com/bradygaster/squad/issues/1268) and [#1269](https://github.com/bradygaster/squad/issues/1269)** propose making Scribe enforce these tiers via the governed memory pipeline. Until those land, tier maintenance is best-effort. + +--- + +## See also + +- [Memory & Knowledge](/squad/docs/concepts/memory-and-knowledge/) — the broader memory architecture +- [Skills](/squad/docs/features/skills/) — how built-in skills work +- [Context Hygiene](/squad/docs/features/context-hygiene/) — related practices for keeping spawn context small diff --git a/docs/src/content/docs/features/upstream-inheritance.md b/docs/src/content/docs/features/upstream-inheritance.md index d6288a92e..d9247c0e9 100644 --- a/docs/src/content/docs/features/upstream-inheritance.md +++ b/docs/src/content/docs/features/upstream-inheritance.md @@ -8,7 +8,7 @@ Upstream inheritance lets you declare external Squad sources (from repositories, At session start, the coordinator reads all declared upstreams from `upstream.json` and makes their context available to every agent: -- **Skills** — `.squad/skills/*/SKILL.md` +- **Skills** — `.copilot/skills/*/SKILL.md` - **Decisions** — `.squad/decisions.md` - **Wisdom** — `.squad/identity/wisdom.md` - **Casting policy** — `.squad/casting/policy.json` diff --git a/docs/src/content/docs/guide.md b/docs/src/content/docs/guide.md index 98ccaeb47..01e0f6a87 100644 --- a/docs/src/content/docs/guide.md +++ b/docs/src/content/docs/guide.md @@ -276,7 +276,7 @@ When a universe runs out of names, the overflow strategy determines what happens ## Skills system -Skills are reusable knowledge patterns that agents load on demand. They live in `.squad/skills/{name}/SKILL.md` and teach agents how to handle specific tasks — branching workflows, deployment strategies, testing patterns, or domain expertise. +Skills are reusable knowledge patterns that agents load on demand. They live in `.copilot/skills/{name}/SKILL.md` and teach agents how to handle specific tasks — branching workflows, deployment strategies, testing patterns, or domain expertise. Skills have a confidence lifecycle: `low` → `medium` → `high`, and track their source: `manual` (you wrote it), `observed` (agent saw a pattern), `earned` (validated through use), or `extracted` (imported from another project). diff --git a/docs/src/content/docs/reference/glossary.md b/docs/src/content/docs/reference/glossary.md index 7a5ef32f9..5642dd0ba 100644 --- a/docs/src/content/docs/reference/glossary.md +++ b/docs/src/content/docs/reference/glossary.md @@ -28,7 +28,7 @@ Key terms defined in one sentence each. Alphabetical order. **Scribe** — The silent agent that tracks decisions and logs sessions, merging proposals from all agents into `.squad/decisions.md`. -**Skill** — A reusable capability stored in `.squad/skills/` that agents can learn and execute. +**Skill** — A reusable capability stored in `.copilot/skills/` that agents can learn and execute. **Spawn** — The act of starting an agent as an independent subprocess with its own context window, tools, and memory. diff --git a/docs/src/content/docs/scenarios/cross-org-auth.md b/docs/src/content/docs/scenarios/cross-org-auth.md index 34d4e823d..b0f54ba5f 100644 --- a/docs/src/content/docs/scenarios/cross-org-auth.md +++ b/docs/src/content/docs/scenarios/cross-org-auth.md @@ -138,7 +138,7 @@ Squad agents will read this instruction and switch accounts when they detect a c Capture the cross-org auth pattern as a Squad skill. When authentication fails, the skill detects the error and suggests or attempts account switching. -Create `.squad/skills/cross-org-auth-recovery.md`: +Create `.copilot/skills/cross-org-auth-recovery.md`: ```markdown # Cross-Organization Authentication Recovery diff --git a/docs/src/content/docs/scenarios/existing-repo.md b/docs/src/content/docs/scenarios/existing-repo.md index eac0535ea..09932181c 100644 --- a/docs/src/content/docs/scenarios/existing-repo.md +++ b/docs/src/content/docs/scenarios/existing-repo.md @@ -25,7 +25,7 @@ squad ✅ .github/agents/squad.agent.md (v0.2.0) ✅ .github/workflows/ (10 workflows) ✅ .squad/templates/ -✅ .squad/skills/ (starter skills) +✅ .copilot/skills/ (starter skills) ✅ .squad/ceremonies.md ✅ .gitattributes (merge=union rules) diff --git a/docs/src/content/docs/scenarios/monorepo.md b/docs/src/content/docs/scenarios/monorepo.md index 05cc6d4c8..eb70f0036 100644 --- a/docs/src/content/docs/scenarios/monorepo.md +++ b/docs/src/content/docs/scenarios/monorepo.md @@ -142,7 +142,7 @@ All four work in parallel, each in their own service directory. Some patterns apply **across all services**: -`.squad/skills/service-logging-pattern.md`: +`.copilot/skills/service-logging-pattern.md`: ```markdown # Service Logging Pattern @@ -166,7 +166,7 @@ Every service must log: This skill is read by **all agents**, regardless of which service they're working on. Consistent logging across the monorepo. -`.squad/skills/inter-service-communication.md`: +`.copilot/skills/inter-service-communication.md`: ```markdown # Inter-Service Communication diff --git a/docs/src/content/docs/scenarios/solo-dev.md b/docs/src/content/docs/scenarios/solo-dev.md index b770e53a3..340140480 100644 --- a/docs/src/content/docs/scenarios/solo-dev.md +++ b/docs/src/content/docs/scenarios/solo-dev.md @@ -77,7 +77,7 @@ This is the safety net you don't have as a solo dev. Michael catches issues befo After Michael rejects Fredo's code for missing rate limiting, **it gets encoded as a skill**: -`.squad/skills/auth-rate-limiting.md`: +`.copilot/skills/auth-rate-limiting.md`: ```markdown # Authentication Endpoints Must Be Rate-Limited @@ -98,7 +98,7 @@ Next time any agent builds an auth feature, they read this skill first. The mist As a solo dev, you juggle dozens of context switches. Squad doesn't forget: - **Decisions** are logged. "Why did I use PostgreSQL instead of MongoDB?" — check `.squad/decisions.md`. -- **Skills** capture patterns. "How do I structure FastAPI routes?" — check `.squad/skills/`. +- **Skills** capture patterns. "How do I structure FastAPI routes?" — check `.copilot/skills/`. - **Histories** track what each agent learned. Fredo knows your database schema after one session. You get back to a project after 3 weeks, and the team is still up to speed. @@ -161,6 +161,6 @@ Michael's not always right, but having a second opinion is invaluable. - **Start with 3–4 agents.** You don't need a frontend and backend specialist if you're only building one layer. - **The Lead is your reviewer.** Use them to review your own code before committing. -- **Skills are YOUR documentation.** After 10 sessions, `.squad/skills/` is a custom knowledge base. +- **Skills are YOUR documentation.** After 10 sessions, `.copilot/skills/` is a custom knowledge base. - **Agents work while you're thinking.** Give a vague task and agents research in parallel. - **You get testing discipline.** The Tester writes tests you'd skip. Coverage goes up without extra effort. diff --git a/docs/src/content/docs/scenarios/troubleshooting.md b/docs/src/content/docs/scenarios/troubleshooting.md index 36fae207f..54a2df64c 100644 --- a/docs/src/content/docs/scenarios/troubleshooting.md +++ b/docs/src/content/docs/scenarios/troubleshooting.md @@ -156,3 +156,50 @@ If below v20, upgrade to the latest LTS: - Use PowerShell or Git Bash (not cmd.exe) - Ensure git is in your PATH - Ensure `gh` CLI is in your PATH + +--- + +## "⚠ squad pre-commit: refusing to commit two-layer state into the working tree" + +**Problem:** A `git commit` is blocked with the message above. + +**Cause:** You're on the `orphan` or `two-layer` backend, and one or more state files (`.squad/decisions.md`, `.squad/agents/*/history.md`, `.squad/casting/`, `.squad/routing/`) were staged for commit. These files belong on the `squad-state` orphan branch, not in your working branch. Something wrote them back to disk after the migration — a direct `fs.writeFile` call, an editor auto-save, or an external tool — and you staged them unintentionally. + +**Recovery flow:** + +1. **Unstage the state files:** + ```bash + git restore --staged .squad/decisions.md + git restore --staged ".squad/agents/*/history.md" + ``` + +2. **Check whether the orphan branch already has the content** (it should, if `squad sync` has run): + ```bash + git show squad-state:decisions.md + git show squad-state:agents//history.md + ``` + +3. **If the working-tree copy contains new content not yet on the orphan branch**, lift it through Squad before deleting: + ```bash + squad memory write --file .squad/decisions.md + ``` + +4. **Remove the working-tree copies:** + ```bash + # PowerShell + Remove-Item .squad\decisions.md -ErrorAction SilentlyContinue + Get-ChildItem .squad\agents -Recurse -Filter history.md | Remove-Item + ``` + ```bash + # bash + rm -f .squad/decisions.md .squad/agents/*/history.md + ``` + +5. **Commit normally** — the `post-commit` hook will call `squad sync --quiet` automatically: + ```bash + git commit -m "your commit message" + ``` + +**When to use `SQUAD_SYNC_ACTIVE=1`:** Rarely. This env var bypasses both the pre-commit and post-commit hooks. It's intended for internal use by `squad sync` itself to prevent recursion. If you set it to unblock a commit, the state files will land in your working-branch history and appear in PRs — exactly what two-layer is designed to prevent. Use the recovery flow above instead. + +--- diff --git a/docs/src/content/docs/scenarios/upgrading.md b/docs/src/content/docs/scenarios/upgrading.md index c29acde17..567b92978 100644 --- a/docs/src/content/docs/scenarios/upgrading.md +++ b/docs/src/content/docs/scenarios/upgrading.md @@ -153,3 +153,40 @@ No changes to `.ai-team/` — the diff is limited to Squad-owned files. - **Upgrade is safe.** It only overwrites files that Squad owns. Your team state is never modified. - **Don't customize `squad.agent.md`.** Any changes you make will be overwritten on the next upgrade. If you need custom behavior, use directives in `decisions.md` instead. - **Re-running upgrade is harmless.** If you're not sure whether an upgrade completed, run it again. It's idempotent. + +--- + +## After upgrading to `two-layer` or `orphan` + +After running `squad upgrade --state-backend two-layer` (or `--state-backend orphan`), verify the migration completed cleanly: + +### 1. Working tree is clean + +```bash +git status +``` + +You should see no changes. The migration removes the working-tree state files after copying them to the orphan branch — if `.squad/decisions.md` or any `history.md` files still appear as untracked or modified, rerun the migration. + +### 2. Orphan branch contains your state + +```bash +git ls-tree --name-only -r squad-state +``` + +You should see your state files listed (e.g. `decisions.md`, `agents/scribe/history.md`). If the branch is missing or empty, the migration may not have completed. + +### 3. Push the orphan branch and notes once + +The orphan branch and any git notes need to be pushed to the remote so collaborators can access them: + +```bash +git push origin squad-state +git push origin 'refs/notes/squad*:refs/notes/squad*' +``` + +After this, the `post-commit` hook keeps both in sync automatically on every commit. + +### If state files reappear later + +If `.squad/decisions.md` or `history.md` files reappear in the working tree and a `git commit` is blocked with `⚠ squad pre-commit: refusing to commit two-layer state into the working tree`, see the [pre-commit troubleshooting entry](./troubleshooting#squad-pre-commit-refusing-to-commit-two-layer-state-into-the-working-tree) for the recovery flow. diff --git a/docs/src/content/docs/sdk-first-mode.md b/docs/src/content/docs/sdk-first-mode.md index f02c86228..8470a1f8f 100644 --- a/docs/src/content/docs/sdk-first-mode.md +++ b/docs/src/content/docs/sdk-first-mode.md @@ -398,7 +398,7 @@ const gitWorkflow = defineSkill({ | `content` | string | ✅ | The skill body (patterns, examples) | | `tools` | `SkillTool[]` | ❌ | MCP tools relevant to this skill | -Skills defined in `squad.config.ts` are generated to `.squad/skills/{name}/SKILL.md` when you run `squad build`. +Skills defined in `squad.config.ts` are generated to `.copilot/skills/{name}/SKILL.md` when you run `squad build`. --- diff --git a/package-lock.json b/package-lock.json index 1a7070ec7..bb9847fb8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8527,11 +8527,11 @@ }, "packages/squad-cli": { "name": "@bradygaster/squad-cli", - "version": "0.9.6-preview.15", + "version": "0.10.0", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@bradygaster/squad-sdk": ">=0.9.6-preview", + "@bradygaster/squad-sdk": ">=0.10.0", "@modelcontextprotocol/sdk": "^1.29.0", "ink": "^6.8.0", "react": "^19.2.4", @@ -8558,7 +8558,7 @@ }, "packages/squad-sdk": { "name": "@bradygaster/squad-sdk", - "version": "0.9.6-preview.13", + "version": "0.10.0", "license": "MIT", "dependencies": { "@github/copilot-sdk": "^0.3.0", diff --git a/packages/squad-cli/README.md b/packages/squad-cli/README.md index 59b95d2aa..3d15cb896 100644 --- a/packages/squad-cli/README.md +++ b/packages/squad-cli/README.md @@ -223,7 +223,7 @@ When you run `squad init`, Squad creates a `.squad/` directory with this structu ## Built-in Skills -When you run `squad init`, Squad installs **8 curated skills** into `.copilot/skills/`. These skills teach your agents best practices and conventions: +When you run `squad init`, Squad installs **8 curated skills** into `.github/skills/`. These skills teach your agents best practices and conventions: | Skill | Purpose | |-------|---------| @@ -236,19 +236,19 @@ When you run `squad init`, Squad installs **8 curated skills** into `.copilot/sk | `test-discipline` | Test-first discipline and coverage expectations | | `agent-collaboration` | Multi-agent collaboration and handoff patterns | -Each skill is a `SKILL.md` file inside `.copilot/skills//`. +Each skill is a `SKILL.md` file inside `.github/skills//`. ### Skill lifecycle -- **`squad init`** — Installs the 8 manifest skills on first run. If `.copilot/skills/` already has content, init skips skill installation (idempotent). +- **`squad init`** — Installs the 8 manifest skills on first run. If `.github/skills/` already has content, init skips skill installation (idempotent). - **`squad upgrade`** — Refreshes manifest skills to their latest versions. Skills marked `overwriteOnUpgrade: true` (all built-in skills) are always updated to pick up fixes and improvements. ### Adding custom skills -You can add your own skills by creating a new directory under `.copilot/skills/`: +You can add your own skills by creating a new directory under `.github/skills/`: ``` -.copilot/skills/my-custom-skill/ +.github/skills/my-custom-skill/ └── SKILL.md ``` diff --git a/packages/squad-cli/package.json b/packages/squad-cli/package.json index c96cffb14..df7fe17c6 100644 --- a/packages/squad-cli/package.json +++ b/packages/squad-cli/package.json @@ -174,7 +174,8 @@ "dist", "templates", "scripts", - "README.md" + "README.md", + "CHANGELOG.md" ], "scripts": { "postinstall": "node scripts/patch-esm-imports.mjs && node scripts/patch-ink-rendering.mjs", @@ -186,7 +187,7 @@ "node": ">=22.5.0" }, "dependencies": { - "@bradygaster/squad-sdk": ">=0.10.0", + "@bradygaster/squad-sdk": ">=0.9.0", "@modelcontextprotocol/sdk": "^1.29.0", "ink": "^6.8.0", "react": "^19.2.4", diff --git a/packages/squad-cli/src/cli-entry.ts b/packages/squad-cli/src/cli-entry.ts index 6dc4d2a73..3832aeb69 100644 --- a/packages/squad-cli/src/cli-entry.ts +++ b/packages/squad-cli/src/cli-entry.ts @@ -264,6 +264,10 @@ async function main(): Promise { console.log(` ${BOLD}discover${RESET} List known squads and their capabilities`); console.log(` ${BOLD}delegate${RESET} Create work in another squad`); console.log(` Usage: delegate `); + console.log(` ${BOLD}registry${RESET} Manage peer squads for cross-squad discovery (no inheritance)`); + console.log(` Usage: registry add `); + console.log(` registry list`); + console.log(` registry remove `); console.log(` ${BOLD}upstream${RESET} Manage upstream Squad sources`); console.log(` Usage: upstream add [--name ] [--ref ]`); console.log(` upstream remove `); @@ -1098,6 +1102,12 @@ async function main(): Promise { return; } + if (cmd === 'registry') { + const { registryCommand } = await import('./cli/commands/cross-squad.js'); + await registryCommand(args.slice(1)); + return; + } + if (cmd === 'economy') { const { runEconomy } = await import('./cli/commands/economy.js'); await runEconomy(getSquadStartDir(), args.slice(1)); diff --git a/packages/squad-cli/src/cli/commands/build.ts b/packages/squad-cli/src/cli/commands/build.ts index 6980091c2..234d0627f 100644 --- a/packages/squad-cli/src/cli/commands/build.ts +++ b/packages/squad-cli/src/cli/commands/build.ts @@ -271,7 +271,7 @@ function generateCeremoniesDispatchTable(ceremonies: readonly CeremonyDefinition const schedule = c.schedule ?? '—'; const participants = c.participants?.join(', ') ?? '—'; const slug = c.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); - const skillPath = `.copilot/skills/ceremony-${slug}/SKILL.md`; + const skillPath = `.github/skills/ceremony-${slug}/SKILL.md`; lines.push(`| ${c.name} | ${trigger} | ${schedule} | ${participants} | \`${skillPath}\` |`); } @@ -383,7 +383,7 @@ function buildFilePlan(config: SquadSDKConfig): GeneratedFile[] { for (const c of config.ceremonies) { const slug = c.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); files.push({ - relPath: `.copilot/skills/ceremony-${slug}/SKILL.md`, + relPath: `.github/skills/ceremony-${slug}/SKILL.md`, content: generateCeremonySkillFile(c), }); } @@ -400,7 +400,7 @@ function buildFilePlan(config: SquadSDKConfig): GeneratedFile[] { if (config.skills && config.skills.length > 0) { for (const skill of config.skills) { files.push({ - relPath: `.copilot/skills/${skill.name}/SKILL.md`, + relPath: `.github/skills/${skill.name}/SKILL.md`, content: generateSkillFile(skill), }); } diff --git a/packages/squad-cli/src/cli/commands/cross-squad.ts b/packages/squad-cli/src/cli/commands/cross-squad.ts index 6123beda5..78cd9ac23 100644 --- a/packages/squad-cli/src/cli/commands/cross-squad.ts +++ b/packages/squad-cli/src/cli/commands/cross-squad.ts @@ -1,15 +1,20 @@ /** - * squad discover / squad delegate — CLI commands for cross-squad orchestration. + * squad discover / squad delegate / squad registry — CLI commands for + * cross-squad orchestration. * * Commands: - * squad discover — list known squads and capabilities + * squad discover — list known squads and capabilities * squad delegate — create work in another squad + * squad registry add — register a peer squad (no inheritance) + * squad registry list — show registered peer squads + * squad registry remove — remove a registered peer squad * * @module cli/commands/cross-squad */ import { execFile } from 'node:child_process'; import { promisify } from 'node:util'; +import { resolve as resolvePath } from 'node:path'; import { success, warn, info, BOLD, RESET, DIM } from '../core/output.js'; import { fatal } from '../core/errors.js'; import { detectSquadDir } from '../core/detect-squad-dir.js'; @@ -18,6 +23,9 @@ import { formatDiscoveryTable, findSquadByName, buildDelegationArgs, + readSquadRegistry, + addRegistryEntry, + removeRegistryEntry, type DiscoveredSquad, } from '@bradygaster/squad-sdk'; @@ -98,3 +106,78 @@ function buildDelegationBody(description: string, target: DiscoveredSquad): stri `${DIM}Created by squad cross-squad orchestration${RESET}`, ].join('\n'); } + +/** + * `squad registry` — manage peer squads in `.squad/squad-registry.json`. + * + * Unlike `squad upstream add`, registry entries are discovery-only — peer + * squads are findable via `squad discover` and addressable via `squad + * delegate`, but their skills/decisions/wisdom are NOT inherited by your + * coordinator at session start. + */ +export async function registryCommand(args: string[]): Promise { + const action = args[0]; + if (!action || (action !== 'add' && action !== 'list' && action !== 'remove')) { + fatal('Usage: squad registry add | list | remove '); + } + + const squadDirInfo = detectSquadDir(process.cwd()); + const squadDir = squadDirInfo.path; + + if (action === 'list') { + const entries = readSquadRegistry(squadDir); + if (entries.length === 0) { + info(`${DIM}No peer squads registered. Add one with: squad registry add ${RESET}`); + return; + } + info(`\n${BOLD}Registered peer squads${RESET} (.squad/squad-registry.json):\n`); + for (const entry of entries) { + info(` ${BOLD}${entry.name}${RESET} → ${entry.path}`); + } + info(''); + return; + } + + if (action === 'add') { + const name = args[1]; + const rawPath = args[2]; + if (!name || !rawPath) { + fatal('Usage: squad registry add '); + } + // Resolve to absolute path so the registry is portable wrt cwd at write time + const absPath = resolvePath(rawPath); + const result = addRegistryEntry(squadDir, name, absPath); + if (result.added) { + success(`Registered peer squad "${name}" → ${absPath}`); + info(` Capabilities: ${result.manifest!.capabilities.join(', ')}`); + info(` Repo: ${result.manifest!.contact.repo}`); + info(` Accepts: ${result.manifest!.accepts.join(', ')}`); + info(`\n${DIM}Run "squad discover" to see all registered peers.${RESET}`); + return; + } + if (result.reason === 'duplicate-name') { + fatal(`A peer named "${name}" is already registered. Remove it first: squad registry remove ${name}`); + } + if (result.reason === 'invalid-manifest') { + fatal( + `No valid .squad/manifest.json found at ${absPath}.\n` + + ` Expected: ${absPath}/.squad/manifest.json (or ${absPath}/manifest.json if you pointed at .squad/).\n` + + ` Ask the peer team to publish a manifest, or verify the path resolves locally.`, + ); + } + fatal('Registry add failed for an unknown reason.'); + } + + if (action === 'remove') { + const name = args[1]; + if (!name) { + fatal('Usage: squad registry remove '); + } + const removed = removeRegistryEntry(squadDir, name); + if (removed) { + success(`Removed peer squad "${name}" from the registry.`); + } else { + warn(`No peer squad named "${name}" was registered.`); + } + } +} diff --git a/packages/squad-cli/src/cli/commands/migrate-backend.ts b/packages/squad-cli/src/cli/commands/migrate-backend.ts index fb4d457ec..ac0831d69 100644 --- a/packages/squad-cli/src/cli/commands/migrate-backend.ts +++ b/packages/squad-cli/src/cli/commands/migrate-backend.ts @@ -19,6 +19,8 @@ import { execFileSync } from 'node:child_process'; import path from 'node:path'; import fs from 'node:fs'; import { installGitHooks } from './install-hooks.js'; +import { FSStorageProvider } from '@bradygaster/squad-sdk'; +import { addSquadStateGitignoreBlock, removeSquadStateGitignoreBlock } from '@bradygaster/squad-sdk'; const GREEN = '\x1b[32m'; const YELLOW = '\x1b[33m'; @@ -280,6 +282,27 @@ export async function migrateStateBackend(dest: string, target: string): Promise installGitHooks(dest, { force: true }); } + // Step 5: Manage .gitignore marker block for squad-state files. + // When migrating TO an orphan/two-layer backend: add the marker block so + // git add . / IDE "stage all" cannot accidentally stage state files. + // When migrating TO a local/worktree backend: remove the block so those + // files are committable again. + const gitignorePath = path.join(dest, '.gitignore'); + const storage = new FSStorageProvider(); + if (ORPHAN_BACKENDS.has(target) && !ORPHAN_BACKENDS.has(current)) { + // Transitioning FROM a worktree backend TO orphan/two-layer — add block + const added = addSquadStateGitignoreBlock(gitignorePath, storage); + if (added) { + console.log(` ${GREEN}✓${RESET} added 2 entries to .gitignore (.squad/decisions.md, .squad/agents/*/history.md) — these now live on squad-state branch`); + } + } else if (WORKTREE_BACKENDS.has(target) && ORPHAN_BACKENDS.has(current)) { + // Transitioning FROM orphan/two-layer BACK TO a local backend — remove block + const removed = removeSquadStateGitignoreBlock(gitignorePath, storage); + if (removed) { + console.log(` ${GREEN}✓${RESET} removed 2 entries from .gitignore — .squad/decisions.md and agent histories are now committable again`); + } + } + console.log(`\n${GREEN}${BOLD}✓ Migration complete.${RESET} Backend is now '${target}'.\n`); } diff --git a/packages/squad-cli/src/cli/commands/skill.ts b/packages/squad-cli/src/cli/commands/skill.ts index 1acea5082..461bac059 100644 --- a/packages/squad-cli/src/cli/commands/skill.ts +++ b/packages/squad-cli/src/cli/commands/skill.ts @@ -144,15 +144,16 @@ async function publish(dest: string, skillName?: string): Promise { // Build the skill's own apm.yml inside its directory const apmSkillPath = join(skillsDir, skillName, 'apm.yml'); + const safeDesc = fm['description'] ? JSON.stringify(fm['description']) : null; const skillApm = [ `name: ${fm['name'] ?? skillName}`, `version: ${fm['version'] ?? '1.0.0'}`, - fm['description'] ? `description: "${fm['description']}"` : null, + safeDesc ? `description: ${safeDesc}` : null, ``, `skills:`, ` - name: ${fm['name'] ?? skillName}`, ` path: skill.md`, - fm['description'] ? ` description: "${fm['description']}"` : null, + safeDesc ? ` description: ${safeDesc}` : null, ] .filter(l => l !== null) .join('\n'); @@ -195,7 +196,7 @@ async function publish(dest: string, skillName?: string): Promise { ...skills.map(s => [ ` - name: ${s.name}`, - s.description ? ` description: "${s.description}"` : null, + s.description ? ` description: ${JSON.stringify(s.description)}` : null, ` path: ${s.path}`, s.version ? ` version: ${s.version}` : null, ] diff --git a/packages/squad-cli/src/cli/commands/state-mcp.ts b/packages/squad-cli/src/cli/commands/state-mcp.ts index 733b722d6..b2ec59b7c 100644 --- a/packages/squad-cli/src/cli/commands/state-mcp.ts +++ b/packages/squad-cli/src/cli/commands/state-mcp.ts @@ -32,6 +32,12 @@ const MCP_TOOL_ALIASES: Record = { squad_state_delete: 'squad_state_delete', squad_state_list: 'squad_state_list', squad_state_health: 'squad_state_health', + 'memory.classify': 'memory.classify', + 'memory.write': 'memory.write', + 'memory.search': 'memory.search', + 'memory.promote': 'memory.promote', + 'memory.delete': 'memory.delete', + 'memory.audit': 'memory.audit', }; function parseObject(value: unknown): Record { diff --git a/packages/squad-cli/src/cli/core/cast.ts b/packages/squad-cli/src/cli/core/cast.ts index ac7309cef..eb3126a06 100644 --- a/packages/squad-cli/src/cli/core/cast.ts +++ b/packages/squad-cli/src/cli/core/cast.ts @@ -498,6 +498,82 @@ function RaiMember(): CastMember { return { name: 'Rai', role: 'RAI Reviewer', scope: 'Content safety, bias detection, credential scanning, ethical review', emoji: '🛡️' }; } +function factCheckerMember(): CastMember { + return { name: 'Fact Checker', role: 'Fact Checker', scope: 'Claim verification, hallucination detection, counter-hypothesis analysis, source validation', emoji: '🔍' }; +} + +function factCheckerCharter(): string { + return `# Fact Checker + +> Trust, but verify. Every claim gets a source check. + +## Identity + +- **Name:** Fact Checker +- **Role:** Devil's Advocate & Verification Agent +- **Emoji:** 🔍 +- **Style:** Rigorous but constructive. Flags issues clearly without being abrasive. + +## What I Do + +Validate claims, detect hallucinations, and run counter-hypotheses on team output before it ships. + +## Verification Methodology + +For every claim or assertion I review: + +1. **Source Check:** What evidence supports this? Can I verify it? +2. **Counter-Hypothesis:** What would disprove this? Is there an alternative explanation? +3. **Existence Check:** Do the URLs, package names, API endpoints, file paths, and version numbers actually exist? +4. **Consistency Check:** Does this contradict anything in \`.squad/decisions.md\` or prior team output? + +## Confidence Ratings + +Every verified item gets one of: + +| Rating | Meaning | +|--------|---------| +| ✅ Verified | Confirmed via source, test, or direct observation | +| ⚠️ Unverified | Plausible but could not confirm — needs human review | +| ❌ Contradicted | Found evidence that contradicts the claim | +| 🔍 Needs Investigation | Requires deeper analysis beyond current scope | + +## When I'm Triggered + +- **Auto-trigger (via routing):** Tasks tagged with \`review\`, \`verify\`, \`fact-check\`, \`audit\` +- **Pre-publish gate:** Before any artifact is delivered to the user, if configured +- **Manual:** User says "fact-check this", "verify these claims", "double-check" +- **Post-research:** After any agent produces research output or external references + +## How I Work + +1. **Read the artifact** — understand what's being claimed +2. **Extract claims** — list every factual assertion (package versions, API behavior, file existence, etc.) +3. **Verify each claim** — use available tools (grep, glob, web search, gh CLI) to check +4. **Run counter-hypotheses** — for key assumptions, ask "what if this is wrong?" +5. **Produce a verification report** +6. **Write decision** if I found issues: \`.squad/decisions/inbox/fact-checker-{slug}.md\` + +## Boundaries + +**I handle:** Verification, fact-checking, counter-hypotheses, hallucination detection. + +**I don't handle:** Implementation, design, testing, or docs. I review, not create. + +**I am not a blocker by default.** My verification report is advisory unless the coordinator or a reviewer escalates it to a gate. + +## Collaboration + +Before starting work, run \`git rev-parse --show-toplevel\` to find the repo root, or use the \`TEAM ROOT\` provided in the spawn prompt. All \`.squad/\` paths must be resolved relative to this root. + +After making a decision others should know, write it to \`.squad/decisions/inbox/fact-checker-{brief-slug}.md\`. + +## Learnings + +Initial setup complete. Ready for verification work. +`; +} + function RaiCharter(): string { return `# Rai — RAI Reviewer @@ -618,6 +694,9 @@ export async function createTeam(teamRoot: string, proposal: CastProposal): Prom const hasRai = proposal.members.some(m => /Rai/i.test(m.name)); if (!hasRai) allMembers.push(RaiMember()); + const hasFactChecker = proposal.members.some(m => /fact.?checker/i.test(m.name)); + if (!hasFactChecker) allMembers.push(factCheckerMember()); + // Create agent directories and files for (const member of allMembers) { const nameLower = member.name.toLowerCase(); @@ -631,6 +710,8 @@ export async function createTeam(teamRoot: string, proposal: CastProposal): Prom charter = ralphCharter(); } else if (member.name === 'Rai' && !hasRai) { charter = RaiCharter(); + } else if (member.name === 'Fact Checker' && !hasFactChecker) { + charter = factCheckerCharter(); } else { charter = generateCharter(member); } @@ -805,5 +886,10 @@ export function formatCastSummary(proposal: CastProposal): string { lines.push(`🛡️ ${'Rai'.padEnd(10)} — ${'(background)'.padEnd(15)} RAI awareness, content safety`); } + const hasFactChecker = proposal.members.some(m => /fact.?checker/i.test(m.name)); + if (!hasFactChecker) { + lines.push(`🔍 ${'Fact Checker'.padEnd(10)} — ${'(advisory)'.padEnd(15)} Claim verification, hallucination detection`); + } + return lines.join('\n'); } diff --git a/packages/squad-cli/src/cli/core/init.ts b/packages/squad-cli/src/cli/core/init.ts index 3280e3a1b..1c1e71eb8 100644 --- a/packages/squad-cli/src/cli/core/init.ts +++ b/packages/squad-cli/src/cli/core/init.ts @@ -17,6 +17,12 @@ import { liftInitMutableStateOntoOrphan } from '../commands/migrate-backend.js'; import { resolveSquadStateMcpSpec } from './mcp-spec.js'; import { describeMcpSpec } from './upgrade.js'; import { ensureSquadStateMcpInRoot, tombstoneStaleSquadStateInProjectMcp } from './mcp-root.js'; +import { + readTeamMd, + writeTeamMd, + hasCopilot, + insertCopilotSection, +} from './team-md.js'; const storage = new FSStorageProvider(); @@ -226,6 +232,11 @@ export async function runInit(dest: string, options: RunInitOptions = {}): Promi name: 'Rai', role: 'Rai', displayName: 'Rai', + }, + { + name: 'fact-checker', + role: 'fact-checker', + displayName: 'Fact Checker', } ], configFormat: options.sdk ? 'sdk' : 'markdown', @@ -239,6 +250,7 @@ export async function runInit(dest: string, options: RunInitOptions = {}): Promi prompt: options.prompt, extractionDisabled: options.extractionDisabled, roles: options.roles, + stateBackend: options.stateBackend, }; // Handle SIGINT to cleanup orphan .init-prompt @@ -356,7 +368,8 @@ export async function runInit(dest: string, options: RunInitOptions = {}): Promi // leaving the bridge unwired. Force-insert/pin the squad_state entry so // the MCP server is reachable regardless of pre-existing config. // iter-8: write squad_state to repo-root `.mcp.json` (auto-loaded by - // Copilot CLI 5.3+ walking up from cwd to git root) and tombstone any + // Copilot CLI ≥1.0.59, which walks up from cwd to git root finding + // .mcp.json files — see sdk/index.js loader) and tombstone any // stale project-level entry left by the SDK init writer in // `.copilot/mcp-config.json`. No HOME modifications. try { @@ -388,6 +401,13 @@ export async function runInit(dest: string, options: RunInitOptions = {}): Promi if (rootResult.written) { success(`installed squad_state MCP server to .mcp.json (${describeMcpSpec(mcpSpec)}) — Copilot CLI will auto-load on next invocation`); } + // iter-8: do NOT write to ~/.copilot/mcp-config.json. The repo-root + // .mcp.json write above is sufficient for Copilot CLI ≥1.0.59 (which + // walks up from cwd to git root looking for .mcp.json) AND for + // `copilot -p` invocations launched from the project root. Users who + // launch `copilot -p` from outside the project root should use + // `--additional-mcp-config @.mcp.json` (already documented at the end + // of this command). See bradygaster/squad#1296. const tomb = tombstoneStaleSquadStateInProjectMcp(dest); if (tomb.removed) { success(`removed stale squad_state from ${tomb.path} (now lives in .mcp.json)`); @@ -413,6 +433,48 @@ export async function runInit(dest: string, options: RunInitOptions = {}): Promi console.log(`${DIM}${file} already exists — skipping${RESET}`); } + // ── Copilot agent prompt ─────────────────────────────────────────── + // Ask if the user wants to add @copilot as an autonomous team member. + // This enables .github/copilot-instructions.md and adds Coding Agent + // to the team roster, allowing squad-labeled issues to be auto-assigned. + const teamContent = readTeamMd(squadDir); + if (!hasCopilot(teamContent)) { + if (process.stdin.isTTY) { + const { createInterface } = await import('node:readline/promises'); + const rl = createInterface({ input: process.stdin, output: process.stdout }); + try { + console.log(); + const answer = (await rl.question(` Add ${BOLD}@copilot${RESET} as an autonomous team member? [Y/n]: `)).trim().toLowerCase(); + if (answer === '' || answer === 'y' || answer === 'yes') { + const updated = insertCopilotSection(teamContent, false); + writeTeamMd(squadDir, updated); + success('Added @copilot (Coding Agent) to team roster'); + + // Copy copilot-instructions.md from templates + const currentFileUrl = new URL(import.meta.url); + const currentFilePath = currentFileUrl.pathname.startsWith('/') && process.platform === 'win32' + ? currentFileUrl.pathname.substring(1) + : currentFileUrl.pathname; + const templatesSrc = path.resolve(path.dirname(currentFilePath), '..', '..', '..', 'templates'); + const instructionsSrc = path.join(templatesSrc, 'copilot-instructions.md'); + const instructionsDest = path.join(dest, '.github', 'copilot-instructions.md'); + + if (storage.existsSync(instructionsSrc) && !storage.existsSync(instructionsDest)) { + storage.mkdirSync(path.dirname(instructionsDest), { recursive: true }); + storage.copySync(instructionsSrc, instructionsDest); + success('.github/copilot-instructions.md'); + } + } else { + console.log(`${DIM} Skipped — add later with: squad copilot enable${RESET}`); + } + } finally { + rl.close(); + } + } else { + console.log(`${DIM} Non-interactive mode — skipping @copilot setup. Add later with: squad copilot enable${RESET}`); + } + } + // ── Celebration ceremony ────────────────────────────────────────── console.log(); await typewrite(`${CYAN}${BOLD}◆ SQUAD${RESET}`, 10); @@ -429,7 +491,7 @@ export async function runInit(dest: string, options: RunInitOptions = {}): Promi console.log(`${GREEN}${BOLD}Squad initialized.${RESET} Run ${CYAN}${BOLD}copilot --agent squad${RESET} and tell it what you're building.`); console.log(); console.log(`${DIM}Tip: for non-interactive scripts that need squad_state tools, add to package.json:${RESET}`); - console.log(`${DIM} "squad:copilot": "copilot --additional-mcp-config @.mcp.json"${RESET}`); + console.log(`${DIM} "squad:copilot": "copilot --agent squad --additional-mcp-config @.mcp.json"${RESET}`); console.log(); // ── Personal squad bridge ─────────────────────────────────────────── diff --git a/packages/squad-cli/src/cli/core/mcp-root.ts b/packages/squad-cli/src/cli/core/mcp-root.ts index ed8da25f5..ddb276974 100644 --- a/packages/squad-cli/src/cli/core/mcp-root.ts +++ b/packages/squad-cli/src/cli/core/mcp-root.ts @@ -9,7 +9,7 @@ * * iter-8 flips it back inside the project: we write `squad_state` to a * repo-root `.mcp.json` under the plain (un-namespaced) `squad_state` - * key. Copilot CLI 5.3+ auto-loads `.mcp.json` walking up from cwd to + * key. Copilot CLI ≥1.0.59 auto-loads `.mcp.json` walking up from cwd to * the git root, so the entry is picked up by bare * `copilot --yolo --autopilot --agent squad ...` invocations with no * wrapper script and no HOME modifications. @@ -162,3 +162,86 @@ export function tombstoneStaleSquadStateInProjectMcp(dest: string): TombstoneRes storage.writeSync(cfgPath, JSON.stringify(config, null, 2) + '\n'); return { removed: true, path: cfgPath }; } + +/** + * Pin `squad_state` to user-level `~/.copilot/mcp-config.json` so that + * external `copilot -p` invocations (which skip workspace `.mcp.json` due + * to the folder-trust security gate) still have access to squad_state tools. + * + * Unlike the repo-root writer, this writes a SINGLE entry keyed by the + * project's absolute path hash to avoid collisions when multiple Squad + * projects coexist. The key format is `squad_state_`. + * + * Best-effort: silently no-ops on parse failure to avoid corrupting the + * user's hand-edited config. + */ +export function ensureSquadStateMcpInUserConfig( + dest: string, + spec: SquadStateMcpSpec, +): EnsureRootResult { + const homedir = process.env.HOME || process.env.USERPROFILE || ''; + if (!homedir) return { written: false, key: '', path: '' }; + + const cfgPath = path.join(homedir, '.copilot', 'mcp-config.json'); + // Stable short hash of the project path for the key suffix + const hash = simpleHash(path.resolve(dest)); + const key = `squad_state_${hash}`; + + let parsed: McpConfigShape; + if (storage.existsSync(cfgPath)) { + const raw = storage.readSync(cfgPath) ?? '{}'; + try { + const obj = JSON.parse(raw) as unknown; + if (!obj || typeof obj !== 'object' || Array.isArray(obj)) { + return { written: false, key, path: cfgPath }; + } + parsed = obj as McpConfigShape; + } catch { + return { written: false, key, path: cfgPath }; + } + } else { + parsed = {}; + } + + if (!parsed.mcpServers || typeof parsed.mcpServers !== 'object') { + parsed.mcpServers = {}; + } + + const existing = parsed.mcpServers[key]; + const desired: McpServerEntry = { + command: spec.command, + args: [...spec.args], + env: {}, + tools: ['*'], + }; + + if ( + existing && + existing.command === desired.command && + Array.isArray(existing.args) && + existing.args.length === desired.args!.length && + existing.args.every((a, i) => a === desired.args![i]) + ) { + return { written: false, key, path: cfgPath }; + } + + parsed.mcpServers[key] = desired; + + // Ensure parent directory exists + const cfgDir = path.dirname(cfgPath); + if (!storage.existsSync(cfgDir)) { + storage.mkdirSync(cfgDir, { recursive: true }); + } + + storage.writeSync(cfgPath, JSON.stringify(parsed, null, 2) + '\n'); + return { written: true, key, path: cfgPath }; +} + +/** Simple deterministic 8-char hex hash for path-based keys. */ +function simpleHash(input: string): string { + let h = 0; + for (let i = 0; i < input.length; i++) { + h = ((h << 5) - h + input.charCodeAt(i)) | 0; + } + return (h >>> 0).toString(16).padStart(8, '0'); +} diff --git a/packages/squad-cli/src/cli/core/mcp-spec.ts b/packages/squad-cli/src/cli/core/mcp-spec.ts index fac175547..8c7aae55a 100644 --- a/packages/squad-cli/src/cli/core/mcp-spec.ts +++ b/packages/squad-cli/src/cli/core/mcp-spec.ts @@ -58,8 +58,9 @@ export async function resolveSquadStateMcpSpec( options: ResolveSquadStateMcpSpecOptions = {}, ): Promise { // 1. Try the pinned version on the public registry. Skip for placeholder - // versions ('', '0.0.0') — the registry will obviously not have them. - if (cliVersion && cliVersion !== '0.0.0') { + // versions ('', '0.0.0') and local/dev builds with build metadata ('+') + // — the registry will obviously not have them (#1204). + if (cliVersion && cliVersion !== '0.0.0' && !cliVersion.includes('+')) { const probe = options.publishedCheck ?? defaultPublishedCheck; const published = await probe(cliVersion); if (published) { diff --git a/packages/squad-cli/src/cli/core/templates.ts b/packages/squad-cli/src/cli/core/templates.ts index 7917ca34c..0745b9c1c 100644 --- a/packages/squad-cli/src/cli/core/templates.ts +++ b/packages/squad-cli/src/cli/core/templates.ts @@ -153,6 +153,12 @@ export const TEMPLATE_MANIFEST: TemplateFile[] = [ overwriteOnUpgrade: true, description: 'Fact checker charter template', }, + { + source: 'fact-checker-policy.md', + destination: 'templates/fact-checker-policy.md', + overwriteOnUpgrade: true, + description: 'Fact checker policy template (verification + DA methodology)', + }, { source: 'skill.md', destination: 'templates/skill.md', @@ -199,64 +205,100 @@ export const TEMPLATE_MANIFEST: TemplateFile[] = [ // Skills subdirectory (squad-owned) { source: 'skills/squad-conventions/SKILL.md', - destination: '../.copilot/skills/squad-conventions/SKILL.md', + destination: '../.github/skills/squad-conventions/SKILL.md', overwriteOnUpgrade: true, description: 'Squad conventions skill definition', }, { source: 'skills/error-recovery/SKILL.md', - destination: '../.copilot/skills/error-recovery/SKILL.md', + destination: '../.github/skills/error-recovery/SKILL.md', overwriteOnUpgrade: true, description: 'Graceful error recovery patterns', }, { source: 'skills/secret-handling/SKILL.md', - destination: '../.copilot/skills/secret-handling/SKILL.md', + destination: '../.github/skills/secret-handling/SKILL.md', overwriteOnUpgrade: true, description: 'Secrets management and credential safety', }, { source: 'skills/git-workflow/SKILL.md', - destination: '../.copilot/skills/git-workflow/SKILL.md', + destination: '../.github/skills/git-workflow/SKILL.md', overwriteOnUpgrade: true, description: 'Git workflow conventions and branch management', }, { source: 'skills/session-recovery/SKILL.md', - destination: '../.copilot/skills/session-recovery/SKILL.md', + destination: '../.github/skills/session-recovery/SKILL.md', overwriteOnUpgrade: true, description: 'Session checkpoint and recovery patterns', }, { source: 'skills/reviewer-protocol/SKILL.md', - destination: '../.copilot/skills/reviewer-protocol/SKILL.md', + destination: '../.github/skills/reviewer-protocol/SKILL.md', overwriteOnUpgrade: true, description: 'Code review protocol and reviewer gate patterns', }, { source: 'skills/test-discipline/SKILL.md', - destination: '../.copilot/skills/test-discipline/SKILL.md', + destination: '../.github/skills/test-discipline/SKILL.md', overwriteOnUpgrade: true, description: 'Test-first discipline and coverage expectations', }, { source: 'skills/agent-collaboration/SKILL.md', - destination: '../.copilot/skills/agent-collaboration/SKILL.md', + destination: '../.github/skills/agent-collaboration/SKILL.md', overwriteOnUpgrade: true, description: 'Multi-agent collaboration and handoff patterns', }, { - source: 'skills/squad-commands/SKILL.md', - destination: '../.copilot/skills/squad-commands/SKILL.md', + source: 'skills/squad/SKILL.md', + destination: '../.github/skills/squad/SKILL.md', overwriteOnUpgrade: true, - description: 'In-chat command discovery — categorized menu of Squad operations', + description: 'Squad command catalog — invokable via /squad slash command', }, { source: 'skills/squad-version-check/SKILL.md', - destination: '../.copilot/skills/squad-version-check/SKILL.md', + destination: '../.github/skills/squad-version-check/SKILL.md', overwriteOnUpgrade: true, description: 'Squad CLI internals — version stamping & upgrade mechanics', }, + { + source: 'skills/squad-help/SKILL.md', + destination: '../.github/skills/squad-help/SKILL.md', + overwriteOnUpgrade: true, + description: 'How to actually use Squad — agent vs skill vs slash command (#1297 redirect)', + }, + { + source: 'skills/cross-squad-communication/SKILL.md', + destination: '../.github/skills/cross-squad-communication/SKILL.md', + overwriteOnUpgrade: true, + description: 'Cross-squad delegation — calling another squad as a sub-agent', + }, + { + source: 'skills/tiered-memory/SKILL.md', + destination: '../.github/skills/tiered-memory/SKILL.md', + overwriteOnUpgrade: true, + description: 'Tiered-memory access patterns (hot/cold/wiki)', + }, + { + source: 'skills/iterative-retrieval/SKILL.md', + destination: '../.github/skills/iterative-retrieval/SKILL.md', + overwriteOnUpgrade: true, + description: 'Iterative retrieval for long-running squad work', + }, + { + source: 'skills/reflect/SKILL.md', + destination: '../.github/skills/reflect/SKILL.md', + overwriteOnUpgrade: true, + description: 'Reflection skill for capturing session learnings', + }, + { + source: 'skills/cross-squad/SKILL.md', + destination: '../.github/skills/cross-squad/SKILL.md', + overwriteOnUpgrade: true, + description: 'Cross-squad discovery — finding peer squads via registry/upstream', + }, // Session init reference (squad-owned, coordinator reads at session start) { diff --git a/packages/squad-cli/src/cli/core/upgrade.ts b/packages/squad-cli/src/cli/core/upgrade.ts index c8a74b608..67216c88e 100644 --- a/packages/squad-cli/src/cli/core/upgrade.ts +++ b/packages/squad-cli/src/cli/core/upgrade.ts @@ -29,14 +29,37 @@ interface McpServerSpec { env?: Record; } +/** + * Returns true if the version looks like a local dev build or unpublished + * pre-release that cannot be resolved from the public npm registry. + * Guards against writing unresolvable version strings into MCP config + * (see #1204). + */ +export function isLocalOrUnpublishedVersion(version: string): boolean { + if (!version || version === '0.0.0') return true; + // Local linked builds often have 0.0.0-development or similar sentinel + if (/^0\.0\.0/.test(version)) return true; + // Versions with `+` build metadata (e.g. 0.10.0+local.1234) are not + // publishable to npm — they indicate a local build. + if (version.includes('+')) return true; + return false; +} + function buildMcpServerSpecs(isGitHub: boolean, cliVersion?: string): McpServerSpec[] { // Pin the squad-cli package to the currently-installed CLI version so that // `npx -y @bradygaster/squad-cli state-mcp` does NOT silently resolve to the // npm `latest` dist-tag (which may predate the `state-mcp` command and thus // expose zero tools to Copilot — see MCP-BRIDGE-BROKEN root cause). - const pkgSpec = cliVersion && cliVersion !== '0.0.0' - ? `@bradygaster/squad-cli@${cliVersion}` - : '@bradygaster/squad-cli'; + // + // #1204: When the CLI is a local dev build or unpublished pre-release, fall + // back to the @insider dist-tag to avoid writing an unresolvable version + // string that breaks npx resolution at session start. + let pkgSpec: string; + if (!cliVersion || isLocalOrUnpublishedVersion(cliVersion)) { + pkgSpec = '@bradygaster/squad-cli@insider'; + } else { + pkgSpec = `@bradygaster/squad-cli@${cliVersion}`; + } const servers: McpServerSpec[] = [ { name: 'squad_state', @@ -444,7 +467,7 @@ const ENSURE_DIRECTORIES = [ '.squad/decisions/inbox', '.squad/casting', '.squad/agents', - '.copilot/skills', + '.github/skills', ]; /** @@ -601,7 +624,78 @@ function warnIfSkillCustomized(srcPath: string, destPath: string, sourceName: st } /** - * Sync manifest-declared skills to .copilot/skills/, respecting overwriteOnUpgrade. + * Migrate skills from the legacy `.copilot/skills//` location to the + * canonical `.github/skills//`. Only migrates skills that appear in + * `TEMPLATE_MANIFEST` (manifest-curated Squad skills) — does NOT touch + * user-added skills in `.copilot/skills/`. After successful migration, + * removes the now-empty `.copilot/skills//` directories. + * + * Idempotent: when a skill already exists at the new location the legacy + * copy is tombstoned without comparing content — this protects any + * in-place customization the user made at `.github/skills//` from + * being clobbered, and means re-running upgrade is a no-op on the new + * location. If only the legacy copy exists, it is moved over. + * + * See bradygaster/squad#1126 (canonical issue) — this is the migration + * piece of that fix; #1304 is the PR that implements it. + */ +function migrateLegacyCopilotSkills(dest: string): { migrated: string[]; tombstoned: string[] } { + const legacyDir = path.join(dest, '.copilot', 'skills'); + if (!storage.existsSync(legacyDir)) return { migrated: [], tombstoned: [] }; + + const manifestSkills = new Set( + TEMPLATE_MANIFEST + .filter(f => f.source.startsWith('skills/')) + .map(f => f.source.split('/')[1]) // 'skills/foo/SKILL.md' -> 'foo' + .filter((s): s is string => Boolean(s)), + ); + + const newDir = path.join(dest, '.github', 'skills'); + const migrated: string[] = []; + const tombstoned: string[] = []; + + for (const entry of storage.listSync(legacyDir)) { + if (!manifestSkills.has(entry)) continue; // leave user-added skills alone + const legacySkillDir = path.join(legacyDir, entry); + if (!storage.isDirectorySync(legacySkillDir)) continue; + + const newSkillDir = path.join(newDir, entry); + if (storage.existsSync(newSkillDir)) { + // New location already has it — drop the legacy copy without overwriting. + try { + storage.deleteDirSync(legacySkillDir); + tombstoned.push(path.posix.join('.copilot/skills', entry)); + } catch { + // best-effort; leave the legacy dir if we can't remove it + } + continue; + } + + try { + storage.mkdirSync(newDir, { recursive: true }); + copyDirRecursive(legacySkillDir, newSkillDir); + storage.deleteDirSync(legacySkillDir); + migrated.push(path.posix.join('.github/skills', entry)); + } catch { + // best-effort; leave the legacy dir if anything fails + } + } + + // If the legacy `.copilot/skills/` directory is now empty, remove it too. + try { + if (storage.existsSync(legacyDir) && storage.listSync(legacyDir).length === 0) { + storage.deleteDirSync(legacyDir); + tombstoned.push('.copilot/skills'); + } + } catch { + // best-effort + } + + return { migrated, tombstoned }; +} + +/** + * Sync manifest-declared skills to .github/skills/, respecting overwriteOnUpgrade. * Only skills listed in TEMPLATE_MANIFEST are installed — not the entire templates/skills/ dir. */ function syncAllSkills(dest: string, templatesDir: string): number { @@ -679,9 +773,25 @@ async function runEnsureChecks(dest: string, templatesDir: string, filesUpdated: filesUpdated.push(...memoryFiles); } + const builtinAgents = ensureBuiltinAgents(dest, templatesDir); + if (builtinAgents.length > 0) { + const uniqueAgentNames = Array.from(new Set(builtinAgents.map(p => path.basename(path.dirname(p))))); + success(`scaffolded ${uniqueAgentNames.length} built-in agent(s): ${uniqueAgentNames.join(', ')}`); + filesUpdated.push(...builtinAgents); + } + + const skillMigration = migrateLegacyCopilotSkills(dest); + if (skillMigration.migrated.length > 0) { + success(`migrated ${skillMigration.migrated.length} skill(s) from .copilot/skills/ → .github/skills/`); + filesUpdated.push(...skillMigration.migrated); + } + if (skillMigration.tombstoned.length > 0) { + success(`removed ${skillMigration.tombstoned.length} stale .copilot/skills entries (now live at .github/skills/)`); + } + const skillCount = syncAllSkills(dest, templatesDir); if (skillCount > 0) { - success(`synced ${skillCount} skills to .copilot/skills/`); + success(`synced ${skillCount} skills to .github/skills/`); filesUpdated.push(`skills (${skillCount})`); } @@ -690,9 +800,10 @@ async function runEnsureChecks(dest: string, templatesDir: string, filesUpdated: filesUpdated.push('.squad/templates/'); // iter-8: write squad_state MCP entry to repo-root `.mcp.json` - // (auto-loaded by Copilot CLI 5.3+ walking up from cwd to git root) - // and tombstone any stale project-level entry left by older Squad - // versions in `.copilot/mcp-config.json`. No HOME modifications. + // (auto-loaded by Copilot CLI ≥1.0.59, which walks up from cwd to the + // git root looking for .mcp.json) and tombstone any stale project-level + // entry left by older Squad versions in `.copilot/mcp-config.json`. + // No HOME modifications. const pinnedSpec = await resolveSquadStateMcpSpec(getPackageVersion()); try { const rootResult = ensureSquadStateMcpInRoot(dest, getPackageVersion(), pinnedSpec); @@ -703,6 +814,11 @@ async function runEnsureChecks(dest: string, templatesDir: string, filesUpdated: } catch (err) { warn(`Could not write .mcp.json: ${err instanceof Error ? err.message : err}`); } + // iter-8: do NOT write to ~/.copilot/mcp-config.json on upgrade. The + // repo-root .mcp.json write above is sufficient for Copilot CLI ≥1.0.59 + // (walks from cwd up looking for .mcp.json) AND for `copilot -p` from + // the project root. Out-of-tree `copilot -p` should use + // `--additional-mcp-config @.mcp.json`. See bradygaster/squad#1296. const tomb = tombstoneStaleSquadStateInProjectMcp(dest); if (tomb.removed) { success(`removed stale squad_state from ${tomb.path} (now lives in .mcp.json)`); @@ -757,6 +873,77 @@ export function ensureMemoryGovernanceUpgradeDefaults(dest: string): string[] { return created; } +/** + * Scaffold always-on built-in agent charters (Rai, Fact Checker) that ship + * as templates but may be missing from older squads. Idempotent — only writes + * when the agent directory is absent. Never overwrites existing charters or + * history. Sources charter content from the shipped `templates/{name}-charter.md` + * files when available, falling back to a minimal placeholder otherwise. + * + * Scribe and Ralph are intentionally NOT scaffolded here — they should already + * exist in any squad that ran a prior init, and their charters are inlined in + * cast.ts (no shipped template file). Adding them here would risk overwriting + * customized versions on legacy squads. + * + * @param dest Root directory containing .squad/ + * @param templatesDir Directory containing shipped charter templates + * @returns Paths (relative to dest) of created agent files + */ +export function ensureBuiltinAgents(dest: string, templatesDir: string): string[] { + const agentsDir = path.join(dest, '.squad', 'agents'); + if (!storage.existsSync(agentsDir)) { + storage.mkdirSync(agentsDir, { recursive: true }); + } + + // Built-in agents that ship as charter templates. Each entry maps to: + // - dirName: case-preserving directory name under .squad/agents/ + // - templateFile: filename under templatesDir (charter template) + // - displayName: shown in history.md header + const builtins: Array<{ dirName: string; templateFile: string; displayName: string }> = [ + { dirName: 'Rai', templateFile: 'Rai-charter.md', displayName: 'Rai' }, + { dirName: 'fact-checker', templateFile: 'fact-checker-charter.md', displayName: 'Fact Checker' }, + ]; + + const created: string[] = []; + for (const agent of builtins) { + const agentDir = path.join(agentsDir, agent.dirName); + const charterPath = path.join(agentDir, 'charter.md'); + const historyPath = path.join(agentDir, 'history.md'); + + // Idempotent: skip if agent directory already exists. Never overwrite + // existing charters or history files (preserves user customization). + if (storage.existsSync(agentDir)) continue; + + // Source charter content from the shipped template; fall back to a + // minimal placeholder if the template file is missing (defensive — should + // not happen in a well-formed install, but better than crashing upgrade). + const tplPath = path.join(templatesDir, agent.templateFile); + let charterContent: string; + if (storage.existsSync(tplPath)) { + charterContent = storage.readSync(tplPath) ?? ''; + } else { + charterContent = `# ${agent.displayName}\n\n> Charter template not found in shipped templates. Run \`squad upgrade\` after reinstalling the CLI to repair.\n`; + } + + try { + storage.mkdirSync(agentDir, { recursive: true }); + storage.writeSync(charterPath, charterContent); + created.push(path.join('.squad', 'agents', agent.dirName, 'charter.md')); + + storage.writeSync(historyPath, `# ${agent.displayName} — History\n\n## Learnings\n\nInitial scaffold via \`squad upgrade\`. Ready for work.\n`); + created.push(path.join('.squad', 'agents', agent.dirName, 'history.md')); + } catch (err: unknown) { + if (err instanceof Error && 'code' in err && ['EPERM', 'EACCES'].includes((err as NodeJS.ErrnoException).code ?? '')) { + warn(`Could not scaffold built-in agent ${agent.displayName} (read-only). Create .squad/agents/${agent.dirName}/ manually.`); + continue; + } + throw err; + } + } + + return created; +} + /** * Run the upgrade command */ diff --git a/packages/squad-cli/templates/fact-checker-policy.md b/packages/squad-cli/templates/fact-checker-policy.md new file mode 100644 index 000000000..5cf43ee7c --- /dev/null +++ b/packages/squad-cli/templates/fact-checker-policy.md @@ -0,0 +1,104 @@ +# Fact Checker Policy + +> Authoritative verification & devil's-advocate methodology for this project. Fact Checker enforces these standards. + +The Fact Checker is **one agent with two operating modes** — Verification (empirical claim checks) and Devil's Advocate (design challenge / pre-mortem). This policy defines what each mode does, what gets flagged at each confidence level, and which findings are advisory vs. blocking. + +--- + +## Mode 1: Verification + +Empirical check of claims against sources. Triggered by `"fact-check this"`, `"verify these claims"`, `"is this true?"`, Pre-Ship ceremony, or after any agent produces external references. + +### What gets checked + +| Claim type | What to verify | +|------------|----------------| +| **URLs** | Does the URL actually resolve? (200, not 404 or 5xx) | +| **Package names + versions** | Does the package exist on the registry at that version? | +| **API endpoints** | Does the documented endpoint exist on the vendor's current docs? | +| **File paths** | Does the file exist in the repo at the claimed path? | +| **Function / type signatures** | Do they match the actual source? | +| **Quoted text** | Does the source actually contain the quoted text verbatim? | +| **Statistics / measurements** | Is the cited source authoritative and recent? | +| **Cross-references to team decisions** | Does `.squad/decisions.md` actually say what was claimed? | + +### Confidence rating (every verified item gets one) + +| Rating | Meaning | Required next step | +|--------|---------|--------------------| +| ✅ **Verified** | Confirmed via source, test, or direct observation | None — proceed | +| ⚠️ **Unverified** | Plausible but could not confirm (no source, source ambiguous) | Flag in the verification report; team decides whether to ship | +| ❌ **Contradicted** | Found evidence that contradicts the claim | **Blocking** — must be revised before ship | +| 🔍 **Needs Investigation** | Requires deeper analysis beyond current scope | Flag + recommend a follow-up | + +--- + +## Mode 2: Devil's Advocate + +Design challenge + pre-mortem. Triggered by `"play devil's advocate"`, `"what's wrong with this plan?"`, `"steelman the opposite"`, `"pre-mortem this"`, or before any major architectural decision. + +### What gets produced (every DA brief) + +1. **Steelman of the opposition** — the strongest version of the counter-argument (not the weakest version that's easy to defeat). +2. **Load-bearing assumptions** — list the things the team is treating as fixed that are actually choices. *"We assumed we had to use Postgres — what if we couldn't?"* +3. **Pre-mortem** — concrete failure scenario in 30 days. *"Imagine this shipped and failed. Write the post-mortem now."* +4. **Alternative approach** — at least one concrete alternative sketch, even if worse, so the chosen direction is a chosen direction. +5. **Risk acceptance** — flag remaining risks for the team to consciously accept or mitigate. Never a veto. + +--- + +## Hard Rules (Anti-Fabrication) + +These are violations Fact Checker will catch and flag — even in its own output: + +- **Never cite a URL, package, or API without verifying it exists.** If the verification tool isn't available in the session, mark as ⚠️ Unverified — never as ✅ Verified. +- **Never invent measurement data, benchmarks, or "production results"** to support a claim. Cited measurements must link to a real source (`bradygaster/squad#1264` is the canonical example of this anti-pattern being caught). +- **Never fabricate a counter-hypothesis** for Devil's Advocate mode. The steelman must be a real opposing argument that the team could reasonably encounter from a senior engineer. +- **Never block on opinion.** Devil's Advocate flags risks; it does not veto. Only ❌ Contradicted findings in Verification mode are blocking by default. + +--- + +## Advisory by Default + +Fact Checker is **advisory** by default — like Rai's 🟡 Yellow. Findings are surfaced; the team or coordinator decides whether to act. + +Two exceptions where Fact Checker becomes a **blocking gate**: + +1. **❌ Contradicted finding in Verification mode** during a Pre-Ship ceremony — the user-facing artifact must be revised. +2. **Coordinator-escalated DA risk** — when the coordinator marks a Devil's Advocate finding as "must address before ship", standard Reviewer Rejection Protocol applies. + +--- + +## Opt-Out Model + +- **Cannot disable** the anti-fabrication hard rules above. They are framework-level guarantees. +- **Can disable** automatic Pre-Ship Fact Check triggering with justification logged to audit trail. +- **Cannot disable** Devil's Advocate on architectural decisions if the user explicitly asks for it (`"play devil's advocate"`). +- **Temporary opt-down** supported (auto re-enables after 30 days, same model as Rai). + +--- + +## Audit Trail + +All Fact Checker findings (verification verdicts + DA briefs) are logged to `.squad/fact-checker/audit-trail.md` (append-only). Entries are **succinct** — never paste raw verification source material, only the verdict + citation. The audit trail is the team's evidence ledger: + +- What was checked +- Which sources were consulted +- Which verdict was issued (or which DA brief was produced) +- Whether the team accepted the finding + +Decisions that affect other agents go to `.squad/decisions/inbox/fact-checker-{slug}.md` for Scribe to merge into `.squad/decisions.md`. + +--- + +## Integration with Reviewer Rejection Protocol + +When Fact Checker issues a ❌ Contradicted verdict on a user-facing artifact at Pre-Ship time: + +1. **Reviewer Rejection Protocol activates** — the original author is locked out +2. **Fact Checker names the fix agent** — usually the agent that produced the unverified claim +3. **Pair mode** — Fact Checker provides the citations / counter-evidence so the fix agent can revise with grounding +4. **Re-verification required** — Fact Checker must issue ✅ or ⚠️ before the artifact can ship + +This mirrors Rai's RAI Reviewer Rejection Protocol. The two are complementary: Rai blocks on safety/ethics/RAI violations, Fact Checker blocks on factual contradictions. diff --git a/packages/squad-cli/templates/skills/cross-squad-communication/SKILL.md b/packages/squad-cli/templates/skills/cross-squad-communication/SKILL.md new file mode 100644 index 000000000..9286d5fa2 --- /dev/null +++ b/packages/squad-cli/templates/skills/cross-squad-communication/SKILL.md @@ -0,0 +1,399 @@ +--- +name: "cross-squad-communication" +description: "Protocol for sending queries, delegating tasks, and sharing context between independent Squad instances across different repositories" +domain: "multi-repo coordination" +confidence: "medium" +source: "Ported from tamirdresher/squad-skills (plugins/cross-squad-communication). Companion to the registry-aware cross-squad skill — this one teaches the actual communication protocols once a peer is discovered. Pattern 0 (synchronous CLI) is the only end-to-end-validated pattern; Patterns 1, 2, 3 are documented from design but require live validation against your own setup before relying on them in production. See the Validation Status section at the bottom of this skill." +--- + +## Context + +When multiple repositories each have their own Squad (AI team), they need to exchange information: knowledge queries, PR reviews, task delegation, and dependency analysis. Each squad has its own agents, MCP tools, and issue tracker — there is no shared runtime. + +> **Companion skill — read first:** `cross-squad/SKILL.md` covers **discovery** of peer squads via `squad registry add/list/remove`. This skill picks up after a peer is known and covers the **communication protocols** themselves — the four numbered patterns below: Pattern 0 (synchronous CLI), Pattern 1 (read-only knowledge query), Pattern 2 (git-based async), and Pattern 3 (GitHub-issue-based delegation). A separate non-numbered appendix (Cross-Repo Dependency Scan) is provided as a related analysis tool, not a communication pattern. The two skills are designed to be used together. + +**When this skill applies:** +- A squad agent needs information from another squad-enabled repo +- A task needs to be delegated to another squad +- Cross-repo dependency analysis is needed +- PR review requests span repo boundaries + +**Key constraint:** Each squad has its own runtime, MCP tools, and issue tracker. Cross-squad communication can be **synchronous** (via CLI session targeting the other repo) or **asynchronous** (file-based or issue-based). The coordinator decides which approach fits. + +--- + +## Patterns + +### Decision Tree: Choosing the Right Pattern + +``` +Is the target repo cloned locally? +├─ NO → Use Pattern 3 (Issue-Based) or Pattern 2 (Git-Based Async) +└─ YES + ├─ Is this a quick query / knowledge lookup? + │ └─ YES → Use Pattern 0 (Synchronous CLI) — fastest + ├─ Does the work need to persist as artifacts? + │ └─ YES → Use Pattern 2 (Git-Based Async) + ├─ Is it a long-running analysis or multi-cycle task? + │ └─ YES → Use Pattern 2 (Git-Based Async) + └─ Is the target squad's Ralph running? + ├─ YES → Pattern 2 or 3 (async processing available) + └─ NO → Pattern 0 (Synchronous CLI) or Pattern 1 (Read-Only) +``` + +--- + +## Universal rule: every `copilot` spawn into a peer squad MUST pass `--agent squad` + +The `copilot` CLI accepts `--agent ` to select a custom agent (see `copilot --help`). Squad installs ship `.github/agents/squad.agent.md`, which is loaded **only when `--agent squad` is specified**. Without it the spawned session runs as a generic Copilot CLI session that does NOT load the peer's `team.md`, routing, MCP tools, casting, or coordinator behaviour — so you get an off-the-shelf model answering, not the peer's Squad. **Every command example in this skill that spawns `copilot` into a peer repo includes `--agent squad`; do not strip it.** + +This rule also applies anywhere else you spawn `copilot` into a Squad-initialised repo (not just cross-squad protocols) — e.g., `squad init`'s post-init tip and any automation that invokes the CLI on a squadified folder. The only case where you may omit `--agent` is when resuming an existing session (`copilot --resume `) — the resumed session preserves its original agent context. + +--- + +### Pattern 0: Synchronous CLI Session (Fastest for Interactive Queries) + +For quick knowledge queries, decision lookups, or short analyses — spawn a Copilot CLI session with the working directory set to the target squad's repo. This lets you send a prompt and get a response within the same session, using the target repo's full context. + +This is the same technique used by `ralph-watch.ps1`: write the prompt to a temp file, then invoke the CLI with that file as input. The key insight is that setting the working directory to the target repo gives the CLI session access to that squad's `.squad/` metadata, codebase, and conventions. + +**Protocol:** +1. Write prompt to a temp file (avoids argument-splitting issues, as learned in `ralph-watch.ps1`) +2. Read the file into a string and invoke `copilot -p ` with `-C ` set to the target repo (`-p` takes prompt text, NOT a file path) AND `--agent squad` so the spawned session uses the peer squad's coordinator (without `--agent` you get a generic Copilot CLI session that doesn't load the peer's `team.md`, MCP tools, or skills) +3. Receive response in the same session + +**Invocation:** +```powershell +# Spawn a Copilot CLI session targeting another squad's repo +$targetRepo = "C:\repos\platform-squad-repo" +$promptFile = New-TemporaryFile +@" +You are working in a Squad-enabled repository. +Read .squad/team.md and .squad/decisions.md first. + +[CROSS-SQUAD REQUEST] +From: research-squad +Request Type: knowledge_query +Query: What is the current architecture of the platform? What services does it expose? +Response Format: Brief structured summary +"@ | Out-File $promptFile -Encoding utf8 + +# Option A: copilot with prompt file (read file into string; -p takes text, not a path) +# --agent squad is REQUIRED: the target is another Squad install, so the spawned +# session must use that squad's coordinator (not a generic Copilot CLI session). +copilot -C $targetRepo --agent squad -p (Get-Content $promptFile -Raw) --allow-all-tools + +# Option B: Start-Process for non-blocking (ralph-watch.ps1 style) +Start-Process pwsh -ArgumentList "-NoProfile -Command `"copilot -C '$targetRepo' --agent squad -p (Get-Content '$promptFile' -Raw) --allow-all-tools`"" -Wait + +# Option C: Pipe directly (stdin is the prompt text) +"What is the platform architecture?" | copilot -C $targetRepo --agent squad --allow-all-tools +``` + +**When to use synchronous vs async:** + +| Scenario | Pattern | Why | +|----------|---------|-----| +| Quick knowledge query | Synchronous CLI (Pattern 0) | Fast answer, no overhead | +| "What did you decide about X?" | Synchronous CLI (Pattern 0) | Read decisions.md via the target squad's context | +| PR review request | Either (Pattern 0 or 2/3) | Sync for quick feedback, async for thorough review | +| Task delegation (do work in their repo) | Async (Pattern 2 or 3) | Work needs to persist beyond the session | +| Long-running analysis | Async (Pattern 2) | May take multiple cycles | +| Target repo not locally cloned | Async (Pattern 3) | Can't set working directory to a remote repo | + +**The coordinator decides which pattern to use based on:** +1. Is the target repo cloned locally? → If yes, sync CLI is available +2. Is this a quick query or a long task? → Quick = sync, long = async +3. Does the work need to persist? → If yes, use async (creates artifacts) +4. Is the target squad's Ralph running? → Needed for async processing + +**Requirements:** +- Target repo must be cloned locally (for `copilot -C `) +- Target repo must be Squad-initialised (`.squad/config.json` + `.github/agents/squad.agent.md` present), so `--agent squad` resolves to the peer's coordinator +- Prompt file avoids argument-splitting bugs (see `ralph-watch.ps1` lines 2166-2184) + +**Response quality:** ⭐⭐⭐⭐⭐ — the CLI session has full context of the target repo, including code, squad metadata, and MCP tools. + +### Liveness Protocol for Pattern 0 + +The synchronous CLI session requires monitoring to avoid false timeouts. With 7+ MCP servers initializing and `.squad/` metadata being read, startup can take 30-60 seconds. A hard timeout kills valid sessions before they complete. Instead, monitor the agency session's activity log directory. + +**Health Check Approach:** + +Instead of a fixed wall-clock timeout, monitor the agency session log directory for activity: + +```powershell +# The Copilot CLI creates a session log directory at ~/.copilot/logs/. +# Older `agency` runtimes wrote to ~/.agency/logs/; fall back to that +# location if the new path doesn't exist yet on the user's machine. +# e.g., ~/.copilot/logs/session_20260325_071211_57824 +$copilotLogs = "$env:USERPROFILE\.copilot\logs" +$agencyLogs = "$env:USERPROFILE\.agency\logs" +$logRoot = if (Test-Path $copilotLogs) { $copilotLogs } elseif (Test-Path $agencyLogs) { $agencyLogs } else { $null } +if ($logRoot) { + $logDir = Get-ChildItem $logRoot -Directory | Sort-Object LastWriteTime -Descending | Select-Object -First 1 +} +$lastSize = 0 +$stallCount = 0 + +while ($proc -and -not $proc.HasExited) { + Start-Sleep -Seconds 15 + $currentSize = (Get-ChildItem $logDir -Recurse -File | Measure-Object -Property Length -Sum).Sum + + if ($currentSize -eq $lastSize) { + $stallCount++ + if ($stallCount -ge 4) { # 60s with no progress + Write-Warning "Session stalled — no log activity for 60s" + break + } + } else { + $stallCount = 0 + $lastSize = $currentSize + } +} +``` + +**Progress Indicators (What Counts as "Alive"):** + +- New files appearing in the session log directory (e.g., `transcript.log`, `mcp-server-logs/`) +- Log file size increasing (indicates active processing) +- New or modified `.squad/` files in the target repo (e.g., `decisions/inbox.md`, `identity/history.md`) +- Process still running and consuming non-idle CPU time + +**Stall Detection (When to Intervene):** + +- **No log activity for 60s** → Issue a warning; session may be slow but not hung +- **No log activity for 120s** → Likely stuck; consider terminating and checking logs +- **Process exited with non-zero exit code** → Failed; examine `transcript.log` and `stderr` for errors +- **MCP server connection timeout** → Session blocked waiting for an MCP server response + +**Recovery Actions When Stalled:** + +1. **Check for user input waiting:** Inspect logs for prompts or dialogs (shouldn't happen with `--autopilot`) +2. **Check MCP server health:** Review `mcp-server-logs/` for connection errors or timeouts +3. **Retry with `--disable-builtin-mcps` flag:** For lightweight queries that don't require MCP tools + ```powershell + # Retry without MCP servers — faster startup, limited capability + copilot -C $targetRepo --agent squad -p (Get-Content $promptFile -Raw) --disable-builtin-mcps --allow-all-tools + ``` +4. **Increase timeout threshold:** If MCP server initialization is consistently slow (>90s), raise threshold before declaring stall + +--- + +### Pattern 1: Read-Only Knowledge Query (No CLI Needed) + +For questions about another squad's architecture, decisions, or current state — read their `.squad/` metadata directly. + +**Protocol:** +1. Read target repo's `.squad/team.md` → get stack, members, issue source +2. Read `.squad/decisions.md` → get architectural decisions +3. Read `.squad/routing.md` → understand who handles what +4. Read `.squad/identity/now.md` → get current focus +5. Scan code structure if needed (csproj files, directory layout) + +**Requirements:** +- Target repo must be cloned locally or accessible via git +- No authentication needed beyond git read access + +**Example:** +```powershell +# Query another squad's architecture +$targetRepo = "C:\repos\platform-squad-repo" +Get-Content "$targetRepo\.squad\team.md" +Get-Content "$targetRepo\.squad\decisions.md" +Get-Content "$targetRepo\.squad\identity\now.md" +``` + +**Response quality:** ⭐⭐⭐⭐ — excellent for structural/architectural questions. + +--- + +### Pattern 2: Async Task Request (Git-Based) + +For work that needs the target squad to execute (PR reviews, issue analysis, code changes). + +**Protocol:** +1. Create request file in YOUR repo: `.squad/cross-squad/requests/{timestamp}-{target}-{id}.yaml` +2. Commit and push +3. Target squad's Ralph detects on next cycle +4. Target squad processes and writes response to their `.squad/cross-squad/responses/` +5. Your Ralph picks up the response + +**Request File Format:** +```yaml +id: req-2026-06-13-001 +source_squad: research-squad +source_repo: your-org/research-squad-repo +target_squad: platform-squad +target_repo: your-org/platform-squad-repo +request_type: knowledge_query | pr_review | task_delegation | dependency_check +priority: high | normal | low +created_at: 2026-06-13T10:00:00Z +query: "What is the current architecture of the platform?" +routing_hint: "lead" # optional — which agent should handle this +status: pending +``` + +**Response File Format:** +```yaml +id: req-2026-06-13-001 +responding_squad: platform-squad +responding_agent: lead +responded_at: 2026-06-13T10:15:00Z +status: completed | partial | rejected +response: | + The platform architecture consists of... +artifacts: [] # optional file paths +``` + +--- + +### Pattern 3: Issue-Based Delegation (For GitHub-Hosted Repos) + +For repos on GitHub, use issues with labels as the message bus. + +**Protocol:** +1. Create issue in target repo with label `squad:cross-squad` +2. Include source squad identifier and routing hint in issue body +3. Target squad's Ralph picks up and routes to appropriate agent +4. Response posted as issue comment +5. Issue closed when complete + +**Example:** +```bash +gh issue create \ + --repo your-org/platform-squad-repo \ + --title "[Cross-Squad] Architecture query from research-squad" \ + --body "Source: research-squad\nQuery: What services does the platform expose?\nRouting: lead" \ + --label "squad:cross-squad" +``` + +**Limitation:** Only works for repos on GitHub. Other platforms (Azure DevOps, GitLab, etc.) need different approach. + +--- + +### Appendix: Cross-Repo Dependency Scan (Related Analysis Tool — Not a Communication Pattern) + +> This section is intentionally listed as an appendix rather than "Pattern 4" — it is a one-off analysis utility for discovering how two repos relate, not a protocol the coordinator picks from the decision tree above. The four numbered communication patterns are 0–3. + +For discovering how two repos relate to each other. + +**Protocol:** +1. Search both repos for mutual references: + ```powershell + Select-String -Path (Get-ChildItem $repoA -Recurse -Include "*.md","*.cs","*.json","*.csproj") ` + -Pattern $repoB_name + Select-String -Path (Get-ChildItem $repoB -Recurse -Include "*.md","*.cs","*.json","*.csproj") ` + -Pattern $repoA_name + ``` +2. Check shared NuGet packages / npm packages +3. Check shared ADO project or GitHub org +4. Document relationship type: code dependency, operational coupling, shared infra + +--- + +## Discovery Protocol + +Before sending any cross-squad request, verify the target: + +``` +1. Does .squad/team.md exist? → Squad is installed +2. What is the issue_source? → GitHub Issues | ADO | Planner +3. What agents are active? → Check member status column +4. What is the routing table? → Read routing.md +5. What is the current focus? → Read identity/now.md +6. Is Ralph running? → Check for recent commits by Ralph +``` + +If `.squad/team.md` doesn't exist, the repo is not squad-enabled. Fall back to standard human communication. + +--- + +## Platform Compatibility Matrix + +| Source Issue Tracker | Target Issue Tracker | Mechanism | +|---------------------|---------------------|-----------| +| GitHub Issues | GitHub Issues | Issue-based (Pattern 3) | +| GitHub Issues | ADO Work Items | Git-based (Pattern 2) | +| GitHub Issues | Planner | Git-based (Pattern 2) | +| ADO Work Items | GitHub Issues | Issue-based (Pattern 3) via `gh` CLI | +| ADO Work Items | ADO Work Items | ADO cross-project work items | +| Any | Any | Git-based (Pattern 2) — universal fallback | + +--- + +## Examples + +### Example 1: research-squad queries platform-squad architecture + +```powershell +# Step 1: Read metadata (Pattern 1) +$target = "C:\repos\platform-squad-repo" +$team = Get-Content "$target\.squad\team.md" -Raw +$decisions = Get-Content "$target\.squad\decisions.md" -Raw + +# Step 2: Extract answer from metadata +# team.md reveals tech stack and member roles +# decisions.md reveals architectural choices + +# Step 3: If deeper analysis needed, create async request (Pattern 2) +``` + +### Example 2: Request PR review from another squad + +```yaml +# .squad/cross-squad/requests/2026-06-13-platform-squad-pr-review.yaml +id: pr-review-001 +source_squad: research-squad +target_squad: platform-squad +request_type: pr_review +priority: normal +query: "Review PR #54 — package version fix. Check for correctness." +routing_hint: "lead" +status: pending +``` + +--- + +## Anti-Patterns + +### ⚠️ Know when synchronous CLI is NOT the right choice +```powershell +# WRONG — don't use sync CLI for long-running tasks that need artifacts +copilot -C $targetRepo --agent squad -p (Get-Content $promptFile -Raw) --allow-all-tools +# If the task creates files, PRs, or takes multiple cycles → use async (Pattern 2 or 3) + +# WRONG — don't use sync CLI when the target repo isn't cloned locally +copilot -C "C:\not\cloned\yet" --agent squad --allow-all-tools +# If the repo isn't available locally → use issue-based delegation (Pattern 3) +``` +Synchronous CLI sessions (Pattern 0) are valid for quick queries and knowledge lookups. Use async patterns for work that needs to persist or where the target repo isn't available locally. + +### ❌ Don't assume shared MCP tools +Each squad has its own MCP server instances. You cannot invoke another squad's ADO tools or GitHub tools from your session. + +### ❌ Don't skip the discovery step +Always read `team.md` first. The target squad may use a different issue tracker, have different agents, or be in a different state than expected. + +### ❌ Don't send requests to squads without Ralph +If the target squad doesn't have Ralph (Work Monitor) running, async requests will never be processed. Check for recent Ralph activity first. + +### ❌ Don't mix up repo platforms +Different repos may use GitHub Issues vs Azure DevOps Work Items vs Jira. Check `team.md` / repository metadata for the right tooling before sending requests. + +--- + +## Validation Status + +This skill was originally drafted against two prototype squad setups (a GitHub-hosted platform squad with ~10 agents and an Azure DevOps-hosted automation squad with ~4 agents). The protocols are platform-agnostic; the examples in this document use generic names so you can substitute your own repos. Patterns 0 and 1 have been exercised end-to-end in those prototypes; Patterns 2 and 3 are documented from design but have not been end-to-end-validated against a live target repo. + +| Scenario | Result | +|----------|--------| +| Knowledge query (read-only) | ✅ Works via Pattern 1 | +| Step handler discovery | ✅ Works via file scan | +| PR review (basic) | ⚠️ Partial — git log only, no API | +| Backlog enumeration | ⚠️ Partial — depends on issue platform | +| Dependency analysis | ✅ Works via cross-reference scan | +| CLI invocation (sync) + Liveness Protocol | ✅ Works — session launches successfully; log monitoring prevents false timeouts | + +**Confidence: MEDIUM** — Synchronous CLI pattern (Pattern 0) validated end-to-end. Liveness protocol provides operational robustness against slow MCP initialization. Git-based async (Pattern 2) and issue-based (Pattern 3) untested end-to-end. Production readiness requires Ralph integration on both sides. diff --git a/packages/squad-cli/templates/skills/cross-squad/SKILL.md b/packages/squad-cli/templates/skills/cross-squad/SKILL.md index 1d4e3a251..d008e85b8 100644 --- a/packages/squad-cli/templates/skills/cross-squad/SKILL.md +++ b/packages/squad-cli/templates/skills/cross-squad/SKILL.md @@ -1,9 +1,20 @@ --- name: "cross-squad" -description: "Coordinating work across multiple Squad instances" +description: "Coordinating work across multiple Squad instances — discovery, delegation, and disambiguation when the user says 'squad' (the product) vs casual English 'group of agents'." domain: "orchestration" confidence: "medium" source: "manual" +triggers: + - "spawn N squads" + - "spawn a squad" + - "another squad" + - "two squads of" + - "second squad" + - "fan out to squads" + - "delegate to a squad" + - "set up squads for" + - "create a squad to review" + - "ask the other squad" tools: - name: "squad-discover" description: "List known squads and their capabilities" @@ -14,21 +25,60 @@ tools: --- ## Context + +> **Read this FIRST any time the user says "squad" as a thing to spawn, delegate to, address, or fan out to** — e.g., *"spawn two squads of designers and devs"*, *"ask the other squad"*, *"delegate to a squad"*. In Squad-PRODUCT vocabulary, "squad" is a **peer** (an independent installation with its own `.squad/`, `team.md`, MCP server, and agents) — NOT a generic English synonym for "team" or "group". Do not fan out raw `task` agents inside your own coordinator context when the user means "another squad". Use the discovery and communication patterns below (and the companion `cross-squad-communication` skill for the actual protocols). + When an organization runs multiple Squad instances (e.g., platform-squad, frontend-squad, data-squad), those squads need to discover each other, share context, and hand off work across repository boundaries. This skill teaches agents how to coordinate across squads without creating tight coupling. +> **Companion skill — for protocol details:** `cross-squad-communication/SKILL.md` covers the four communication patterns (synchronous CLI, read-only knowledge query, git-based async, and GitHub-issue-based delegation) once a peer squad is discovered via the registry below. This skill answers "who?" — the companion answers "how?". + Cross-squad orchestration applies when: - A task requires capabilities owned by another squad - An architectural decision affects multiple squads - A feature spans multiple repositories with different squads - A squad needs to request infrastructure, tooling, or support from another squad +## Disambiguation: "squad" vs ad-hoc agents + +When the user uses the word **"squad" / "squads"** or asks to **"spawn a team"**, the coordinator MUST treat it as a literal reference to a Squad install (a `.squad/` directory with its own roster, casting, and coordinator) — NOT as a casual synonym for "a group of sub-agents". + +### Default behaviour (apply unless explicitly told otherwise) + +| User says | Coordinator does | +|---|---| +| *"spawn two squads of X and Y"* / *"set up squads for X, Y, Z"* | Bootstrap N **real** Squad installs — separate folder + `git init` + `squad init` per squad — then use the cross-squad patterns below (`.squad/manifest.json`, `squad registry add`, `squad delegate`) and the protocols in the `cross-squad-communication` skill | +| *"ask the other squad about X"* / *"delegate to the data squad"* | Discover the peer via `squad registry list` (or by reading a known `.squad/manifest.json`), then use `cross-squad-communication` Pattern 0 / 1 / 2 / 3 — never re-implement the protocol with `task` | +| *"spawn a few agents to do X"* / *"have some agents review X"* / *"in parallel, get sub-agents to..."* | Ad-hoc `task` fan-out is fine — no `.squad/` bootstrap needed. This is the only path where raw `task` is appropriate when the user mentioned a multi-agent activity | + +### Ambiguous? `ask_user`, never silently downgrade + +If the request **could** be either interpretation AND bootstrapping real squads is non-trivial (more than one or two `squad init` runs), you MUST use the `ask_user` tool with a 2-choice prompt before proceeding: + +``` +question: "Should I create separate Squad installs or just dispatch ad-hoc agents?" +choices: + - "Real squads — separate .squad/ per squad (heavier, persistent, can be re-engaged later)" + - "Ad-hoc agents — one-shot `task` dispatch (lighter, ephemeral, no .squad/ created)" +``` + +The cost of asking is one `ask_user`. The cost of getting it wrong is the user has to redo the work. **Never silently pick the cheaper option just because it feels disproportionate for the task size — surface the trade-off and let the user pick.** + +### Anti-patterns (every one of these is a real failure mode observed in production) + +- **Calling `task` sub-agents "squad-alpha" / "squad-beta"** and treating them as squads. Naming something a squad doesn't make it one — a squad has its own `.squad/`, `team.md`, MCP server, and coordinator. If those aren't there, it's not a squad. +- **Matching a prior session's ad-hoc pattern without re-checking current intent.** If you see existing `reviews/squad-alpha/` folders from a previous run, that's a hint, NOT a contract — the user may have meant something different this time. Re-evaluate from scratch. +- **Silently choosing the cheaper interpretation because "bootstrapping two real squads for a 30-line app feels disproportionate".** That's a judgment call for the USER to make, not the coordinator. Use `ask_user`. +- **Loading the `cross-squad` skill, reading it, then doing `task` fan-out anyway** because the eager-execution / parallel-fan-out doctrine pulled you back. The disambiguation rule on this page OVERRIDES the generic fan-out doctrine when "squad" was the trigger. + ## Patterns ### Discovery via Manifest -Each squad publishes a `.squad/manifest.json` declaring its name, capabilities, and contact information. Squads discover each other through: -1. **Well-known paths**: Check `.squad/manifest.json` in known org repos -2. **Upstream config**: Squads already listed in `.squad/upstream.json` are checked for manifests -3. **Explicit registry**: A central `squad-registry.json` can list all squads in an org +Each squad publishes a `.squad/manifest.json` declaring its name, capabilities, and contact information. Squads discover each other through two mechanisms: + +1. **`.squad/squad-registry.json`** — **discovery-only.** Peer squads are findable via `squad discover` and addressable via `squad delegate`, but their skills/decisions/wisdom are NOT loaded into your coordinator. Manage with `squad registry add/list/remove`. +2. **`.squad/upstream.json`** — **discovery + inheritance.** Squads listed here are also discoverable, AND your coordinator inherits their skills/decisions/wisdom/routing at session start. Manage with `squad upstream add/list/remove/sync`. + +Both forms read the peer's manifest via the same code path. The `path` field is the **repository root** (e.g. `../friend-repo`), and Squad appends `.squad/manifest.json` internally. Pointing at the `.squad/` directory works too — Squad accepts both forms (`readManifest` strips a trailing `.squad` if present). ```json { @@ -73,9 +123,19 @@ Track delegated work completion: ## Examples +### Registering a peer squad (no inheritance) +```bash +# Friend's repo is checked out at ../friend-platform/ +squad registry add platform-squad ../friend-platform + +# Verify +squad registry list +squad discover +``` + ### Discovering squads ```bash -# List all squads discoverable from upstreams and known repos +# List all squads discoverable from registry + upstreams squad discover # Output: diff --git a/packages/squad-cli/templates/skills/release-process/SKILL.md b/packages/squad-cli/templates/skills/release-process/SKILL.md index c80603986..18e0e84b1 100644 --- a/packages/squad-cli/templates/skills/release-process/SKILL.md +++ b/packages/squad-cli/templates/skills/release-process/SKILL.md @@ -9,7 +9,7 @@ source: "earned" # Release Process > Earned knowledge from the v0.9.0→v0.9.1 and v0.9.4 incidents. Every agent involved in releases MUST read this before starting release work. -> See also: `.copilot/skills/release-process/SKILL.md` for the Copilot-facing runbook. +> See also: `.github/skills/release-process/SKILL.md` for the Copilot-facing runbook. ## SCOPE @@ -213,5 +213,5 @@ See `.github/workflows/squad-ci.yml` → `publish-policy` job for implementation - v0.9.4 fixes: PR #1042 (CHANGELOG), PR #1043 (root package.json), PR #1044 (lockfile integrity) - Retro: `.squad/decisions/inbox/surgeon-v091-retrospective.md` - CI audit: `.squad/decisions/inbox/booster-ci-audit.md` -- Copilot-level skill: `.copilot/skills/release-process/SKILL.md` +- Copilot-level skill: `.github/skills/release-process/SKILL.md` - Playbook: `PUBLISH-README.md` (repo root) diff --git a/packages/squad-cli/templates/skills/squad-help/SKILL.md b/packages/squad-cli/templates/skills/squad-help/SKILL.md new file mode 100644 index 000000000..81bc4ae2a --- /dev/null +++ b/packages/squad-cli/templates/skills/squad-help/SKILL.md @@ -0,0 +1,97 @@ +--- +name: "squad-help" +description: "How to actually use Squad — Squad is a custom Copilot agent (invoked via the task tool with agent_type='Squad'), not a skill. This file explains the right invocation paths for setting up a team, listing squad commands, and initializing Squad in a new project." +allowedTools: [] +confidence: high +domain: squad-onboarding +--- + +# Skill: squad-help + +> **Quick reference.** If you're reading this because a user said "use squad" or "squad" or "set up a squad", you're in the right place — read on for the correct invocation paths. + +--- + +## Squad is a custom agent, not a skill + +The Squad framework registers a **custom Copilot CLI agent** at `.github/agents/squad.agent.md`. The agent is named **`Squad`** and its description is *"Your AI team. Describe what you're building, get a team of specialists that live in your repo."* + +Copilot CLI agents and skills are different things: + +| Thing | How to invoke | Example | +|---|---|---| +| **Skill** | `skill(name)` tool call or natural-language match | `skill(squad-commands)` | +| **Agent** | `task` tool with `agent_type=` | `task(name="...", agent_type="Squad", prompt="...")` | +| **Slash command** | Built-in CLI keyword | `/agent`, `/skills`, `/mcp` | + +Calling `skill(Squad)` will fail with *"Skill not found: Squad"* because Squad is the agent, not a skill. (`/squad` as a slash command also does not exist — only built-in CLI keywords like `/agent`, `/skills`, `/mcp` are slash commands. There's no way to map a skill name to a slash command without a Copilot CLI feature change.) + +--- + +## How to actually use Squad + +Pick the path that matches the user's intent: + +### A) Invoke the Squad coordinator agent (most common) + +The Squad coordinator orchestrates a team of specialists. It routes work to the right agent, scaffolds a team if none exists, and enforces handoffs. + +```text +task( + name="", + agent_type="Squad", + prompt="" +) +``` + +Use this when the user says things like: +- *"Use Squad to build X"* +- *"Set up an AI team for this project"* +- *"Have the Squad coordinator design Y"* +- *"Spawn Squad"* / *"Squad, help me with ..."* + +### B) See what Squad commands exist + +The `squad-commands` skill is a categorized catalog of common Squad operations. The coordinator presents it as an interactive menu. + +Trigger by natural-language match: `"squad commands"`, `"what can squad do"`, `"show me squad options"`, `"slash commands"`, `"what commands are available"`. + +Use this when the user says things like: +- *"What can Squad do?"* +- *"Show me the squad commands"* +- *"squad help"* + +### C) Initialize Squad in a fresh project + +`squad init` is a **shell command**, not a tool call. The user runs it in their terminal in a project that has no `.squad/` directory yet. + +```bash +squad init +``` + +Do **not** try to invoke this from inside an existing Copilot session — `.squad/` is already initialized if you're reading this file. + +--- + +## What NOT to do + +- ❌ Do not call `skill(Squad)`, `skill(squad)`, or `skill(squad-coordinator)` — Squad is not a skill. +- ❌ Do not type `/squad` expecting a slash command — slash commands are CLI keywords, not skill names. Use `/agent` (browse) or invoke the `Squad` agent via the `task` tool. +- ❌ Do not call `task(agent_type="Squad", …)` for tiny tasks the current agent can handle directly. Squad is for work that needs orchestration; trivial edits do not. + +--- + +## How this skill was discovered + +This skill ships from the Squad SDK templates and is wired into `MANIFEST_SKILL_NAMES`. It lives at `.copilot/skills/squad-help/SKILL.md` so the Copilot CLI's `/skills` loader picks it up alongside the other bundled Squad skills. + +If you removed this skill on purpose, the model will fall back to its own reasoning and may make the lookup mistakes described above. + +--- + +## See also + +- `.github/agents/squad.agent.md` — the actual Squad coordinator agent +- `.copilot/skills/squad-commands/SKILL.md` — the command catalog +- `.copilot/skills/squad-conventions/SKILL.md` — conventions for working on the Squad codebase itself +- `.copilot/skills/squad-version-check/SKILL.md` — version-stamping mechanics diff --git a/packages/squad-cli/templates/skills/squad-version-check/SKILL.md b/packages/squad-cli/templates/skills/squad-version-check/SKILL.md index 291a4d337..3f3aebb0a 100644 --- a/packages/squad-cli/templates/skills/squad-version-check/SKILL.md +++ b/packages/squad-cli/templates/skills/squad-version-check/SKILL.md @@ -1,3 +1,12 @@ +--- +name: "squad-version-check" +description: "Internals of how @bradygaster/squad-cli stamps its version, how `squad upgrade` works (what it preserves vs overwrites), and how to probe the npm registry for the latest version from a coordinator prompt." +allowedTools: [] +confidence: medium +domain: squad-internals +source: "Discovered by Data; validated in bradygaster/squad#1173 recon (2026-05-26)." +--- + # SKILL: Squad CLI Internals — Version Stamping & Upgrade Mechanics **Confidence:** medium diff --git a/packages/squad-cli/templates/skills/squad/SKILL.md b/packages/squad-cli/templates/skills/squad/SKILL.md new file mode 100644 index 000000000..d9caa13f4 --- /dev/null +++ b/packages/squad-cli/templates/skills/squad/SKILL.md @@ -0,0 +1,299 @@ +--- +name: squad +description: >- + Squad's command catalog and interactive menu. Invoke via /squad (slash command) or natural language ("squad commands", "what can squad do", "show me squad options"). Presents categorized operations (Install & Upgrade, Team Management, Issues & PRs, Plugins & Skills, Model & Cost, Sessions & State) as an interactive picker. Routes to the right squad CLI command or the Squad coordinator agent. +user-invocable: true +allowedTools: [] +--- + +## Menu Presentation Rules + +When the user triggers this skill (via `/squad` slash command, "squad commands", "help", "what can squad do", etc.): + +1. **Category-level menu first.** Present category names as an `ask_user` choice list: + ``` + 📋 Squad Commands — pick a category: + 1. Install & Upgrade + 2. Team Management + 3. Issues & PRs + 4. Plugins & Skills + 5. Model & Cost + 6. Sessions & State + ``` +2. **Drill-down.** After selection, show operation titles in that category as a second `ask_user` list. +3. **Direct match skips the menu.** If the user says "how do I upgrade with state backend," match to the specific entry and go straight to argument collection. +4. **Compact fallback.** If `ask_user` is unavailable, render as a markdown table instead. +5. **Back / Cancel.** Include "← Back to categories" in sub-menus. Include "Cancel" in confirmation prompts. Respect "never mind" / "cancel" at any point. + +**Argument collection:** For entries with `args`, iterate the list sequentially. Use `ask_user` with choices when `choices` is provided; free-text prompt otherwise. If the user says "just do it" or "defaults are fine," skip remaining args and use their defaults. + +**Confirmation template:** +``` +⚠️ This will {action-description}. +{what will change} +Proceed? (yes / no) +``` + +--- + +## Install & Upgrade + +### Upgrade Squad CLI + +- **intent:** upgrade squad, update squad, install latest version, get new version +- **summary:** Upgrade Squad CLI to the latest version for your channel +- **action:** shell +- **command:** squad upgrade +- **args:** + - `state-backend`: Which state backend? | choices: {worktree, git-notes, orphan, two-layer} | default: (keep current) +- **confirm:** false +- **platform_caveats:** Requires terminal. In VS Code, open the integrated terminal and run the command directly. + +### Initialize Squad + +- **intent:** set up squad, initialize squad, create team, start squad in this project +- **summary:** Scaffold Squad in the current directory (idempotent) +- **action:** shell +- **command:** squad init +- **args:** (none) +- **confirm:** false +- **platform_caveats:** Requires terminal. Recommend a standalone terminal for best results. + +### Switch State Backend + +- **intent:** switch state backend, change state storage, use git-notes, use orphan branch +- **summary:** Change where Squad stores mutable state (config.json) +- **action:** file-edit +- **command:** .squad/config.json → stateBackend +- **args:** + - `stateBackend`: Which state backend? | choices: {worktree, git-notes, orphan, two-layer} | default: (keep current) +- **confirm:** true +- **platform_caveats:** May require migration if switching away from worktree. Show current value and new value before confirming. + +--- + +## Team Management + +### Add Team Member + +- **intent:** add team member, hire agent, add agent, add developer, recruit +- **summary:** Add a new agent to the team roster +- **action:** coordinator +- **command:** Add Team Member flow (Init Mode / Team Mode) +- **args:** + - `role`: What role should this agent fill? (e.g., Frontend Dev, Backend Dev, QA Engineer) + - `name`: Preferred name or casting universe? | default: (auto-cast from active universe) +- **confirm:** false + +### Remove Team Member + +- **intent:** remove team member, fire agent, delete agent, remove developer +- **summary:** Remove an agent and delete their charter and history files +- **action:** coordinator +- **command:** Remove Team Member flow +- **args:** + - `member`: Which team member to remove? (name or role) +- **confirm:** true + +### Reassign Roles + +- **intent:** reassign role, change role, swap roles, update team member role +- **summary:** Update a team member's role in team.md and their charter +- **action:** coordinator +- **command:** Update team.md roster + charter.md +- **args:** + - `member`: Which team member? + - `newRole`: New role? +- **confirm:** false + +### Show Roster + +- **intent:** show roster, who is on the team, list team members, show team, capability profile +- **summary:** Display the current team roster and capability profile +- **action:** coordinator +- **command:** Direct Mode — read team.md, answer +- **args:** (none) +- **confirm:** false + +--- + +## Issues & PRs + +### Connect GitHub Repo + +- **intent:** connect github, enable issues, set up issues, link repository, github issues mode +- **summary:** Connect this project to GitHub Issues via gh auth +- **action:** coordinator +- **command:** GitHub Issues Mode (connection flow) +- **args:** (none) +- **confirm:** false +- **platform_caveats:** Requires `gh auth login` to have been run in the terminal. + +### Triage Issues + +- **intent:** triage issues, review issues, assign issues, label issues +- **summary:** Run the Lead triage flow on open GitHub issues +- **action:** coordinator +- **command:** GitHub Issues Mode → Lead triage +- **args:** (none) +- **confirm:** false + +### Activate Ralph + +- **intent:** activate ralph, start ralph, ralph go, start work monitor, start auto-work +- **summary:** Activate Ralph — Work Monitor — to pick up and run queued issues +- **action:** coordinator +- **command:** Ralph — Work Monitor triggers +- **args:** (none) +- **confirm:** false + +### Set Ralph Polling Interval + +- **intent:** set ralph interval, change ralph timing, how often does ralph check, ralph every N minutes +- **summary:** Tell Ralph how frequently to poll for new work +- **action:** coordinator +- **command:** Ralph trigger: "Ralph, check every N minutes" +- **args:** + - `interval`: How often should Ralph poll? (in minutes) | default: 10 +- **confirm:** false + +### Start Squad Watch + +- **intent:** start watch, squad watch, monitor issues, watch for issues, auto-triage +- **summary:** Start squad watch to continuously poll and triage issues +- **action:** shell +- **command:** squad watch +- **args:** + - `interval`: Poll interval in minutes | default: 10 +- **confirm:** false +- **platform_caveats:** CLI-only — long-running foreground process. Not viable in VS Code without an integrated terminal. Run: `squad watch --interval {n}` in your terminal. + +--- + +## Plugins & Skills + +### Browse Plugin Marketplace + +- **intent:** browse plugins, explore plugins, what plugins are available, plugin marketplace +- **summary:** Browse available plugins in the Squad marketplace +- **action:** shell +- **command:** squad plugin marketplace browse +- **args:** + - `name`: Plugin name to search for | default: (browse all) +- **confirm:** false + +### Add Marketplace Plugin + +- **intent:** add plugin, install plugin, get plugin from marketplace +- **summary:** Add a plugin from the marketplace to this Squad +- **action:** shell +- **command:** squad plugin marketplace add +- **args:** + - `plugin`: Plugin owner/repo (e.g., owner/plugin-name) +- **confirm:** false + +### Remove Marketplace Plugin + +- **intent:** remove plugin, uninstall plugin, delete plugin +- **summary:** Remove an installed marketplace plugin +- **action:** shell +- **command:** squad plugin marketplace remove +- **args:** + - `name`: Plugin name to remove +- **confirm:** true + +### List Marketplace Plugins + +- **intent:** list plugins, show installed plugins, what plugins do I have +- **summary:** List all plugins registered in this Squad +- **action:** shell +- **command:** squad plugin marketplace list +- **args:** (none) +- **confirm:** false + +### List Installed Skills + +- **intent:** list skills, show skills, what skills are installed, skill catalog +- **summary:** List all skills installed in .squad/skills/ and .copilot/skills/ +- **action:** coordinator +- **command:** Direct Mode — list .squad/skills/ and .copilot/skills/ directories +- **args:** (none) +- **confirm:** false + +--- + +## Model & Cost + +### Set Default Model + +- **intent:** set default model, change model, use gpt-4, use claude, switch model +- **summary:** Set the default model for all agents in config.json +- **action:** file-edit +- **command:** .squad/config.json → defaultModel +- **args:** + - `model`: Model name (e.g., gpt-4o, claude-sonnet-4.5, o3) +- **confirm:** false + +### Override Per-Agent Model + +- **intent:** set model for agent, agent model override, use different model for one agent +- **summary:** Set a model override for a specific agent in config.json +- **action:** file-edit +- **command:** .squad/config.json → agentModelOverrides.{agentName} +- **args:** + - `agent`: Agent name (must match name in team.md) + - `model`: Model name (e.g., gpt-4o, claude-sonnet-4.5) +- **confirm:** false + +### Clear Model Preference + +- **intent:** clear model, reset model, remove model preference, use default model +- **summary:** Remove a model override from config.json (reverts to system default) +- **action:** file-edit +- **command:** .squad/config.json → remove defaultModel or agentModelOverrides.{agentName} +- **args:** + - `scope`: Clear default or a specific agent? | choices: {default model, specific agent} | default: default model + - `agent`: Agent name (only if scope = specific agent) +- **confirm:** false + +--- + +## Sessions & State + +### Catch-Up Summary + +- **intent:** catch me up, what happened, status, what did the team do, session summary +- **summary:** Summarize recent agent activity and key decisions +- **action:** coordinator +- **command:** Session catch-up flow (lazy scan) +- **args:** (none) +- **confirm:** false + +### Show Recent Decisions + +- **intent:** show decisions, recent decisions, what decisions were made, decision log +- **summary:** Display recent entries from .squad/decisions.md +- **action:** coordinator +- **command:** Direct Mode — read decisions.md, answer +- **args:** (none) +- **confirm:** false + +### Archive Old Decisions + +- **intent:** archive decisions, clean up decisions, move old decisions, compact decisions +- **summary:** Move old decisions from decisions.md to decisions-archive.md +- **action:** coordinator +- **command:** Move entries older than threshold from .squad/decisions.md → .squad/decisions-archive.md +- **args:** + - `olderThan`: Archive decisions older than how many days? | default: 30 +- **confirm:** true + +### Summarize Agent History + +- **intent:** summarize history, what did agent do, agent history, compress history +- **summary:** Spawn an agent to summarize and compress a team member's history file +- **action:** coordinator +- **command:** Spawn agent with history.md summarization task +- **args:** + - `member`: Which team member's history to summarize? +- **confirm:** false diff --git a/packages/squad-cli/templates/skills/tiered-memory/SKILL.md b/packages/squad-cli/templates/skills/tiered-memory/SKILL.md index bb82e662c..5921eb80a 100644 --- a/packages/squad-cli/templates/skills/tiered-memory/SKILL.md +++ b/packages/squad-cli/templates/skills/tiered-memory/SKILL.md @@ -1,33 +1,35 @@ --- name: tiered-memory -description: Three-tier agent memory model (hot/cold/wiki) for 20-55% context reduction per spawn +description: Three-tier agent memory model (hot/cold/wiki) for context reduction per spawn domain: memory-management, performance -confidence: high -source: earned (production measurements in tamirdresher/tamresearch1, 34-74KB baseline payloads) +confidence: design (runtime not yet implemented) +source: design proposal --- # Skill: Tiered Agent Memory +> **Status (v0.10.0):** This skill describes a **design proposal**, not a shipped runtime. Skill files install via `squad init`/`upgrade`, but the underlying tier scaffolding (`.squad/memory/hot/`, `cold/`, `wiki/`), Scribe promotion logic, and spawn-template tier-aware reads are tracked in [bradygaster/squad#1264](https://github.com/bradygaster/squad/issues/1264). Until those land, agents continue to load full `history.md` + `decisions.md` on every spawn. + ## Overview -Squad agents currently load their full context history on every spawn, resulting in 34–74KB payloads per agent (8,800–18,500 tokens). Measurement shows 82–96% of that context is "old noise" — information that is no longer relevant to the current task. The Tiered Agent Memory skill introduces a three-tier memory model that eliminates this bloat, achieving 20–55% context reduction per spawn in production. +Squad agents today load their full context history on every spawn, which grows unboundedly across sessions. The Tiered Agent Memory model proposes a three-tier separation so agents only load the bytes that are actually relevant to the current task, with older context kept available on demand. --- ## Memory Tiers ### 🔥 Hot Tier — Current Session Context -- **Size target:** ~2–4KB +- **Size target:** keep small (~2–4KB typical) - **Load policy:** Always loaded. Every spawn includes hot memory by default. - **Contents:** Current task description, active decisions made this session, immediate blockers, last 3–5 actions taken, who you are talking to right now. - **Lifetime:** Current session only. Discarded after session ends (Scribe promotes relevant parts to Cold). - **Purpose:** Provide immediate task context without any latency or load decision. ### ❄️ Cold Tier — Summarized Cross-Session History -- **Size target:** ~8–12KB +- **Size target:** larger summary, not full transcript (~8–12KB typical) - **Load policy:** Load on demand. Include only when the task explicitly needs history. - **Contents:** Summarized past sessions (compressed by Scribe), cross-session decisions, recurring patterns, unresolved issues from prior work. -- **Lifetime:** 30 days rolling window. After 30 days, Scribe promotes to Wiki tier. +- **Lifetime:** Rolling window (default proposal: 30 days). Eligible entries are then promoted to Wiki. - **Purpose:** Answer "what have we tried before?" and "what was decided?" without replaying full transcripts. - **How to include:** Pass `--include-cold` in spawn template or add `## Cold Memory` section. @@ -89,49 +91,34 @@ See: .squad/memory/wiki/{topic}.md --- -## Measurement Data - -Baseline measurements from tamirdresher/tamresearch1 production runs (June 2025): - -| Agent | Total Context | Old Noise % | Hot-Only Size | Savings | -|-------|--------------|-------------|---------------|---------| -| Picard (Lead) | 74KB / 18.5K tokens | 96% | ~3KB | 55% | -| Scribe | 52KB / 13K tokens | 91% | ~4KB | 48% | -| Data | 43KB / 10.7K tokens | 88% | ~3.5KB | 42% | -| Ralph | 38KB / 9.5K tokens | 85% | ~3KB | 38% | -| Worf | 34KB / 8.5K tokens | 82% | ~3KB | 20% | - -**Average savings: 20–55% per spawn** with Hot-only loading. Cold + Wiki on-demand adds ~2–8KB when needed, still well below current baselines. - ---- - -## Integration with Scribe Agent +## Integration with Scribe Agent (design — not yet implemented) -Scribe is the memory coordinator for this system. It automates tier promotion: +Scribe is the proposed memory coordinator for this system. Once the runtime lands, Scribe will: -1. **End of session:** Scribe compresses Hot → Cold summary (keeps ~10% of session verbosity) -2. **After 30 days:** Scribe promotes Cold → Wiki for decisions/facts that aged into stable knowledge -3. **On-demand wiki writes:** Any agent can request Scribe to write a wiki entry mid-session using `scribe:wiki-write` +1. **End of session:** Compress Hot → Cold summary (target: ~10% of session verbosity) +2. **Aged cold entries:** Promote Cold → Wiki for decisions/facts that aged into stable knowledge +3. **On-demand wiki writes:** Any agent can request Scribe to write a wiki entry mid-session -See Scribe charter: `.squad/agents/scribe/charter.md` +Until then, see the Scribe charter for current behavior: `.squad/agents/scribe/charter.md` --- -## Implementation Checklist +## Implementation Checklist (tracked in #1264) - [ ] Scribe writes Hot context file at session start (`.squad/memory/hot/{agent}.md`) - [ ] Scribe compresses and writes Cold summary at session end - [ ] Spawn templates default to Hot-only - [ ] Coordinators add `--include-cold` / `--include-wiki` flags as needed - [ ] Wiki entries stored in `.squad/memory/wiki/` -- [ ] Cold entries stored in `.squad/memory/cold/` with 30-day TTL +- [ ] Cold entries stored in `.squad/memory/cold/` with rolling TTL --- ## References -- Upstream issue: bradygaster/squad#600 -- Production data: tamirdresher/tamresearch1 (June 2025) +- Tracking issue: [bradygaster/squad#1264](https://github.com/bradygaster/squad/issues/1264) — installation gap + runtime status +- Original design spike: [bradygaster/squad#686](https://github.com/bradygaster/squad/issues/686) — tiered memory implementation plan +- Related: [bradygaster/squad#600](https://github.com/bradygaster/squad/issues/600) — context payload growth --- @@ -162,7 +149,7 @@ Use this template when spawning any Squad agent. By default it loads **Hot tier ### 🔥 Hot (always included) -> Paste current session context here (2–4KB max): +> Paste current session context here (~2–4KB target): ``` Current task: {task_description} @@ -178,12 +165,12 @@ Talking to: {current_interlocutor} > Load on demand. Do not inline unless specifically needed. -Summarized cross-session history is at: +Summarized cross-session history is at: `.squad/memory/cold/{agent-name}.md` Include when: - Resuming interrupted work -- Debugging a recurring issue +- Debugging a recurring issue - "What have we tried before?" **To load cold memory, add this section and fetch the file before spawning:** @@ -218,17 +205,17 @@ Include when: ## Escalation If blocked or uncertain: -- Architecture questions → @picard -- Security concerns → @worf -- Infrastructure/deployment → @belanna -- Memory/history questions → @scribe +- Architecture questions → @picard +- Security concerns → @worf +- Infrastructure/deployment → @belanna +- Memory/history questions → @scribe --- ## Notes -- Hot tier is always included and should stay under 4KB -- Cold adds ~8–12KB; only include when history is relevant +- Hot tier is always included; keep it focused +- Cold adds a summary; only include when history is relevant - Wiki adds variable size; only include specific relevant docs -- See `skills/tiered-memory/SKILL.md` for full tier reference -- See `docs/tiered-memory-guide.md` for wiring instructions +- Runtime backing is tracked in [bradygaster/squad#1264](https://github.com/bradygaster/squad/issues/1264) — until those changes land, this skill is design-only and agents continue to load full history.md + decisions.md on every spawn + diff --git a/packages/squad-cli/templates/spawn-reference.md b/packages/squad-cli/templates/spawn-reference.md index 94c2509d9..a2571c820 100644 --- a/packages/squad-cli/templates/spawn-reference.md +++ b/packages/squad-cli/templates/spawn-reference.md @@ -81,7 +81,7 @@ prompt: | Read `decisions.md` with `squad_state_read` when state tools are available; otherwise fall back to `.squad/decisions.md`. If .squad/identity/wisdom.md exists, read it before starting work. If .squad/identity/now.md exists, read it at spawn time. - Check project skill directories (.squad/skills/, .copilot/skills/, .github/skills/, .claude/skills/, .agents/skills/) for any SKILL.md the coordinator attached to your prompt. + Check project skill directories (.squad/skills/, .github/skills/, .copilot/skills/, .claude/skills/, .agents/skills/) for any SKILL.md the coordinator attached to your prompt. Read any relevant SKILL.md files before working. ⚠️ WORK FRESHNESS: When determining what to work on: diff --git a/packages/squad-cli/templates/squad.agent.md.template b/packages/squad-cli/templates/squad.agent.md.template index 9c71cacce..49a15598b 100644 --- a/packages/squad-cli/templates/squad.agent.md.template +++ b/packages/squad-cli/templates/squad.agent.md.template @@ -53,13 +53,14 @@ No team exists yet. Propose one — but **DO NOT create any files until the user 1. **Identify the user.** Run `git config user.name` to learn who you're working with. Use their name in conversation (e.g., *"Hey {user}, what are you building?"*). Store their name (NOT email) in `team.md` under Project Context. **Never read or store `git config user.email` — email addresses are PII and must not be written to committed files.** 2. Ask: *"What are you building? (language, stack, what it does)"* 3. **Cast the team.** Before proposing names, run the Casting & Persistent Naming algorithm (see that section): - - Determine team size (typically 4–5 + Scribe). + - Determine team size: pick **4–5 cast (user-domain) agents**, then add the **4 always-on built-ins** (Scribe + Ralph + Rai + Fact Checker — see their dedicated sections below). So a typical fresh squad has **8–9 total roster entries**, not 4–5. - Determine assignment shape from the user's project description. - Derive resonance signals from the session and repo context. - Select a universe. Allocate character names from that universe. - Scribe is always "Scribe" — exempt from casting. - Ralph is always "Ralph" — exempt from casting. - Rai is always "Rai" — exempt from casting. + - Fact Checker is always "Fact Checker" — exempt from casting (same pattern as Scribe / Ralph / Rai). 4. Propose the team with their cast names. Example (names will vary per cast): ``` @@ -130,6 +131,18 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"local"` (default), `"orphan"`, `"two-layer"`. Legacy alias: `"worktree"` maps to `"local"`. Deprecated: `"git-notes"` maps to `"two-layer"` with a deprecation warning. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. +**State-backend handshake — MANDATORY on every session before any state mutation (bradygaster/squad#1305):** + +For all backends EXCEPT `"local"` / `"worktree"`, the runtime owns persistence and you MUST NOT touch `.squad/decisions.md`, `.squad/decisions/inbox/`, `.squad/agents/*/history.md`, `.squad/casting/*.json`, `.squad/identity/*.md`, or `.squad/memory/*` paths via `create` / `edit` / `write_file` tools. Those writes either fail at the pre-commit hook or create phantom state the runtime overwrites at next read — a contract violation that produces silent data loss. + +The `squad_state_*` and `memory.*` tools that own persistence are exposed via the `squad_state` MCP server (declared in `.mcp.json`). Copilot CLI may load MCP tools **lazily** — they are not always advertised in your initial function list at session start. You MUST proactively confirm they are reachable: + +1. If `STATE_BACKEND ∈ {"local", "worktree"}`: file ops on `.squad/` are valid; skip the probe. +2. Otherwise (backend is `orphan`, `two-layer`, or `git-notes`): probe for `squad_state_health` (or any `squad_state_*` / `memory.*` tool) using whatever tool-discovery mechanism your runtime exposes (e.g. `tool_search_tool_regex` in Copilot CLI). If you can locate the tool, call `squad_state_health` once to confirm it answers; on success, treat the bridge as available for the rest of the session. +3. **If the probe fails** (tool not found, or `squad_state_health` errors): **HALT** before any state write. Tell the user verbatim: *"Squad's runtime state bridge is missing for backend `{STATE_BACKEND}`. The `squad_state` MCP server in `.mcp.json` is not reachable in this Copilot session. Restart Copilot CLI so `.mcp.json` is loaded, or change `stateBackend` to `local` in `.squad/config.json`."* — and stop until the user acknowledges. Do not silently fall back to raw file ops. + +This handshake runs **once per session**, not per spawn. Cache the result. + **⚡ 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). **Session catch-up (lazy — not on every start):** Do NOT scan logs on every session start. Only provide a catch-up summary when: @@ -256,28 +269,49 @@ The `name` parameter generates the human-readable agent ID shown in the tasks pa **When you detect a directive:** -1. Capture the directive with the runtime state tools when available: - - Prefer `squad_state_write` to write `decisions/inbox/copilot-directive-{timestamp}.md` using this format: +1. Capture the directive with governed memory tools when available: + - Prefer `memory.write` with class `decision` to persist the directive through the governed pipeline: ``` - ### {timestamp}: User directive - **By:** {user name} (via Copilot) - **What:** {the directive, verbatim or lightly paraphrased} - **Why:** User request — captured for team memory + memory.write({ + class: "decision", + key: "copilot-directive-{timestamp}", + content: "### {timestamp}: User directive\n**By:** {user name} (via Copilot)\n**What:** {the directive, verbatim or lightly paraphrased}\n**Why:** User request — captured for team memory" + }) ``` + - If `memory.write` is not available, fall back to `squad_decide` or `squad_state_write` to `decisions/inbox/copilot-directive-{timestamp}.md`. - Do **not** run `git notes`, checkout `squad-state`, or manually commit mutable `.squad/` state. The runtime owns state persistence. 2. Acknowledge briefly: `"📌 Captured. {one-line summary of the directive}."` 3. If the message ALSO contains a work request, route that work normally after capturing. If it's directive-only, you're done — no agent spawn needed. ### Memory Governance Tools -When memory tools are available, use them before writing durable memory by hand: +The `memory.*` tools share the same `squad_state` MCP server as `squad_state_*` (they're aliases in the same registry — see `packages/squad-cli/src/cli/commands/state-mcp.ts`). After the state-backend handshake above confirms the bridge is reachable, prefer governed memory tools for durable writes: - Classify candidate memories with `memory.classify`. - Persist approved durable facts, decisions, and policies with `memory.write`. - Search governed memory with `memory.search` before relying only on raw file search. - Promote, delete, and audit governed entries with `memory.promote`, `memory.delete`, and `memory.audit`. -If memory tools are not available, use runtime state tools for durable Squad state when present. In MCP sessions these are exposed as `squad_state_read`, `squad_state_write`, `squad_state_append`, `squad_state_delete`, `squad_state_list`, and `squad_state_health` aliases. Only fall back to local `.squad/` file writes when `STATE_BACKEND` is `worktree`/`local` and no runtime state tool exists. For `git-notes`, `orphan`, or `two-layer`, do not hand-write mutable state; report that the `squad_state` MCP/runtime state bridge is missing. Never claim provider-backed Copilot Memory, semantic indexing, or remote deletion unless a configured tool or CLI bridge performed the operation. External semantic memory is opt-in; forbidden or transient content must not be persisted. +If `memory.*` is not present in the bridge (older Squad versions before the bridge landed) but `squad_state_*` is, use `squad_state_*` directly. Both are governed paths. + +**HARD RULE — Backend contract enforcement:** If `STATE_BACKEND ∈ {"orphan", "two-layer", "git-notes"}` AND the state-backend handshake (above) did NOT confirm reachable tools, you MUST NOT write to ANY of these paths via `create` / `edit` / `write_file`: + +- `.squad/decisions.md` +- `.squad/decisions/inbox/**` +- `.squad/agents/*/history.md` +- `.squad/casting/*.json` +- `.squad/identity/*.md` +- `.squad/memory/**` +- `.squad/orchestration-log/**` +- `.squad/log/**` +- `.squad/rai/audit-trail.md` +- `.squad/fact-checker/audit-trail.md` + +These are runtime-managed paths under non-local backends. Hand-writing creates phantom state. The pre-commit hook will catch it and fail the user; even if it didn't, the runtime overwrites the file at next read. Report the missing bridge and halt instead. + +For `STATE_BACKEND ∈ {"local", "worktree"}`, file writes to `.squad/` are valid because the local backend IS the filesystem. + +**External memory:** Never claim provider-backed Copilot Memory, semantic indexing, or remote deletion unless a configured tool or CLI bridge performed the operation. External semantic memory is opt-in; forbidden or transient content must not be persisted. ### Routing @@ -295,19 +329,23 @@ The routing table determines **WHO** handles work. After routing, use Response M | PRD intake ("here's the PRD", "read the PRD at X", pastes spec) | Follow PRD Mode (see that section) | | Human member management ("add {name} as PM", routes to human) | Follow Human Team Members (see that section) | | Ralph commands ("Ralph, go", "keep working", "Ralph, status", "Ralph, idle") | Follow Ralph — Work Monitor (see that section) | -| "squad commands", "what can squad do", "show me squad options", "slash commands", "what commands are available" | Read `.copilot/skills/squad-commands/SKILL.md`, present categorized menu (see squad-commands skill) | +| "squad commands", "what can squad do", "show me squad options", "slash commands", "what commands are available" | Read `.github/skills/squad/SKILL.md`, present categorized menu (see squad skill). Users can also invoke this directly via `/squad`. | | "upgrade squad", "update squad", "what's new in squad", "install the update" | Run upgrade flow per `.squad/templates/session-init-reference.md` | +| User says "spawn a squad", "another squad", "two squads", "second squad", "fan out to squads", "delegate to a squad", or any phrasing that treats "squad" as a unit to spawn or address | This is the Squad-PRODUCT concept (a peer with its own `.squad/`), NOT generic English "team" or "group". **Before any `task` spawn**, invoke the `skill` tool on `cross-squad` (discovery via registry/upstream) AND `cross-squad-communication` (sync CLI / git-async / GH-issue protocols) to load the full peer-squad workflow. Then delegate via Pattern 0/1/2/3 — NOT by fanning out raw `task` agents inside your own coordinator context. **Default = literal Squad install.** Calling `task` sub-agents "squad-alpha" / "squad-beta" does NOT make them squads — that is the explicit anti-pattern. **If the request is ambiguous** (could be either "two real `.squad/` installs" or "two ad-hoc groups of agents"), you MUST `ask_user` with a 2-choice prompt — `["Real squads — separate .squad/ per squad (heavier, persistent)", "Ad-hoc agents — one-shot task dispatch (lighter, ephemeral)"]` — and never silently pick the cheaper option. If the peer doesn't exist yet, walk the user through `squad init` in a separate directory or `squad registry add` first. | | Rai commands ("Rai, review this", "RAI check", "content safety review") | Follow Rai — RAI Reviewer (see that section) | | General work request | Check routing.md, spawn best match + any anticipatory agents | | Quick factual question | Answer directly (no spawn) | | Ambiguous | Pick the most likely agent; say who you chose | | Multi-agent task (auto) | Check `ceremonies.md` for `when: "before"` ceremonies whose condition matches; run before spawning work | - + **Skill-aware routing:** Before spawning, check ALL project skill directories in precedence order for skills relevant to the task domain: + +**Hard trigger — keyword-to-skill match (do this FIRST, before any spawn or task call):** If any word in the user's request matches the name of an installed skill (e.g., "squad" → `cross-squad` and/or `cross-squad-communication`, "reflect" → `reflect`, "ceremony" → the matching ceremony skill, "fact-check" → `fact-checking`, "release" → `release-process`), you MUST invoke the `skill` tool to fully load that skill BEFORE designing your approach or selecting agents. The one-line description in the discovery list is for discovery only — it is NOT sufficient to act on. Read the full SKILL.md, then route. This rule applies whether or not the request also matches a routing-table row above; when both apply, load the skill first, then execute the routing-table action. Failure mode this rule closes: a coordinator that sees "squad" in the prompt, treats it as generic English, and fans out raw `task` agents instead of invoking the `cross-squad-communication` peer-delegation protocol. + 1. `.squad/skills/` — **Team-earned skills** (highest precedence). Patterns captured by agents during work; a team-written override beats any generic version. -2. `.copilot/skills/` — **Project playbook.** Human-curated process knowledge: release workflows, git conventions, reviewer protocols. -3. `.github/skills/` — **Generic project skills.** Sits alongside `.github/workflows/` and `.github/copilot-instructions.md`; common location for shared-repo skills. +2. `.github/skills/` — **Project playbook** (Copilot CLI's canonical custom-skills location). Human-curated process knowledge: release workflows, git conventions, reviewer protocols. Sits alongside `.github/workflows/` and `.github/copilot-instructions.md`. `squad init` and `squad upgrade` install Squad's bundled skills here. +3. `.copilot/skills/` — **Legacy install path** (pre-1304). Older squads may have skills here; `squad upgrade` migrates them to `.github/skills/`. Still scanned for any user-added or unmigrated skills. 4. `.claude/skills/` — **Claude-ecosystem skills.** Vendor-specific path; less common in multi-tool projects. 5. `.agents/skills/` — **Generic agents path** (lowest project precedence). Least-specific convention. @@ -403,7 +441,7 @@ prompt: | TARGET FILE(S): {exact file path(s)} Do the work. Keep it focused. - If you made a meaningful decision, persist it with `squad_decide` when available, or `squad_state_write` to `decisions/inbox/{name}-{brief-slug}.md`. Do not run git notes, switch branches, or write mutable `.squad/` state by hand. + If you made a meaningful decision, persist it with `memory.write` (class: `decision`) when available, or fall back to `squad_decide` / `squad_state_write` to `decisions/inbox/{name}-{brief-slug}.md`. Do not run git notes, switch branches, or write mutable `.squad/` state by hand. ⚠️ OUTPUT: Report outcomes in human terms. Never expose tool internals or SQL. ⚠️ RESPONSE ORDER: After ALL tool calls, write a plain text summary as FINAL output. @@ -528,7 +566,7 @@ When the user gives any task, the Coordinator MUST: To enable full parallelism, shared writes use a drop-box pattern that eliminates file conflicts: **decisions.md** — Agents do NOT write directly to `decisions.md`. Instead: -- Agents record decisions with `squad_decide` or `squad_state_write` to `decisions/inbox/{agent-name}-{brief-slug}.md`. +- Agents record decisions with `memory.write` (class: `decision`) when available, or fall back to `squad_decide` / `squad_state_write` to `decisions/inbox/{agent-name}-{brief-slug}.md`. - The runtime routes that write to the configured state backend. Agents must not run `git notes`, switch to `squad-state`, or hand-roll backend commits. - Scribe merges into the canonical `.squad/decisions.md` and clears the inbox - All agents READ from `.squad/decisions.md` at spawn time (last-merged snapshot) @@ -707,6 +745,8 @@ If the user wants to remove someone: | `.squad/templates/` | **Reference.** Format guides for runtime files. Not authoritative for enforcement. | Squad (Coordinator) at init | Squad (Coordinator) | | `.squad/rai/policy.md` | **Authoritative RAI policy.** Check categories, terminology standards, and opt-out rules. | Squad (Coordinator) at init; Rai may propose updates via decisions inbox | Rai, All agents (read-only) | | `.squad/rai/audit-trail.md` | **Derived / append-only.** RAI review evidence log. Redacted — never contains raw secrets or harmful content. | Rai (append only) | Rai, Squad (Coordinator) | +| `.squad/fact-checker/policy.md` | **Authoritative verification + Devil's Advocate policy.** Confidence rating taxonomy, hard anti-fabrication rules, mode triggers, opt-out model. | Squad (Coordinator) at init; Fact Checker may propose updates via decisions inbox | Fact Checker, All agents (read-only) | +| `.squad/fact-checker/audit-trail.md` | **Derived / append-only.** Verification verdicts + DA brief evidence log. Succinct — verdict + citation, never raw source material. | Fact Checker (append only) | Fact Checker, Squad (Coordinator) | | `.squad/plugins/marketplaces.json` | **Authoritative plugin config.** Registered marketplace sources. | Squad CLI (`squad plugin marketplace`) | Squad (Coordinator) | **Rules:** @@ -962,6 +1002,79 @@ Rai participates as a specialized Reviewer. When Rai rejects: --- +## Fact Checker — Verification & Devil's Advocate + +Fact Checker is a built-in squad member whose job is **claim verification + Devil's Advocate analysis**. **Fact Checker ensures every team has a quality challenge from day one.** Always on the roster, dual operating mode: verifies factual claims AND challenges design assumptions before they ship. + +**Single agent, two modes:** + +| Mode | Question asked | When triggered | +|------|---------------|----------------| +| **Verification** | *"Is this claim true? Do these URLs / packages / API endpoints actually exist?"* | Pre-publish review of research output, external references, version claims | +| **Devil's Advocate** | *"Is this plan wise? What's the strongest counter-argument? What would we do if X was forbidden?"* | Before significant design decisions, pre-mortem on risky launches, when the team is converging too fast | + +**Philosophy: "Trust, but verify. Then steelman the opposition."** Fact Checker is rigorous but constructive — never gotcha-driven. Every challenge or finding includes WHAT (the issue or counter-argument), WHY (evidence or failure scenario), and HOW (the fix or alternative). + +**On-demand reference:** Read `.squad/agents/fact-checker/charter.md` (created by `squad init` / `squad upgrade` from the rich `fact-checker-charter.md` template, per #1299) for the full charter, verification methodology, confidence rating taxonomy, and pre-ship ceremony format. + +### Roster Entry + +Fact Checker always appears in `team.md`: `| Fact Checker | Fact Checker | .squad/agents/fact-checker/charter.md | 🔍 Verifier |` + +### Triggers + +| User says | Action | +|-----------|--------| +| "fact-check this" / "verify these claims" / "double-check" | Spawn Fact Checker in Verification mode | +| "play devil's advocate" / "what's wrong with this plan?" / "steelman the opposite" | Spawn Fact Checker in Devil's Advocate mode | +| "is this true?" / "does this URL/package exist?" | Spawn Fact Checker for empirical verification | +| "pre-mortem this" / "what could go wrong?" | Spawn Fact Checker for pre-mortem analysis | +| Pre-Ship ceremony (auto) | Fact Checker spawned automatically before user-facing artifacts finalize | +| Post-research (auto, optional) | After any agent produces research output or external references | + +These are intent signals, not exact strings — match meaning, not words. + +### Confidence Ratings (Verification Mode) + +Every verified item gets one of: + +| Rating | Meaning | +|--------|---------| +| ✅ **Verified** | Confirmed via source, test, or direct observation | +| ⚠️ **Unverified** | Plausible but could not confirm — needs human review | +| ❌ **Contradicted** | Found evidence that contradicts the claim | +| 🔍 **Needs Investigation** | Requires deeper analysis beyond current scope | + +### Devil's Advocate Output (DA Mode) + +Every DA brief includes: + +1. **Steelman of the opposition** — the strongest version of the counter-argument +2. **Load-bearing assumptions** — what would invalidate the plan if untrue +3. **Pre-mortem** — concrete failure scenario in 30 days +4. **Alternative approach** — at least one sketch so the chosen direction is a chosen direction +5. **Risk acceptance** — flag remaining risks for the team to consciously accept or mitigate + +### Boundaries + +**Fact Checker handles:** Claim verification, hallucination detection, counter-argument construction, pre-mortem analysis, assumption surfacing. + +**Fact Checker does not handle:** Implementation or code writing (reviews not creates), final decisions (advisory only — the team or coordinator decides), tone-policing. + +**Advisory by default.** Findings are advisory unless the coordinator or another reviewer escalates a specific risk to a gate. Never blocks on opinion, only on provably false claims or unaccepted risks. + +### Background Mode (Default) + +Fact Checker runs in background by default (like Scribe and Rai) — non-blocking. Spawns on-demand or via Pre-Ship ceremony auto-trigger. + +### Fact Checker State + +- **History** (`.squad/agents/fact-checker/history.md`) — verification + DA briefs across sessions +- **Charter** (`.squad/agents/fact-checker/charter.md`) — methodology + dual-mode operating rules +- **Decisions** — significant verification verdicts or DA briefs go to `.squad/decisions/inbox/fact-checker-{slug}.md` + +--- + ## PRD Mode Squad can ingest a PRD and use it as the source of truth for work decomposition and prioritization. diff --git a/packages/squad-sdk/package.json b/packages/squad-sdk/package.json index 1dc9b7666..a77512bdb 100644 --- a/packages/squad-sdk/package.json +++ b/packages/squad-sdk/package.json @@ -226,7 +226,8 @@ "files": [ "dist", "templates", - "README.md" + "README.md", + "CHANGELOG.md" ], "scripts": { "prepublishOnly": "npm run build", diff --git a/packages/squad-sdk/src/config/gitignore-state.ts b/packages/squad-sdk/src/config/gitignore-state.ts new file mode 100644 index 000000000..155d6ae36 --- /dev/null +++ b/packages/squad-sdk/src/config/gitignore-state.ts @@ -0,0 +1,95 @@ +/** + * Helpers for managing the squad-state .gitignore marker block. + * + * When the state backend is 'two-layer' or 'orphan', .squad/decisions.md + * and .squad/agents/history.md are owned by the squad-state orphan branch. + * Adding them to .gitignore prevents accidental staging via git add ., + * git add -A, IDE "stage all", or git commit -am -- defense-in-depth + * complement to the pre-commit hook installed by squad upgrade --state-backend. + * + * The block is delimited by marker comments so it can be reliably added and + * removed without touching unrelated .gitignore content. + */ + +import type { StorageProvider } from '../storage/index.js'; + +export const SQUAD_STATE_GITIGNORE_OPEN_MARKER = + '# Squad: state owned by squad-state branch (two-layer/orphan backend)'; +export const SQUAD_STATE_GITIGNORE_CLOSE_MARKER = + '# /Squad: state owned by squad-state branch'; + +const SQUAD_STATE_GITIGNORE_BLOCK = + SQUAD_STATE_GITIGNORE_OPEN_MARKER + '\n' + + '.squad/decisions.md\n' + + '.squad/agents/*/history.md\n' + + SQUAD_STATE_GITIGNORE_CLOSE_MARKER + '\n'; + +/** + * Append the squad-state marker block to .gitignore if not already present. + * + * Idempotent: if the opening marker already exists in the file, this is a + * no-op. Returns true if the block was added, false if it was already present. + */ +export function addSquadStateGitignoreBlock( + gitignorePath: string, + storage: StorageProvider, +): boolean { + let content = ''; + if (storage.existsSync(gitignorePath)) { + content = storage.readSync(gitignorePath) ?? ''; + } + + if (content.includes(SQUAD_STATE_GITIGNORE_OPEN_MARKER)) { + return false; + } + + const prefix = content.length > 0 && !content.endsWith('\n') ? '\n' : ''; + storage.writeSync(gitignorePath, content + prefix + SQUAD_STATE_GITIGNORE_BLOCK); + return true; +} + +/** + * Remove the squad-state marker block from .gitignore if present. + * + * Finds the opening and closing marker lines and removes all lines between + * them (inclusive), plus one leading blank line in what follows if present. + * Returns true if the block was removed, false if it was not present. + */ +export function removeSquadStateGitignoreBlock( + gitignorePath: string, + storage: StorageProvider, +): boolean { + if (!storage.existsSync(gitignorePath)) { + return false; + } + + const content = storage.readSync(gitignorePath) ?? ''; + if (!content.includes(SQUAD_STATE_GITIGNORE_OPEN_MARKER)) { + return false; + } + + const lines = content.split('\n'); + const openIdx = lines.findIndex((l) => l === SQUAD_STATE_GITIGNORE_OPEN_MARKER); + if (openIdx === -1) return false; + + // Find the closing marker starting from the opening marker + let closeIdx = -1; + for (let i = openIdx + 1; i < lines.length; i++) { + if (lines[i] === SQUAD_STATE_GITIGNORE_CLOSE_MARKER) { + closeIdx = i; + break; + } + } + + // If no closing marker found, remove from opening marker to end + const endIdx = closeIdx !== -1 ? closeIdx : lines.length - 1; + + const before = lines.slice(0, openIdx); + const after = lines.slice(endIdx + 1); + + // Rejoin: before-lines + after-lines (including trailing empty string if present, + // which preserves a trailing newline from the original file content) + const newContent = [...before, ...after].join('\n'); + storage.writeSync(gitignorePath, newContent); + return true; +} \ No newline at end of file diff --git a/packages/squad-sdk/src/config/index.ts b/packages/squad-sdk/src/config/index.ts index cee7a823d..68a8497c8 100644 --- a/packages/squad-sdk/src/config/index.ts +++ b/packages/squad-sdk/src/config/index.ts @@ -13,3 +13,4 @@ export * from './migration.js'; export * from './markdown-migration.js'; export * from './legacy-fallback.js'; export * from './feature-audit.js'; +export * from './gitignore-state.js'; diff --git a/packages/squad-sdk/src/config/init.ts b/packages/squad-sdk/src/config/init.ts index 9e37d7363..840cabf68 100644 --- a/packages/squad-sdk/src/config/init.ts +++ b/packages/squad-sdk/src/config/init.ts @@ -19,6 +19,7 @@ import type { SubSquadDefinition } from '../streams/types.js'; import { ENGINEERING_ROLE_IDS } from '../roles/catalog.js'; import { getRoleById } from '../roles/index.js'; import { ensureMemoryGovernanceDefaults } from '../memory/index.js'; +import { addSquadStateGitignoreBlock, removeSquadStateGitignoreBlock } from './gitignore-state.js'; // ============================================================================ // Manifest-Curated Skills (must stay in sync with TEMPLATE_MANIFEST in CLI) @@ -27,8 +28,13 @@ import { ensureMemoryGovernanceDefaults } from '../memory/index.js'; /** * The curated built-in skills shipped on init. * Only these skills are installed — not the full templates/skills/ directory. + * + * Drift policy: every entry MUST have a corresponding directory under + * packages/squad-sdk/templates/skills/. The install loop (below) throws on + * missing source dirs instead of silently skipping — see bradygaster/squad#1289 + * for the prior silent-skip bug that shipped two missing skills in v0.10.0. */ -const MANIFEST_SKILL_NAMES = [ +export const MANIFEST_SKILL_NAMES = [ 'squad-conventions', 'error-recovery', 'secret-handling', @@ -37,8 +43,14 @@ const MANIFEST_SKILL_NAMES = [ 'reviewer-protocol', 'test-discipline', 'agent-collaboration', - 'squad-commands', + 'squad', 'squad-version-check', + 'squad-help', + 'cross-squad-communication', + 'tiered-memory', + 'iterative-retrieval', + 'reflect', + 'cross-squad', ] as const; // ============================================================================ @@ -152,6 +164,14 @@ export interface InitOptions { areaPath?: string; iterationPath?: string; }; + /** + * State backend to use for this project. + * When 'orphan' or 'two-layer', adds a marker-delimited block to .gitignore + * so .squad/decisions.md and .squad/agents/*\/history.md are not accidentally + * staged into the working-tree commit graph. + * When 'local' (or undefined), removes the marker block if present. + */ + stateBackend?: 'local' | 'orphan' | 'two-layer' | 'external' | string; } /** @@ -819,7 +839,7 @@ export async function initSquad(options: InitOptions, storage: StorageProvider = join(squadDir, 'decisions'), join(squadDir, 'decisions', 'inbox'), join(squadDir, 'memory'), - join(teamRoot, '.copilot', 'skills'), + join(teamRoot, '.github', 'skills'), join(squadDir, 'plugins'), join(squadDir, 'identity'), join(squadDir, 'orchestration-log'), @@ -929,6 +949,74 @@ export async function initSquad(options: InitOptions, storage: StorageProvider = skippedFiles.push(toRelativePath(raiAuditTrailPath)); } + // ------------------------------------------------------------------------- + // Seed .squad/fact-checker/ files (policy and audit trail) + // + // Mirrors the Rai pattern above. Fact Checker is an always-on built-in + // (per bradygaster/squad#789 + #1254 — single agent, dual operating mode: + // Verification + Devil's Advocate) and gets its own state dir so its + // policy + audit trail are first-class artifacts the coordinator can + // reference, not embedded inside the agent's charter file. + // + // See bradygaster/squad#1299 for the design rationale. + // ------------------------------------------------------------------------- + + const factCheckerDir = join(squadDir, 'fact-checker'); + const factCheckerPolicyPath = join(factCheckerDir, 'policy.md'); + if (!storage.existsSync(factCheckerPolicyPath)) { + const templateSrc = templatesDir ? join(templatesDir, 'fact-checker-policy.md') : null; + if (templateSrc && storage.existsSync(templateSrc)) { + storage.copySync(templateSrc, factCheckerPolicyPath); + } else { + // Minimal fallback if the template was stripped from the install (e.g., + // pre-1299 squad-sdk). The full template at + // .squad-templates/fact-checker-policy.md is the canonical source. + const factCheckerPolicyFallback = `# Fact Checker Policy + +> Verification & devil's-advocate methodology for this project. + +## Verification Mode + +Check claims about URLs, package names, API endpoints, file paths, function signatures, quoted text, and cross-references to team decisions. Issue one of: + +- ✅ Verified — confirmed via source +- ⚠️ Unverified — plausible but could not confirm (flag, do not block) +- ❌ Contradicted — evidence contradicts the claim (**blocking** at Pre-Ship) +- 🔍 Needs Investigation — beyond current scope + +## Devil's Advocate Mode + +Produce briefs that include: steelman of the opposition, load-bearing assumptions, pre-mortem in 30 days, alternative approach, risk acceptance. + +## Hard Rules + +- Never cite a URL/package/API without verifying it exists +- Never invent measurement data or "production results" +- Never fabricate a counter-hypothesis +- Never block on opinion — only on ❌ Contradicted findings + +## Audit Trail + +All findings logged to \`.squad/fact-checker/audit-trail.md\` (append-only, succinct — verdict + citation, never raw source). +`; + await storage.write(factCheckerPolicyPath, factCheckerPolicyFallback); + } + createdFiles.push(toRelativePath(factCheckerPolicyPath)); + } else { + skippedFiles.push(toRelativePath(factCheckerPolicyPath)); + } + + const factCheckerAuditTrailPath = join(factCheckerDir, 'audit-trail.md'); + if (!storage.existsSync(factCheckerAuditTrailPath)) { + await storage.write( + factCheckerAuditTrailPath, + '# Fact Checker Audit Trail\n\n> Append-only evidence log. Entries are succinct — verdict + citation, never raw source material.\n\n\n', + ); + createdFiles.push(toRelativePath(factCheckerAuditTrailPath)); + } else { + skippedFiles.push(toRelativePath(factCheckerAuditTrailPath)); + } + // ------------------------------------------------------------------------- // Create .squad/config.json for squad settings // ------------------------------------------------------------------------- @@ -1036,8 +1124,47 @@ export async function initSquad(options: InitOptions, storage: StorageProvider = agentDirs.push(agentDir); // Create charter.md + // + // Built-in always-on agents (Rai, fact-checker) ship with rich + // charter templates at `templates/{role}-charter.md`. If a template + // exists for this agent's role, use that instead of the generic + // role-based stub — this gives the agent its full operating manual + // (verdict protocol, audit-trail rules, dual-mode declarations) from + // day one of `squad init`, matching the behavior of `squad upgrade`'s + // `ensureBuiltinAgents` path. See bradygaster/squad#1299. const charterPath = join(agentDir, 'charter.md'); - const charterContent = generateCharter(agent, projectName, projectDescription); + let charterContent: string | null = null; + if (templatesDir) { + // Lookup priority: exact role / name first, then their lowercase + // variants. This is needed because rich-charter files in `templates/` + // are stored lowercase-hyphenated (`rai-charter.md`, + // `fact-checker-charter.md`) while agent specs may use mixed case + // (e.g. role/name = "Rai"). On case-sensitive filesystems (Linux CI) + // the mixed-case lookup misses without the toLowerCase() fallback, + // and the agent silently falls back to the 478-byte generic stub — + // exactly the regression #1299 was fixing. Each candidate is only + // added if non-empty to avoid `-charter.md` lookups from blank names. + const seen = new Set(); + const candidates: string[] = []; + for (const key of [agent.role, agent.name, agent.role?.toLowerCase(), agent.name?.toLowerCase()]) { + if (!key) continue; + const filename = `${key}-charter.md`; + if (seen.has(filename)) continue; + seen.add(filename); + candidates.push(join(templatesDir, filename)); + } + for (const candidate of candidates) { + if (storage.existsSync(candidate)) { + charterContent = storage.readSync(candidate) ?? null; + if (charterContent) break; + } + } + } + if (charterContent === null) { + // Fall back to the generic role-based charter for user-defined agents + // that don't have a rich template (everyone except Rai + fact-checker). + charterContent = generateCharter(agent, projectName, projectDescription); + } await writeIfNotExists(charterPath, charterContent); // Create history.md @@ -1163,26 +1290,64 @@ ${projectDescription ? `- **Description:** ${projectDescription}\n` : ''}- **Cre // ------------------------------------------------------------------------- // Copy starter skills + // + // Skills live at `.github/skills/{name}/SKILL.md` — Copilot CLI's canonical + // custom-skills location (used by Copilot's own /skills loader and + // referenced by the CLI's built-in agent prompts at sdk/index.js:2246, + // 2252, 2595). Earlier versions of Squad installed to `.copilot/skills/`; + // `squad upgrade` migrates any leftover manifest skills to the new + // location (see upgrade.ts). + // + // bradygaster/squad#1126 (canonical issue; PR #1304) — adopt the canonical + // .github/skills/ path. // ------------------------------------------------------------------------- - const skillsDir = join(teamRoot, '.copilot', 'skills'); + const skillsDir = join(teamRoot, '.github', 'skills'); if (templatesDir && storage.existsSync(join(templatesDir, 'skills'))) { const skillsSrc = join(templatesDir, 'skills'); const existingSkills = storage.existsSync(skillsDir) ? storage.listSync(skillsDir) : []; if (existingSkills.length === 0) { + // Pre-check drift BEFORE writing any skill file so a manifest/template + // mismatch fails atomically with no partial install left on disk. The + // earlier shape ran the copy loop first and threw at the end — users + // saw 8 of 10 skills appear, then an error, with no clean rollback. + const missing: string[] = []; + for (const skillName of MANIFEST_SKILL_NAMES) { + if (!storage.existsSync(join(skillsSrc, skillName))) { + missing.push(skillName); + } + } + if (missing.length > 0) { + // Manifest/templates drift — fail loudly so v0.10.0-style silent-skip + // regressions (#1289) cannot reach users. End-users see this when an + // installed @bradygaster/squad-sdk ships with its templates dir + // missing skills the SDK code knows about — almost always a packaging + // bug. The fix is to upgrade to a non-broken SDK version + // (`squad upgrade`); the dev-facing sync-skill-templates.mjs script + // is referenced only as the contributor-side root cause. + throw new Error( + `Skill template drift in installed @bradygaster/squad-sdk: ` + + `MANIFEST_SKILL_NAMES references ${missing.length} skill(s) ` + + `missing from the SDK templates dir (${skillsSrc}): ${missing.join(', ')}. ` + + `This is a packaging bug — try \`squad upgrade\` or reinstall the SDK; ` + + `if it persists, please report it at https://github.com/bradygaster/squad/issues. ` + + `(Contributors: re-run \`node scripts/sync-skill-templates.mjs\` from the repo root before packaging.)` + ); + } storage.mkdirSync(skillsDir, { recursive: true }); for (const skillName of MANIFEST_SKILL_NAMES) { const srcSkill = join(skillsSrc, skillName); - if (storage.existsSync(srcSkill)) { - copyRecursiveSync(srcSkill, join(skillsDir, skillName), storage); - } + copyRecursiveSync(srcSkill, join(skillsDir, skillName), storage); } - createdFiles.push('.copilot/skills'); + createdFiles.push('.github/skills'); } } // ------------------------------------------------------------------------- // Create .gitattributes for merge drivers + // Append-only mutable state files use union merge to handle concurrent appends. + // Note: .squad/casting/* files are identity (not mutable) so they don't need + // union merge and are NOT listed here. // ------------------------------------------------------------------------- const gitattributesPath = join(teamRoot, '.gitattributes'); @@ -1192,6 +1357,7 @@ ${projectDescription ? `- **Description:** ${projectDescription}\n` : ''}- **Cre '.squad/log/** merge=union', '.squad/orchestration-log/** merge=union', '.squad/rai/audit-trail.md merge=union', + '.squad/fact-checker/audit-trail.md merge=union', ]; let existingAttrs = ''; @@ -1212,6 +1378,10 @@ ${projectDescription ? `- **Description:** ${projectDescription}\n` : ''}- **Cre // Create .gitignore entries for runtime state (logs, inbox, sessions) // These paths are written during normal squad operation but should not be // committed to version control (they are runtime state). + // + // Note: .squad/casting/* is AUTHORITATIVE IDENTITY (team-defined agent + // universe, persistent name registry, history). It belongs on 'main', not + // on the squad-state orphan branch. Do NOT add casting/* to ignoreEntries. // ------------------------------------------------------------------------- const gitignorePath = join(teamRoot, '.gitignore'); @@ -1233,15 +1403,26 @@ ${projectDescription ? `- **Description:** ${projectDescription}\n` : ''}- **Cre if (missingIgnore.length > 0) { const block = (existingIgnore && !existingIgnore.endsWith('\n') ? '\n' : '') + '# Squad: ignore runtime state (logs, inbox, sessions)\n' + + '# Note: .squad/casting/* is identity and MUST be committed to main, not ignored\n' + missingIgnore.join('\n') + '\n'; await storage.append(gitignorePath, block); createdFiles.push(toRelativePath(gitignorePath)); } // ------------------------------------------------------------------------- - // Detect platform from git remote + // Conditionally add/remove squad-state .gitignore block + // When stateBackend is 'orphan' or 'two-layer', .squad/decisions.md and + // .squad/agents/*/history.md are owned by the squad-state branch — add a + // marker-delimited block so `git add .` cannot accidentally stage them. + // When stateBackend is 'local' (or undefined), remove the block if present. // ------------------------------------------------------------------------- + if (options.stateBackend === 'orphan' || options.stateBackend === 'two-layer') { + addSquadStateGitignoreBlock(gitignorePath, storage); + } else { + removeSquadStateGitignoreBlock(gitignorePath, storage); + } + let isGitHub = true; try { const remoteUrl = execFileSync('git', ['remote', 'get-url', 'origin'], { cwd: teamRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim(); diff --git a/packages/squad-sdk/src/index.ts b/packages/squad-sdk/src/index.ts index 3833c1fb4..6f1473237 100644 --- a/packages/squad-sdk/src/index.ts +++ b/packages/squad-sdk/src/index.ts @@ -44,11 +44,17 @@ export { type CrossSquadIssueOptions, type CrossSquadIssueResult, type CrossSquadWorkStatus, + type RegistryEntry, + type AddRegistryEntryResult, validateManifest, readManifest, discoverSquads, discoverFromUpstreams, discoverFromRegistry, + readSquadRegistry, + writeSquadRegistry, + addRegistryEntry, + removeRegistryEntry, buildDelegationArgs, buildStatusCheckArgs, parseIssueStatus, diff --git a/packages/squad-sdk/src/presets/index.ts b/packages/squad-sdk/src/presets/index.ts index ed4be59d2..ffdd4dc8b 100644 --- a/packages/squad-sdk/src/presets/index.ts +++ b/packages/squad-sdk/src/presets/index.ts @@ -14,7 +14,8 @@ import { readdirSync, statSync, lstatSync, rmSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { FSStorageProvider } from '../storage/fs-storage-provider.js'; import { resolvePresetsDir, ensureSquadHome } from '../resolution.js'; -import type { PresetManifest, PresetApplyResult } from './types.js'; +import type { PresetManifest, PresetApplyResult, PresetAgent } from './types.js'; +import { scaffoldPresetIntoSquad } from './scaffold.js'; export type { PresetManifest, PresetAgent, PresetApplyResult } from './types.js'; @@ -138,6 +139,39 @@ export function applyPreset( } } + // After copying charters, wire the preset agents into team.md, routing.md, + // and the casting state files (registry/history/policy). Without this, the + // coordinator's mode-switch check sees an empty ## Members table and + // treats every session as Init Mode — see bradygaster/squad#1288. We only + // include agents that did not error out (installed + skipped-because- + // already-present) so the scaffolded team reflects the user's intent. + const wireableAgents: PresetAgent[] = manifest.agents.filter(a => + results.some(r => r.agent === a.name && r.status !== 'error'), + ); + if (wireableAgents.length > 0) { + const squadDir = path.dirname(targetDir); + try { + scaffoldPresetIntoSquad(squadDir, wireableAgents, presetName); + } catch (err) { + // Scaffolding failed but charters were copied — surface as a single + // synthetic error result so the CLI can warn but does not mask the + // per-agent install results. + // + // Use a clearly non-agent sentinel for the `agent` field (``, + // wrapped in angle brackets which validateName rejects) instead of the + // preset name. The preset name is a legal agent name, and if a preset + // happens to ship an agent that shares the preset's name (`squad + // preset apply geektime` where the preset includes an agent literally + // called `geektime`), the consumer of the results could not tell the + // synthetic scaffold-failure row apart from a real per-agent error. + results.push({ + agent: '', + status: 'error', + reason: `Charters copied (see per-agent rows above), but failed to wire team.md/routing.md/casting state for preset '${presetName}': ${String(err)}`, + }); + } + } + return results; } diff --git a/packages/squad-sdk/src/presets/scaffold.ts b/packages/squad-sdk/src/presets/scaffold.ts new file mode 100644 index 000000000..137ad2f7c --- /dev/null +++ b/packages/squad-sdk/src/presets/scaffold.ts @@ -0,0 +1,401 @@ +/** + * Preset application scaffolders. + * + * `applyPreset` copies agent charters into `.squad/agents/`, but to make the + * resulting squad usable by the coordinator (and to satisfy the post-init + * "Established Mode" mode-switch check) we must also wire the preset agents + * into team.md, routing.md, and the casting state files. These helpers do + * that in a merge-friendly way so calling `applyPreset` repeatedly is + * idempotent and does not clobber pre-existing rows or entries. + * + * The cast command (`packages/squad-cli/src/cli/core/cast.ts`) has its own + * fresh-write versions of these writers — see #1288 follow-up for a possible + * future refactor that shares the logic. For now, the scope of #1288 is + * focused on making preset apply produce a Coordinator-ready squad. + * + * @module presets/scaffold + */ + +import path from 'node:path'; +import { FSStorageProvider } from '../storage/fs-storage-provider.js'; +import type { PresetAgent } from './types.js'; + +const storage = new FSStorageProvider(); + +const MEMBERS_HEADER = '## Members'; +const ROUTING_HEADER = '## Work Type → Agent'; + +interface ScaffoldOptions { + /** Universe tag for the casting registry. Defaults to `preset:`. */ + universe: string; +} + +/** + * Map an agent's role to the Members-table Status cell. + * + * Mirrors the role-to-status mapping in `packages/squad-cli/src/cli/core/cast.ts:652-655` + * so a team produced by `preset apply` looks identical to one produced by + * a fresh cast — Scribe/Ralph/Rai/Fact-Checker each get their canonical + * status label instead of all rows being '✅ Active'. Per-role rather + * than per-name because presets may rename the agent but keep the role. + */ +function statusForRole(role: string): string { + // Case-insensitive comparison so presets that lowercase "session logger" + // still get the silent status. + const r = role.toLowerCase(); + if (r === 'session logger' || r === 'scribe') return '📋 Silent'; + if (r === 'work monitor' || r === 'ralph') return '🔄 Monitor'; + if (r === 'rai reviewer' || r === 'rai') return '🛡️ RAI'; + if (r === 'fact checker' || r === 'fact-checker') return '🔍 Verifier'; + return '✅ Active'; +} + +/** + * Build a single Members table row for a preset agent. + */ +function memberRow(agent: PresetAgent): string { + const nameLower = agent.name.toLowerCase(); + return `| ${agent.name} | ${agent.role} | \`.squad/agents/${nameLower}/charter.md\` | ${statusForRole(agent.role)} |`; +} + +/** + * Build a single routing table row for a preset agent (work type = role). + */ +function routingRow(agent: PresetAgent): string { + return `| ${agent.role} | ${agent.name} | — |`; +} + +/** + * Parse the existing Members table from team.md content (if any) and return + * the set of names already present (case-insensitive comparison key). + */ +function existingMemberNames(teamContent: string): Set { + const names = new Set(); + const idx = teamContent.indexOf(MEMBERS_HEADER); + if (idx === -1) return names; + const afterHeader = teamContent.slice(idx + MEMBERS_HEADER.length); + const nextHeaderIdx = afterHeader.search(/\n## /); + const section = nextHeaderIdx === -1 ? afterHeader : afterHeader.slice(0, nextHeaderIdx); + // Match table rows: "| Name | Role | Charter | Status |" + // Skip header row and the separator row. + const rowRe = /^\|\s*([^|]+?)\s*\|/gm; + let match: RegExpExecArray | null; + while ((match = rowRe.exec(section)) !== null) { + const first = match[1]!.trim(); + if (!first || first === 'Name' || /^-+$/.test(first)) continue; + names.add(first.toLowerCase()); + } + return names; +} + +/** + * Parse the existing routing table from routing.md content (if any) and + * return the set of agent names already present. + */ +function existingRoutingAgents(routingContent: string): Set { + const names = new Set(); + const idx = routingContent.indexOf(ROUTING_HEADER); + if (idx === -1) return names; + const afterHeader = routingContent.slice(idx + ROUTING_HEADER.length); + const nextHeaderIdx = afterHeader.search(/\n## /); + const section = nextHeaderIdx === -1 ? afterHeader : afterHeader.slice(0, nextHeaderIdx); + // Routing rows: "| | | |" + // We want the Primary column (2nd cell). + const rowRe = /^\|\s*[^|]+?\s*\|\s*([^|]+?)\s*\|/gm; + let match: RegExpExecArray | null; + while ((match = rowRe.exec(section)) !== null) { + const primary = match[1]!.trim(); + if (!primary || primary === 'Primary' || /^-+$/.test(primary)) continue; + names.add(primary.toLowerCase()); + } + return names; +} + +/** + * Write or update `.squad/team.md` so its `## Members` table contains the + * preset's agents. Existing members are preserved; only new names are added. + * If team.md does not exist, a minimal one is created. + */ +function writeOrMergeTeamMembers(squadDir: string, agents: PresetAgent[], presetName: string): void { + const teamPath = path.join(squadDir, 'team.md'); + const existing = storage.existsSync(teamPath) ? (storage.readSync(teamPath) ?? '') : ''; + + if (!existing) { + // Create from scratch (preset apply onto a bare .squad/ directory) + const fresh = [ + '# Squad Team', + '', + `> Created by \`squad preset apply ${presetName}\``, + '', + '## Coordinator', + '', + '| Name | Role | Notes |', + '|------|------|-------|', + '| Squad | Coordinator | Routes work, enforces handoffs and reviewer gates. |', + '', + MEMBERS_HEADER, + '', + '| Name | Role | Charter | Status |', + '|------|------|---------|--------|', + ...agents.map(memberRow), + '', + '## Project Context', + '', + `- **Preset:** ${presetName}`, + `- **Created:** ${new Date().toISOString().split('T')[0]}`, + '', + ].join('\n'); + storage.mkdirSync(squadDir, { recursive: true }); + storage.writeSync(teamPath, fresh); + return; + } + + // team.md exists — merge into existing ## Members table + const already = existingMemberNames(existing); + const newRows = agents + .filter(a => !already.has(a.name.toLowerCase())) + .map(memberRow); + if (newRows.length === 0) return; // nothing to do, all already present + + const membersIdx = existing.indexOf(MEMBERS_HEADER); + if (membersIdx === -1) { + // No Members section — append one + const block = [ + '', + MEMBERS_HEADER, + '', + '| Name | Role | Charter | Status |', + '|------|------|---------|--------|', + ...newRows, + '', + ].join('\n'); + storage.writeSync(teamPath, existing.trimEnd() + '\n' + block); + return; + } + + // Members section exists — find end of its table and insert rows there + const afterHeader = existing.slice(membersIdx); + const nextHeaderMatch = afterHeader.match(/\n## /); + const sectionEnd = nextHeaderMatch + ? membersIdx + (nextHeaderMatch.index ?? afterHeader.length) + : existing.length; + const section = existing.slice(membersIdx, sectionEnd); + + // Find the last table row in the section (line starting with `|`) + const sectionLines = section.split('\n'); + let lastTableLineRel = -1; + for (let i = sectionLines.length - 1; i >= 0; i--) { + if (sectionLines[i]!.trimStart().startsWith('|')) { lastTableLineRel = i; break; } + } + + if (lastTableLineRel === -1) { + // Section has the header but no table — append a fresh table + const headerLines = [ + '', + '| Name | Role | Charter | Status |', + '|------|------|---------|--------|', + ...newRows, + '', + ]; + const newSection = MEMBERS_HEADER + '\n' + headerLines.join('\n') + '\n'; + const updated = existing.slice(0, membersIdx) + newSection + existing.slice(sectionEnd); + storage.writeSync(teamPath, updated); + return; + } + + // Append new rows after the last existing table row + const before = sectionLines.slice(0, lastTableLineRel + 1).join('\n'); + const after = sectionLines.slice(lastTableLineRel + 1).join('\n'); + const newSection = before + '\n' + newRows.join('\n') + (after ? '\n' + after : '\n'); + const updated = existing.slice(0, membersIdx) + newSection + existing.slice(sectionEnd); + storage.writeSync(teamPath, updated); +} + +/** + * Write or update `.squad/routing.md` so it includes a `## Work Type → Agent` + * table with the preset's agents. Existing rows are preserved; only new + * primary agents are added. If routing.md does not exist, a minimal one is + * created. + */ +function writeOrMergeRouting(squadDir: string, agents: PresetAgent[]): void { + const routingPath = path.join(squadDir, 'routing.md'); + const existing = storage.existsSync(routingPath) ? (storage.readSync(routingPath) ?? '') : ''; + + if (!existing) { + const fresh = [ + '# Squad Routing', + '', + ROUTING_HEADER, + '', + '| Work Type | Primary | Secondary |', + '|-----------|---------|----------|', + ...agents.map(routingRow), + '', + '## Governance', + '', + '- Route based on work type and agent expertise', + '- Update this file as team capabilities evolve', + '', + ].join('\n'); + storage.mkdirSync(squadDir, { recursive: true }); + storage.writeSync(routingPath, fresh); + return; + } + + const already = existingRoutingAgents(existing); + const newRows = agents + .filter(a => !already.has(a.name.toLowerCase())) + .map(routingRow); + if (newRows.length === 0) return; + + const headerIdx = existing.indexOf(ROUTING_HEADER); + if (headerIdx === -1) { + // Append a fresh routing block + const block = [ + '', + ROUTING_HEADER, + '', + '| Work Type | Primary | Secondary |', + '|-----------|---------|----------|', + ...newRows, + '', + ].join('\n'); + storage.writeSync(routingPath, existing.trimEnd() + '\n' + block); + return; + } + + // Existing routing section — append new rows after its last table row + const afterHeader = existing.slice(headerIdx); + const nextHeaderMatch = afterHeader.match(/\n## /); + const sectionEnd = nextHeaderMatch + ? headerIdx + (nextHeaderMatch.index ?? afterHeader.length) + : existing.length; + const section = existing.slice(headerIdx, sectionEnd); + const sectionLines = section.split('\n'); + let lastTableLineRel = -1; + for (let i = sectionLines.length - 1; i >= 0; i--) { + if (sectionLines[i]!.trimStart().startsWith('|')) { lastTableLineRel = i; break; } + } + if (lastTableLineRel === -1) { + const headerLines = [ + '', + '| Work Type | Primary | Secondary |', + '|-----------|---------|----------|', + ...newRows, + '', + ]; + const newSection = ROUTING_HEADER + '\n' + headerLines.join('\n') + '\n'; + const updated = existing.slice(0, headerIdx) + newSection + existing.slice(sectionEnd); + storage.writeSync(routingPath, updated); + return; + } + const before = sectionLines.slice(0, lastTableLineRel + 1).join('\n'); + const after = sectionLines.slice(lastTableLineRel + 1).join('\n'); + const newSection = before + '\n' + newRows.join('\n') + (after ? '\n' + after : '\n'); + const updated = existing.slice(0, headerIdx) + newSection + existing.slice(sectionEnd); + storage.writeSync(routingPath, updated); +} + +/** + * Write or update `.squad/casting/registry.json`, `history.json`, and + * `policy.json` with entries for the preset's agents. Registry entries are + * merged (new agents added, existing entries left untouched). A new + * preset-apply snapshot is appended to history.json. policy.json is created + * with sensible defaults only if missing. + */ +function writeOrMergeCastingState( + squadDir: string, + agents: PresetAgent[], + options: ScaffoldOptions, +): void { + const castingDir = path.join(squadDir, 'casting'); + storage.mkdirSync(castingDir, { recursive: true }); + const now = new Date().toISOString(); + + // ---- registry.json ---- + const registryPath = path.join(castingDir, 'registry.json'); + let registry: { agents: Record } = { agents: {} }; + if (storage.existsSync(registryPath)) { + try { + const raw = storage.readSync(registryPath) ?? '{}'; + const parsed = JSON.parse(raw); + if (parsed && typeof parsed === 'object' && parsed.agents && typeof parsed.agents === 'object') { + registry = parsed as { agents: Record }; + } + } catch { + // Corrupt or unparsable — start fresh (don't lose preset wiring) + registry = { agents: {} }; + } + } + for (const agent of agents) { + const key = agent.name.toLowerCase(); + if (!(key in registry.agents)) { + registry.agents[key] = { + created_at: now, + persistent_name: agent.name, + universe: options.universe, + status: 'active', + }; + } + } + storage.writeSync(registryPath, JSON.stringify(registry, null, 2) + '\n'); + + // ---- history.json ---- + const historyPath = path.join(castingDir, 'history.json'); + interface CastingHistory { + assignment_cast_snapshots: Record; + universe_usage_history: Array<{ universe: string; used_at: string }>; + } + let history: CastingHistory = { + assignment_cast_snapshots: {}, + universe_usage_history: [], + }; + if (storage.existsSync(historyPath)) { + try { + const raw = storage.readSync(historyPath) ?? '{}'; + const parsed = JSON.parse(raw); + if (parsed && typeof parsed === 'object') { + history = { + assignment_cast_snapshots: parsed.assignment_cast_snapshots ?? {}, + universe_usage_history: parsed.universe_usage_history ?? [], + }; + } + } catch { + // Keep the fresh defaults above + } + } + const snapshotKey = `preset-${options.universe}-${now}`; + history.assignment_cast_snapshots[snapshotKey] = { + created_at: now, + agents: agents.map(a => a.name.toLowerCase()), + universe: options.universe, + }; + history.universe_usage_history.push({ universe: options.universe, used_at: now }); + storage.writeSync(historyPath, JSON.stringify(history, null, 2) + '\n'); + + // ---- policy.json ---- + const policyPath = path.join(castingDir, 'policy.json'); + if (!storage.existsSync(policyPath)) { + const policy = { universe_allowlist: ['*'], max_capacity: 25 }; + storage.writeSync(policyPath, JSON.stringify(policy, null, 2) + '\n'); + } +} + +/** + * Wire a preset's agents into team.md, routing.md, and casting state files. + * + * Call this after `applyPreset` has copied the agent charters. The function + * is merge-friendly: it preserves existing rows, agents, and snapshots, and + * only adds entries that aren't already present. + */ +export function scaffoldPresetIntoSquad( + squadDir: string, + agents: PresetAgent[], + presetName: string, +): void { + if (agents.length === 0) return; + const universe = `preset:${presetName}`; + writeOrMergeTeamMembers(squadDir, agents, presetName); + writeOrMergeRouting(squadDir, agents); + writeOrMergeCastingState(squadDir, agents, { universe }); +} diff --git a/packages/squad-sdk/src/runtime/cross-squad.ts b/packages/squad-sdk/src/runtime/cross-squad.ts index 755bb44f2..86b544c8a 100644 --- a/packages/squad-sdk/src/runtime/cross-squad.ts +++ b/packages/squad-sdk/src/runtime/cross-squad.ts @@ -116,10 +116,22 @@ export function validateManifest(data: unknown): data is SquadManifest { /** * Read and parse a squad manifest from a directory path. + * * Looks for `.squad/manifest.json` relative to the given root. + * + * **Dual-path acceptance:** If `repoPath` already ends in `.squad` (or + * `.squad/` / `.squad\`), the trailing segment is stripped before the + * lookup so both the repo root AND the `.squad` directory work as input. + * This matches how users naturally describe a squad in registry/upstream + * configuration ("point me at their `.squad/` dir" vs "point me at their + * repo root") without forcing one form. */ export function readManifest(repoPath: string): SquadManifest | null { - const manifestPath = join(repoPath, '.squad', 'manifest.json'); + const normalized = repoPath.replace(/[/\\]$/, ''); + const root = normalized.endsWith('.squad') || normalized.endsWith('/.squad') || normalized.endsWith('\\.squad') + ? normalized.slice(0, normalized.length - '.squad'.length).replace(/[/\\]$/, '') + : normalized; + const manifestPath = join(root, '.squad', 'manifest.json'); if (!storage.existsSync(manifestPath)) return null; try { const raw = storage.readSync(manifestPath) ?? ''; @@ -250,6 +262,99 @@ export function discoverSquads(squadDir: string): DiscoveredSquad[] { return merged; } +// ============================================================================ +// Registry CRUD — manage .squad/squad-registry.json +// ============================================================================ + +/** A single entry in `.squad/squad-registry.json`. */ +export interface RegistryEntry { + /** Identifier within this repo's registry. Unique. */ + name: string; + /** Filesystem path to the peer squad (repo root OR its .squad dir). */ + path: string; +} + +/** + * Read `.squad/squad-registry.json` and return its entries. + * Returns an empty array if the file does not exist or is malformed. + */ +export function readSquadRegistry(squadDir: string): RegistryEntry[] { + const registryPath = join(squadDir, 'squad-registry.json'); + if (!storage.existsSync(registryPath)) return []; + try { + const raw = storage.readSync(registryPath) ?? ''; + const parsed: unknown = JSON.parse(raw); + if (!Array.isArray(parsed)) return []; + return parsed + .filter((e): e is RegistryEntry => + typeof e === 'object' && e !== null && + typeof (e as RegistryEntry).name === 'string' && + typeof (e as RegistryEntry).path === 'string', + ); + } catch { + return []; + } +} + +/** + * Overwrite `.squad/squad-registry.json` with the given entries. + * Creates the file (and `.squad/` directory) if missing. + */ +export function writeSquadRegistry(squadDir: string, entries: RegistryEntry[]): void { + if (!storage.existsSync(squadDir)) { + storage.mkdirSync(squadDir, { recursive: true }); + } + const registryPath = join(squadDir, 'squad-registry.json'); + storage.writeSync(registryPath, JSON.stringify(entries, null, 2) + '\n'); +} + +/** Result of `addRegistryEntry`. */ +export interface AddRegistryEntryResult { + /** True if a new entry was written. */ + added: boolean; + /** Reason if not added: 'duplicate-name' | 'invalid-manifest'. */ + reason?: 'duplicate-name' | 'invalid-manifest'; + /** The manifest validated against the path (when added). */ + manifest?: SquadManifest; +} + +/** + * Add a peer squad to `.squad/squad-registry.json`. + * + * Validates that the path resolves to a readable manifest before writing. + * Refuses on duplicate name. Accepts both repo-root and `.squad`-suffixed + * paths (see `readManifest`). + */ +export function addRegistryEntry( + squadDir: string, + name: string, + path: string, +): AddRegistryEntryResult { + const existing = readSquadRegistry(squadDir); + if (existing.some(e => e.name === name)) { + return { added: false, reason: 'duplicate-name' }; + } + const manifest = readManifest(path); + if (!manifest) { + return { added: false, reason: 'invalid-manifest' }; + } + const next = [...existing, { name, path }]; + writeSquadRegistry(squadDir, next); + return { added: true, manifest }; +} + +/** + * Remove an entry from `.squad/squad-registry.json` by name. + * Returns true if an entry was removed, false if no matching name was found. + */ +export function removeRegistryEntry(squadDir: string, name: string): boolean { + const existing = readSquadRegistry(squadDir); + const next = existing.filter(e => e.name !== name); + if (next.length === existing.length) return false; + writeSquadRegistry(squadDir, next); + return true; +} + // ============================================================================ // Delegation // ============================================================================ @@ -317,7 +422,7 @@ export function parseIssueStatus(jsonOutput: string, issueUrl: string): CrossSqu /** Format discovered squads for terminal display. */ export function formatDiscoveryTable(squads: DiscoveredSquad[]): string { if (squads.length === 0) { - return 'No squads discovered. Add upstreams with "squad upstream add" or create a squad-registry.json.'; + return 'No squads discovered. Add a peer with "squad registry add ", or an upstream with "squad upstream add".'; } const lines: string[] = ['\nDiscovered squads:\n']; diff --git a/packages/squad-sdk/src/skills/handler-types.ts b/packages/squad-sdk/src/skills/handler-types.ts index ecc79465e..6e80ddb5b 100644 --- a/packages/squad-sdk/src/skills/handler-types.ts +++ b/packages/squad-sdk/src/skills/handler-types.ts @@ -1,7 +1,7 @@ /** * Type system for the skill-script model. * - * This module defines the complete type contract for backend skills in `.copilot/skills/`. + * This module defines the complete type contract for backend skills in `.github/skills/`. * Skills contain `scripts/` directories with executable JS handlers that replace built-in * tool handlers in ToolRegistry. * diff --git a/packages/squad-sdk/src/tools/index.ts b/packages/squad-sdk/src/tools/index.ts index ad802320f..8a728de07 100644 --- a/packages/squad-sdk/src/tools/index.ts +++ b/packages/squad-sdk/src/tools/index.ts @@ -122,7 +122,7 @@ export interface StatusQuery { } export interface SkillRequest { - /** Skill name (maps to .copilot/skills/{name}/SKILL.md) */ + /** Skill name (maps to .github/skills/{name}/SKILL.md) */ skillName: string; /** Operation: read the skill or write/update it */ operation: 'read' | 'write'; @@ -1084,7 +1084,7 @@ export class ToolRegistry { // squad_skill: Read/write agent skills const squadSkill = defineTool({ name: 'squad_skill', - description: 'Read or write agent skill definitions. Skills are stored in .copilot/skills/{name}/SKILL.md.', + description: 'Read or write agent skill definitions. Skills are stored in .github/skills/{name}/SKILL.md.', parameters: { type: 'object', properties: { @@ -1115,13 +1115,23 @@ export class ToolRegistry { } try { const projectRoot = path.dirname(this.squadRoot); - const legacySkillDir = path.join(this.squadRoot, 'skills', args.skillName); + // .github/skills/ is the canonical write location (matches squad init/upgrade + // since #1126/#1304). The legacy locations are read-only fallbacks so users + // who haven't migrated yet can still read existing skills via this tool. + const githubSkillDir = path.join(projectRoot, '.github', 'skills', args.skillName); const copilotSkillDir = path.join(projectRoot, '.copilot', 'skills', args.skillName); - const skillDir = args.operation === 'write' - ? copilotSkillDir - : this.storage.existsSync(path.join(copilotSkillDir, 'SKILL.md')) - ? copilotSkillDir - : legacySkillDir; + const legacySkillDir = path.join(this.squadRoot, 'skills', args.skillName); + + let skillDir: string; + if (args.operation === 'write') { + skillDir = githubSkillDir; + } else if (this.storage.existsSync(path.join(githubSkillDir, 'SKILL.md'))) { + skillDir = githubSkillDir; + } else if (this.storage.existsSync(path.join(copilotSkillDir, 'SKILL.md'))) { + skillDir = copilotSkillDir; + } else { + skillDir = legacySkillDir; + } const skillFile = path.join(skillDir, 'SKILL.md'); if (args.operation === 'read') { @@ -1161,7 +1171,7 @@ export class ToolRegistry { this.storage.writeSync(skillFile, skillContent); return { - textResultForLlm: `Skill written: ${args.skillName} (.copilot/skills/${args.skillName}/SKILL.md)`, + textResultForLlm: `Skill written: ${args.skillName} (.github/skills/${args.skillName}/SKILL.md)`, resultType: 'success', toolTelemetry: { skillName: args.skillName, operation: 'write', confidence: args.confidence }, }; diff --git a/packages/squad-sdk/templates/fact-checker-charter.md b/packages/squad-sdk/templates/fact-checker-charter.md new file mode 100644 index 000000000..1d03e0b4e --- /dev/null +++ b/packages/squad-sdk/templates/fact-checker-charter.md @@ -0,0 +1,83 @@ +# Fact Checker + +> Trust, but verify. Every claim gets a source check. + +## Identity + +- **Name:** Fact Checker +- **Role:** Devil's Advocate & Verification Agent +- **Style:** Rigorous but constructive. Flags issues clearly without being abrasive. +- **Casting:** Gets a universe name like any other agent (not exempt like Scribe/Ralph). + +## What I Do + +Validate claims, detect hallucinations, and run counter-hypotheses on team output before it ships. + +## Verification Methodology + +For every claim or assertion I review: + +1. **Source Check:** What evidence supports this? Can I verify it? +2. **Counter-Hypothesis:** What would disprove this? Is there an alternative explanation? +3. **Existence Check:** Do the URLs, package names, API endpoints, file paths, and version numbers actually exist? +4. **Consistency Check:** Does this contradict anything in `.squad/decisions.md` or prior team output? + +## Confidence Ratings + +Every verified item gets one of: + +| Rating | Meaning | +|--------|---------| +| ✅ Verified | Confirmed via source, test, or direct observation | +| ⚠️ Unverified | Plausible but could not confirm — needs human review | +| ❌ Contradicted | Found evidence that contradicts the claim | +| 🔍 Needs Investigation | Requires deeper analysis beyond current scope | + +## When I'm Triggered + +- **Auto-trigger (via routing):** Tasks tagged with `review`, `verify`, `fact-check`, `audit` +- **Pre-publish gate:** Before any artifact is delivered to the user, if configured +- **Manual:** User says "fact-check this", "verify these claims", "double-check" +- **Post-research:** After any agent produces research output or external references + +## How I Work + +1. **Read the artifact** — understand what's being claimed +2. **Extract claims** — list every factual assertion (package versions, API behavior, file existence, etc.) +3. **Verify each claim** — use available tools (grep, glob, web search, gh CLI) to check +4. **Run counter-hypotheses** — for key assumptions, ask "what if this is wrong?" +5. **Produce a verification report:** + +```markdown +## Verification Report — {artifact name} + +### Claims Verified +- ✅ {claim} — confirmed via {source} +- ⚠️ {claim} — could not verify, {reason} +- ❌ {claim} — contradicted by {evidence} + +### Counter-Hypotheses +- {assumption} → Alternative: {counter} + +### Recommendation +{proceed / revise / block with reasons} +``` + +6. **Write decision** if I found issues: `.squad/decisions/inbox/fact-checker-{slug}.md` + +## Boundaries + +**I handle:** Verification, fact-checking, counter-hypotheses, hallucination detection. + +**I don't handle:** Implementation, design, testing, or docs. I review, not create. + +**I am not a blocker by default.** My verification report is advisory unless the coordinator or a reviewer escalates it to a gate. + +## Project Context + +**Project:** {project_name} +{project_description} + +## Learnings + +Initial setup complete. Ready for verification work. diff --git a/packages/squad-sdk/templates/fact-checker-policy.md b/packages/squad-sdk/templates/fact-checker-policy.md new file mode 100644 index 000000000..5cf43ee7c --- /dev/null +++ b/packages/squad-sdk/templates/fact-checker-policy.md @@ -0,0 +1,104 @@ +# Fact Checker Policy + +> Authoritative verification & devil's-advocate methodology for this project. Fact Checker enforces these standards. + +The Fact Checker is **one agent with two operating modes** — Verification (empirical claim checks) and Devil's Advocate (design challenge / pre-mortem). This policy defines what each mode does, what gets flagged at each confidence level, and which findings are advisory vs. blocking. + +--- + +## Mode 1: Verification + +Empirical check of claims against sources. Triggered by `"fact-check this"`, `"verify these claims"`, `"is this true?"`, Pre-Ship ceremony, or after any agent produces external references. + +### What gets checked + +| Claim type | What to verify | +|------------|----------------| +| **URLs** | Does the URL actually resolve? (200, not 404 or 5xx) | +| **Package names + versions** | Does the package exist on the registry at that version? | +| **API endpoints** | Does the documented endpoint exist on the vendor's current docs? | +| **File paths** | Does the file exist in the repo at the claimed path? | +| **Function / type signatures** | Do they match the actual source? | +| **Quoted text** | Does the source actually contain the quoted text verbatim? | +| **Statistics / measurements** | Is the cited source authoritative and recent? | +| **Cross-references to team decisions** | Does `.squad/decisions.md` actually say what was claimed? | + +### Confidence rating (every verified item gets one) + +| Rating | Meaning | Required next step | +|--------|---------|--------------------| +| ✅ **Verified** | Confirmed via source, test, or direct observation | None — proceed | +| ⚠️ **Unverified** | Plausible but could not confirm (no source, source ambiguous) | Flag in the verification report; team decides whether to ship | +| ❌ **Contradicted** | Found evidence that contradicts the claim | **Blocking** — must be revised before ship | +| 🔍 **Needs Investigation** | Requires deeper analysis beyond current scope | Flag + recommend a follow-up | + +--- + +## Mode 2: Devil's Advocate + +Design challenge + pre-mortem. Triggered by `"play devil's advocate"`, `"what's wrong with this plan?"`, `"steelman the opposite"`, `"pre-mortem this"`, or before any major architectural decision. + +### What gets produced (every DA brief) + +1. **Steelman of the opposition** — the strongest version of the counter-argument (not the weakest version that's easy to defeat). +2. **Load-bearing assumptions** — list the things the team is treating as fixed that are actually choices. *"We assumed we had to use Postgres — what if we couldn't?"* +3. **Pre-mortem** — concrete failure scenario in 30 days. *"Imagine this shipped and failed. Write the post-mortem now."* +4. **Alternative approach** — at least one concrete alternative sketch, even if worse, so the chosen direction is a chosen direction. +5. **Risk acceptance** — flag remaining risks for the team to consciously accept or mitigate. Never a veto. + +--- + +## Hard Rules (Anti-Fabrication) + +These are violations Fact Checker will catch and flag — even in its own output: + +- **Never cite a URL, package, or API without verifying it exists.** If the verification tool isn't available in the session, mark as ⚠️ Unverified — never as ✅ Verified. +- **Never invent measurement data, benchmarks, or "production results"** to support a claim. Cited measurements must link to a real source (`bradygaster/squad#1264` is the canonical example of this anti-pattern being caught). +- **Never fabricate a counter-hypothesis** for Devil's Advocate mode. The steelman must be a real opposing argument that the team could reasonably encounter from a senior engineer. +- **Never block on opinion.** Devil's Advocate flags risks; it does not veto. Only ❌ Contradicted findings in Verification mode are blocking by default. + +--- + +## Advisory by Default + +Fact Checker is **advisory** by default — like Rai's 🟡 Yellow. Findings are surfaced; the team or coordinator decides whether to act. + +Two exceptions where Fact Checker becomes a **blocking gate**: + +1. **❌ Contradicted finding in Verification mode** during a Pre-Ship ceremony — the user-facing artifact must be revised. +2. **Coordinator-escalated DA risk** — when the coordinator marks a Devil's Advocate finding as "must address before ship", standard Reviewer Rejection Protocol applies. + +--- + +## Opt-Out Model + +- **Cannot disable** the anti-fabrication hard rules above. They are framework-level guarantees. +- **Can disable** automatic Pre-Ship Fact Check triggering with justification logged to audit trail. +- **Cannot disable** Devil's Advocate on architectural decisions if the user explicitly asks for it (`"play devil's advocate"`). +- **Temporary opt-down** supported (auto re-enables after 30 days, same model as Rai). + +--- + +## Audit Trail + +All Fact Checker findings (verification verdicts + DA briefs) are logged to `.squad/fact-checker/audit-trail.md` (append-only). Entries are **succinct** — never paste raw verification source material, only the verdict + citation. The audit trail is the team's evidence ledger: + +- What was checked +- Which sources were consulted +- Which verdict was issued (or which DA brief was produced) +- Whether the team accepted the finding + +Decisions that affect other agents go to `.squad/decisions/inbox/fact-checker-{slug}.md` for Scribe to merge into `.squad/decisions.md`. + +--- + +## Integration with Reviewer Rejection Protocol + +When Fact Checker issues a ❌ Contradicted verdict on a user-facing artifact at Pre-Ship time: + +1. **Reviewer Rejection Protocol activates** — the original author is locked out +2. **Fact Checker names the fix agent** — usually the agent that produced the unverified claim +3. **Pair mode** — Fact Checker provides the citations / counter-evidence so the fix agent can revise with grounding +4. **Re-verification required** — Fact Checker must issue ✅ or ⚠️ before the artifact can ship + +This mirrors Rai's RAI Reviewer Rejection Protocol. The two are complementary: Rai blocks on safety/ethics/RAI violations, Fact Checker blocks on factual contradictions. diff --git a/packages/squad-sdk/templates/skills/cross-squad-communication/SKILL.md b/packages/squad-sdk/templates/skills/cross-squad-communication/SKILL.md new file mode 100644 index 000000000..9286d5fa2 --- /dev/null +++ b/packages/squad-sdk/templates/skills/cross-squad-communication/SKILL.md @@ -0,0 +1,399 @@ +--- +name: "cross-squad-communication" +description: "Protocol for sending queries, delegating tasks, and sharing context between independent Squad instances across different repositories" +domain: "multi-repo coordination" +confidence: "medium" +source: "Ported from tamirdresher/squad-skills (plugins/cross-squad-communication). Companion to the registry-aware cross-squad skill — this one teaches the actual communication protocols once a peer is discovered. Pattern 0 (synchronous CLI) is the only end-to-end-validated pattern; Patterns 1, 2, 3 are documented from design but require live validation against your own setup before relying on them in production. See the Validation Status section at the bottom of this skill." +--- + +## Context + +When multiple repositories each have their own Squad (AI team), they need to exchange information: knowledge queries, PR reviews, task delegation, and dependency analysis. Each squad has its own agents, MCP tools, and issue tracker — there is no shared runtime. + +> **Companion skill — read first:** `cross-squad/SKILL.md` covers **discovery** of peer squads via `squad registry add/list/remove`. This skill picks up after a peer is known and covers the **communication protocols** themselves — the four numbered patterns below: Pattern 0 (synchronous CLI), Pattern 1 (read-only knowledge query), Pattern 2 (git-based async), and Pattern 3 (GitHub-issue-based delegation). A separate non-numbered appendix (Cross-Repo Dependency Scan) is provided as a related analysis tool, not a communication pattern. The two skills are designed to be used together. + +**When this skill applies:** +- A squad agent needs information from another squad-enabled repo +- A task needs to be delegated to another squad +- Cross-repo dependency analysis is needed +- PR review requests span repo boundaries + +**Key constraint:** Each squad has its own runtime, MCP tools, and issue tracker. Cross-squad communication can be **synchronous** (via CLI session targeting the other repo) or **asynchronous** (file-based or issue-based). The coordinator decides which approach fits. + +--- + +## Patterns + +### Decision Tree: Choosing the Right Pattern + +``` +Is the target repo cloned locally? +├─ NO → Use Pattern 3 (Issue-Based) or Pattern 2 (Git-Based Async) +└─ YES + ├─ Is this a quick query / knowledge lookup? + │ └─ YES → Use Pattern 0 (Synchronous CLI) — fastest + ├─ Does the work need to persist as artifacts? + │ └─ YES → Use Pattern 2 (Git-Based Async) + ├─ Is it a long-running analysis or multi-cycle task? + │ └─ YES → Use Pattern 2 (Git-Based Async) + └─ Is the target squad's Ralph running? + ├─ YES → Pattern 2 or 3 (async processing available) + └─ NO → Pattern 0 (Synchronous CLI) or Pattern 1 (Read-Only) +``` + +--- + +## Universal rule: every `copilot` spawn into a peer squad MUST pass `--agent squad` + +The `copilot` CLI accepts `--agent ` to select a custom agent (see `copilot --help`). Squad installs ship `.github/agents/squad.agent.md`, which is loaded **only when `--agent squad` is specified**. Without it the spawned session runs as a generic Copilot CLI session that does NOT load the peer's `team.md`, routing, MCP tools, casting, or coordinator behaviour — so you get an off-the-shelf model answering, not the peer's Squad. **Every command example in this skill that spawns `copilot` into a peer repo includes `--agent squad`; do not strip it.** + +This rule also applies anywhere else you spawn `copilot` into a Squad-initialised repo (not just cross-squad protocols) — e.g., `squad init`'s post-init tip and any automation that invokes the CLI on a squadified folder. The only case where you may omit `--agent` is when resuming an existing session (`copilot --resume `) — the resumed session preserves its original agent context. + +--- + +### Pattern 0: Synchronous CLI Session (Fastest for Interactive Queries) + +For quick knowledge queries, decision lookups, or short analyses — spawn a Copilot CLI session with the working directory set to the target squad's repo. This lets you send a prompt and get a response within the same session, using the target repo's full context. + +This is the same technique used by `ralph-watch.ps1`: write the prompt to a temp file, then invoke the CLI with that file as input. The key insight is that setting the working directory to the target repo gives the CLI session access to that squad's `.squad/` metadata, codebase, and conventions. + +**Protocol:** +1. Write prompt to a temp file (avoids argument-splitting issues, as learned in `ralph-watch.ps1`) +2. Read the file into a string and invoke `copilot -p ` with `-C ` set to the target repo (`-p` takes prompt text, NOT a file path) AND `--agent squad` so the spawned session uses the peer squad's coordinator (without `--agent` you get a generic Copilot CLI session that doesn't load the peer's `team.md`, MCP tools, or skills) +3. Receive response in the same session + +**Invocation:** +```powershell +# Spawn a Copilot CLI session targeting another squad's repo +$targetRepo = "C:\repos\platform-squad-repo" +$promptFile = New-TemporaryFile +@" +You are working in a Squad-enabled repository. +Read .squad/team.md and .squad/decisions.md first. + +[CROSS-SQUAD REQUEST] +From: research-squad +Request Type: knowledge_query +Query: What is the current architecture of the platform? What services does it expose? +Response Format: Brief structured summary +"@ | Out-File $promptFile -Encoding utf8 + +# Option A: copilot with prompt file (read file into string; -p takes text, not a path) +# --agent squad is REQUIRED: the target is another Squad install, so the spawned +# session must use that squad's coordinator (not a generic Copilot CLI session). +copilot -C $targetRepo --agent squad -p (Get-Content $promptFile -Raw) --allow-all-tools + +# Option B: Start-Process for non-blocking (ralph-watch.ps1 style) +Start-Process pwsh -ArgumentList "-NoProfile -Command `"copilot -C '$targetRepo' --agent squad -p (Get-Content '$promptFile' -Raw) --allow-all-tools`"" -Wait + +# Option C: Pipe directly (stdin is the prompt text) +"What is the platform architecture?" | copilot -C $targetRepo --agent squad --allow-all-tools +``` + +**When to use synchronous vs async:** + +| Scenario | Pattern | Why | +|----------|---------|-----| +| Quick knowledge query | Synchronous CLI (Pattern 0) | Fast answer, no overhead | +| "What did you decide about X?" | Synchronous CLI (Pattern 0) | Read decisions.md via the target squad's context | +| PR review request | Either (Pattern 0 or 2/3) | Sync for quick feedback, async for thorough review | +| Task delegation (do work in their repo) | Async (Pattern 2 or 3) | Work needs to persist beyond the session | +| Long-running analysis | Async (Pattern 2) | May take multiple cycles | +| Target repo not locally cloned | Async (Pattern 3) | Can't set working directory to a remote repo | + +**The coordinator decides which pattern to use based on:** +1. Is the target repo cloned locally? → If yes, sync CLI is available +2. Is this a quick query or a long task? → Quick = sync, long = async +3. Does the work need to persist? → If yes, use async (creates artifacts) +4. Is the target squad's Ralph running? → Needed for async processing + +**Requirements:** +- Target repo must be cloned locally (for `copilot -C `) +- Target repo must be Squad-initialised (`.squad/config.json` + `.github/agents/squad.agent.md` present), so `--agent squad` resolves to the peer's coordinator +- Prompt file avoids argument-splitting bugs (see `ralph-watch.ps1` lines 2166-2184) + +**Response quality:** ⭐⭐⭐⭐⭐ — the CLI session has full context of the target repo, including code, squad metadata, and MCP tools. + +### Liveness Protocol for Pattern 0 + +The synchronous CLI session requires monitoring to avoid false timeouts. With 7+ MCP servers initializing and `.squad/` metadata being read, startup can take 30-60 seconds. A hard timeout kills valid sessions before they complete. Instead, monitor the agency session's activity log directory. + +**Health Check Approach:** + +Instead of a fixed wall-clock timeout, monitor the agency session log directory for activity: + +```powershell +# The Copilot CLI creates a session log directory at ~/.copilot/logs/. +# Older `agency` runtimes wrote to ~/.agency/logs/; fall back to that +# location if the new path doesn't exist yet on the user's machine. +# e.g., ~/.copilot/logs/session_20260325_071211_57824 +$copilotLogs = "$env:USERPROFILE\.copilot\logs" +$agencyLogs = "$env:USERPROFILE\.agency\logs" +$logRoot = if (Test-Path $copilotLogs) { $copilotLogs } elseif (Test-Path $agencyLogs) { $agencyLogs } else { $null } +if ($logRoot) { + $logDir = Get-ChildItem $logRoot -Directory | Sort-Object LastWriteTime -Descending | Select-Object -First 1 +} +$lastSize = 0 +$stallCount = 0 + +while ($proc -and -not $proc.HasExited) { + Start-Sleep -Seconds 15 + $currentSize = (Get-ChildItem $logDir -Recurse -File | Measure-Object -Property Length -Sum).Sum + + if ($currentSize -eq $lastSize) { + $stallCount++ + if ($stallCount -ge 4) { # 60s with no progress + Write-Warning "Session stalled — no log activity for 60s" + break + } + } else { + $stallCount = 0 + $lastSize = $currentSize + } +} +``` + +**Progress Indicators (What Counts as "Alive"):** + +- New files appearing in the session log directory (e.g., `transcript.log`, `mcp-server-logs/`) +- Log file size increasing (indicates active processing) +- New or modified `.squad/` files in the target repo (e.g., `decisions/inbox.md`, `identity/history.md`) +- Process still running and consuming non-idle CPU time + +**Stall Detection (When to Intervene):** + +- **No log activity for 60s** → Issue a warning; session may be slow but not hung +- **No log activity for 120s** → Likely stuck; consider terminating and checking logs +- **Process exited with non-zero exit code** → Failed; examine `transcript.log` and `stderr` for errors +- **MCP server connection timeout** → Session blocked waiting for an MCP server response + +**Recovery Actions When Stalled:** + +1. **Check for user input waiting:** Inspect logs for prompts or dialogs (shouldn't happen with `--autopilot`) +2. **Check MCP server health:** Review `mcp-server-logs/` for connection errors or timeouts +3. **Retry with `--disable-builtin-mcps` flag:** For lightweight queries that don't require MCP tools + ```powershell + # Retry without MCP servers — faster startup, limited capability + copilot -C $targetRepo --agent squad -p (Get-Content $promptFile -Raw) --disable-builtin-mcps --allow-all-tools + ``` +4. **Increase timeout threshold:** If MCP server initialization is consistently slow (>90s), raise threshold before declaring stall + +--- + +### Pattern 1: Read-Only Knowledge Query (No CLI Needed) + +For questions about another squad's architecture, decisions, or current state — read their `.squad/` metadata directly. + +**Protocol:** +1. Read target repo's `.squad/team.md` → get stack, members, issue source +2. Read `.squad/decisions.md` → get architectural decisions +3. Read `.squad/routing.md` → understand who handles what +4. Read `.squad/identity/now.md` → get current focus +5. Scan code structure if needed (csproj files, directory layout) + +**Requirements:** +- Target repo must be cloned locally or accessible via git +- No authentication needed beyond git read access + +**Example:** +```powershell +# Query another squad's architecture +$targetRepo = "C:\repos\platform-squad-repo" +Get-Content "$targetRepo\.squad\team.md" +Get-Content "$targetRepo\.squad\decisions.md" +Get-Content "$targetRepo\.squad\identity\now.md" +``` + +**Response quality:** ⭐⭐⭐⭐ — excellent for structural/architectural questions. + +--- + +### Pattern 2: Async Task Request (Git-Based) + +For work that needs the target squad to execute (PR reviews, issue analysis, code changes). + +**Protocol:** +1. Create request file in YOUR repo: `.squad/cross-squad/requests/{timestamp}-{target}-{id}.yaml` +2. Commit and push +3. Target squad's Ralph detects on next cycle +4. Target squad processes and writes response to their `.squad/cross-squad/responses/` +5. Your Ralph picks up the response + +**Request File Format:** +```yaml +id: req-2026-06-13-001 +source_squad: research-squad +source_repo: your-org/research-squad-repo +target_squad: platform-squad +target_repo: your-org/platform-squad-repo +request_type: knowledge_query | pr_review | task_delegation | dependency_check +priority: high | normal | low +created_at: 2026-06-13T10:00:00Z +query: "What is the current architecture of the platform?" +routing_hint: "lead" # optional — which agent should handle this +status: pending +``` + +**Response File Format:** +```yaml +id: req-2026-06-13-001 +responding_squad: platform-squad +responding_agent: lead +responded_at: 2026-06-13T10:15:00Z +status: completed | partial | rejected +response: | + The platform architecture consists of... +artifacts: [] # optional file paths +``` + +--- + +### Pattern 3: Issue-Based Delegation (For GitHub-Hosted Repos) + +For repos on GitHub, use issues with labels as the message bus. + +**Protocol:** +1. Create issue in target repo with label `squad:cross-squad` +2. Include source squad identifier and routing hint in issue body +3. Target squad's Ralph picks up and routes to appropriate agent +4. Response posted as issue comment +5. Issue closed when complete + +**Example:** +```bash +gh issue create \ + --repo your-org/platform-squad-repo \ + --title "[Cross-Squad] Architecture query from research-squad" \ + --body "Source: research-squad\nQuery: What services does the platform expose?\nRouting: lead" \ + --label "squad:cross-squad" +``` + +**Limitation:** Only works for repos on GitHub. Other platforms (Azure DevOps, GitLab, etc.) need different approach. + +--- + +### Appendix: Cross-Repo Dependency Scan (Related Analysis Tool — Not a Communication Pattern) + +> This section is intentionally listed as an appendix rather than "Pattern 4" — it is a one-off analysis utility for discovering how two repos relate, not a protocol the coordinator picks from the decision tree above. The four numbered communication patterns are 0–3. + +For discovering how two repos relate to each other. + +**Protocol:** +1. Search both repos for mutual references: + ```powershell + Select-String -Path (Get-ChildItem $repoA -Recurse -Include "*.md","*.cs","*.json","*.csproj") ` + -Pattern $repoB_name + Select-String -Path (Get-ChildItem $repoB -Recurse -Include "*.md","*.cs","*.json","*.csproj") ` + -Pattern $repoA_name + ``` +2. Check shared NuGet packages / npm packages +3. Check shared ADO project or GitHub org +4. Document relationship type: code dependency, operational coupling, shared infra + +--- + +## Discovery Protocol + +Before sending any cross-squad request, verify the target: + +``` +1. Does .squad/team.md exist? → Squad is installed +2. What is the issue_source? → GitHub Issues | ADO | Planner +3. What agents are active? → Check member status column +4. What is the routing table? → Read routing.md +5. What is the current focus? → Read identity/now.md +6. Is Ralph running? → Check for recent commits by Ralph +``` + +If `.squad/team.md` doesn't exist, the repo is not squad-enabled. Fall back to standard human communication. + +--- + +## Platform Compatibility Matrix + +| Source Issue Tracker | Target Issue Tracker | Mechanism | +|---------------------|---------------------|-----------| +| GitHub Issues | GitHub Issues | Issue-based (Pattern 3) | +| GitHub Issues | ADO Work Items | Git-based (Pattern 2) | +| GitHub Issues | Planner | Git-based (Pattern 2) | +| ADO Work Items | GitHub Issues | Issue-based (Pattern 3) via `gh` CLI | +| ADO Work Items | ADO Work Items | ADO cross-project work items | +| Any | Any | Git-based (Pattern 2) — universal fallback | + +--- + +## Examples + +### Example 1: research-squad queries platform-squad architecture + +```powershell +# Step 1: Read metadata (Pattern 1) +$target = "C:\repos\platform-squad-repo" +$team = Get-Content "$target\.squad\team.md" -Raw +$decisions = Get-Content "$target\.squad\decisions.md" -Raw + +# Step 2: Extract answer from metadata +# team.md reveals tech stack and member roles +# decisions.md reveals architectural choices + +# Step 3: If deeper analysis needed, create async request (Pattern 2) +``` + +### Example 2: Request PR review from another squad + +```yaml +# .squad/cross-squad/requests/2026-06-13-platform-squad-pr-review.yaml +id: pr-review-001 +source_squad: research-squad +target_squad: platform-squad +request_type: pr_review +priority: normal +query: "Review PR #54 — package version fix. Check for correctness." +routing_hint: "lead" +status: pending +``` + +--- + +## Anti-Patterns + +### ⚠️ Know when synchronous CLI is NOT the right choice +```powershell +# WRONG — don't use sync CLI for long-running tasks that need artifacts +copilot -C $targetRepo --agent squad -p (Get-Content $promptFile -Raw) --allow-all-tools +# If the task creates files, PRs, or takes multiple cycles → use async (Pattern 2 or 3) + +# WRONG — don't use sync CLI when the target repo isn't cloned locally +copilot -C "C:\not\cloned\yet" --agent squad --allow-all-tools +# If the repo isn't available locally → use issue-based delegation (Pattern 3) +``` +Synchronous CLI sessions (Pattern 0) are valid for quick queries and knowledge lookups. Use async patterns for work that needs to persist or where the target repo isn't available locally. + +### ❌ Don't assume shared MCP tools +Each squad has its own MCP server instances. You cannot invoke another squad's ADO tools or GitHub tools from your session. + +### ❌ Don't skip the discovery step +Always read `team.md` first. The target squad may use a different issue tracker, have different agents, or be in a different state than expected. + +### ❌ Don't send requests to squads without Ralph +If the target squad doesn't have Ralph (Work Monitor) running, async requests will never be processed. Check for recent Ralph activity first. + +### ❌ Don't mix up repo platforms +Different repos may use GitHub Issues vs Azure DevOps Work Items vs Jira. Check `team.md` / repository metadata for the right tooling before sending requests. + +--- + +## Validation Status + +This skill was originally drafted against two prototype squad setups (a GitHub-hosted platform squad with ~10 agents and an Azure DevOps-hosted automation squad with ~4 agents). The protocols are platform-agnostic; the examples in this document use generic names so you can substitute your own repos. Patterns 0 and 1 have been exercised end-to-end in those prototypes; Patterns 2 and 3 are documented from design but have not been end-to-end-validated against a live target repo. + +| Scenario | Result | +|----------|--------| +| Knowledge query (read-only) | ✅ Works via Pattern 1 | +| Step handler discovery | ✅ Works via file scan | +| PR review (basic) | ⚠️ Partial — git log only, no API | +| Backlog enumeration | ⚠️ Partial — depends on issue platform | +| Dependency analysis | ✅ Works via cross-reference scan | +| CLI invocation (sync) + Liveness Protocol | ✅ Works — session launches successfully; log monitoring prevents false timeouts | + +**Confidence: MEDIUM** — Synchronous CLI pattern (Pattern 0) validated end-to-end. Liveness protocol provides operational robustness against slow MCP initialization. Git-based async (Pattern 2) and issue-based (Pattern 3) untested end-to-end. Production readiness requires Ralph integration on both sides. diff --git a/packages/squad-sdk/templates/skills/cross-squad/SKILL.md b/packages/squad-sdk/templates/skills/cross-squad/SKILL.md index 1d4e3a251..d008e85b8 100644 --- a/packages/squad-sdk/templates/skills/cross-squad/SKILL.md +++ b/packages/squad-sdk/templates/skills/cross-squad/SKILL.md @@ -1,9 +1,20 @@ --- name: "cross-squad" -description: "Coordinating work across multiple Squad instances" +description: "Coordinating work across multiple Squad instances — discovery, delegation, and disambiguation when the user says 'squad' (the product) vs casual English 'group of agents'." domain: "orchestration" confidence: "medium" source: "manual" +triggers: + - "spawn N squads" + - "spawn a squad" + - "another squad" + - "two squads of" + - "second squad" + - "fan out to squads" + - "delegate to a squad" + - "set up squads for" + - "create a squad to review" + - "ask the other squad" tools: - name: "squad-discover" description: "List known squads and their capabilities" @@ -14,21 +25,60 @@ tools: --- ## Context + +> **Read this FIRST any time the user says "squad" as a thing to spawn, delegate to, address, or fan out to** — e.g., *"spawn two squads of designers and devs"*, *"ask the other squad"*, *"delegate to a squad"*. In Squad-PRODUCT vocabulary, "squad" is a **peer** (an independent installation with its own `.squad/`, `team.md`, MCP server, and agents) — NOT a generic English synonym for "team" or "group". Do not fan out raw `task` agents inside your own coordinator context when the user means "another squad". Use the discovery and communication patterns below (and the companion `cross-squad-communication` skill for the actual protocols). + When an organization runs multiple Squad instances (e.g., platform-squad, frontend-squad, data-squad), those squads need to discover each other, share context, and hand off work across repository boundaries. This skill teaches agents how to coordinate across squads without creating tight coupling. +> **Companion skill — for protocol details:** `cross-squad-communication/SKILL.md` covers the four communication patterns (synchronous CLI, read-only knowledge query, git-based async, and GitHub-issue-based delegation) once a peer squad is discovered via the registry below. This skill answers "who?" — the companion answers "how?". + Cross-squad orchestration applies when: - A task requires capabilities owned by another squad - An architectural decision affects multiple squads - A feature spans multiple repositories with different squads - A squad needs to request infrastructure, tooling, or support from another squad +## Disambiguation: "squad" vs ad-hoc agents + +When the user uses the word **"squad" / "squads"** or asks to **"spawn a team"**, the coordinator MUST treat it as a literal reference to a Squad install (a `.squad/` directory with its own roster, casting, and coordinator) — NOT as a casual synonym for "a group of sub-agents". + +### Default behaviour (apply unless explicitly told otherwise) + +| User says | Coordinator does | +|---|---| +| *"spawn two squads of X and Y"* / *"set up squads for X, Y, Z"* | Bootstrap N **real** Squad installs — separate folder + `git init` + `squad init` per squad — then use the cross-squad patterns below (`.squad/manifest.json`, `squad registry add`, `squad delegate`) and the protocols in the `cross-squad-communication` skill | +| *"ask the other squad about X"* / *"delegate to the data squad"* | Discover the peer via `squad registry list` (or by reading a known `.squad/manifest.json`), then use `cross-squad-communication` Pattern 0 / 1 / 2 / 3 — never re-implement the protocol with `task` | +| *"spawn a few agents to do X"* / *"have some agents review X"* / *"in parallel, get sub-agents to..."* | Ad-hoc `task` fan-out is fine — no `.squad/` bootstrap needed. This is the only path where raw `task` is appropriate when the user mentioned a multi-agent activity | + +### Ambiguous? `ask_user`, never silently downgrade + +If the request **could** be either interpretation AND bootstrapping real squads is non-trivial (more than one or two `squad init` runs), you MUST use the `ask_user` tool with a 2-choice prompt before proceeding: + +``` +question: "Should I create separate Squad installs or just dispatch ad-hoc agents?" +choices: + - "Real squads — separate .squad/ per squad (heavier, persistent, can be re-engaged later)" + - "Ad-hoc agents — one-shot `task` dispatch (lighter, ephemeral, no .squad/ created)" +``` + +The cost of asking is one `ask_user`. The cost of getting it wrong is the user has to redo the work. **Never silently pick the cheaper option just because it feels disproportionate for the task size — surface the trade-off and let the user pick.** + +### Anti-patterns (every one of these is a real failure mode observed in production) + +- **Calling `task` sub-agents "squad-alpha" / "squad-beta"** and treating them as squads. Naming something a squad doesn't make it one — a squad has its own `.squad/`, `team.md`, MCP server, and coordinator. If those aren't there, it's not a squad. +- **Matching a prior session's ad-hoc pattern without re-checking current intent.** If you see existing `reviews/squad-alpha/` folders from a previous run, that's a hint, NOT a contract — the user may have meant something different this time. Re-evaluate from scratch. +- **Silently choosing the cheaper interpretation because "bootstrapping two real squads for a 30-line app feels disproportionate".** That's a judgment call for the USER to make, not the coordinator. Use `ask_user`. +- **Loading the `cross-squad` skill, reading it, then doing `task` fan-out anyway** because the eager-execution / parallel-fan-out doctrine pulled you back. The disambiguation rule on this page OVERRIDES the generic fan-out doctrine when "squad" was the trigger. + ## Patterns ### Discovery via Manifest -Each squad publishes a `.squad/manifest.json` declaring its name, capabilities, and contact information. Squads discover each other through: -1. **Well-known paths**: Check `.squad/manifest.json` in known org repos -2. **Upstream config**: Squads already listed in `.squad/upstream.json` are checked for manifests -3. **Explicit registry**: A central `squad-registry.json` can list all squads in an org +Each squad publishes a `.squad/manifest.json` declaring its name, capabilities, and contact information. Squads discover each other through two mechanisms: + +1. **`.squad/squad-registry.json`** — **discovery-only.** Peer squads are findable via `squad discover` and addressable via `squad delegate`, but their skills/decisions/wisdom are NOT loaded into your coordinator. Manage with `squad registry add/list/remove`. +2. **`.squad/upstream.json`** — **discovery + inheritance.** Squads listed here are also discoverable, AND your coordinator inherits their skills/decisions/wisdom/routing at session start. Manage with `squad upstream add/list/remove/sync`. + +Both forms read the peer's manifest via the same code path. The `path` field is the **repository root** (e.g. `../friend-repo`), and Squad appends `.squad/manifest.json` internally. Pointing at the `.squad/` directory works too — Squad accepts both forms (`readManifest` strips a trailing `.squad` if present). ```json { @@ -73,9 +123,19 @@ Track delegated work completion: ## Examples +### Registering a peer squad (no inheritance) +```bash +# Friend's repo is checked out at ../friend-platform/ +squad registry add platform-squad ../friend-platform + +# Verify +squad registry list +squad discover +``` + ### Discovering squads ```bash -# List all squads discoverable from upstreams and known repos +# List all squads discoverable from registry + upstreams squad discover # Output: diff --git a/packages/squad-sdk/templates/skills/release-process/SKILL.md b/packages/squad-sdk/templates/skills/release-process/SKILL.md index c80603986..18e0e84b1 100644 --- a/packages/squad-sdk/templates/skills/release-process/SKILL.md +++ b/packages/squad-sdk/templates/skills/release-process/SKILL.md @@ -9,7 +9,7 @@ source: "earned" # Release Process > Earned knowledge from the v0.9.0→v0.9.1 and v0.9.4 incidents. Every agent involved in releases MUST read this before starting release work. -> See also: `.copilot/skills/release-process/SKILL.md` for the Copilot-facing runbook. +> See also: `.github/skills/release-process/SKILL.md` for the Copilot-facing runbook. ## SCOPE @@ -213,5 +213,5 @@ See `.github/workflows/squad-ci.yml` → `publish-policy` job for implementation - v0.9.4 fixes: PR #1042 (CHANGELOG), PR #1043 (root package.json), PR #1044 (lockfile integrity) - Retro: `.squad/decisions/inbox/surgeon-v091-retrospective.md` - CI audit: `.squad/decisions/inbox/booster-ci-audit.md` -- Copilot-level skill: `.copilot/skills/release-process/SKILL.md` +- Copilot-level skill: `.github/skills/release-process/SKILL.md` - Playbook: `PUBLISH-README.md` (repo root) diff --git a/packages/squad-sdk/templates/skills/squad-commands/SKILL.md b/packages/squad-sdk/templates/skills/squad-commands/SKILL.md new file mode 100644 index 000000000..046dde513 --- /dev/null +++ b/packages/squad-sdk/templates/skills/squad-commands/SKILL.md @@ -0,0 +1,303 @@ +--- +name: squad-commands +description: > + Categorized catalog of common Squad operations. Coordinator reads this + file and presents it as an interactive menu when the user asks for + available commands or help. +domain: squad-operations +confidence: high +source: first-party +triggers: ["squad commands", "what can squad do", "show me squad options", "slash commands"] +--- + +## Menu Presentation Rules + +When the user triggers this skill ("squad commands", "help", "what can squad do", etc.): + +1. **Category-level menu first.** Present category names as an `ask_user` choice list: + ``` + 📋 Squad Commands — pick a category: + 1. Install & Upgrade + 2. Team Management + 3. Issues & PRs + 4. Plugins & Skills + 5. Model & Cost + 6. Sessions & State + ``` +2. **Drill-down.** After selection, show operation titles in that category as a second `ask_user` list. +3. **Direct match skips the menu.** If the user says "how do I upgrade with state backend," match to the specific entry and go straight to argument collection. +4. **Compact fallback.** If `ask_user` is unavailable, render as a markdown table instead. +5. **Back / Cancel.** Include "← Back to categories" in sub-menus. Include "Cancel" in confirmation prompts. Respect "never mind" / "cancel" at any point. + +**Argument collection:** For entries with `args`, iterate the list sequentially. Use `ask_user` with choices when `choices` is provided; free-text prompt otherwise. If the user says "just do it" or "defaults are fine," skip remaining args and use their defaults. + +**Confirmation template:** +``` +⚠️ This will {action-description}. +{what will change} +Proceed? (yes / no) +``` + +--- + +## Install & Upgrade + +### Upgrade Squad CLI + +- **intent:** upgrade squad, update squad, install latest version, get new version +- **summary:** Upgrade Squad CLI to the latest version for your channel +- **action:** shell +- **command:** squad upgrade +- **args:** + - `state-backend`: Which state backend? | choices: {worktree, git-notes, orphan, two-layer} | default: (keep current) +- **confirm:** false +- **platform_caveats:** Requires terminal. In VS Code, open the integrated terminal and run the command directly. + +### Initialize Squad + +- **intent:** set up squad, initialize squad, create team, start squad in this project +- **summary:** Scaffold Squad in the current directory (idempotent) +- **action:** shell +- **command:** squad init +- **args:** (none) +- **confirm:** false +- **platform_caveats:** Requires terminal. Recommend a standalone terminal for best results. + +### Switch State Backend + +- **intent:** switch state backend, change state storage, use git-notes, use orphan branch +- **summary:** Change where Squad stores mutable state (config.json) +- **action:** file-edit +- **command:** .squad/config.json → stateBackend +- **args:** + - `stateBackend`: Which state backend? | choices: {worktree, git-notes, orphan, two-layer} | default: (keep current) +- **confirm:** true +- **platform_caveats:** May require migration if switching away from worktree. Show current value and new value before confirming. + +--- + +## Team Management + +### Add Team Member + +- **intent:** add team member, hire agent, add agent, add developer, recruit +- **summary:** Add a new agent to the team roster +- **action:** coordinator +- **command:** Add Team Member flow (Init Mode / Team Mode) +- **args:** + - `role`: What role should this agent fill? (e.g., Frontend Dev, Backend Dev, QA Engineer) + - `name`: Preferred name or casting universe? | default: (auto-cast from active universe) +- **confirm:** false + +### Remove Team Member + +- **intent:** remove team member, fire agent, delete agent, remove developer +- **summary:** Remove an agent and delete their charter and history files +- **action:** coordinator +- **command:** Remove Team Member flow +- **args:** + - `member`: Which team member to remove? (name or role) +- **confirm:** true + +### Reassign Roles + +- **intent:** reassign role, change role, swap roles, update team member role +- **summary:** Update a team member's role in team.md and their charter +- **action:** coordinator +- **command:** Update team.md roster + charter.md +- **args:** + - `member`: Which team member? + - `newRole`: New role? +- **confirm:** false + +### Show Roster + +- **intent:** show roster, who is on the team, list team members, show team, capability profile +- **summary:** Display the current team roster and capability profile +- **action:** coordinator +- **command:** Direct Mode — read team.md, answer +- **args:** (none) +- **confirm:** false + +--- + +## Issues & PRs + +### Connect GitHub Repo + +- **intent:** connect github, enable issues, set up issues, link repository, github issues mode +- **summary:** Connect this project to GitHub Issues via gh auth +- **action:** coordinator +- **command:** GitHub Issues Mode (connection flow) +- **args:** (none) +- **confirm:** false +- **platform_caveats:** Requires `gh auth login` to have been run in the terminal. + +### Triage Issues + +- **intent:** triage issues, review issues, assign issues, label issues +- **summary:** Run the Lead triage flow on open GitHub issues +- **action:** coordinator +- **command:** GitHub Issues Mode → Lead triage +- **args:** (none) +- **confirm:** false + +### Activate Ralph + +- **intent:** activate ralph, start ralph, ralph go, start work monitor, start auto-work +- **summary:** Activate Ralph — Work Monitor — to pick up and run queued issues +- **action:** coordinator +- **command:** Ralph — Work Monitor triggers +- **args:** (none) +- **confirm:** false + +### Set Ralph Polling Interval + +- **intent:** set ralph interval, change ralph timing, how often does ralph check, ralph every N minutes +- **summary:** Tell Ralph how frequently to poll for new work +- **action:** coordinator +- **command:** Ralph trigger: "Ralph, check every N minutes" +- **args:** + - `interval`: How often should Ralph poll? (in minutes) | default: 10 +- **confirm:** false + +### Start Squad Watch + +- **intent:** start watch, squad watch, monitor issues, watch for issues, auto-triage +- **summary:** Start squad watch to continuously poll and triage issues +- **action:** shell +- **command:** squad watch +- **args:** + - `interval`: Poll interval in minutes | default: 10 +- **confirm:** false +- **platform_caveats:** CLI-only — long-running foreground process. Not viable in VS Code without an integrated terminal. Run: `squad watch --interval {n}` in your terminal. + +--- + +## Plugins & Skills + +### Browse Plugin Marketplace + +- **intent:** browse plugins, explore plugins, what plugins are available, plugin marketplace +- **summary:** Browse available plugins in the Squad marketplace +- **action:** shell +- **command:** squad plugin marketplace browse +- **args:** + - `name`: Plugin name to search for | default: (browse all) +- **confirm:** false + +### Add Marketplace Plugin + +- **intent:** add plugin, install plugin, get plugin from marketplace +- **summary:** Add a plugin from the marketplace to this Squad +- **action:** shell +- **command:** squad plugin marketplace add +- **args:** + - `plugin`: Plugin owner/repo (e.g., owner/plugin-name) +- **confirm:** false + +### Remove Marketplace Plugin + +- **intent:** remove plugin, uninstall plugin, delete plugin +- **summary:** Remove an installed marketplace plugin +- **action:** shell +- **command:** squad plugin marketplace remove +- **args:** + - `name`: Plugin name to remove +- **confirm:** true + +### List Marketplace Plugins + +- **intent:** list plugins, show installed plugins, what plugins do I have +- **summary:** List all plugins registered in this Squad +- **action:** shell +- **command:** squad plugin marketplace list +- **args:** (none) +- **confirm:** false + +### List Installed Skills + +- **intent:** list skills, show skills, what skills are installed, skill catalog +- **summary:** List all skills installed in .squad/skills/ and .copilot/skills/ +- **action:** coordinator +- **command:** Direct Mode — list .squad/skills/ and .copilot/skills/ directories +- **args:** (none) +- **confirm:** false + +--- + +## Model & Cost + +### Set Default Model + +- **intent:** set default model, change model, use gpt-4, use claude, switch model +- **summary:** Set the default model for all agents in config.json +- **action:** file-edit +- **command:** .squad/config.json → defaultModel +- **args:** + - `model`: Model name (e.g., gpt-4o, claude-sonnet-4.5, o3) +- **confirm:** false + +### Override Per-Agent Model + +- **intent:** set model for agent, agent model override, use different model for one agent +- **summary:** Set a model override for a specific agent in config.json +- **action:** file-edit +- **command:** .squad/config.json → agentModelOverrides.{agentName} +- **args:** + - `agent`: Agent name (must match name in team.md) + - `model`: Model name (e.g., gpt-4o, claude-sonnet-4.5) +- **confirm:** false + +### Clear Model Preference + +- **intent:** clear model, reset model, remove model preference, use default model +- **summary:** Remove a model override from config.json (reverts to system default) +- **action:** file-edit +- **command:** .squad/config.json → remove defaultModel or agentModelOverrides.{agentName} +- **args:** + - `scope`: Clear default or a specific agent? | choices: {default model, specific agent} | default: default model + - `agent`: Agent name (only if scope = specific agent) +- **confirm:** false + +--- + +## Sessions & State + +### Catch-Up Summary + +- **intent:** catch me up, what happened, status, what did the team do, session summary +- **summary:** Summarize recent agent activity and key decisions +- **action:** coordinator +- **command:** Session catch-up flow (lazy scan) +- **args:** (none) +- **confirm:** false + +### Show Recent Decisions + +- **intent:** show decisions, recent decisions, what decisions were made, decision log +- **summary:** Display recent entries from .squad/decisions.md +- **action:** coordinator +- **command:** Direct Mode — read decisions.md, answer +- **args:** (none) +- **confirm:** false + +### Archive Old Decisions + +- **intent:** archive decisions, clean up decisions, move old decisions, compact decisions +- **summary:** Move old decisions from decisions.md to decisions-archive.md +- **action:** coordinator +- **command:** Move entries older than threshold from .squad/decisions.md → .squad/decisions-archive.md +- **args:** + - `olderThan`: Archive decisions older than how many days? | default: 30 +- **confirm:** true + +### Summarize Agent History + +- **intent:** summarize history, what did agent do, agent history, compress history +- **summary:** Spawn an agent to summarize and compress a team member's history file +- **action:** coordinator +- **command:** Spawn agent with history.md summarization task +- **args:** + - `member`: Which team member's history to summarize? +- **confirm:** false diff --git a/packages/squad-sdk/templates/skills/squad-help/SKILL.md b/packages/squad-sdk/templates/skills/squad-help/SKILL.md new file mode 100644 index 000000000..81bc4ae2a --- /dev/null +++ b/packages/squad-sdk/templates/skills/squad-help/SKILL.md @@ -0,0 +1,97 @@ +--- +name: "squad-help" +description: "How to actually use Squad — Squad is a custom Copilot agent (invoked via the task tool with agent_type='Squad'), not a skill. This file explains the right invocation paths for setting up a team, listing squad commands, and initializing Squad in a new project." +allowedTools: [] +confidence: high +domain: squad-onboarding +--- + +# Skill: squad-help + +> **Quick reference.** If you're reading this because a user said "use squad" or "squad" or "set up a squad", you're in the right place — read on for the correct invocation paths. + +--- + +## Squad is a custom agent, not a skill + +The Squad framework registers a **custom Copilot CLI agent** at `.github/agents/squad.agent.md`. The agent is named **`Squad`** and its description is *"Your AI team. Describe what you're building, get a team of specialists that live in your repo."* + +Copilot CLI agents and skills are different things: + +| Thing | How to invoke | Example | +|---|---|---| +| **Skill** | `skill(name)` tool call or natural-language match | `skill(squad-commands)` | +| **Agent** | `task` tool with `agent_type=` | `task(name="...", agent_type="Squad", prompt="...")` | +| **Slash command** | Built-in CLI keyword | `/agent`, `/skills`, `/mcp` | + +Calling `skill(Squad)` will fail with *"Skill not found: Squad"* because Squad is the agent, not a skill. (`/squad` as a slash command also does not exist — only built-in CLI keywords like `/agent`, `/skills`, `/mcp` are slash commands. There's no way to map a skill name to a slash command without a Copilot CLI feature change.) + +--- + +## How to actually use Squad + +Pick the path that matches the user's intent: + +### A) Invoke the Squad coordinator agent (most common) + +The Squad coordinator orchestrates a team of specialists. It routes work to the right agent, scaffolds a team if none exists, and enforces handoffs. + +```text +task( + name="", + agent_type="Squad", + prompt="" +) +``` + +Use this when the user says things like: +- *"Use Squad to build X"* +- *"Set up an AI team for this project"* +- *"Have the Squad coordinator design Y"* +- *"Spawn Squad"* / *"Squad, help me with ..."* + +### B) See what Squad commands exist + +The `squad-commands` skill is a categorized catalog of common Squad operations. The coordinator presents it as an interactive menu. + +Trigger by natural-language match: `"squad commands"`, `"what can squad do"`, `"show me squad options"`, `"slash commands"`, `"what commands are available"`. + +Use this when the user says things like: +- *"What can Squad do?"* +- *"Show me the squad commands"* +- *"squad help"* + +### C) Initialize Squad in a fresh project + +`squad init` is a **shell command**, not a tool call. The user runs it in their terminal in a project that has no `.squad/` directory yet. + +```bash +squad init +``` + +Do **not** try to invoke this from inside an existing Copilot session — `.squad/` is already initialized if you're reading this file. + +--- + +## What NOT to do + +- ❌ Do not call `skill(Squad)`, `skill(squad)`, or `skill(squad-coordinator)` — Squad is not a skill. +- ❌ Do not type `/squad` expecting a slash command — slash commands are CLI keywords, not skill names. Use `/agent` (browse) or invoke the `Squad` agent via the `task` tool. +- ❌ Do not call `task(agent_type="Squad", …)` for tiny tasks the current agent can handle directly. Squad is for work that needs orchestration; trivial edits do not. + +--- + +## How this skill was discovered + +This skill ships from the Squad SDK templates and is wired into `MANIFEST_SKILL_NAMES`. It lives at `.copilot/skills/squad-help/SKILL.md` so the Copilot CLI's `/skills` loader picks it up alongside the other bundled Squad skills. + +If you removed this skill on purpose, the model will fall back to its own reasoning and may make the lookup mistakes described above. + +--- + +## See also + +- `.github/agents/squad.agent.md` — the actual Squad coordinator agent +- `.copilot/skills/squad-commands/SKILL.md` — the command catalog +- `.copilot/skills/squad-conventions/SKILL.md` — conventions for working on the Squad codebase itself +- `.copilot/skills/squad-version-check/SKILL.md` — version-stamping mechanics diff --git a/packages/squad-sdk/templates/skills/squad-version-check/SKILL.md b/packages/squad-sdk/templates/skills/squad-version-check/SKILL.md new file mode 100644 index 000000000..3f3aebb0a --- /dev/null +++ b/packages/squad-sdk/templates/skills/squad-version-check/SKILL.md @@ -0,0 +1,169 @@ +--- +name: "squad-version-check" +description: "Internals of how @bradygaster/squad-cli stamps its version, how `squad upgrade` works (what it preserves vs overwrites), and how to probe the npm registry for the latest version from a coordinator prompt." +allowedTools: [] +confidence: medium +domain: squad-internals +source: "Discovered by Data; validated in bradygaster/squad#1173 recon (2026-05-26)." +--- + +# SKILL: Squad CLI Internals — Version Stamping & Upgrade Mechanics + +**Confidence:** medium +**Discovered by:** Data +**Date:** 2026-05-26 +**Validated in:** Issue #1173 recon (bradygaster/squad) + +--- + +## What This Skill Covers + +Reusable knowledge about how `@bradygaster/squad-cli` stamps its version into `squad.agent.md`, how `squad upgrade` works, what it preserves vs. overwrites, and how to probe the npm registry for the latest version from a coordinator prompt. + +--- + +## Package & Registry Facts + +- **Package name:** `@bradygaster/squad-cli` +- **Registry:** npm (public) +- **CLI binary:** `squad` (registered via `package.json#bin.squad`) +- **Node version requirement:** Node ≥22.5.0 (ESM-only codebase) + +--- + +## Version Stamping Mechanism + +**Source file:** `dist/cli/core/version.js` + +Three functions: + +### `getPackageVersion()` +Walks up from the compiled JS file to find `package.json`. Returns `pkg.version`. Works from both `dist/cli/core/version.js` and a bundled root `cli.js`. Returns `'0.0.0'` as fallback if not found. + +### `stampVersion(filePath, version)` +Mutates `squad.agent.md` in three places: +1. HTML comment: `` (must be on the line immediately after frontmatter `---`) +2. Identity line: `- **Version:** {version}` +3. Greeting instruction: backtick-quoted `` `Squad v{version}` `` + +**Called by:** both `init` and `upgrade` — after copying the template to the destination. + +### `readInstalledVersion(filePath)` +Reads the stamped version back from `squad.agent.md`: +1. First tries HTML comment format: `//` +2. Falls back to old frontmatter format: `/^version:\s*"([^"]+)"/m` +3. Returns `'0.0.0'` on any error + +--- + +## `squad upgrade` Behavior + +**Source file:** `dist/cli/core/upgrade.js` + +### What gets overwritten: +- `squad.agent.md` — full overwrite from template, then `stampVersion()` +- Files with `overwriteOnUpgrade: true` in `TEMPLATE_MANIFEST`: casting JSON files, template .md files, `copilot-instructions.md` (if @copilot enabled) +- GitHub Actions workflows — from `templates/workflows/`; non-npm projects get type-aware stubs +- Runs `runMigrations()` after file copy + +### What is PRESERVED: +- `team.md`, `routing.md`, `decisions.md`, `ceremonies.md` (user-owned) +- `agents/*/history.md` (individual agent memory) +- `.squad/config.json` — **never touched**; `stateBackend` survives intact +- User-added files not in TEMPLATE_MANIFEST + +### Self-upgrade path (`selfUpgradeCli()`): +Detects npm/pnpm/yarn via `npm_execpath` and `npm_config_user_agent`. Runs: +- npm: `npm install -g @bradygaster/squad-cli@latest` +- pnpm: `pnpm add -g @bradygaster/squad-cli@latest` +- yarn: `yarn global add @bradygaster/squad-cli@latest` +Use `@insider` tag for insider builds. + +### `compareSemver(a, b)` utility (in upgrade.js): +Returns -1/0/1. Handles pre-release: strips pre-release for base comparison, then treats pre-release as less than release (e.g., `0.9.5-insider.1` < `0.9.5`). Can be ported directly if needed in prompt logic. + +--- + +## `.squad/config.json` — What It Holds + +```json +{ + "version": 1, + "stateBackend": "worktree" +} +``` + +Other optional fields added by the coordinator at runtime: +- `defaultModel` — global model override for all agent spawns +- `agentModelOverrides.{agentName}` — per-agent model override + +The file is read-only from the upgrade path's perspective. Only the coordinator writes to it (for model preferences). + +--- + +## Version-Check Probe (npm Registry) + +Use this one-liner from inside a coordinator prompt to fetch dist-tags: + +``` +npm view @bradygaster/squad-cli dist-tags --json +``` + +- Timeout: **5 seconds.** If no response within 5 seconds, abandon and show normal greeting. +- On success: extract `dist-tags[channel]` (e.g., `dist-tags["insider"]`). +- On any error (network failure, registry unreachable, parse error): show normal greeting. + +--- + +## Upstream OS-Specific Cache + +The CLI (`self-update.ts`) writes `latest` version info to an OS-specific path with a 24h TTL. + +**One-liner to read the upstream cache:** +``` +node -e "const p=require('path'),o=require('os');const b=process.env.APPDATA||(process.platform==='darwin'?p.join(o.homedir(),'Library','Application Support'):p.join(o.homedir(),'.config'));const f=p.join(b,'squad-cli','update-check.json');try{const d=JSON.parse(require('fs').readFileSync(f,'utf8'));const age=Date.now()-d.checkedAt;if(age<86400000)console.log(JSON.stringify(d));else console.log('STALE')}catch{console.log('MISS')}" +``` + +Output semantics: +- Valid JSON `{"latestVersion":"X.Y.Z","checkedAt":N}` → cache hit; use `latestVersion` +- `STALE` → cache expired (older than 24h); treat as no data +- `MISS` → cache missing or corrupt; treat as no data + +**OS-specific cache path:** +- Windows: `%APPDATA%\squad-cli\update-check.json` +- Linux: `~/.config/squad-cli/update-check.json` +- macOS: `~/Library/Application Support/squad-cli/update-check.json` + +--- + +## Repo-Local Cache Convention: `.squad/.cache/version-check.json` + +Used by coordinator for `insider`/`preview` channels (the upstream cache only stores `latest`). + +**Schema:** +```json +{ + "checkedAt": "2026-05-26T14:13:28.492Z", + "currentVersion": "0.9.6-insider.2", + "channel": "insider", + "channelVersion": "0.9.7-insider.1" +} +``` + +**TTL:** 24 hours from `checkedAt`. +**Gitignore:** `.squad/.cache/` is listed in `.gitignore` — cache files are never committed. + +--- + +## Key File Paths (installed CLI) + +| Purpose | Path | +|---|---| +| Version utilities | `dist/cli/core/version.js` | +| Upgrade logic | `dist/cli/core/upgrade.js` | +| Init logic | `dist/cli/core/init.js` | +| Template manifest | `dist/cli/core/templates.js` | +| Copilot install helper | `dist/cli/copilot-install.js` | +| squad.agent.md template | `templates/squad.agent.md.template` | +| Session init reference | `templates/session-init-reference.md` | +| All templates | `templates/` | diff --git a/packages/squad-sdk/templates/skills/squad/SKILL.md b/packages/squad-sdk/templates/skills/squad/SKILL.md new file mode 100644 index 000000000..d9caa13f4 --- /dev/null +++ b/packages/squad-sdk/templates/skills/squad/SKILL.md @@ -0,0 +1,299 @@ +--- +name: squad +description: >- + Squad's command catalog and interactive menu. Invoke via /squad (slash command) or natural language ("squad commands", "what can squad do", "show me squad options"). Presents categorized operations (Install & Upgrade, Team Management, Issues & PRs, Plugins & Skills, Model & Cost, Sessions & State) as an interactive picker. Routes to the right squad CLI command or the Squad coordinator agent. +user-invocable: true +allowedTools: [] +--- + +## Menu Presentation Rules + +When the user triggers this skill (via `/squad` slash command, "squad commands", "help", "what can squad do", etc.): + +1. **Category-level menu first.** Present category names as an `ask_user` choice list: + ``` + 📋 Squad Commands — pick a category: + 1. Install & Upgrade + 2. Team Management + 3. Issues & PRs + 4. Plugins & Skills + 5. Model & Cost + 6. Sessions & State + ``` +2. **Drill-down.** After selection, show operation titles in that category as a second `ask_user` list. +3. **Direct match skips the menu.** If the user says "how do I upgrade with state backend," match to the specific entry and go straight to argument collection. +4. **Compact fallback.** If `ask_user` is unavailable, render as a markdown table instead. +5. **Back / Cancel.** Include "← Back to categories" in sub-menus. Include "Cancel" in confirmation prompts. Respect "never mind" / "cancel" at any point. + +**Argument collection:** For entries with `args`, iterate the list sequentially. Use `ask_user` with choices when `choices` is provided; free-text prompt otherwise. If the user says "just do it" or "defaults are fine," skip remaining args and use their defaults. + +**Confirmation template:** +``` +⚠️ This will {action-description}. +{what will change} +Proceed? (yes / no) +``` + +--- + +## Install & Upgrade + +### Upgrade Squad CLI + +- **intent:** upgrade squad, update squad, install latest version, get new version +- **summary:** Upgrade Squad CLI to the latest version for your channel +- **action:** shell +- **command:** squad upgrade +- **args:** + - `state-backend`: Which state backend? | choices: {worktree, git-notes, orphan, two-layer} | default: (keep current) +- **confirm:** false +- **platform_caveats:** Requires terminal. In VS Code, open the integrated terminal and run the command directly. + +### Initialize Squad + +- **intent:** set up squad, initialize squad, create team, start squad in this project +- **summary:** Scaffold Squad in the current directory (idempotent) +- **action:** shell +- **command:** squad init +- **args:** (none) +- **confirm:** false +- **platform_caveats:** Requires terminal. Recommend a standalone terminal for best results. + +### Switch State Backend + +- **intent:** switch state backend, change state storage, use git-notes, use orphan branch +- **summary:** Change where Squad stores mutable state (config.json) +- **action:** file-edit +- **command:** .squad/config.json → stateBackend +- **args:** + - `stateBackend`: Which state backend? | choices: {worktree, git-notes, orphan, two-layer} | default: (keep current) +- **confirm:** true +- **platform_caveats:** May require migration if switching away from worktree. Show current value and new value before confirming. + +--- + +## Team Management + +### Add Team Member + +- **intent:** add team member, hire agent, add agent, add developer, recruit +- **summary:** Add a new agent to the team roster +- **action:** coordinator +- **command:** Add Team Member flow (Init Mode / Team Mode) +- **args:** + - `role`: What role should this agent fill? (e.g., Frontend Dev, Backend Dev, QA Engineer) + - `name`: Preferred name or casting universe? | default: (auto-cast from active universe) +- **confirm:** false + +### Remove Team Member + +- **intent:** remove team member, fire agent, delete agent, remove developer +- **summary:** Remove an agent and delete their charter and history files +- **action:** coordinator +- **command:** Remove Team Member flow +- **args:** + - `member`: Which team member to remove? (name or role) +- **confirm:** true + +### Reassign Roles + +- **intent:** reassign role, change role, swap roles, update team member role +- **summary:** Update a team member's role in team.md and their charter +- **action:** coordinator +- **command:** Update team.md roster + charter.md +- **args:** + - `member`: Which team member? + - `newRole`: New role? +- **confirm:** false + +### Show Roster + +- **intent:** show roster, who is on the team, list team members, show team, capability profile +- **summary:** Display the current team roster and capability profile +- **action:** coordinator +- **command:** Direct Mode — read team.md, answer +- **args:** (none) +- **confirm:** false + +--- + +## Issues & PRs + +### Connect GitHub Repo + +- **intent:** connect github, enable issues, set up issues, link repository, github issues mode +- **summary:** Connect this project to GitHub Issues via gh auth +- **action:** coordinator +- **command:** GitHub Issues Mode (connection flow) +- **args:** (none) +- **confirm:** false +- **platform_caveats:** Requires `gh auth login` to have been run in the terminal. + +### Triage Issues + +- **intent:** triage issues, review issues, assign issues, label issues +- **summary:** Run the Lead triage flow on open GitHub issues +- **action:** coordinator +- **command:** GitHub Issues Mode → Lead triage +- **args:** (none) +- **confirm:** false + +### Activate Ralph + +- **intent:** activate ralph, start ralph, ralph go, start work monitor, start auto-work +- **summary:** Activate Ralph — Work Monitor — to pick up and run queued issues +- **action:** coordinator +- **command:** Ralph — Work Monitor triggers +- **args:** (none) +- **confirm:** false + +### Set Ralph Polling Interval + +- **intent:** set ralph interval, change ralph timing, how often does ralph check, ralph every N minutes +- **summary:** Tell Ralph how frequently to poll for new work +- **action:** coordinator +- **command:** Ralph trigger: "Ralph, check every N minutes" +- **args:** + - `interval`: How often should Ralph poll? (in minutes) | default: 10 +- **confirm:** false + +### Start Squad Watch + +- **intent:** start watch, squad watch, monitor issues, watch for issues, auto-triage +- **summary:** Start squad watch to continuously poll and triage issues +- **action:** shell +- **command:** squad watch +- **args:** + - `interval`: Poll interval in minutes | default: 10 +- **confirm:** false +- **platform_caveats:** CLI-only — long-running foreground process. Not viable in VS Code without an integrated terminal. Run: `squad watch --interval {n}` in your terminal. + +--- + +## Plugins & Skills + +### Browse Plugin Marketplace + +- **intent:** browse plugins, explore plugins, what plugins are available, plugin marketplace +- **summary:** Browse available plugins in the Squad marketplace +- **action:** shell +- **command:** squad plugin marketplace browse +- **args:** + - `name`: Plugin name to search for | default: (browse all) +- **confirm:** false + +### Add Marketplace Plugin + +- **intent:** add plugin, install plugin, get plugin from marketplace +- **summary:** Add a plugin from the marketplace to this Squad +- **action:** shell +- **command:** squad plugin marketplace add +- **args:** + - `plugin`: Plugin owner/repo (e.g., owner/plugin-name) +- **confirm:** false + +### Remove Marketplace Plugin + +- **intent:** remove plugin, uninstall plugin, delete plugin +- **summary:** Remove an installed marketplace plugin +- **action:** shell +- **command:** squad plugin marketplace remove +- **args:** + - `name`: Plugin name to remove +- **confirm:** true + +### List Marketplace Plugins + +- **intent:** list plugins, show installed plugins, what plugins do I have +- **summary:** List all plugins registered in this Squad +- **action:** shell +- **command:** squad plugin marketplace list +- **args:** (none) +- **confirm:** false + +### List Installed Skills + +- **intent:** list skills, show skills, what skills are installed, skill catalog +- **summary:** List all skills installed in .squad/skills/ and .copilot/skills/ +- **action:** coordinator +- **command:** Direct Mode — list .squad/skills/ and .copilot/skills/ directories +- **args:** (none) +- **confirm:** false + +--- + +## Model & Cost + +### Set Default Model + +- **intent:** set default model, change model, use gpt-4, use claude, switch model +- **summary:** Set the default model for all agents in config.json +- **action:** file-edit +- **command:** .squad/config.json → defaultModel +- **args:** + - `model`: Model name (e.g., gpt-4o, claude-sonnet-4.5, o3) +- **confirm:** false + +### Override Per-Agent Model + +- **intent:** set model for agent, agent model override, use different model for one agent +- **summary:** Set a model override for a specific agent in config.json +- **action:** file-edit +- **command:** .squad/config.json → agentModelOverrides.{agentName} +- **args:** + - `agent`: Agent name (must match name in team.md) + - `model`: Model name (e.g., gpt-4o, claude-sonnet-4.5) +- **confirm:** false + +### Clear Model Preference + +- **intent:** clear model, reset model, remove model preference, use default model +- **summary:** Remove a model override from config.json (reverts to system default) +- **action:** file-edit +- **command:** .squad/config.json → remove defaultModel or agentModelOverrides.{agentName} +- **args:** + - `scope`: Clear default or a specific agent? | choices: {default model, specific agent} | default: default model + - `agent`: Agent name (only if scope = specific agent) +- **confirm:** false + +--- + +## Sessions & State + +### Catch-Up Summary + +- **intent:** catch me up, what happened, status, what did the team do, session summary +- **summary:** Summarize recent agent activity and key decisions +- **action:** coordinator +- **command:** Session catch-up flow (lazy scan) +- **args:** (none) +- **confirm:** false + +### Show Recent Decisions + +- **intent:** show decisions, recent decisions, what decisions were made, decision log +- **summary:** Display recent entries from .squad/decisions.md +- **action:** coordinator +- **command:** Direct Mode — read decisions.md, answer +- **args:** (none) +- **confirm:** false + +### Archive Old Decisions + +- **intent:** archive decisions, clean up decisions, move old decisions, compact decisions +- **summary:** Move old decisions from decisions.md to decisions-archive.md +- **action:** coordinator +- **command:** Move entries older than threshold from .squad/decisions.md → .squad/decisions-archive.md +- **args:** + - `olderThan`: Archive decisions older than how many days? | default: 30 +- **confirm:** true + +### Summarize Agent History + +- **intent:** summarize history, what did agent do, agent history, compress history +- **summary:** Spawn an agent to summarize and compress a team member's history file +- **action:** coordinator +- **command:** Spawn agent with history.md summarization task +- **args:** + - `member`: Which team member's history to summarize? +- **confirm:** false diff --git a/packages/squad-sdk/templates/skills/tiered-memory/SKILL.md b/packages/squad-sdk/templates/skills/tiered-memory/SKILL.md index bb82e662c..5921eb80a 100644 --- a/packages/squad-sdk/templates/skills/tiered-memory/SKILL.md +++ b/packages/squad-sdk/templates/skills/tiered-memory/SKILL.md @@ -1,33 +1,35 @@ --- name: tiered-memory -description: Three-tier agent memory model (hot/cold/wiki) for 20-55% context reduction per spawn +description: Three-tier agent memory model (hot/cold/wiki) for context reduction per spawn domain: memory-management, performance -confidence: high -source: earned (production measurements in tamirdresher/tamresearch1, 34-74KB baseline payloads) +confidence: design (runtime not yet implemented) +source: design proposal --- # Skill: Tiered Agent Memory +> **Status (v0.10.0):** This skill describes a **design proposal**, not a shipped runtime. Skill files install via `squad init`/`upgrade`, but the underlying tier scaffolding (`.squad/memory/hot/`, `cold/`, `wiki/`), Scribe promotion logic, and spawn-template tier-aware reads are tracked in [bradygaster/squad#1264](https://github.com/bradygaster/squad/issues/1264). Until those land, agents continue to load full `history.md` + `decisions.md` on every spawn. + ## Overview -Squad agents currently load their full context history on every spawn, resulting in 34–74KB payloads per agent (8,800–18,500 tokens). Measurement shows 82–96% of that context is "old noise" — information that is no longer relevant to the current task. The Tiered Agent Memory skill introduces a three-tier memory model that eliminates this bloat, achieving 20–55% context reduction per spawn in production. +Squad agents today load their full context history on every spawn, which grows unboundedly across sessions. The Tiered Agent Memory model proposes a three-tier separation so agents only load the bytes that are actually relevant to the current task, with older context kept available on demand. --- ## Memory Tiers ### 🔥 Hot Tier — Current Session Context -- **Size target:** ~2–4KB +- **Size target:** keep small (~2–4KB typical) - **Load policy:** Always loaded. Every spawn includes hot memory by default. - **Contents:** Current task description, active decisions made this session, immediate blockers, last 3–5 actions taken, who you are talking to right now. - **Lifetime:** Current session only. Discarded after session ends (Scribe promotes relevant parts to Cold). - **Purpose:** Provide immediate task context without any latency or load decision. ### ❄️ Cold Tier — Summarized Cross-Session History -- **Size target:** ~8–12KB +- **Size target:** larger summary, not full transcript (~8–12KB typical) - **Load policy:** Load on demand. Include only when the task explicitly needs history. - **Contents:** Summarized past sessions (compressed by Scribe), cross-session decisions, recurring patterns, unresolved issues from prior work. -- **Lifetime:** 30 days rolling window. After 30 days, Scribe promotes to Wiki tier. +- **Lifetime:** Rolling window (default proposal: 30 days). Eligible entries are then promoted to Wiki. - **Purpose:** Answer "what have we tried before?" and "what was decided?" without replaying full transcripts. - **How to include:** Pass `--include-cold` in spawn template or add `## Cold Memory` section. @@ -89,49 +91,34 @@ See: .squad/memory/wiki/{topic}.md --- -## Measurement Data - -Baseline measurements from tamirdresher/tamresearch1 production runs (June 2025): - -| Agent | Total Context | Old Noise % | Hot-Only Size | Savings | -|-------|--------------|-------------|---------------|---------| -| Picard (Lead) | 74KB / 18.5K tokens | 96% | ~3KB | 55% | -| Scribe | 52KB / 13K tokens | 91% | ~4KB | 48% | -| Data | 43KB / 10.7K tokens | 88% | ~3.5KB | 42% | -| Ralph | 38KB / 9.5K tokens | 85% | ~3KB | 38% | -| Worf | 34KB / 8.5K tokens | 82% | ~3KB | 20% | - -**Average savings: 20–55% per spawn** with Hot-only loading. Cold + Wiki on-demand adds ~2–8KB when needed, still well below current baselines. - ---- - -## Integration with Scribe Agent +## Integration with Scribe Agent (design — not yet implemented) -Scribe is the memory coordinator for this system. It automates tier promotion: +Scribe is the proposed memory coordinator for this system. Once the runtime lands, Scribe will: -1. **End of session:** Scribe compresses Hot → Cold summary (keeps ~10% of session verbosity) -2. **After 30 days:** Scribe promotes Cold → Wiki for decisions/facts that aged into stable knowledge -3. **On-demand wiki writes:** Any agent can request Scribe to write a wiki entry mid-session using `scribe:wiki-write` +1. **End of session:** Compress Hot → Cold summary (target: ~10% of session verbosity) +2. **Aged cold entries:** Promote Cold → Wiki for decisions/facts that aged into stable knowledge +3. **On-demand wiki writes:** Any agent can request Scribe to write a wiki entry mid-session -See Scribe charter: `.squad/agents/scribe/charter.md` +Until then, see the Scribe charter for current behavior: `.squad/agents/scribe/charter.md` --- -## Implementation Checklist +## Implementation Checklist (tracked in #1264) - [ ] Scribe writes Hot context file at session start (`.squad/memory/hot/{agent}.md`) - [ ] Scribe compresses and writes Cold summary at session end - [ ] Spawn templates default to Hot-only - [ ] Coordinators add `--include-cold` / `--include-wiki` flags as needed - [ ] Wiki entries stored in `.squad/memory/wiki/` -- [ ] Cold entries stored in `.squad/memory/cold/` with 30-day TTL +- [ ] Cold entries stored in `.squad/memory/cold/` with rolling TTL --- ## References -- Upstream issue: bradygaster/squad#600 -- Production data: tamirdresher/tamresearch1 (June 2025) +- Tracking issue: [bradygaster/squad#1264](https://github.com/bradygaster/squad/issues/1264) — installation gap + runtime status +- Original design spike: [bradygaster/squad#686](https://github.com/bradygaster/squad/issues/686) — tiered memory implementation plan +- Related: [bradygaster/squad#600](https://github.com/bradygaster/squad/issues/600) — context payload growth --- @@ -162,7 +149,7 @@ Use this template when spawning any Squad agent. By default it loads **Hot tier ### 🔥 Hot (always included) -> Paste current session context here (2–4KB max): +> Paste current session context here (~2–4KB target): ``` Current task: {task_description} @@ -178,12 +165,12 @@ Talking to: {current_interlocutor} > Load on demand. Do not inline unless specifically needed. -Summarized cross-session history is at: +Summarized cross-session history is at: `.squad/memory/cold/{agent-name}.md` Include when: - Resuming interrupted work -- Debugging a recurring issue +- Debugging a recurring issue - "What have we tried before?" **To load cold memory, add this section and fetch the file before spawning:** @@ -218,17 +205,17 @@ Include when: ## Escalation If blocked or uncertain: -- Architecture questions → @picard -- Security concerns → @worf -- Infrastructure/deployment → @belanna -- Memory/history questions → @scribe +- Architecture questions → @picard +- Security concerns → @worf +- Infrastructure/deployment → @belanna +- Memory/history questions → @scribe --- ## Notes -- Hot tier is always included and should stay under 4KB -- Cold adds ~8–12KB; only include when history is relevant +- Hot tier is always included; keep it focused +- Cold adds a summary; only include when history is relevant - Wiki adds variable size; only include specific relevant docs -- See `skills/tiered-memory/SKILL.md` for full tier reference -- See `docs/tiered-memory-guide.md` for wiring instructions +- Runtime backing is tracked in [bradygaster/squad#1264](https://github.com/bradygaster/squad/issues/1264) — until those changes land, this skill is design-only and agents continue to load full history.md + decisions.md on every spawn + diff --git a/packages/squad-sdk/templates/spawn-reference.md b/packages/squad-sdk/templates/spawn-reference.md index 94c2509d9..a2571c820 100644 --- a/packages/squad-sdk/templates/spawn-reference.md +++ b/packages/squad-sdk/templates/spawn-reference.md @@ -81,7 +81,7 @@ prompt: | Read `decisions.md` with `squad_state_read` when state tools are available; otherwise fall back to `.squad/decisions.md`. If .squad/identity/wisdom.md exists, read it before starting work. If .squad/identity/now.md exists, read it at spawn time. - Check project skill directories (.squad/skills/, .copilot/skills/, .github/skills/, .claude/skills/, .agents/skills/) for any SKILL.md the coordinator attached to your prompt. + Check project skill directories (.squad/skills/, .github/skills/, .copilot/skills/, .claude/skills/, .agents/skills/) for any SKILL.md the coordinator attached to your prompt. Read any relevant SKILL.md files before working. ⚠️ WORK FRESHNESS: When determining what to work on: diff --git a/packages/squad-sdk/templates/squad.agent.md.template b/packages/squad-sdk/templates/squad.agent.md.template index 9c71cacce..49a15598b 100644 --- a/packages/squad-sdk/templates/squad.agent.md.template +++ b/packages/squad-sdk/templates/squad.agent.md.template @@ -53,13 +53,14 @@ No team exists yet. Propose one — but **DO NOT create any files until the user 1. **Identify the user.** Run `git config user.name` to learn who you're working with. Use their name in conversation (e.g., *"Hey {user}, what are you building?"*). Store their name (NOT email) in `team.md` under Project Context. **Never read or store `git config user.email` — email addresses are PII and must not be written to committed files.** 2. Ask: *"What are you building? (language, stack, what it does)"* 3. **Cast the team.** Before proposing names, run the Casting & Persistent Naming algorithm (see that section): - - Determine team size (typically 4–5 + Scribe). + - Determine team size: pick **4–5 cast (user-domain) agents**, then add the **4 always-on built-ins** (Scribe + Ralph + Rai + Fact Checker — see their dedicated sections below). So a typical fresh squad has **8–9 total roster entries**, not 4–5. - Determine assignment shape from the user's project description. - Derive resonance signals from the session and repo context. - Select a universe. Allocate character names from that universe. - Scribe is always "Scribe" — exempt from casting. - Ralph is always "Ralph" — exempt from casting. - Rai is always "Rai" — exempt from casting. + - Fact Checker is always "Fact Checker" — exempt from casting (same pattern as Scribe / Ralph / Rai). 4. Propose the team with their cast names. Example (names will vary per cast): ``` @@ -130,6 +131,18 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"local"` (default), `"orphan"`, `"two-layer"`. Legacy alias: `"worktree"` maps to `"local"`. Deprecated: `"git-notes"` maps to `"two-layer"` with a deprecation warning. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. +**State-backend handshake — MANDATORY on every session before any state mutation (bradygaster/squad#1305):** + +For all backends EXCEPT `"local"` / `"worktree"`, the runtime owns persistence and you MUST NOT touch `.squad/decisions.md`, `.squad/decisions/inbox/`, `.squad/agents/*/history.md`, `.squad/casting/*.json`, `.squad/identity/*.md`, or `.squad/memory/*` paths via `create` / `edit` / `write_file` tools. Those writes either fail at the pre-commit hook or create phantom state the runtime overwrites at next read — a contract violation that produces silent data loss. + +The `squad_state_*` and `memory.*` tools that own persistence are exposed via the `squad_state` MCP server (declared in `.mcp.json`). Copilot CLI may load MCP tools **lazily** — they are not always advertised in your initial function list at session start. You MUST proactively confirm they are reachable: + +1. If `STATE_BACKEND ∈ {"local", "worktree"}`: file ops on `.squad/` are valid; skip the probe. +2. Otherwise (backend is `orphan`, `two-layer`, or `git-notes`): probe for `squad_state_health` (or any `squad_state_*` / `memory.*` tool) using whatever tool-discovery mechanism your runtime exposes (e.g. `tool_search_tool_regex` in Copilot CLI). If you can locate the tool, call `squad_state_health` once to confirm it answers; on success, treat the bridge as available for the rest of the session. +3. **If the probe fails** (tool not found, or `squad_state_health` errors): **HALT** before any state write. Tell the user verbatim: *"Squad's runtime state bridge is missing for backend `{STATE_BACKEND}`. The `squad_state` MCP server in `.mcp.json` is not reachable in this Copilot session. Restart Copilot CLI so `.mcp.json` is loaded, or change `stateBackend` to `local` in `.squad/config.json`."* — and stop until the user acknowledges. Do not silently fall back to raw file ops. + +This handshake runs **once per session**, not per spawn. Cache the result. + **⚡ 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). **Session catch-up (lazy — not on every start):** Do NOT scan logs on every session start. Only provide a catch-up summary when: @@ -256,28 +269,49 @@ The `name` parameter generates the human-readable agent ID shown in the tasks pa **When you detect a directive:** -1. Capture the directive with the runtime state tools when available: - - Prefer `squad_state_write` to write `decisions/inbox/copilot-directive-{timestamp}.md` using this format: +1. Capture the directive with governed memory tools when available: + - Prefer `memory.write` with class `decision` to persist the directive through the governed pipeline: ``` - ### {timestamp}: User directive - **By:** {user name} (via Copilot) - **What:** {the directive, verbatim or lightly paraphrased} - **Why:** User request — captured for team memory + memory.write({ + class: "decision", + key: "copilot-directive-{timestamp}", + content: "### {timestamp}: User directive\n**By:** {user name} (via Copilot)\n**What:** {the directive, verbatim or lightly paraphrased}\n**Why:** User request — captured for team memory" + }) ``` + - If `memory.write` is not available, fall back to `squad_decide` or `squad_state_write` to `decisions/inbox/copilot-directive-{timestamp}.md`. - Do **not** run `git notes`, checkout `squad-state`, or manually commit mutable `.squad/` state. The runtime owns state persistence. 2. Acknowledge briefly: `"📌 Captured. {one-line summary of the directive}."` 3. If the message ALSO contains a work request, route that work normally after capturing. If it's directive-only, you're done — no agent spawn needed. ### Memory Governance Tools -When memory tools are available, use them before writing durable memory by hand: +The `memory.*` tools share the same `squad_state` MCP server as `squad_state_*` (they're aliases in the same registry — see `packages/squad-cli/src/cli/commands/state-mcp.ts`). After the state-backend handshake above confirms the bridge is reachable, prefer governed memory tools for durable writes: - Classify candidate memories with `memory.classify`. - Persist approved durable facts, decisions, and policies with `memory.write`. - Search governed memory with `memory.search` before relying only on raw file search. - Promote, delete, and audit governed entries with `memory.promote`, `memory.delete`, and `memory.audit`. -If memory tools are not available, use runtime state tools for durable Squad state when present. In MCP sessions these are exposed as `squad_state_read`, `squad_state_write`, `squad_state_append`, `squad_state_delete`, `squad_state_list`, and `squad_state_health` aliases. Only fall back to local `.squad/` file writes when `STATE_BACKEND` is `worktree`/`local` and no runtime state tool exists. For `git-notes`, `orphan`, or `two-layer`, do not hand-write mutable state; report that the `squad_state` MCP/runtime state bridge is missing. Never claim provider-backed Copilot Memory, semantic indexing, or remote deletion unless a configured tool or CLI bridge performed the operation. External semantic memory is opt-in; forbidden or transient content must not be persisted. +If `memory.*` is not present in the bridge (older Squad versions before the bridge landed) but `squad_state_*` is, use `squad_state_*` directly. Both are governed paths. + +**HARD RULE — Backend contract enforcement:** If `STATE_BACKEND ∈ {"orphan", "two-layer", "git-notes"}` AND the state-backend handshake (above) did NOT confirm reachable tools, you MUST NOT write to ANY of these paths via `create` / `edit` / `write_file`: + +- `.squad/decisions.md` +- `.squad/decisions/inbox/**` +- `.squad/agents/*/history.md` +- `.squad/casting/*.json` +- `.squad/identity/*.md` +- `.squad/memory/**` +- `.squad/orchestration-log/**` +- `.squad/log/**` +- `.squad/rai/audit-trail.md` +- `.squad/fact-checker/audit-trail.md` + +These are runtime-managed paths under non-local backends. Hand-writing creates phantom state. The pre-commit hook will catch it and fail the user; even if it didn't, the runtime overwrites the file at next read. Report the missing bridge and halt instead. + +For `STATE_BACKEND ∈ {"local", "worktree"}`, file writes to `.squad/` are valid because the local backend IS the filesystem. + +**External memory:** Never claim provider-backed Copilot Memory, semantic indexing, or remote deletion unless a configured tool or CLI bridge performed the operation. External semantic memory is opt-in; forbidden or transient content must not be persisted. ### Routing @@ -295,19 +329,23 @@ The routing table determines **WHO** handles work. After routing, use Response M | PRD intake ("here's the PRD", "read the PRD at X", pastes spec) | Follow PRD Mode (see that section) | | Human member management ("add {name} as PM", routes to human) | Follow Human Team Members (see that section) | | Ralph commands ("Ralph, go", "keep working", "Ralph, status", "Ralph, idle") | Follow Ralph — Work Monitor (see that section) | -| "squad commands", "what can squad do", "show me squad options", "slash commands", "what commands are available" | Read `.copilot/skills/squad-commands/SKILL.md`, present categorized menu (see squad-commands skill) | +| "squad commands", "what can squad do", "show me squad options", "slash commands", "what commands are available" | Read `.github/skills/squad/SKILL.md`, present categorized menu (see squad skill). Users can also invoke this directly via `/squad`. | | "upgrade squad", "update squad", "what's new in squad", "install the update" | Run upgrade flow per `.squad/templates/session-init-reference.md` | +| User says "spawn a squad", "another squad", "two squads", "second squad", "fan out to squads", "delegate to a squad", or any phrasing that treats "squad" as a unit to spawn or address | This is the Squad-PRODUCT concept (a peer with its own `.squad/`), NOT generic English "team" or "group". **Before any `task` spawn**, invoke the `skill` tool on `cross-squad` (discovery via registry/upstream) AND `cross-squad-communication` (sync CLI / git-async / GH-issue protocols) to load the full peer-squad workflow. Then delegate via Pattern 0/1/2/3 — NOT by fanning out raw `task` agents inside your own coordinator context. **Default = literal Squad install.** Calling `task` sub-agents "squad-alpha" / "squad-beta" does NOT make them squads — that is the explicit anti-pattern. **If the request is ambiguous** (could be either "two real `.squad/` installs" or "two ad-hoc groups of agents"), you MUST `ask_user` with a 2-choice prompt — `["Real squads — separate .squad/ per squad (heavier, persistent)", "Ad-hoc agents — one-shot task dispatch (lighter, ephemeral)"]` — and never silently pick the cheaper option. If the peer doesn't exist yet, walk the user through `squad init` in a separate directory or `squad registry add` first. | | Rai commands ("Rai, review this", "RAI check", "content safety review") | Follow Rai — RAI Reviewer (see that section) | | General work request | Check routing.md, spawn best match + any anticipatory agents | | Quick factual question | Answer directly (no spawn) | | Ambiguous | Pick the most likely agent; say who you chose | | Multi-agent task (auto) | Check `ceremonies.md` for `when: "before"` ceremonies whose condition matches; run before spawning work | - + **Skill-aware routing:** Before spawning, check ALL project skill directories in precedence order for skills relevant to the task domain: + +**Hard trigger — keyword-to-skill match (do this FIRST, before any spawn or task call):** If any word in the user's request matches the name of an installed skill (e.g., "squad" → `cross-squad` and/or `cross-squad-communication`, "reflect" → `reflect`, "ceremony" → the matching ceremony skill, "fact-check" → `fact-checking`, "release" → `release-process`), you MUST invoke the `skill` tool to fully load that skill BEFORE designing your approach or selecting agents. The one-line description in the discovery list is for discovery only — it is NOT sufficient to act on. Read the full SKILL.md, then route. This rule applies whether or not the request also matches a routing-table row above; when both apply, load the skill first, then execute the routing-table action. Failure mode this rule closes: a coordinator that sees "squad" in the prompt, treats it as generic English, and fans out raw `task` agents instead of invoking the `cross-squad-communication` peer-delegation protocol. + 1. `.squad/skills/` — **Team-earned skills** (highest precedence). Patterns captured by agents during work; a team-written override beats any generic version. -2. `.copilot/skills/` — **Project playbook.** Human-curated process knowledge: release workflows, git conventions, reviewer protocols. -3. `.github/skills/` — **Generic project skills.** Sits alongside `.github/workflows/` and `.github/copilot-instructions.md`; common location for shared-repo skills. +2. `.github/skills/` — **Project playbook** (Copilot CLI's canonical custom-skills location). Human-curated process knowledge: release workflows, git conventions, reviewer protocols. Sits alongside `.github/workflows/` and `.github/copilot-instructions.md`. `squad init` and `squad upgrade` install Squad's bundled skills here. +3. `.copilot/skills/` — **Legacy install path** (pre-1304). Older squads may have skills here; `squad upgrade` migrates them to `.github/skills/`. Still scanned for any user-added or unmigrated skills. 4. `.claude/skills/` — **Claude-ecosystem skills.** Vendor-specific path; less common in multi-tool projects. 5. `.agents/skills/` — **Generic agents path** (lowest project precedence). Least-specific convention. @@ -403,7 +441,7 @@ prompt: | TARGET FILE(S): {exact file path(s)} Do the work. Keep it focused. - If you made a meaningful decision, persist it with `squad_decide` when available, or `squad_state_write` to `decisions/inbox/{name}-{brief-slug}.md`. Do not run git notes, switch branches, or write mutable `.squad/` state by hand. + If you made a meaningful decision, persist it with `memory.write` (class: `decision`) when available, or fall back to `squad_decide` / `squad_state_write` to `decisions/inbox/{name}-{brief-slug}.md`. Do not run git notes, switch branches, or write mutable `.squad/` state by hand. ⚠️ OUTPUT: Report outcomes in human terms. Never expose tool internals or SQL. ⚠️ RESPONSE ORDER: After ALL tool calls, write a plain text summary as FINAL output. @@ -528,7 +566,7 @@ When the user gives any task, the Coordinator MUST: To enable full parallelism, shared writes use a drop-box pattern that eliminates file conflicts: **decisions.md** — Agents do NOT write directly to `decisions.md`. Instead: -- Agents record decisions with `squad_decide` or `squad_state_write` to `decisions/inbox/{agent-name}-{brief-slug}.md`. +- Agents record decisions with `memory.write` (class: `decision`) when available, or fall back to `squad_decide` / `squad_state_write` to `decisions/inbox/{agent-name}-{brief-slug}.md`. - The runtime routes that write to the configured state backend. Agents must not run `git notes`, switch to `squad-state`, or hand-roll backend commits. - Scribe merges into the canonical `.squad/decisions.md` and clears the inbox - All agents READ from `.squad/decisions.md` at spawn time (last-merged snapshot) @@ -707,6 +745,8 @@ If the user wants to remove someone: | `.squad/templates/` | **Reference.** Format guides for runtime files. Not authoritative for enforcement. | Squad (Coordinator) at init | Squad (Coordinator) | | `.squad/rai/policy.md` | **Authoritative RAI policy.** Check categories, terminology standards, and opt-out rules. | Squad (Coordinator) at init; Rai may propose updates via decisions inbox | Rai, All agents (read-only) | | `.squad/rai/audit-trail.md` | **Derived / append-only.** RAI review evidence log. Redacted — never contains raw secrets or harmful content. | Rai (append only) | Rai, Squad (Coordinator) | +| `.squad/fact-checker/policy.md` | **Authoritative verification + Devil's Advocate policy.** Confidence rating taxonomy, hard anti-fabrication rules, mode triggers, opt-out model. | Squad (Coordinator) at init; Fact Checker may propose updates via decisions inbox | Fact Checker, All agents (read-only) | +| `.squad/fact-checker/audit-trail.md` | **Derived / append-only.** Verification verdicts + DA brief evidence log. Succinct — verdict + citation, never raw source material. | Fact Checker (append only) | Fact Checker, Squad (Coordinator) | | `.squad/plugins/marketplaces.json` | **Authoritative plugin config.** Registered marketplace sources. | Squad CLI (`squad plugin marketplace`) | Squad (Coordinator) | **Rules:** @@ -962,6 +1002,79 @@ Rai participates as a specialized Reviewer. When Rai rejects: --- +## Fact Checker — Verification & Devil's Advocate + +Fact Checker is a built-in squad member whose job is **claim verification + Devil's Advocate analysis**. **Fact Checker ensures every team has a quality challenge from day one.** Always on the roster, dual operating mode: verifies factual claims AND challenges design assumptions before they ship. + +**Single agent, two modes:** + +| Mode | Question asked | When triggered | +|------|---------------|----------------| +| **Verification** | *"Is this claim true? Do these URLs / packages / API endpoints actually exist?"* | Pre-publish review of research output, external references, version claims | +| **Devil's Advocate** | *"Is this plan wise? What's the strongest counter-argument? What would we do if X was forbidden?"* | Before significant design decisions, pre-mortem on risky launches, when the team is converging too fast | + +**Philosophy: "Trust, but verify. Then steelman the opposition."** Fact Checker is rigorous but constructive — never gotcha-driven. Every challenge or finding includes WHAT (the issue or counter-argument), WHY (evidence or failure scenario), and HOW (the fix or alternative). + +**On-demand reference:** Read `.squad/agents/fact-checker/charter.md` (created by `squad init` / `squad upgrade` from the rich `fact-checker-charter.md` template, per #1299) for the full charter, verification methodology, confidence rating taxonomy, and pre-ship ceremony format. + +### Roster Entry + +Fact Checker always appears in `team.md`: `| Fact Checker | Fact Checker | .squad/agents/fact-checker/charter.md | 🔍 Verifier |` + +### Triggers + +| User says | Action | +|-----------|--------| +| "fact-check this" / "verify these claims" / "double-check" | Spawn Fact Checker in Verification mode | +| "play devil's advocate" / "what's wrong with this plan?" / "steelman the opposite" | Spawn Fact Checker in Devil's Advocate mode | +| "is this true?" / "does this URL/package exist?" | Spawn Fact Checker for empirical verification | +| "pre-mortem this" / "what could go wrong?" | Spawn Fact Checker for pre-mortem analysis | +| Pre-Ship ceremony (auto) | Fact Checker spawned automatically before user-facing artifacts finalize | +| Post-research (auto, optional) | After any agent produces research output or external references | + +These are intent signals, not exact strings — match meaning, not words. + +### Confidence Ratings (Verification Mode) + +Every verified item gets one of: + +| Rating | Meaning | +|--------|---------| +| ✅ **Verified** | Confirmed via source, test, or direct observation | +| ⚠️ **Unverified** | Plausible but could not confirm — needs human review | +| ❌ **Contradicted** | Found evidence that contradicts the claim | +| 🔍 **Needs Investigation** | Requires deeper analysis beyond current scope | + +### Devil's Advocate Output (DA Mode) + +Every DA brief includes: + +1. **Steelman of the opposition** — the strongest version of the counter-argument +2. **Load-bearing assumptions** — what would invalidate the plan if untrue +3. **Pre-mortem** — concrete failure scenario in 30 days +4. **Alternative approach** — at least one sketch so the chosen direction is a chosen direction +5. **Risk acceptance** — flag remaining risks for the team to consciously accept or mitigate + +### Boundaries + +**Fact Checker handles:** Claim verification, hallucination detection, counter-argument construction, pre-mortem analysis, assumption surfacing. + +**Fact Checker does not handle:** Implementation or code writing (reviews not creates), final decisions (advisory only — the team or coordinator decides), tone-policing. + +**Advisory by default.** Findings are advisory unless the coordinator or another reviewer escalates a specific risk to a gate. Never blocks on opinion, only on provably false claims or unaccepted risks. + +### Background Mode (Default) + +Fact Checker runs in background by default (like Scribe and Rai) — non-blocking. Spawns on-demand or via Pre-Ship ceremony auto-trigger. + +### Fact Checker State + +- **History** (`.squad/agents/fact-checker/history.md`) — verification + DA briefs across sessions +- **Charter** (`.squad/agents/fact-checker/charter.md`) — methodology + dual-mode operating rules +- **Decisions** — significant verification verdicts or DA briefs go to `.squad/decisions/inbox/fact-checker-{slug}.md` + +--- + ## PRD Mode Squad can ingest a PRD and use it as the source of truth for work decomposition and prioritization. diff --git a/scripts/bump-build.mjs b/scripts/bump-build.mjs index ac0a975ec..90ff7e5d7 100644 --- a/scripts/bump-build.mjs +++ b/scripts/bump-build.mjs @@ -76,4 +76,42 @@ for (const pkgPath of PACKAGE_PATHS) { writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8'); } +// Update package-lock.json workspace entries to match the new version. +// Without this, every build that runs bump-build.mjs produces a lockfile +// drift where package.json says X.Y.Z-build.N but lockfile workspace entries +// still say the previous version → npm ci fails in CI with EUSAGE. +// +// We only touch the two workspace entries (packages/squad-cli and +// packages/squad-sdk) and their cross-dependency reference. No npm +// install is run, so the dependency tree is not touched. +const LOCKFILE_PATH = join(root, 'package-lock.json'); +try { + const lockRaw = readFileSync(LOCKFILE_PATH, 'utf8'); + const lock = JSON.parse(lockRaw); + if (lock.packages) { + let lockChanged = false; + for (const key of ['packages/squad-sdk', 'packages/squad-cli']) { + const entry = lock.packages[key]; + if (entry && entry.version !== newVersion) { + entry.version = newVersion; + lockChanged = true; + } + // Also bump the CLI's @bradygaster/squad-sdk dependency floor so it + // resolves the freshly-built SDK rather than the previous version. + if (key === 'packages/squad-cli' && entry?.dependencies?.['@bradygaster/squad-sdk']) { + const desired = `>=${newVersion}`; + if (entry.dependencies['@bradygaster/squad-sdk'] !== desired) { + entry.dependencies['@bradygaster/squad-sdk'] = desired; + lockChanged = true; + } + } + } + if (lockChanged) { + writeFileSync(LOCKFILE_PATH, JSON.stringify(lock, null, 2) + '\n', 'utf8'); + } + } +} catch (err) { + console.warn(`⚠ Could not update package-lock.json: ${err.message}`); +} + console.log(`Build ${parsed.build}: ${rootPkg.version} → ${newVersion}`); diff --git a/scripts/check-bootstrap-deps.mjs b/scripts/check-bootstrap-deps.mjs index cc89b1da8..7795dd88f 100644 --- a/scripts/check-bootstrap-deps.mjs +++ b/scripts/check-bootstrap-deps.mjs @@ -44,7 +44,10 @@ const NODE_BUILTINS = new Set([ * Accepts both `node:fs` and `fs` forms, as well as subpaths like `node:fs/promises`. */ function isNodeBuiltin(specifier) { - if (specifier.startsWith('node:')) return true; + if (specifier.startsWith('node:')) { + const base = specifier.slice(5).split('/')[0]; + return NODE_BUILTINS.has(base); + } const base = specifier.split('/')[0]; return NODE_BUILTINS.has(base); } diff --git a/scripts/pr-readiness.mjs b/scripts/pr-readiness.mjs index 7b49cbdd5..be3ea4b1c 100644 --- a/scripts/pr-readiness.mjs +++ b/scripts/pr-readiness.mjs @@ -243,6 +243,16 @@ export function checkCIStatus(checkRuns, statuses) { if (otherChecks.length === 0 && (statuses || []).length === 0) { return { pass: false, detail: 'No CI checks have run yet' }; } + // Check legacy combined statuses for failure/error states + const failedStatuses = (statuses || []).filter( + (s) => s.state === 'failure' || s.state === 'error', + ); + if (failedStatuses.length > 0) { + return { + pass: false, + detail: `${failedStatuses.length} status(es) failing: ${failedStatuses.map((s) => s.context).join(', ')}`, + }; + } return { pass: true, detail: 'All checks passing' }; } @@ -312,7 +322,7 @@ export function buildFileList(files) { if (truncated) { const remaining = files.length - MAX_FILE_LIST; - rows.push(`| ... | **+${remaining} more files** | |`); + rows.push(`| ... | **+${remaining} more files** |`); } return [ @@ -442,7 +452,8 @@ export async function run({ env = process.env, fetchFn = globalThis.fetch } = {} let prLabels = []; try { - prLabels = JSON.parse(prLabelsRaw); + const parsed = JSON.parse(prLabelsRaw); + prLabels = Array.isArray(parsed) ? parsed : []; } catch { prLabels = []; } diff --git a/src/Squad.Agents.AI/README.md b/src/Squad.Agents.AI/README.md new file mode 100644 index 000000000..485695b26 --- /dev/null +++ b/src/Squad.Agents.AI/README.md @@ -0,0 +1,323 @@ +# Squad.Agents.AI + +> **Preview package.** `Squad.Agents.AI` is a `0.1.0-preview` NuGet package for early adopters. It targets `net10.0` and depends on preview Microsoft Agent Framework / GitHub Copilot SDK packages, so APIs may change before a stable release. + +## What it does + +`Squad.Agents.AI` exposes a Squad team as a Microsoft Agent Framework `AIAgent`. `SquadAgent` composes the Squad CLI through the GitHub Copilot SDK, delegates MAF sessions/runs/streaming to the inner Copilot agent, and gives .NET consumers a DI-friendly wrapper instead of hand-rolling CLI process setup. + +Public surface in this preview: + +- `SquadAgent` — sealed `AIAgent` wrapper over the Copilot-backed inner agent. Supports both non-streaming and streaming via `RunStreamingAsync`. +- `SquadAgentOptions` — team root, CLI path/args, environment, token, logging, instruction settings, and a `ConfigureCopilotClient` delegate for advanced SDK customization. +- `SquadConnectionFactory` — parses PATH or `squad://` connection strings into options. +- `SquadServiceCollectionExtensions` — registers `SquadAgent` and base `AIAgent` in DI, with standard and keyed overloads. + +Repository: + +## Install + +```bash +dotnet add package Squad.Agents.AI --prerelease +``` + +If you are consuming the PR before publish, pack it locally and add the generated package source: + +```bash +dotnet pack src/Squad.Agents.AI/Squad.Agents.AI.csproj -c Release -o nupkgs +dotnet add package Squad.Agents.AI --prerelease --source ./nupkgs +``` + +## Prerequisites + +- .NET 10 SDK. +- GitHub Copilot CLI available on `PATH` (`copilot --version`). +- Squad CLI and an initialized Squad team root; see the [Squad CLI repo](https://github.com/bradygaster/squad). +- GitHub Copilot authentication through the signed-in user. The quickstart below does not require an app key or environment variable. + +## Five-line quickstart + +```csharp +using Microsoft.Agents.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Squad.Agents.AI; + +var builder = Host.CreateApplicationBuilder(args); +builder.Services.AddSquadAgent(o => +{ + o.SquadFolderPath = @"C:\path\to\your\team-root"; +}); + +using var host = builder.Build(); +var squad = host.Services.GetRequiredService(); +var session = await squad.CreateSessionAsync(); +var response = await squad.RunAsync("What can this Squad team do?", session); +Console.WriteLine(response.Text); +``` + +## Streaming + +`SquadAgent` supports streaming via the MAF `RunStreamingAsync` method. The inner Copilot-backed agent streams response updates as they arrive from the CLI process: + +```csharp +var squad = host.Services.GetRequiredService(); +var session = await squad.CreateSessionAsync(); + +await foreach (var update in squad.RunStreamingAsync("Summarize the team.", session)) +{ + Console.Write(update.Text); +} +``` + +## Keyed DI + +Register multiple Squad teams in the same DI container using .NET 8+ keyed services: + +```csharp +builder.Services.AddKeyedSquadAgent("research", o => +{ + o.SquadFolderPath = @"C:\research-team"; +}); + +builder.Services.AddKeyedSquadAgent("platform", o => +{ + o.SquadFolderPath = @"C:\platform-team"; +}); + +// Resolve in a minimal API endpoint: +app.MapGet("/ask-research", async ([FromKeyedServices("research")] SquadAgent agent) => +{ + var session = await agent.CreateSessionAsync(); + var response = await agent.RunAsync("What's the latest?", session); + return response.Text; +}); +``` + +Keyed registrations include `AddKeyedSquadAgent` overloads for service key, optional connection-string name, and lifetime. Keyed and non-keyed registrations can coexist. + +## Advanced: ConfigureCopilotClient (BYOK) + +Use `ConfigureCopilotClient` to customize the underlying `CopilotClientOptions` after Squad applies its standard values. This is the extension point for injecting custom environment variables, providing a BYOK token, or tuning SDK settings: + +```csharp +builder.Services.AddSquadAgent(o => +{ + o.SquadFolderPath = @"C:\team"; + o.ConfigureCopilotClient = clientOpts => + { + // Inject a BYOK token from your own credential store + clientOpts.GitHubToken = myVault.GetSecret("copilot-token"); + + // Add custom environment variables for the CLI process + clientOpts.Environment = new Dictionary + { + ["MY_CUSTOM_VAR"] = "value" + }; + }; +}); +``` + +> **Routing guard:** Squad enforces a hard routing gate — if the delegate changes `Cwd`, `CliPath`, or `CliArgs`, the original values are restored and a warning is logged. Configure these via `SquadAgentOptions` instead. + +## GitHub Copilot authentication + +The default path mirrors the base GitHub Copilot SDK examples: leave `GitHubToken` and `GitHubTokenProvider` unset, then run with a locally signed-in GitHub Copilot user. For local development, sign in before running the app, for example with `gh auth login`, the Copilot CLI sign-in flow, or a Copilot-supported local sign-in that the SDK runtime can access. + +The SDK documents the credential priority order in [Authenticate Copilot SDK](https://github.com/github/copilot-sdk/blob/main/docs/auth/authenticate.md) and the broader [authentication overview](https://github.com/github/copilot-sdk/blob/main/docs/auth/index.md). Explicit tokens and environment variables are supported by the SDK, but they are not required for the minimal happy path. + +Use `GitHubTokenProvider` only when the host owns token retrieval, such as Key Vault, managed identity, or CI secret flow scenarios. Use `GitHubToken` only when you already have a Copilot-supported token in process and cannot use a provider callback. Use `ConfigureCopilotClient` when you need full control over the SDK's `CopilotClientOptions`, including BYOK token injection. + +## Aspire / configuration path + +`AddSquadAgent()` reads `ConnectionStrings:squad` through `IConfiguration.GetConnectionString("squad")`. In environment variables, use `ConnectionStrings__squad`. + +Named registrations support both connection-string conventions: + +| Style | Example | Looks up | +|---|---|---| +| **Aspire-style direct** (tried first) | `AddSquadAgent("research-squad")` | `ConnectionStrings:research-squad` | +| **Legacy prefixed fallback** | `AddSquadAgent("research")` | `ConnectionStrings:squad-research` | + +So when an Aspire AppHost registers a Squad resource via `builder.AddSquad("research-squad", ...)` and a downstream project does `.WithReference(researchSquad)`, the consumer can resolve the agent with a single `builder.Services.AddKeyedSquadAgent("research-squad")` call — the SDK finds the Aspire-injected connection string automatically. Existing consumers using the prefixed form continue to work; the SDK tries the literal name first and falls back to `squad-{name}` if the direct lookup is empty. + +Supported connection string forms: + +```text +C:\path\to\team-root +squad://localhost?teamRoot=C%3A%5Cteam&cliPath=C%3A%5Ctools%5Ccopilot.exe&cliArgs=--yolo&env=KEY=value +``` + +Parsed URI query keys: `teamRoot`, `cliPath`, `cwd`, `cliArgs` (semicolon-separated), and `env` (`key=value;key2=value2`). Unknown URI host/protocol values are reserved for future use. + +## Coordinator agent selection + +`SquadAgent` exists to wrap a Squad coordinator team, so it passes `--agent squad` to the underlying `copilot.exe` child process by default. That tells the CLI to load `.github/agents/squad.agent.md` as the agent definition — which is what teaches the coordinator to eager-execute, fan out, and dispatch through the `task` tool. Without it, the CLI uses its built-in generic agent and the coordinator role-plays responses inline instead of spawning real subagents — producing SDK behavior that does NOT match running `copilot --agent squad` interactively against the same team root. + +| Scenario | What the SDK does | +|---|---| +| Default — `squad.agent.md` exists | Auto-adds `--agent squad` | +| `squad.agent.md` not found at `.github/agents/` | Silently skips `--agent` (graceful degradation for not-yet-initialized teams; logs a Debug message) | +| Consumer sets `AgentFileName = "data"` (and file exists) | Auto-adds `--agent data` | +| Consumer sets `AgentFileName = null` | Skips `--agent` entirely | +| Consumer already supplied `--agent X` in `CliArgs` | Respects the explicit value; does not add a second `--agent` | + +> SDK note: `SessionConfigBase.Agent` exists but looks up the name in the SDK's `CustomAgents` registry, NOT in `.github/agents/*.agent.md` files on disk. The CLI `--agent` flag is currently the only path that reads the on-disk agent definition. + +## Subagent observability — first-class OpenTelemetry + +`Squad.Agents.AI` emits one OpenTelemetry `Activity` per subagent dispatch out of the box. Hosts that subscribe to the activity source see one `squad.subagent {Name}` span per spawn, with the subagent name as a tag and timeline annotations (`squad.subagent.start`, `squad.subagent.message`, `squad.subagent.completed`, `squad.subagent.failed`) marking every state transition. + +Two-line wiring is all that is required — no manual callback plumbing: + +```csharp +using OpenTelemetry.Trace; +using Squad.Agents.AI; + +builder.Services.AddOpenTelemetry() + .WithTracing(t => t.AddSource(SquadAgentDiagnostics.ActivitySourceName)); + +builder.Services.AddSquadAgent(o => o.SquadFolderPath = @"C:\team"); +``` + +When the coordinator's `task` tool spawns specialists, the host's tracer (Jaeger, Zipkin, Application Insights, or the Aspire dashboard's **Traces** view) shows one `squad.subagent` span per spawn, each tagged with `squad.subagent.name`, `squad.subagent.display_name`, `squad.subagent.sdk_agent_id`, and `squad.subagent.reply_preview` — plus timeline events for every lifecycle transition. + +**Turning telemetry off.** Set `EmitSubagentActivities = false` to suppress Squad's built-in spans (for example, when you want to drive observability entirely from your own callback or avoid double-counting). The `OnSubagentTrace` callback continues to fire either way — it is the customization seam, independent of telemetry. + +```csharp +builder.Services.AddSquadAgent(o => +{ + o.SquadFolderPath = @"C:\team"; + o.EmitSubagentActivities = false; // disable built-in OTel emission + o.OnSubagentTrace = trace => // handle telemetry yourself + { + if (trace.Kind == SquadAgentTraceEventKind.SubagentStarted) + MyMetrics.IncrementSpawn(trace.SubagentName!); + }; +}); +``` + +## Key options + +| Option | Purpose | +|---|---| +| `SquadFolderPath` | Squad team root; PATH connection strings set this and `Cwd`. | +| `CliPath` / `CliArgs` | Override the Copilot CLI executable and add extra CLI flags. | +| `Cwd` | Working directory for the Copilot CLI process; defaults to `SquadFolderPath`. | +| `Environment` | Additional environment variables for the CLI process. Excluded from JSON serialization; token-pattern keys are redacted in `ToString()`. | +| `GitHubToken` | Advanced direct token escape hatch; excluded from JSON serialization and redacted by `ToString()`. | +| `GitHubTokenProvider` | Advanced callback for secure token retrieval; wins over `GitHubToken`. | +| `ConfigureCopilotClient` | Advanced delegate for customizing `CopilotClientOptions`. Routing properties are guarded; see BYOK section. | +| `TraceEvents` | Enables verbose SDK logging and emits a startup warning when enabled. | +| `AgentName` | Display name for the resulting `AIAgent`; defaults to `Squad`. | +| `AgentFileName` | Name of the agent definition under `.github/agents/{name}.agent.md` to load via the CLI's `--agent` flag. Defaults to `"squad"`. Set to `null` to skip auto-injection; the file-existence check makes this safe to leave on for not-yet-initialized teams. | +| `Instructions` | Optional system instructions passed to the inner Copilot agent. | +| `EmitSubagentActivities` | Whether the SDK opens an OpenTelemetry `Activity` per subagent dispatch and annotates lifecycle events. Defaults to `true`. Set to `false` to handle telemetry from your own `OnSubagentTrace` callback. | +| `OnSubagentTrace` | Optional callback invoked for every typed `SquadAgentTraceEvent`. Independent of `EmitSubagentActivities` — both can be on simultaneously. | + +## Security + +- **Credential redaction:** `GitHubToken` and `Environment` values whose keys match `*TOKEN*`, `*KEY*`, `*SECRET*`, `*HMAC*`, `*PASSWORD*`, or `*CREDENTIAL*` are always redacted in `ToString()` output. +- **JSON safety:** `GitHubToken`, `GitHubTokenProvider`, `Environment`, and `ConfigureCopilotClient` are marked `[JsonIgnore]` and will not appear in JSON serialization output. +- **Routing guard:** The `ConfigureCopilotClient` delegate cannot change `Cwd`, `CliPath`, or `CliArgs` — changes are silently restored to prevent routing the agent to an unintended CLI process. +- **TraceEvents warning:** Enabling `TraceEvents` logs a startup warning because verbose SDK traces may include sensitive operational details. +- **Avoid hardcoded tokens:** Never embed tokens in source code. Use `GitHubTokenProvider` for production token retrieval from Key Vault, managed identity, or similar secure stores. + +## Notes for v0.1-preview + +- Default DI lifetime is scoped. An overload accepts any `ServiceLifetime`. +- DI registers both `SquadAgent` and base `AIAgent` (non-keyed). Keyed DI registers both `SquadAgent` and `AIAgent` under the same key. +- Multi-targeting and Aspire telemetry remain candidates for a later preview. +- The package does not validate that `SquadFolderPath` exists; consumers should validate their deployment paths. +- `TraceEvents` can log sensitive operational details. Keep it off unless debugging. + +## Package contents + +- `lib/net10.0/Squad.Agents.AI.dll` +- `lib/net10.0/Squad.Agents.AI.xml` for IntelliSense / API docs +- `README.md` for NuGet.org rendering +- `.nuspec` metadata with authors, tags, repository, and readme pointer +- `LICENSE` copied from the repository root + +## Sample + +A runnable console application is included at +`src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/`. It demonstrates the four +core integration patterns in one place: basic DI, keyed DI with multiple agents, +the `ConfigureCopilotClient` BYOK delegate, and streaming via `RunStreamingAsync`. + +### Prerequisites + +| Prerequisite | Notes | +|---|---| +| [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) | `dotnet --version` should print `10.x.x` | +| GitHub Copilot CLI on `PATH` | Install from [github.com/github/copilot-cli](https://github.com/github/copilot-cli); verify with `copilot --version` | +| Squad CLI installed | Follow the install guide in the [Squad repository README](https://github.com/bradygaster/squad#readme) | +| Initialized Squad team root | Run `squad init` in a directory; this becomes your team root | +| GitHub Copilot authentication | Run `gh auth login` or `copilot auth login` once before running the sample | + +Set the team root path before running: + +```bash +# Linux / macOS +export SQUAD_TEAM_ROOT=/path/to/your/team-root + +# Windows PowerShell +$env:SQUAD_TEAM_ROOT = "C:\path\to\your\team-root" +``` + +### Run + +Run all four flows in sequence: + +```bash +dotnet run --project src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/ +``` + +Run a single flow (1–4): + +```bash +dotnet run --project src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/ -- --flow=1 +dotnet run --project src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/ -- --flow=2 +dotnet run --project src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/ -- --flow=3 +dotnet run --project src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/ -- --flow=4 +``` + +### Flow walkthrough + +**Flow 1 — Basic DI registration** — `AddSquadAgent` registers `SquadAgent` and the +base `AIAgent` interface. Resolves the agent from DI, creates a session, calls +`RunAsync`, and prints `AgentResponse.Text`. + +**Flow 2 — Keyed DI (multiple agents)** — `AddKeyedSquadAgent` registers two agents +under keys `"alpha"` and `"beta"`. Resolution uses +`GetRequiredKeyedService("alpha")`. + +**Flow 3 — BYOK / `ConfigureCopilotClient` delegate** — the delegate receives +`CopilotClientOptions` after Squad has applied its defaults. Inject a custom token +or environment variable. The routing gate prevents accidental redirection of +`Cwd`, `CliPath`, or `CliArgs` — configure those on `SquadAgentOptions` directly. + +**Flow 4 — Streaming** — `RunStreamingAsync` returns +`IAsyncEnumerable`. Each `update.Text` fragment is written to +`Console.Write` without a newline for live token-by-token output. + +### Troubleshooting + +| Error | Cause | Fix | +|---|---|---| +| `GitHub Copilot CLI was not found on PATH` | `copilot` binary is missing or not on `PATH` | Install from [github.com/github/copilot-cli](https://github.com/github/copilot-cli); verify with `copilot --version` | +| `Authentication failed` / `401` | Copilot CLI is not signed in | Run `gh auth login` or `copilot auth login` | +| `SquadFolderPath does not exist` | `SQUAD_TEAM_ROOT` points to a non-existent path | Set `SQUAD_TEAM_ROOT` to an initialized Squad team directory | +| `The system cannot find the file specified` (Win32Exception) | Copilot CLI not found | Same as first row above | +| Build error: `Package Squad.Agents.AI not found` | Sample uses a project reference; ensure you run from the repo root | Run `dotnet build` from the repository root or pass `--project src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/` | + +## See also + +- Root repo: +- Microsoft Agent Framework: +- GitHub Copilot SDK authentication: +- GitHub Copilot CLI: +- Changelog: + +License: MIT. diff --git a/src/Squad.Agents.AI/Squad.Agents.AI.csproj b/src/Squad.Agents.AI/Squad.Agents.AI.csproj new file mode 100644 index 000000000..3657a6800 --- /dev/null +++ b/src/Squad.Agents.AI/Squad.Agents.AI.csproj @@ -0,0 +1,47 @@ + + + net8.0;net9.0;net10.0 + enable + enable + latest + true + Squad.Agents.AI + Squad Agents AI + Community SquadAgent for Microsoft Agent Framework. Exposes the Squad multi-agent CLI as an AIAgent that can be composed into any MAF app or workflow. + Tamir Dresher + squad;agents;ai;copilot;maf;multi-agent + https://github.com/bradygaster/squad + https://github.com/bradygaster/squad.git + git + main + MIT + README.md + true + 0.5.1 + + + + + + + + + + + + + + + + + + + + diff --git a/src/Squad.Agents.AI/SquadAgent.cs b/src/Squad.Agents.AI/SquadAgent.cs new file mode 100644 index 000000000..3511f54c8 --- /dev/null +++ b/src/Squad.Agents.AI/SquadAgent.cs @@ -0,0 +1,396 @@ +using GitHub.Copilot; +using Microsoft.Agents.AI; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Squad.Agents.AI; + +/// +/// Microsoft Agent Framework agent that delegates to a GitHub Copilot SDK client configured for a Squad team root. +/// Extends — Core* pass-through overrides are provided by the base class. +/// +/// +/// +/// Use +/// for DI registration in applications. Direct construction is useful for tests or simple console hosts. +/// +/// +/// +/// var agent = new SquadAgent(@"C:\repo"); +/// var response = await agent.RunAsync("Summarize the team."); +/// +/// +/// +public sealed class SquadAgent : DelegatingAIAgent, IAsyncDisposable +{ + private readonly CopilotClient _copilotClient; + private readonly ILogger? _logger; + private readonly bool _ownsClient; + private readonly SquadAgentOptions _options; + private readonly SquadSubagentTraceMapper? _traceMapper; + + // State-bag to thread pre-base-ctor state through chain constructors. + // DelegatingAIAgent requires the inner AIAgent at base() call time, so we + // build everything via static factory methods before invoking base(). + private readonly record struct SquadAgentState( + AIAgent Inner, + CopilotClient CopilotClient, + ILogger? Logger, + bool OwnsClient, + SquadAgentOptions Options, + SquadSubagentTraceMapper? TraceMapper); + + /// + /// Initializes a new with the Squad team root as the primary required parameter. + /// + /// Path to the initialized Squad team root. Required. + /// + /// Optional options. When a default is used. + /// is overwritten by + /// so there is exactly one source of truth for the team root. + /// + /// Optional logger factory; inject via DI or supply directly. + public SquadAgent(string squadFolderPath, SquadAgentOptions? options = null, ILoggerFactory? loggerFactory = null) + : this(BuildState(squadFolderPath, options, loggerFactory)) + { + } + + /// + /// Initializes a new from the options pattern (DI-friendly). + /// + /// + /// Options wrapper. must be set via a configure callback + /// or connection string before the agent is resolved from DI. + /// + /// Optional logger factory. + /// + /// Thrown when is or whitespace. + /// + public SquadAgent(IOptions options, ILoggerFactory? loggerFactory = null) + : this(BuildStateFromOptions(options, loggerFactory)) + { + } + + private SquadAgent(SquadAgentState state) + : base(state.Inner) + { + _copilotClient = state.CopilotClient; + _logger = state.Logger; + _ownsClient = state.OwnsClient; + _options = state.Options; + _traceMapper = state.TraceMapper; + _logger?.LogInformation("SquadAgent initialized with name '{AgentName}', team root '{TeamRoot}'", + state.Options.AgentName, state.Options.SquadFolderPath); + } + + private static SquadAgentState BuildState(string squadFolderPath, SquadAgentOptions? options, ILoggerFactory? lf) + { + ArgumentException.ThrowIfNullOrWhiteSpace(squadFolderPath); + var resolved = options ?? new SquadAgentOptions(); + resolved.SquadFolderPath = squadFolderPath; // ctor param wins + return BuildStateInternal(resolved, lf); + } + + private static SquadAgentState BuildStateFromOptions(IOptions options, ILoggerFactory? lf) + { + ArgumentNullException.ThrowIfNull(options); + var opts = options.Value ?? throw new ArgumentException("Options.Value cannot be null.", nameof(options)); + if (string.IsNullOrWhiteSpace(opts.SquadFolderPath)) + throw new ArgumentException( + "SquadAgentOptions.SquadFolderPath must be set (via configure callback or connection string).", + nameof(options)); + return BuildStateInternal(opts, lf); + } + + private static SquadAgentState BuildStateInternal(SquadAgentOptions options, ILoggerFactory? lf) + { + var client = CreateCopilotClient(options, lf); + + // Build the SessionConfig. Two non-obvious settings are critical here for the + // SquadAgent to actually work end-to-end against a `.squad/`-initialised team: + // + // 1. ConfigDir + EnableConfigDiscovery = true + // Points the Copilot CLI at the .squad/ folder so it auto-discovers the + // team's agents, skills, instructions, and MCP servers at session start. + // Without these, the agent has to read .squad/team.md (and per-agent + // charters) via runtime file tools — every read becomes a permission + // request, the agent eventually gives up and reports "permission errors". + // + // 2. OnPermissionRequest = PermissionHandler.ApproveAll + // Required by the SDK (CreateSessionAsync throws without it). This is the + // SDK-protocol layer; the CLI also has its own per-tool gate which we + // open with --allow-all in CreateCopilotClient. + // + // Callers can override any of this via SquadAgentOptions.ConfigureSession. + var teamRoot = options.Cwd ?? options.SquadFolderPath; + var squadConfigDir = !string.IsNullOrEmpty(teamRoot) + ? Path.Combine(teamRoot, ".squad") + : null; + + var sessionConfig = new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + WorkingDirectory = teamRoot, + ConfigDirectory = squadConfigDir, + EnableConfigDiscovery = true, + }; + if (!string.IsNullOrEmpty(options.Instructions)) + { + sessionConfig.SystemMessage = new SystemMessageConfig { Content = options.Instructions }; + } + + // Install the subagent trace mapper when EITHER: + // - EmitSubagentActivities is on (default true) — to emit OTel spans + events + // - OnSubagentTrace is set — to forward typed events to the consumer + // The two concerns are independent; the mapper handles whichever combination is active. + SquadSubagentTraceMapper? traceMapper = null; + if (options.EmitSubagentActivities || options.OnSubagentTrace is not null) + { + traceMapper = new SquadSubagentTraceMapper( + options.OnSubagentTrace, + emitActivities: options.EmitSubagentActivities); + sessionConfig.IncludeSubAgentStreamingEvents = true; + sessionConfig.OnEvent = traceMapper.OnSessionEvent; + } + + options.ConfigureSession?.Invoke(sessionConfig); + + var inner = client.AsAIAgent( + sessionConfig: sessionConfig, + ownsClient: true, + id: null, + name: options.AgentName ?? "Squad", + description: null); + + return new SquadAgentState(inner, client, lf?.CreateLogger(), true, options, traceMapper); + } + + // ── Extensibility seam ────────────────────────────────────────────── + // CreateCopilotClient is the single factory method that translates + // SquadAgentOptions into a live CopilotClient. Three extension points + // feed into it: + // 1. GitHubTokenProvider / GitHubToken — credential override. + // 2. ConfigureCopilotClient — BYOK delegate for advanced SDK tuning. + // 3. Connection-string binding (handled before this method by the + // SquadAgentOptionsConfigurator). + // Any future credential-store or model-property hooks should integrate + // through SquadAgentOptions and be applied inside this method. + // ──────────────────────────────────────────────────────────────────── + private static CopilotClient CreateCopilotClient(SquadAgentOptions options, ILoggerFactory? loggerFactory) + { + var logger = loggerFactory?.CreateLogger(); + + // Resolve token: provider takes precedence over direct property + string? resolvedToken = null; + if (options.GitHubTokenProvider is not null) + { + // Call provider synchronously (constructor context requires sync) + resolvedToken = options.GitHubTokenProvider(CancellationToken.None).GetAwaiter().GetResult(); + } + else + { + resolvedToken = options.GitHubToken; + } + + // ── CLI path resolution ──────────────────────────────────────────── + // When this package is consumed via NuGet, GitHub.Copilot.SDK's build + // targets download the copilot CLI binary and copy it to bin/.../runtimes/ + // {rid}/native/copilot.exe. The SDK runtime looks there by default, so + // most consumers don't need to set anything. + // + // (Squad.Agents.AI keeps a *direct* PackageReference to GitHub.Copilot.SDK + // exactly to force those build targets to fire — without it the SDK is only + // a transitive dependency of Microsoft.Agents.AI.GitHub.Copilot and its + // build/ targets don't propagate. Once microsoft/agent-framework#6457 + // merges, this dance will no longer be required.) + // + // SquadAgentOptions.CliPath / CliArgs remain explicit overrides for advanced + // scenarios: custom CLI builds, sandboxed runners, air-gapped environments. + // SDK 1.0.0 expresses both via RuntimeConnection.ForStdio(path, args), so we + // build a single Connection when either is supplied. + // ─────────────────────────────────────────────────────────────────── + var clientOptions = new CopilotClientOptions + { + WorkingDirectory = options.Cwd ?? options.SquadFolderPath, + GitHubToken = resolvedToken, + }; + + // ── CLI permission flags ─────────────────────────────────────────── + // The Copilot CLI enforces THREE independent permission gates: + // 1. Tools (--allow-all-tools) — which tool kinds may run + // 2. Paths (--allow-all-paths) — which filesystem paths may be read/written + // 3. URLs (--allow-all-urls) — which URLs may be fetched + // + // The SDK-level `OnPermissionRequest` handler only covers the SDK protocol; + // each CLI gate is a separate verification step. We pass `--allow-all` by + // default so the SquadAgent can actually drive tool calls end-to-end against + // the entire Squad workspace. Hosts that want stricter behavior can replace + // this via SquadAgentOptions.CliArgs / the ConfigureCopilotClient delegate. + // ─────────────────────────────────────────────────────────────────── + // Skip our `--allow-all` default if the host already opted in via any of the + // CLI's permission-opening flags (or the omnibus `--yolo` alias). Comparison is + // case-insensitive because `copilot --help` documents the flags in lowercase but + // CLI argument parsers on Windows are commonly forgiving of case differences. + var combinedCliArgs = new List(); + bool hostAlreadyOpenedPermissions = options.CliArgs.Any(a => + string.Equals(a, "--allow-all", StringComparison.OrdinalIgnoreCase) || + string.Equals(a, "--allow-all-tools", StringComparison.OrdinalIgnoreCase) || + string.Equals(a, "--allow-all-paths", StringComparison.OrdinalIgnoreCase) || + string.Equals(a, "--allow-all-urls", StringComparison.OrdinalIgnoreCase) || + string.Equals(a, "--yolo", StringComparison.OrdinalIgnoreCase)); + if (!hostAlreadyOpenedPermissions) + { + combinedCliArgs.Add("--allow-all"); + } + + // ── Default coordinator agent selection ──────────────────────────── + // SquadAgent wraps a Squad coordinator team. The CLI's `--agent squad` + // flag is what teaches the coordinator to eager-execute, fan out, and + // dispatch via the task tool by loading `.github/agents/squad.agent.md` + // as the agent definition. Without it the CLI uses its built-in generic + // agent and the coordinator role-plays responses inline instead of + // spawning real subagents — producing SDK behaviour that does NOT match + // running `copilot --agent squad` interactively against the same team + // root. + // + // Note on SessionConfig.Agent: that SDK property exists but selects + // from the SDK's CustomAgents registry, NOT from `.github/agents/*.agent.md` + // files (verified at v1.0.0 — setting it without populating CustomAgents + // produces `Custom agent 'squad' not found`). The CLI `--agent` flag is + // currently the only path that reads the on-disk agent definition. + // + // We auto-add `--agent {AgentFileName}` (default "squad") unless: + // 1. The host already supplied --agent explicitly in CliArgs + // 2. AgentFileName is null or whitespace (explicit opt-out) + // 3. The agent file does not exist at the conventional path on disk + // (graceful degradation for folders that are not yet Squad-initialized, + // where the CLI would error on `--agent squad`) + // ─────────────────────────────────────────────────────────────────── + bool hostSuppliedAgent = options.CliArgs.Any(a => + string.Equals(a, "--agent", StringComparison.OrdinalIgnoreCase)); + if (!hostSuppliedAgent && !string.IsNullOrWhiteSpace(options.AgentFileName)) + { + var defaultAgentTeamRoot = options.Cwd ?? options.SquadFolderPath; + if (!string.IsNullOrWhiteSpace(defaultAgentTeamRoot)) + { + var agentFilePath = Path.Combine(defaultAgentTeamRoot, ".github", "agents", $"{options.AgentFileName}.agent.md"); + if (File.Exists(agentFilePath)) + { + combinedCliArgs.Add("--agent"); + combinedCliArgs.Add(options.AgentFileName); + } + else + { + logger?.LogDebug( + "SquadAgentOptions.AgentFileName is '{AgentFileName}' but the file was not found at '{AgentFilePath}'. Skipping --agent argument; the CLI will fall back to its default agent.", + options.AgentFileName, agentFilePath); + } + } + } + + combinedCliArgs.AddRange(options.CliArgs); + + // Only override the SDK's default child-process connection when the consumer + // supplied a custom CLI path or any extra CLI args. Otherwise let the SDK + // resolve its own bundled binary (downloaded via the build/ targets). + if (!string.IsNullOrEmpty(options.CliPath) || combinedCliArgs.Count > 0) + { + clientOptions.Connection = RuntimeConnection.ForStdio(options.CliPath, combinedCliArgs); + } + + // Copy environment variables + if (options.Environment.Count > 0) + { + var envDict = new Dictionary(); + foreach (var kvp in options.Environment) + { + if (kvp.Value != null) + envDict[kvp.Key] = kvp.Value; + } + clientOptions.Environment = envDict; + } + + // Set logger if trace events enabled + if (options.TraceEvents && loggerFactory != null) + { + clientOptions.Logger = loggerFactory.CreateLogger(); + } + + // ── BYOK / ConfigureCopilotClient delegate ───────────────────── + // Allow consumers to customize the CopilotClientOptions after Squad + // applies its standard values. After the delegate runs, snapshot and + // restore routing-critical properties to prevent accidental or + // malicious changes that would route the agent to a different CLI + // process. (Picard Condition 1: hard routing gate.) + // + // SDK 1.0.0 collapsed CliPath/CliArgs into Connection (RuntimeConnection), + // so the routing gate now snapshots WorkingDirectory and Connection. + if (options.ConfigureCopilotClient is not null) + { + var snapshotWorkingDirectory = clientOptions.WorkingDirectory; + var snapshotConnection = clientOptions.Connection; + + options.ConfigureCopilotClient(clientOptions); + + bool restored = false; + if (!string.Equals(clientOptions.WorkingDirectory, snapshotWorkingDirectory, StringComparison.Ordinal)) + { + logger?.LogWarning( + "ConfigureCopilotClient delegate changed WorkingDirectory from '{Original}' to '{Changed}'; " + + "restoring original value to preserve Squad routing.", + snapshotWorkingDirectory, clientOptions.WorkingDirectory); + clientOptions.WorkingDirectory = snapshotWorkingDirectory; + restored = true; + } + + if (!ReferenceEquals(clientOptions.Connection, snapshotConnection)) + { + logger?.LogWarning( + "ConfigureCopilotClient delegate replaced Connection; " + + "restoring original value to preserve Squad routing. " + + "Configure CLI path / args via SquadAgentOptions.CliPath / CliArgs instead."); + clientOptions.Connection = snapshotConnection; + restored = true; + } + + if (restored) + { + logger?.LogWarning( + "One or more routing properties were restored after ConfigureCopilotClient delegate ran. " + + "To set the team root, CLI path, or CLI args, configure them on SquadAgentOptions instead."); + } + } + + return new CopilotClient(clientOptions); + } + + /// + /// Gets the display name exposed by this agent. + /// Prefers over the inner agent's name. + /// + public override string? Name => _options.AgentName ?? base.Name; + + /// + /// Gets the agent description, falling back to a Squad-specific default when the inner agent provides none. + /// + public override string? Description => base.Description ?? "Squad multi-agent CLI participant"; + + /// + /// Disposes the owned Copilot client and disposable inner agent resources. + /// + /// A value task that completes when disposal finishes. + public async ValueTask DisposeAsync() + { + if (_ownsClient) + await _copilotClient.DisposeAsync().ConfigureAwait(false); + + if (InnerAgent is IAsyncDisposable innerDisposable) + await innerDisposable.DisposeAsync().ConfigureAwait(false); + + // Drain any subagent activities that never received a matching SubagentCompletedEvent + // (e.g. session ended mid-dispatch). Failing to do so leaks Activity instances. + _traceMapper?.Dispose(); + + _logger?.LogDebug("SquadAgent disposed"); + } +} diff --git a/src/Squad.Agents.AI/SquadAgentDiagnostics.cs b/src/Squad.Agents.AI/SquadAgentDiagnostics.cs new file mode 100644 index 000000000..2f245960d --- /dev/null +++ b/src/Squad.Agents.AI/SquadAgentDiagnostics.cs @@ -0,0 +1,124 @@ +using System.Diagnostics; + +namespace Squad.Agents.AI; + +/// +/// Diagnostic constants for Squad.Agents.AI. Consumers wiring OpenTelemetry should +/// add to their tracer provider to surface subagent +/// dispatch spans (one span per task-tool invocation) in their backend. +/// +/// +/// +/// builder.Services.AddOpenTelemetry() +/// .WithTracing(t => t.AddSource(SquadAgentDiagnostics.ActivitySourceName)); +/// +/// +public static class SquadAgentDiagnostics +{ + /// + /// Name of the Squad.Agents.AI emits spans on. + /// + public const string ActivitySourceName = "Microsoft.Agents.AI.Squad"; + + /// + /// Singleton used internally to emit subagent + /// dispatch spans. Consumers normally do not interact with this directly; subscribe via + /// AddSource(SquadAgentDiagnostics.ActivitySourceName) on their OpenTelemetry tracer. + /// + public static readonly ActivitySource ActivitySource = new(ActivitySourceName); +} + +/// +/// Categorises the underlying GitHub.Copilot.SessionEvent for consumers who want a typed view +/// without depending directly on the SDK's polymorphic event hierarchy. +/// +public enum SquadAgentTraceEventKind +{ + /// An event that does not match any of the well-known categories below. + Other = 0, + + /// The coordinator selected a custom agent to dispatch to (precedes ). + SubagentSelected, + + /// A subagent process started (the coordinator's task tool spawned it). + SubagentStarted, + + /// A subagent finished and returned its result back to the coordinator. + SubagentCompleted, + + /// A subagent terminated abnormally. + SubagentFailed, + + /// An assistant turn completed (from the coordinator OR a subagent — see ). + AssistantMessage, + + /// A tool started executing (the task tool is what spawns subagents). + ToolStart, + + /// A tool finished executing. + ToolComplete, + + /// The session became idle (the run is complete). + SessionIdle, +} + +/// +/// Typed envelope around a GitHub.Copilot.SessionEvent for consumers who want to surface +/// subagent dispatch + assistant messages in a UI (e.g. an Aspire dashboard) without writing their +/// own polymorphic dispatch over the raw SDK event hierarchy. +/// +/// +/// +/// Squad.Agents.AI converts the raw event stream into instances +/// inside its own OnEvent handler when is set, +/// then invokes the consumer's callback. Consumers therefore do not need a transitive reference to +/// GitHub.Copilot.SDK to subscribe to subagent activity. +/// +/// +/// The full underlying GitHub.Copilot.SessionEvent is preserved on for +/// advanced consumers that need access to the original payload. +/// +/// +public sealed record SquadAgentTraceEvent +{ + /// The categorised kind of event. + public required SquadAgentTraceEventKind Kind { get; init; } + + /// The raw GitHub.Copilot.SessionEvent type name, e.g. SubagentStartedEvent. + public required string RawEventType { get; init; } + + /// Server-assigned timestamp on the SDK event. + public DateTimeOffset Timestamp { get; init; } + + /// + /// The SDK agent identifier (e.g. toolu_vrtx_01Fc3uBTKapUoDDMnPUSe2ww) the event is attributed to. + /// Null for events emitted by the root coordinator; non-null for subagent-scoped events when + /// is on. + /// + public string? SdkAgentId { get; init; } + + /// Display-friendly name of the spawning subagent (e.g. Picard), when known. + public string? SubagentName { get; init; } + + /// Long-form display name of the spawning subagent, when known. + public string? SubagentDisplayName { get; init; } + + /// SDK ToolCallId for / . + public string? ToolCallId { get; init; } + + /// + /// Assistant message content for ; null for other kinds. + /// Consumers may treat this as the subagent's reply when is non-null. + /// + public string? Content { get; init; } + + /// Tool success flag, for . + public bool? Success { get; init; } + + /// + /// The original underlying GitHub.Copilot.SessionEvent instance. Kept as on this + /// type so consumers do not need a transitive reference to the SDK. Cast to the concrete event type for advanced + /// scenarios. + /// + public object? RawEvent { get; init; } +} diff --git a/src/Squad.Agents.AI/SquadAgentOptions.cs b/src/Squad.Agents.AI/SquadAgentOptions.cs new file mode 100644 index 000000000..f9d0ab619 --- /dev/null +++ b/src/Squad.Agents.AI/SquadAgentOptions.cs @@ -0,0 +1,298 @@ +using System.Text.Json.Serialization; +using GitHub.Copilot; + +namespace Squad.Agents.AI; + +/// +/// Configures construction, connection-string binding, and Copilot CLI process settings. +/// +/// +/// +/// var options = new SquadAgentOptions +/// { +/// SquadFolderPath = @"C:\repo", +/// }; +/// options.CliArgs.Add("--yolo"); +/// +/// +public sealed class SquadAgentOptions +{ + /// + /// Gets or sets the initialized Squad team root. + /// + public string? SquadFolderPath { get; set; } + + /// + /// Gets or sets an explicit Copilot CLI executable path. When unset, the SDK resolves the default CLI from `PATH`. + /// + public string? CliPath { get; set; } + + /// + /// Gets additional CLI arguments appended when the Copilot CLI process is started. + /// + public IList CliArgs { get; } = new List(); + + /// + /// Gets or sets the CLI process working directory. When unset, is used. + /// + public string? Cwd { get; set; } + + /// + /// Gets additional environment variables injected into the CLI process. + /// + /// + /// + /// This dictionary is excluded from JSON serialization to prevent credential leakage. + /// Values for keys matching token/secret patterns are redacted in . + /// + /// Avoid placing user-controlled values in this dictionary. + /// + [JsonIgnore] + public IDictionary Environment { get; } = new Dictionary(); + + /// + /// Gets or sets a direct GitHub token for advanced host-controlled authentication scenarios. + /// + /// + /// The value is ignored by JSON serialization and redacted by , but still lives in the options object. + /// Prefer leaving this unset for local signed-in user authentication; use for production hosts that retrieve tokens dynamically. + /// + [JsonIgnore] + public string? GitHubToken { get; set; } + + /// + /// Gets or sets the callback that provides a GitHub token during agent construction. + /// + /// + /// When set, this provider takes precedence over . + /// + [JsonIgnore] + public Func>? GitHubTokenProvider { get; set; } + + /// + /// Gets or sets whether verbose Copilot SDK logging is enabled. + /// + /// + /// The DI registration emits a startup warning whenever this is enabled because traces may contain sensitive details. + /// + public bool TraceEvents { get; set; } = false; + + /// + /// Gets or sets the display name for the resulting . + /// + public string AgentName { get; set; } = "Squad"; + + /// + /// Gets or sets the Copilot CLI agent definition file (under .github/agents/) the + /// underlying copilot.exe child process should load via the CLI's --agent + /// flag. Defaults to "squad", which selects .github/agents/squad.agent.md + /// — the Squad coordinator system prompt that drives eager execution, parallel fan-out, + /// and dispatch through the task tool. + /// + /// + /// + /// The whole reason exists is to wrap a Squad coordinator team, + /// so the SDK passes --agent squad by default. Without it the CLI uses its built-in + /// generic agent and the coordinator role-plays responses inline instead of dispatching + /// real subagents — exactly the inconsistency between copilot --agent squad (CLI) + /// and SquadAgent.RunAsync (SDK) that this default eliminates. + /// + /// + /// When the named file does not exist at {team-root}/.github/agents/{AgentFileName}.agent.md + /// (e.g. the team root is not Squad-initialized yet), the SDK silently skips the + /// --agent argument so the CLI can still start with its default agent. + /// + /// + /// Set to (or whitespace) to disable the auto-inject entirely. Set + /// to a different name (e.g. "data") to load a custom agent file. If the consumer + /// already supplied --agent in , that explicit value wins and + /// the default is not added. + /// + /// + public string? AgentFileName { get; set; } = "squad"; + + /// + /// Gets or sets optional system instructions passed to the inner Copilot-backed agent. + /// + public string? Instructions { get; set; } + + /// + /// Gets or sets a delegate that customizes the underlying after Squad + /// applies its standard values. Use this for advanced scenarios such as injecting custom environment + /// variables, setting timeouts, or providing a custom logger to the Copilot SDK. + /// + /// + /// Security note: Do not override Cwd, CliPath, or CliArgs through this + /// delegate — Squad enforces routing invariants and will restore its own values if they are changed. + /// A warning is logged when this occurs. + /// The delegate has full read/write access to , including + /// GitHubToken. If the delegate replaces the token, the replacement takes effect. + /// Document this as the final authority on token value. + /// + /// + /// + /// options.ConfigureCopilotClient = clientOpts => + /// { + /// clientOpts.Environment["MY_CUSTOM_VAR"] = "custom-value"; + /// }; + /// + /// + [JsonIgnore] + public Action? ConfigureCopilotClient { get; set; } + + /// + /// Gets or sets a delegate that customizes the used to + /// construct the inner . The delegate runs + /// after Squad has applied its defaults (including an ApproveAll + /// handler and the + /// as the appended system message), so it can override or + /// extend any session-scoped setting such as the permission handler, tool list, + /// model name, or hooks. + /// + /// + /// Use this to inject a stricter permission handler, restrict the available tools, + /// or pin the model used by the inner Copilot session. + /// + /// + /// + /// options.ConfigureSession = sessionConfig => + /// { + /// sessionConfig.OnPermissionRequest = MyCustomPermissionHandler; + /// sessionConfig.Model = "claude-sonnet-4.6"; + /// }; + /// + /// + [JsonIgnore] + public Action? ConfigureSession { get; set; } + + /// + /// Gets or sets a callback that receives instances for every + /// notable session event from the underlying Copilot SDK — including subagent dispatch lifecycle + /// (task-tool spawn / completion), tool calls, and assistant messages from both the root + /// coordinator AND each spawned subagent. + /// + /// + /// + /// Setting this callback has two side effects: + /// + /// + /// is forced to + /// so subagent assistant messages flow up to the parent session (otherwise + /// the subagent's reply stays inside its own session and never reaches the callback). + /// may still override + /// or ; consumers + /// that need a stacked event handler should call OnSubagentTrace from inside their + /// callback to compose the two. + /// + /// + /// OpenTelemetry telemetry is independent — it is controlled by + /// (default ) and emits whether or + /// not this callback is set. Use OnSubagentTrace when you want to layer extra behaviour + /// (custom logging, dashboards, audit trails) on top of the built-in spans. + /// + /// + /// Consumer callback exceptions are caught and swallowed so a misbehaving subscriber cannot tear + /// down the SDK event loop. Add your own try/catch + logging inside the callback if you need to + /// surface those errors. + /// + /// + /// + /// + /// options.OnSubagentTrace = trace => + /// { + /// if (trace.Kind == SquadAgentTraceEventKind.SubagentStarted) + /// Console.WriteLine($"[spawn] {trace.SubagentName} (id={trace.SdkAgentId})"); + /// else if (trace.Kind == SquadAgentTraceEventKind.AssistantMessage && trace.SdkAgentId is not null) + /// Console.WriteLine($"[{trace.SdkAgentId}] {trace.Content}"); + /// }; + /// + /// + [JsonIgnore] + public Action? OnSubagentTrace { get; set; } + + /// + /// Gets or sets whether to emit OpenTelemetry spans + /// and lifecycle events for each subagent dispatch. Defaults to . + /// + /// + /// + /// When , an named + /// opens one + /// per subagent dispatch (tagged with + /// squad.subagent.name, squad.subagent.display_name, squad.subagent.sdk_agent_id, + /// and squad.subagent.reply_preview). Each subagent lifecycle phase + /// (squad.subagent.start, squad.subagent.message, squad.subagent.completed, + /// squad.subagent.failed) is also added as a + /// on the live subagent span so the timeline view in dashboards (e.g. Aspire) shows annotated + /// timestamps for every state transition. + /// + /// + /// Hosts that .AddSource(SquadAgentDiagnostics.ActivitySourceName) on their OpenTelemetry + /// tracer get these spans in their backend automatically — no need to set + /// . + /// + /// + /// Set this to to disable Squad's built-in telemetry (e.g. when you + /// want to handle observability entirely from your own callback, + /// or to avoid double-counting if another layer in your stack is already emitting equivalent + /// spans). + /// + /// + /// + /// + /// // Default (recommended): just AddSource and the dashboard lights up. + /// builder.Services.AddOpenTelemetry() + /// .WithTracing(t => t.AddSource(SquadAgentDiagnostics.ActivitySourceName)); + /// + /// builder.Services.AddSquadAgent(opts => opts.SquadFolderPath = "/team"); + /// + /// // Opt out (custom telemetry): + /// builder.Services.AddSquadAgent(opts => + /// { + /// opts.SquadFolderPath = "/team"; + /// opts.EmitSubagentActivities = false; + /// opts.OnSubagentTrace = trace => MyMetrics.Increment(trace.Kind.ToString()); + /// }); + /// + /// + public bool EmitSubagentActivities { get; set; } = true; + + private static readonly string[] TokenPatterns = { "TOKEN", "KEY", "SECRET", "HMAC", "PASSWORD", "CREDENTIAL" }; + + private static bool IsTokenKey(string key) + { + var upper = key.ToUpperInvariant(); + foreach (var pattern in TokenPatterns) + { + if (upper.Contains(pattern)) + return true; + } + return false; + } + + /// + /// Returns a diagnostic representation with sensitive values redacted. + /// + /// + /// GitHubToken is always shown as [REDACTED]. + /// Environment values whose keys match token/secret patterns + /// (*TOKEN*, *KEY*, *SECRET*, *HMAC*, *PASSWORD*, *CREDENTIAL*) + /// are shown as [REDACTED]; other environment entries show their value. + /// + /// A string containing non-secret option values. + public override string ToString() + { + var envDisplay = "{}"; + if (Environment.Count > 0) + { + var entries = Environment.Select(kvp => + IsTokenKey(kvp.Key) + ? $"{kvp.Key}=[REDACTED]" + : $"{kvp.Key}={kvp.Value}"); + envDisplay = $"{{ {string.Join(", ", entries)} }}"; + } + + return $"SquadAgentOptions {{ SquadFolderPath = {SquadFolderPath}, AgentName = {AgentName}, " + + $"GitHubToken = [REDACTED], Environment = {envDisplay}, TraceEvents = {TraceEvents} }}"; + } +} diff --git a/src/Squad.Agents.AI/SquadAgentOptionsConfigurator.cs b/src/Squad.Agents.AI/SquadAgentOptionsConfigurator.cs new file mode 100644 index 000000000..662c4ae9e --- /dev/null +++ b/src/Squad.Agents.AI/SquadAgentOptionsConfigurator.cs @@ -0,0 +1,103 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; + +namespace Squad.Agents.AI; + +/// +/// Binds SquadAgentOptions from a configured connection string. +/// Runs before user-supplied configure lambda; user settings always win. +/// +/// +/// +/// Resolves the connection string by trying each candidate name in order and using the first +/// one that returns a non-empty value. This lets a single registration accept both the +/// Aspire-style direct name (e.g. ConnectionStrings:research-squad) and the legacy +/// prefixed name (ConnectionStrings:squad-research) without breaking either convention. +/// +/// +internal sealed class SquadAgentOptionsConfigurator : IConfigureNamedOptions +{ + private readonly IConfiguration _configuration; + private readonly string _optionsName; + private readonly string[] _connectionStringNames; + + public SquadAgentOptionsConfigurator(IConfiguration configuration) + : this(configuration, Options.DefaultName, new[] { SquadServiceCollectionExtensions.DefaultConnectionStringName }) + { + } + + internal SquadAgentOptionsConfigurator( + IConfiguration configuration, + string optionsName, + string connectionStringName) + : this( + configuration, + optionsName, + new[] { connectionStringName ?? throw new ArgumentNullException(nameof(connectionStringName)) }) + { + } + + internal SquadAgentOptionsConfigurator( + IConfiguration configuration, + string optionsName, + string[] connectionStringNames) + { + _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + _optionsName = optionsName ?? Options.DefaultName; + _connectionStringNames = connectionStringNames ?? throw new ArgumentNullException(nameof(connectionStringNames)); + if (_connectionStringNames.Length == 0) + throw new ArgumentException("At least one connection string name must be supplied.", nameof(connectionStringNames)); + } + + public void Configure(SquadAgentOptions options) => Configure(Options.DefaultName, options); + + public void Configure(string? name, SquadAgentOptions options) + { + if (!string.Equals(name ?? Options.DefaultName, _optionsName, StringComparison.Ordinal)) + return; + + // Try each candidate name in order; first non-empty wins. This lets a single + // AddSquadAgent("research") call resolve either ConnectionStrings:research + // (Aspire-style) or ConnectionStrings:squad-research (legacy SDK convention). + string? connectionString = null; + foreach (var candidate in _connectionStringNames) + { + var value = _configuration.GetConnectionString(candidate); + if (!string.IsNullOrWhiteSpace(value)) + { + connectionString = value; + break; + } + } + + if (string.IsNullOrWhiteSpace(connectionString)) + return; + + var parsedOptions = SquadConnectionFactory.FromConnectionString(connectionString); + + // Only apply parsed values if user hasn't set them explicitly + // User callback runs AFTER this configurator, so any null/empty here will be overridden + if (string.IsNullOrWhiteSpace(options.SquadFolderPath)) + options.SquadFolderPath = parsedOptions.SquadFolderPath; + + if (string.IsNullOrWhiteSpace(options.CliPath)) + options.CliPath = parsedOptions.CliPath; + + if (string.IsNullOrWhiteSpace(options.Cwd)) + options.Cwd = parsedOptions.Cwd; + + // Merge CLI args (append to existing list) + foreach (var arg in parsedOptions.CliArgs) + { + if (!options.CliArgs.Contains(arg)) + options.CliArgs.Add(arg); + } + + // Merge environment variables (don't overwrite existing) + foreach (var kvp in parsedOptions.Environment) + { + if (!options.Environment.ContainsKey(kvp.Key)) + options.Environment[kvp.Key] = kvp.Value; + } + } +} diff --git a/src/Squad.Agents.AI/SquadConnectionFactory.cs b/src/Squad.Agents.AI/SquadConnectionFactory.cs new file mode 100644 index 000000000..896d5ad15 --- /dev/null +++ b/src/Squad.Agents.AI/SquadConnectionFactory.cs @@ -0,0 +1,102 @@ +namespace Squad.Agents.AI; + +/// +/// Parses Squad connection strings into instances. +/// +public static class SquadConnectionFactory +{ + /// + /// Parses a Squad connection string into agent options. + /// + /// PATH form or `squad://` URI form connection string. + /// Options populated from the supplied connection string. + /// Thrown when is null, empty, or whitespace. + /// + /// PATH form maps the entire value to and . + /// URI form parses `teamRoot`, `cliPath`, `cwd`, `cliArgs`, and `env` query values; the URI host is reserved. + /// + /// + /// + /// var options = SquadConnectionFactory.FromConnectionString(@"C:\repo"); + /// var advanced = SquadConnectionFactory.FromConnectionString( + /// "squad://localhost?teamRoot=C%3A%5Crepo&cliArgs=--yolo"); + /// + /// + public static SquadAgentOptions FromConnectionString(string connectionString) + { + if (string.IsNullOrWhiteSpace(connectionString)) + { + throw new ArgumentException("Connection string cannot be null or empty.", nameof(connectionString)); + } + + var options = new SquadAgentOptions(); + + if (connectionString.StartsWith("squad://", StringComparison.OrdinalIgnoreCase)) + { + // URI form + var uri = new Uri(connectionString); + var query = ParseQueryString(uri.Query); + + if (query.TryGetValue("teamRoot", out var teamRoot)) + options.SquadFolderPath = teamRoot; + + if (query.TryGetValue("cliPath", out var cliPath)) + options.CliPath = cliPath; + + if (query.TryGetValue("cwd", out var cwd)) + options.Cwd = cwd; + + if (query.TryGetValue("cliArgs", out var cliArgsString)) + { + foreach (var arg in cliArgsString.Split(';', StringSplitOptions.RemoveEmptyEntries)) + { + options.CliArgs.Add(arg); + } + } + + // Parse environment variables if present (format: key1=val1;key2=val2) + if (query.TryGetValue("env", out var envString)) + { + foreach (var pair in envString.Split(';', StringSplitOptions.RemoveEmptyEntries)) + { + var parts = pair.Split('=', 2); + if (parts.Length == 2) + { + options.Environment[parts[0]] = parts[1]; + } + } + } + } + else + { + // PATH form — treat entire string as team root path + options.SquadFolderPath = connectionString; + options.Cwd = connectionString; + } + + return options; + } + + private static Dictionary ParseQueryString(string query) + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + + if (string.IsNullOrEmpty(query)) + return result; + + // Remove leading '?' if present + if (query.StartsWith('?')) + query = query.Substring(1); + + foreach (var pair in query.Split('&', StringSplitOptions.RemoveEmptyEntries)) + { + var parts = pair.Split('=', 2); + if (parts.Length == 2) + { + result[Uri.UnescapeDataString(parts[0])] = Uri.UnescapeDataString(parts[1]); + } + } + + return result; + } +} diff --git a/src/Squad.Agents.AI/SquadServiceCollectionExtensions.cs b/src/Squad.Agents.AI/SquadServiceCollectionExtensions.cs new file mode 100644 index 000000000..053e6bd7f --- /dev/null +++ b/src/Squad.Agents.AI/SquadServiceCollectionExtensions.cs @@ -0,0 +1,307 @@ +using Microsoft.Agents.AI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Squad.Agents.AI; + +/// +/// Extension methods for registering in dependency injection. +/// +public static class SquadServiceCollectionExtensions +{ + internal const string DefaultConnectionStringName = "squad"; + + /// + /// Registers and base with scoped lifetime. + /// + /// Service collection to update. + /// Optional callback that customizes after connection-string binding. + /// The same instance for chaining. + /// + /// + /// builder.Services.AddSquadAgent(o => o.SquadFolderPath = @"C:\repo"); + /// + /// + public static IServiceCollection AddSquadAgent( + this IServiceCollection services, + Action? configure = null) + => AddSquadAgentCore(services, name: null, ServiceLifetime.Scoped, configure); + + /// + /// Registers and base with scoped lifetime using a named connection string. + /// + /// Service collection to update. + /// Logical Squad name. For example, "research" reads ConnectionStrings:squad-research. + /// Optional callback that customizes after connection-string binding. + /// The same instance for chaining. + public static IServiceCollection AddSquadAgent( + this IServiceCollection services, + string name, + Action? configure = null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + return AddSquadAgentCore(services, name, ServiceLifetime.Scoped, configure); + } + + /// + /// Registers and base with the specified lifetime. + /// + /// Service collection to update. + /// Lifetime used for the concrete and base agent registrations. + /// Optional callback that customizes after connection-string binding. + /// The same instance for chaining. + public static IServiceCollection AddSquadAgent( + this IServiceCollection services, + ServiceLifetime lifetime, + Action? configure = null) + => AddSquadAgentCore(services, name: null, lifetime, configure); + + /// + /// Registers and base with the specified lifetime using a named connection string. + /// + /// Service collection to update. + /// Logical Squad name. For example, "research" reads ConnectionStrings:squad-research. + /// Lifetime used for the concrete and base agent registrations. + /// Optional callback that customizes after connection-string binding. + /// The same instance for chaining. + public static IServiceCollection AddSquadAgent( + this IServiceCollection services, + string name, + ServiceLifetime lifetime, + Action? configure = null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + return AddSquadAgentCore(services, name, lifetime, configure); + } + + // ── Keyed DI overloads ─────────────────────────────────────────────────── + + /// + /// Registers as a keyed service with scoped lifetime. Resolve via + /// [FromKeyedServices("myKey")] SquadAgent agent or + /// provider.GetRequiredKeyedService<SquadAgent>("myKey"). + /// + /// Service collection to update. + /// The DI service key for keyed resolution. + /// Optional callback that customizes after connection-string binding. + /// The same instance for chaining. + /// + /// + /// builder.Services.AddKeyedSquadAgent("research", o => o.SquadFolderPath = @"C:\research-team"); + /// builder.Services.AddKeyedSquadAgent("platform", o => o.SquadFolderPath = @"C:\platform-team"); + /// + /// // Resolve in a controller or minimal API: + /// app.MapGet("/ask", ([FromKeyedServices("research")] SquadAgent agent) => ...); + /// + /// + public static IServiceCollection AddKeyedSquadAgent( + this IServiceCollection services, + string serviceKey, + Action? configure = null) + => AddKeyedSquadAgentCore(services, serviceKey, name: null, ServiceLifetime.Scoped, configure); + + /// + /// Registers as a keyed service with scoped lifetime using a named connection string. + /// + /// Service collection to update. + /// The DI service key for keyed resolution. + /// Logical Squad name for connection-string lookup. For example, "research" reads ConnectionStrings:squad-research. + /// Optional callback that customizes after connection-string binding. + /// The same instance for chaining. + public static IServiceCollection AddKeyedSquadAgent( + this IServiceCollection services, + string serviceKey, + string name, + Action? configure = null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + return AddKeyedSquadAgentCore(services, serviceKey, name, ServiceLifetime.Scoped, configure); + } + + /// + /// Registers as a keyed service with the specified lifetime. + /// + /// Service collection to update. + /// The DI service key for keyed resolution. + /// Lifetime used for the keyed agent registration. + /// Optional callback that customizes after connection-string binding. + /// The same instance for chaining. + public static IServiceCollection AddKeyedSquadAgent( + this IServiceCollection services, + string serviceKey, + ServiceLifetime lifetime, + Action? configure = null) + => AddKeyedSquadAgentCore(services, serviceKey, name: null, lifetime, configure); + + /// + /// Registers as a keyed service with the specified lifetime using a named connection string. + /// + /// Service collection to update. + /// The DI service key for keyed resolution. + /// Logical Squad name for connection-string lookup. + /// Lifetime used for the keyed agent registration. + /// Optional callback that customizes after connection-string binding. + /// The same instance for chaining. + public static IServiceCollection AddKeyedSquadAgent( + this IServiceCollection services, + string serviceKey, + string name, + ServiceLifetime lifetime, + Action? configure = null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + return AddKeyedSquadAgentCore(services, serviceKey, name, lifetime, configure); + } + + // ── Core implementation (non-keyed) ────────────────────────────────────── + + private static IServiceCollection AddSquadAgentCore( + IServiceCollection services, + string? name, + ServiceLifetime lifetime, + Action? configure) + { + ArgumentNullException.ThrowIfNull(services); + + var optionsName = GetOptionsName(name); + var connectionStringNames = GetConnectionStringNames(name); + + RegisterOptionsInfrastructure(services, optionsName, connectionStringNames, configure); + + // Register SquadAgent with specified lifetime + services.Add(new ServiceDescriptor( + typeof(SquadAgent), + sp => + { + var options = sp.GetRequiredService>().Get(optionsName); + var loggerFactory = sp.GetService(); + var folderPath = options.SquadFolderPath; + if (string.IsNullOrWhiteSpace(folderPath)) + throw new InvalidOperationException( + "SquadAgentOptions.SquadFolderPath must be configured (via configure callback or connection string) before resolving SquadAgent."); + return new SquadAgent(folderPath, options, loggerFactory); + }, + lifetime)); + + // AIAgent base registration — so consumers can resolve via IEnumerable + services.Add(new ServiceDescriptor( + typeof(AIAgent), + sp => sp.GetRequiredService(), + lifetime)); + + return services; + } + + // ── Core implementation (keyed) ────────────────────────────────────────── + + private static IServiceCollection AddKeyedSquadAgentCore( + IServiceCollection services, + string serviceKey, + string? name, + ServiceLifetime lifetime, + Action? configure) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentException.ThrowIfNullOrWhiteSpace(serviceKey); + + // Use the serviceKey as the options name when no explicit name is given + var optionsName = GetOptionsName(name ?? serviceKey); + var connectionStringNames = GetConnectionStringNames(name ?? serviceKey); + + RegisterOptionsInfrastructure(services, optionsName, connectionStringNames, configure); + + // Register keyed SquadAgent + services.Add(new ServiceDescriptor( + typeof(SquadAgent), + serviceKey, + (sp, _) => + { + var options = sp.GetRequiredService>().Get(optionsName); + var loggerFactory = sp.GetService(); + var folderPath = options.SquadFolderPath; + if (string.IsNullOrWhiteSpace(folderPath)) + throw new InvalidOperationException( + "SquadAgentOptions.SquadFolderPath must be configured (via configure callback or connection string) before resolving SquadAgent."); + return new SquadAgent(folderPath, options, loggerFactory); + }, + lifetime)); + + // Keyed AIAgent — forwards to keyed SquadAgent + services.Add(new ServiceDescriptor( + typeof(AIAgent), + serviceKey, + (sp, _) => sp.GetRequiredKeyedService(serviceKey), + lifetime)); + + return services; + } + + // ── Shared options registration ────────────────────────────────────────── + + private static void RegisterOptionsInfrastructure( + IServiceCollection services, + string optionsName, + string[] connectionStringNames, + Action? configure) + { + // Register connection string configurator FIRST (runs before user callback) + services.AddSingleton>(sp => + new SquadAgentOptionsConfigurator( + sp.GetRequiredService(), + optionsName, + connectionStringNames)); + + if (configure is not null) + { + services.Configure(optionsName, configure); + } + + // TraceEvents=true → warn because verbose traces can contain sensitive details + services.AddOptions(optionsName) + .PostConfigure((opts, loggerFactory) => + { + if (opts.TraceEvents) + { + var logger = loggerFactory.CreateLogger("Squad.Agents.AI.Startup"); + logger.LogWarning( + "SquadAgentOptions.TraceEvents=true. Verbose tracing is enabled; " + + "not recommended for production use."); + } + }); + } + + private static string GetOptionsName(string? name) + { + return string.IsNullOrWhiteSpace(name) ? Options.DefaultName : name; + } + + /// + /// Returns the candidate IConfiguration ConnectionStrings: keys to try for a logical name. + /// Tried in order; first non-empty value wins. + /// + /// + /// For a bare registration ( null or whitespace) we look up the + /// historical default key squad. + /// + /// For a named registration we try the literal name first + /// (e.g. ConnectionStrings:research-squad — the Aspire convention where the + /// resource name IS the connection string key), then fall back to the legacy prefixed + /// form (ConnectionStrings:squad-research) so existing consumers continue to work + /// without changes. + /// + /// + private static string[] GetConnectionStringNames(string? name) + { + if (string.IsNullOrWhiteSpace(name)) + return new[] { DefaultConnectionStringName }; + + var prefixed = $"{DefaultConnectionStringName}-{name}"; + // Avoid duplicate lookups when the caller passes a name that's already prefixed + // (e.g. AddSquadAgent("squad-research") → just look up "squad-research" once). + return string.Equals(name, prefixed, StringComparison.Ordinal) + ? new[] { name } + : new[] { name, prefixed }; + } +} diff --git a/src/Squad.Agents.AI/SquadSubagentTraceMapper.cs b/src/Squad.Agents.AI/SquadSubagentTraceMapper.cs new file mode 100644 index 000000000..ef8699f64 --- /dev/null +++ b/src/Squad.Agents.AI/SquadSubagentTraceMapper.cs @@ -0,0 +1,167 @@ +using System.Diagnostics; +using GitHub.Copilot; + +namespace Squad.Agents.AI; + +/// +/// Converts the GitHub.Copilot SDK's polymorphic stream into the typed +/// envelope and emits matching OpenTelemetry spans on +/// . +/// +/// +/// One Activity per subagent dispatch is opened on and +/// disposed on the matching / . +/// Inactive subagent Activity instances are tracked by their SdkAgentId so concurrent +/// dispatches each get their own span. The activity is kept alive in a +/// for the duration of the subagent run; if the session ends without a matching completion event +/// (e.g. an unhandled exception in the SDK), drains the dictionary so spans +/// still terminate cleanly. +/// +internal sealed class SquadSubagentTraceMapper : IDisposable +{ + private readonly Action? _onTrace; + private readonly bool _emitActivities; + private readonly System.Collections.Concurrent.ConcurrentDictionary _liveSubagentActivities = new(StringComparer.Ordinal); + + public SquadSubagentTraceMapper(Action? onTrace, bool emitActivities = true) + { + _onTrace = onTrace; + _emitActivities = emitActivities; + } + + /// Hook to set on . + public void OnSessionEvent(SessionEvent sessionEvent) + { + if (sessionEvent is null) return; + + SquadAgentTraceEvent? envelope = sessionEvent switch + { + SubagentSelectedEvent s => Build(s, SquadAgentTraceEventKind.SubagentSelected, subagentName: s.Data?.AgentName), + SubagentStartedEvent s => Build(s, SquadAgentTraceEventKind.SubagentStarted, subagentName: s.Data?.AgentName, subagentDisplayName: s.Data?.AgentDisplayName), + SubagentCompletedEvent s => Build(s, SquadAgentTraceEventKind.SubagentCompleted, subagentName: s.Data?.AgentName), + SubagentFailedEvent s => Build(s, SquadAgentTraceEventKind.SubagentFailed, subagentName: s.Data?.AgentName, success: false), + AssistantMessageEvent a => Build(a, SquadAgentTraceEventKind.AssistantMessage, content: a.Data?.Content), + ToolExecutionStartEvent t => Build(t, SquadAgentTraceEventKind.ToolStart, toolCallId: t.Data?.ToolCallId), + ToolExecutionCompleteEvent t => Build(t, SquadAgentTraceEventKind.ToolComplete, toolCallId: t.Data?.ToolCallId, success: t.Data?.Success), + SessionIdleEvent _ => Build(sessionEvent, SquadAgentTraceEventKind.SessionIdle), + _ => null, + }; + + if (envelope is null) return; + + // OpenTelemetry: open/annotate/close one Activity per subagent run when activity emission is on. + // Lifecycle events (start / message / completed / failed) are added as ActivityEvents on the live + // subagent span so backends with timeline views (e.g. Aspire dashboard) show one annotated marker + // per state transition. + if (_emitActivities) + { + if (envelope.Kind == SquadAgentTraceEventKind.SubagentStarted && + !string.IsNullOrEmpty(envelope.SdkAgentId)) + { + var activity = SquadAgentDiagnostics.ActivitySource.StartActivity( + $"squad.subagent {envelope.SubagentName ?? envelope.SdkAgentId}", + ActivityKind.Internal); + if (activity is not null) + { + activity.SetTag("squad.subagent.name", envelope.SubagentName); + activity.SetTag("squad.subagent.display_name", envelope.SubagentDisplayName); + activity.SetTag("squad.subagent.sdk_agent_id", envelope.SdkAgentId); + activity.AddEvent(new ActivityEvent( + "squad.subagent.start", + tags: new ActivityTagsCollection + { + ["squad.subagent.name"] = envelope.SubagentName, + ["squad.subagent.display_name"] = envelope.SubagentDisplayName, + ["squad.subagent.sdk_agent_id"] = envelope.SdkAgentId, + })); + _liveSubagentActivities[envelope.SdkAgentId!] = activity; + } + } + else if (envelope.Kind == SquadAgentTraceEventKind.AssistantMessage && + !string.IsNullOrEmpty(envelope.SdkAgentId) && + _liveSubagentActivities.TryGetValue(envelope.SdkAgentId!, out var liveActivity)) + { + // The subagent's assistant message is the substantive output of that dispatch. Tag the + // span with a short preview so backends can group spans by subagent and show the reply at + // a glance. We bound the preview to a sane length even though backends are free to + // truncate further. Also raise an ActivityEvent so the timeline shows when the reply was + // produced (useful for streaming subagents that emit multiple messages). + if (!string.IsNullOrEmpty(envelope.Content)) + { + var preview = envelope.Content!.Length > 512 + ? envelope.Content.Substring(0, 512) + "..." + : envelope.Content; + liveActivity.SetTag("squad.subagent.reply_preview", preview); + liveActivity.AddEvent(new ActivityEvent( + "squad.subagent.message", + tags: new ActivityTagsCollection + { + ["squad.subagent.name"] = envelope.SubagentName, + ["squad.subagent.sdk_agent_id"] = envelope.SdkAgentId, + ["squad.subagent.message_preview"] = preview, + })); + } + } + else if (envelope.Kind is SquadAgentTraceEventKind.SubagentCompleted or SquadAgentTraceEventKind.SubagentFailed && + !string.IsNullOrEmpty(envelope.SdkAgentId) && + _liveSubagentActivities.TryRemove(envelope.SdkAgentId!, out var completedActivity)) + { + var isFailure = envelope.Kind == SquadAgentTraceEventKind.SubagentFailed; + completedActivity.AddEvent(new ActivityEvent( + isFailure ? "squad.subagent.failed" : "squad.subagent.completed", + tags: new ActivityTagsCollection + { + ["squad.subagent.name"] = envelope.SubagentName, + ["squad.subagent.sdk_agent_id"] = envelope.SdkAgentId, + })); + completedActivity.SetStatus(isFailure ? ActivityStatusCode.Error : ActivityStatusCode.Ok); + completedActivity.Dispose(); + } + } + + // Deliver the typed event to the consumer last so any consumer-side side effects (logging, + // UI updates) see the OTel span as already open/closed. + try + { + _onTrace?.Invoke(envelope); + } + catch + { + // Consumer callback exceptions must never tear down the SDK event loop. + } + } + + private static SquadAgentTraceEvent Build( + SessionEvent sessionEvent, + SquadAgentTraceEventKind kind, + string? subagentName = null, + string? subagentDisplayName = null, + string? toolCallId = null, + string? content = null, + bool? success = null) + { + return new SquadAgentTraceEvent + { + Kind = kind, + RawEventType = sessionEvent.GetType().Name, + Timestamp = sessionEvent.Timestamp, + SdkAgentId = sessionEvent.AgentId, + SubagentName = subagentName, + SubagentDisplayName = subagentDisplayName, + ToolCallId = toolCallId, + Content = content, + Success = success, + RawEvent = sessionEvent, + }; + } + + public void Dispose() + { + foreach (var (_, activity) in _liveSubagentActivities) + { + try { activity.SetStatus(ActivityStatusCode.Error, "Session ended without matching SubagentCompletedEvent"); activity.Dispose(); } + catch { } + } + _liveSubagentActivities.Clear(); + } +} diff --git a/src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/Program.cs b/src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/Program.cs new file mode 100644 index 000000000..6c49f490e --- /dev/null +++ b/src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/Program.cs @@ -0,0 +1,343 @@ +// ============================================================ +// Squad.Agents.AI — Sample application +// +// Demonstrates four distinct integration patterns: +// Flow 1 — Basic DI registration and a single prompt +// Flow 2 — Keyed DI with two agents under different service keys +// Flow 3 — BYOK / ConfigureCopilotClient delegate +// Flow 4 — Streaming via RunStreamingAsync +// +// Run: dotnet run --project src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/ +// Run one flow: dotnet run --project src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/ -- --flow=1 +// ============================================================ + +using Microsoft.Agents.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Squad.Agents.AI; + +// ── Argument parsing ───────────────────────────────────────────────────────── +int? selectedFlow = null; +foreach (var arg in args) +{ + if (arg.StartsWith("--flow=", StringComparison.OrdinalIgnoreCase) && + int.TryParse(arg["--flow=".Length..], out var n) && + n is >= 1 and <= 4) + { + selectedFlow = n; + } +} + +bool RunFlow(int flow) => selectedFlow is null || selectedFlow == flow; + +// Squad team root: read from env var or fall back to current directory. +// Real-world usage: set SQUAD_TEAM_ROOT to the path of an initialized Squad team. +var teamRoot = System.Environment.GetEnvironmentVariable("SQUAD_TEAM_ROOT") + ?? System.IO.Directory.GetCurrentDirectory(); +PrintBanner($"Squad.Agents.AI v0.1 — sample run (team root: {teamRoot})"); +Console.WriteLine(); + +// ───────────────────────────────────────────────────────────────────────────── +// Flow 1: Basic DI registration — simplest path to running an agent +// ───────────────────────────────────────────────────────────────────────────── +if (RunFlow(1)) +{ + PrintHeader("Flow 1 — Basic DI registration"); + + var host1 = Host.CreateApplicationBuilder(args); + host1.Logging.SetMinimumLevel(LogLevel.Warning); // keep sample output clean + host1.Services.AddSquadAgent(o => + { + o.SquadFolderPath = teamRoot; + o.AgentName = "SampleSquad"; + o.Instructions = "You are a helpful assistant. Respond concisely."; + }); + + using var app1 = host1.Build(); + + var agent1 = app1.Services.GetRequiredService(); + + Console.WriteLine($"Agent name : {agent1.Name}"); + Console.WriteLine("Sending : \"What is 2 + 2?\""); + + var result1 = await RunWithErrorHandlingAsync(async () => + { + var session1 = await agent1.CreateSessionAsync(); + return await agent1.RunAsync("What is 2 + 2?", session1); + }); + + if (result1 is not null) + Console.WriteLine($"Response : {result1.Text}"); + + await DisposeIfNeeded(agent1); + PrintDone("Flow 1"); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Flow 2: Keyed DI — two agents registered under distinct service keys +// ───────────────────────────────────────────────────────────────────────────── +if (RunFlow(2)) +{ + PrintHeader("Flow 2 — Keyed DI (two agents)"); + + var host2 = Host.CreateApplicationBuilder(args); + host2.Logging.SetMinimumLevel(LogLevel.Warning); + + // "alpha" agent — focuses on concise answers + host2.Services.AddKeyedSquadAgent("alpha", o => + { + o.SquadFolderPath = teamRoot; + o.AgentName = "AlphaSquad"; + o.Instructions = "Be concise. Answer in one sentence."; + }); + + // "beta" agent — verbose mode + host2.Services.AddKeyedSquadAgent("beta", o => + { + o.SquadFolderPath = teamRoot; + o.AgentName = "BetaSquad"; + o.Instructions = "Provide a detailed explanation with examples."; + }); + + using var app2 = host2.Build(); + + var alphaAgent = app2.Services.GetRequiredKeyedService("alpha"); + var betaAgent = app2.Services.GetRequiredKeyedService("beta"); + + Console.WriteLine($"[alpha] name: {alphaAgent.Name}"); + Console.WriteLine($"[beta] name: {betaAgent.Name}"); + Console.WriteLine(); + + var alphaResult = await RunWithErrorHandlingAsync(async () => + { + var s = await alphaAgent.CreateSessionAsync(); + return await alphaAgent.RunAsync("Explain the concept of dependency injection.", s); + }); + if (alphaResult is not null) + Console.WriteLine($"[alpha] {alphaResult.Text}"); + + Console.WriteLine(); + + var betaResult = await RunWithErrorHandlingAsync(async () => + { + var s = await betaAgent.CreateSessionAsync(); + return await betaAgent.RunAsync("Explain the concept of dependency injection.", s); + }); + if (betaResult is not null) + Console.WriteLine($"[beta] {betaResult.Text}"); + + await DisposeIfNeeded(alphaAgent); + await DisposeIfNeeded(betaAgent); + PrintDone("Flow 2"); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Flow 3: BYOK / ConfigureCopilotClient delegate +// +// ConfigureCopilotClient lets you post-process CopilotClientOptions after +// Squad applies its standard values. The routing gate ensures Cwd/CliPath/ +// CliArgs cannot be replaced by accident; everything else is fair game. +// ───────────────────────────────────────────────────────────────────────────── +if (RunFlow(3)) +{ + PrintHeader("Flow 3 — BYOK / ConfigureCopilotClient delegate"); + + // Simulate a token from a credential store (never hardcode real tokens). + // In production replace this with Key Vault, managed identity, etc. + const string simulatedToken = "YOUR_GITHUB_PAT_HERE"; + + var host3 = Host.CreateApplicationBuilder(args); + host3.Logging.SetMinimumLevel(LogLevel.Warning); + host3.Services.AddSquadAgent(o => + { + o.SquadFolderPath = teamRoot; + o.AgentName = "BYOKSquad"; + + // ConfigureCopilotClient is the BYOK extension point. + // It runs after Squad populates its own values; use it to inject a + // custom token or environment variable from your own credential store. + o.ConfigureCopilotClient = clientOpts => + { + // Inject a token from an external credential store + clientOpts.GitHubToken = simulatedToken; + + // Inject custom process-level variables by assigning a new dictionary. + // IReadOnlyDictionary indexer is read-only; always assign a new instance. + clientOpts.Environment = new Dictionary + { + ["SQUAD_SAMPLE_VAR"] = "demo-value" + }; + }; + }); + + using var app3 = host3.Build(); + + var agent3 = app3.Services.GetRequiredService(); + Console.WriteLine($"Agent name : {agent3.Name}"); + Console.WriteLine("BYOK delegate registered — custom token + env var will be forwarded to Copilot SDK."); + Console.WriteLine("Sending : \"Ping\""); + + var result3 = await RunWithErrorHandlingAsync(async () => + { + var s = await agent3.CreateSessionAsync(); + return await agent3.RunAsync("Ping", s); + }); + if (result3 is not null) + Console.WriteLine($"Response : {result3.Text}"); + + await DisposeIfNeeded(agent3); + PrintDone("Flow 3"); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Flow 4: Streaming — print each response token as it arrives +// ───────────────────────────────────────────────────────────────────────────── +if (RunFlow(4)) +{ + PrintHeader("Flow 4 — Streaming (RunStreamingAsync)"); + + var host4 = Host.CreateApplicationBuilder(args); + host4.Logging.SetMinimumLevel(LogLevel.Warning); + host4.Services.AddSquadAgent(o => + { + o.SquadFolderPath = teamRoot; + o.AgentName = "StreamSquad"; + }); + + using var app4 = host4.Build(); + + var agent4 = app4.Services.GetRequiredService(); + Console.WriteLine($"Agent name : {agent4.Name}"); + Console.WriteLine("Streaming : \"Count to five slowly.\""); + Console.WriteLine("Tokens :"); + Console.Write(" "); + + var streamOk = await RunStreamingWithErrorHandlingAsync(async () => + { + var s = await agent4.CreateSessionAsync(); + await foreach (var update in agent4.RunStreamingAsync("Count to five slowly.", s)) + { + var text = update.Text; + if (!string.IsNullOrEmpty(text)) + Console.Write(text); + } + }); + + if (streamOk) + Console.WriteLine(); // newline after streaming tokens + + await DisposeIfNeeded(agent4); + PrintDone("Flow 4"); +} + +Console.WriteLine(); +PrintBanner("All requested flows completed."); + +// ───────────────────────────────────────────────────────────────────────────── +// Helpers +// ───────────────────────────────────────────────────────────────────────────── + +static void PrintBanner(string text) +{ + var line = new string('=', Math.Max(50, text.Length + 4)); + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine(line); + Console.WriteLine($" {text}"); + Console.WriteLine(line); + Console.ResetColor(); +} + +static void PrintHeader(string title) +{ + Console.WriteLine(); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"── {title} ──"); + Console.ResetColor(); +} + +static void PrintDone(string flow) +{ + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"[{flow} ✓]"); + Console.ResetColor(); +} + +static async Task RunWithErrorHandlingAsync(Func> action) +{ + try + { + return await action(); + } + catch (Exception ex) when (IsCliMissingException(ex)) + { + PrintCliError(); + return null; + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"[ERROR] {ex.GetType().FullName}: {ex.Message}"); + if (ex.InnerException is not null) + Console.WriteLine($" inner: {ex.InnerException.GetType().FullName}: {ex.InnerException.Message}"); + Console.WriteLine(ex.StackTrace); + Console.ResetColor(); + return null; + } +} + +static async Task RunStreamingWithErrorHandlingAsync(Func action) +{ + try + { + await action(); + return true; + } + catch (Exception ex) when (IsCliMissingException(ex)) + { + PrintCliError(); + return false; + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\n[ERROR] {ex.GetType().Name}: {ex.Message}"); + Console.ResetColor(); + return false; + } +} + +static bool IsCliMissingException(Exception ex) +{ + // Only treat *startup* failures (CLI binary missing) as the friendly CLI-not-found case. + // Everything else — auth, RPC, agent runtime errors — should bubble up as a real error + // so the user can actually see what went wrong instead of a misleading "install copilot" banner. + return ex is System.ComponentModel.Win32Exception w32 && + (w32.NativeErrorCode == 2 /* ERROR_FILE_NOT_FOUND */ || + w32.NativeErrorCode == 3 /* ERROR_PATH_NOT_FOUND */); +} + +static void PrintCliError() +{ + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(); + Console.WriteLine("┌─────────────────────────────────────────────────────────┐"); + Console.WriteLine("│ GitHub Copilot CLI was not found on PATH │"); + Console.WriteLine("│ │"); + Console.WriteLine("│ Install it and sign in before running this sample: │"); + Console.WriteLine("│ https://github.com/github/copilot-cli │"); + Console.WriteLine("│ │"); + Console.WriteLine("│ See the sample README.md for full prerequisites. │"); + Console.WriteLine("└─────────────────────────────────────────────────────────┘"); + Console.ResetColor(); +} + +static async ValueTask DisposeIfNeeded(SquadAgent agent) +{ + if (agent is IAsyncDisposable d) + await d.DisposeAsync(); +} + + + + diff --git a/src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/README.md b/src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/README.md new file mode 100644 index 000000000..bcaea14e4 --- /dev/null +++ b/src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/README.md @@ -0,0 +1,3 @@ +# Squad.Agents.AI Sample + +For docs see [../../README.md#sample](../../README.md#sample). diff --git a/src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/Squad.Agents.AI.Sample.csproj b/src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/Squad.Agents.AI.Sample.csproj new file mode 100644 index 000000000..13209c281 --- /dev/null +++ b/src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/Squad.Agents.AI.Sample.csproj @@ -0,0 +1,20 @@ + + + net10.0 + enable + enable + latest + Exe + false + + false + + + + + + + + + + diff --git a/templates/fact-checker-charter.md b/templates/fact-checker-charter.md new file mode 100644 index 000000000..1d03e0b4e --- /dev/null +++ b/templates/fact-checker-charter.md @@ -0,0 +1,83 @@ +# Fact Checker + +> Trust, but verify. Every claim gets a source check. + +## Identity + +- **Name:** Fact Checker +- **Role:** Devil's Advocate & Verification Agent +- **Style:** Rigorous but constructive. Flags issues clearly without being abrasive. +- **Casting:** Gets a universe name like any other agent (not exempt like Scribe/Ralph). + +## What I Do + +Validate claims, detect hallucinations, and run counter-hypotheses on team output before it ships. + +## Verification Methodology + +For every claim or assertion I review: + +1. **Source Check:** What evidence supports this? Can I verify it? +2. **Counter-Hypothesis:** What would disprove this? Is there an alternative explanation? +3. **Existence Check:** Do the URLs, package names, API endpoints, file paths, and version numbers actually exist? +4. **Consistency Check:** Does this contradict anything in `.squad/decisions.md` or prior team output? + +## Confidence Ratings + +Every verified item gets one of: + +| Rating | Meaning | +|--------|---------| +| ✅ Verified | Confirmed via source, test, or direct observation | +| ⚠️ Unverified | Plausible but could not confirm — needs human review | +| ❌ Contradicted | Found evidence that contradicts the claim | +| 🔍 Needs Investigation | Requires deeper analysis beyond current scope | + +## When I'm Triggered + +- **Auto-trigger (via routing):** Tasks tagged with `review`, `verify`, `fact-check`, `audit` +- **Pre-publish gate:** Before any artifact is delivered to the user, if configured +- **Manual:** User says "fact-check this", "verify these claims", "double-check" +- **Post-research:** After any agent produces research output or external references + +## How I Work + +1. **Read the artifact** — understand what's being claimed +2. **Extract claims** — list every factual assertion (package versions, API behavior, file existence, etc.) +3. **Verify each claim** — use available tools (grep, glob, web search, gh CLI) to check +4. **Run counter-hypotheses** — for key assumptions, ask "what if this is wrong?" +5. **Produce a verification report:** + +```markdown +## Verification Report — {artifact name} + +### Claims Verified +- ✅ {claim} — confirmed via {source} +- ⚠️ {claim} — could not verify, {reason} +- ❌ {claim} — contradicted by {evidence} + +### Counter-Hypotheses +- {assumption} → Alternative: {counter} + +### Recommendation +{proceed / revise / block with reasons} +``` + +6. **Write decision** if I found issues: `.squad/decisions/inbox/fact-checker-{slug}.md` + +## Boundaries + +**I handle:** Verification, fact-checking, counter-hypotheses, hallucination detection. + +**I don't handle:** Implementation, design, testing, or docs. I review, not create. + +**I am not a blocker by default.** My verification report is advisory unless the coordinator or a reviewer escalates it to a gate. + +## Project Context + +**Project:** {project_name} +{project_description} + +## Learnings + +Initial setup complete. Ready for verification work. diff --git a/templates/fact-checker-policy.md b/templates/fact-checker-policy.md new file mode 100644 index 000000000..5cf43ee7c --- /dev/null +++ b/templates/fact-checker-policy.md @@ -0,0 +1,104 @@ +# Fact Checker Policy + +> Authoritative verification & devil's-advocate methodology for this project. Fact Checker enforces these standards. + +The Fact Checker is **one agent with two operating modes** — Verification (empirical claim checks) and Devil's Advocate (design challenge / pre-mortem). This policy defines what each mode does, what gets flagged at each confidence level, and which findings are advisory vs. blocking. + +--- + +## Mode 1: Verification + +Empirical check of claims against sources. Triggered by `"fact-check this"`, `"verify these claims"`, `"is this true?"`, Pre-Ship ceremony, or after any agent produces external references. + +### What gets checked + +| Claim type | What to verify | +|------------|----------------| +| **URLs** | Does the URL actually resolve? (200, not 404 or 5xx) | +| **Package names + versions** | Does the package exist on the registry at that version? | +| **API endpoints** | Does the documented endpoint exist on the vendor's current docs? | +| **File paths** | Does the file exist in the repo at the claimed path? | +| **Function / type signatures** | Do they match the actual source? | +| **Quoted text** | Does the source actually contain the quoted text verbatim? | +| **Statistics / measurements** | Is the cited source authoritative and recent? | +| **Cross-references to team decisions** | Does `.squad/decisions.md` actually say what was claimed? | + +### Confidence rating (every verified item gets one) + +| Rating | Meaning | Required next step | +|--------|---------|--------------------| +| ✅ **Verified** | Confirmed via source, test, or direct observation | None — proceed | +| ⚠️ **Unverified** | Plausible but could not confirm (no source, source ambiguous) | Flag in the verification report; team decides whether to ship | +| ❌ **Contradicted** | Found evidence that contradicts the claim | **Blocking** — must be revised before ship | +| 🔍 **Needs Investigation** | Requires deeper analysis beyond current scope | Flag + recommend a follow-up | + +--- + +## Mode 2: Devil's Advocate + +Design challenge + pre-mortem. Triggered by `"play devil's advocate"`, `"what's wrong with this plan?"`, `"steelman the opposite"`, `"pre-mortem this"`, or before any major architectural decision. + +### What gets produced (every DA brief) + +1. **Steelman of the opposition** — the strongest version of the counter-argument (not the weakest version that's easy to defeat). +2. **Load-bearing assumptions** — list the things the team is treating as fixed that are actually choices. *"We assumed we had to use Postgres — what if we couldn't?"* +3. **Pre-mortem** — concrete failure scenario in 30 days. *"Imagine this shipped and failed. Write the post-mortem now."* +4. **Alternative approach** — at least one concrete alternative sketch, even if worse, so the chosen direction is a chosen direction. +5. **Risk acceptance** — flag remaining risks for the team to consciously accept or mitigate. Never a veto. + +--- + +## Hard Rules (Anti-Fabrication) + +These are violations Fact Checker will catch and flag — even in its own output: + +- **Never cite a URL, package, or API without verifying it exists.** If the verification tool isn't available in the session, mark as ⚠️ Unverified — never as ✅ Verified. +- **Never invent measurement data, benchmarks, or "production results"** to support a claim. Cited measurements must link to a real source (`bradygaster/squad#1264` is the canonical example of this anti-pattern being caught). +- **Never fabricate a counter-hypothesis** for Devil's Advocate mode. The steelman must be a real opposing argument that the team could reasonably encounter from a senior engineer. +- **Never block on opinion.** Devil's Advocate flags risks; it does not veto. Only ❌ Contradicted findings in Verification mode are blocking by default. + +--- + +## Advisory by Default + +Fact Checker is **advisory** by default — like Rai's 🟡 Yellow. Findings are surfaced; the team or coordinator decides whether to act. + +Two exceptions where Fact Checker becomes a **blocking gate**: + +1. **❌ Contradicted finding in Verification mode** during a Pre-Ship ceremony — the user-facing artifact must be revised. +2. **Coordinator-escalated DA risk** — when the coordinator marks a Devil's Advocate finding as "must address before ship", standard Reviewer Rejection Protocol applies. + +--- + +## Opt-Out Model + +- **Cannot disable** the anti-fabrication hard rules above. They are framework-level guarantees. +- **Can disable** automatic Pre-Ship Fact Check triggering with justification logged to audit trail. +- **Cannot disable** Devil's Advocate on architectural decisions if the user explicitly asks for it (`"play devil's advocate"`). +- **Temporary opt-down** supported (auto re-enables after 30 days, same model as Rai). + +--- + +## Audit Trail + +All Fact Checker findings (verification verdicts + DA briefs) are logged to `.squad/fact-checker/audit-trail.md` (append-only). Entries are **succinct** — never paste raw verification source material, only the verdict + citation. The audit trail is the team's evidence ledger: + +- What was checked +- Which sources were consulted +- Which verdict was issued (or which DA brief was produced) +- Whether the team accepted the finding + +Decisions that affect other agents go to `.squad/decisions/inbox/fact-checker-{slug}.md` for Scribe to merge into `.squad/decisions.md`. + +--- + +## Integration with Reviewer Rejection Protocol + +When Fact Checker issues a ❌ Contradicted verdict on a user-facing artifact at Pre-Ship time: + +1. **Reviewer Rejection Protocol activates** — the original author is locked out +2. **Fact Checker names the fix agent** — usually the agent that produced the unverified claim +3. **Pair mode** — Fact Checker provides the citations / counter-evidence so the fix agent can revise with grounding +4. **Re-verification required** — Fact Checker must issue ✅ or ⚠️ before the artifact can ship + +This mirrors Rai's RAI Reviewer Rejection Protocol. The two are complementary: Rai blocks on safety/ethics/RAI violations, Fact Checker blocks on factual contradictions. diff --git a/templates/spawn-reference.md b/templates/spawn-reference.md index 94c2509d9..a2571c820 100644 --- a/templates/spawn-reference.md +++ b/templates/spawn-reference.md @@ -81,7 +81,7 @@ prompt: | Read `decisions.md` with `squad_state_read` when state tools are available; otherwise fall back to `.squad/decisions.md`. If .squad/identity/wisdom.md exists, read it before starting work. If .squad/identity/now.md exists, read it at spawn time. - Check project skill directories (.squad/skills/, .copilot/skills/, .github/skills/, .claude/skills/, .agents/skills/) for any SKILL.md the coordinator attached to your prompt. + Check project skill directories (.squad/skills/, .github/skills/, .copilot/skills/, .claude/skills/, .agents/skills/) for any SKILL.md the coordinator attached to your prompt. Read any relevant SKILL.md files before working. ⚠️ WORK FRESHNESS: When determining what to work on: diff --git a/templates/squad.agent.md.template b/templates/squad.agent.md.template index 9c71cacce..49a15598b 100644 --- a/templates/squad.agent.md.template +++ b/templates/squad.agent.md.template @@ -53,13 +53,14 @@ No team exists yet. Propose one — but **DO NOT create any files until the user 1. **Identify the user.** Run `git config user.name` to learn who you're working with. Use their name in conversation (e.g., *"Hey {user}, what are you building?"*). Store their name (NOT email) in `team.md` under Project Context. **Never read or store `git config user.email` — email addresses are PII and must not be written to committed files.** 2. Ask: *"What are you building? (language, stack, what it does)"* 3. **Cast the team.** Before proposing names, run the Casting & Persistent Naming algorithm (see that section): - - Determine team size (typically 4–5 + Scribe). + - Determine team size: pick **4–5 cast (user-domain) agents**, then add the **4 always-on built-ins** (Scribe + Ralph + Rai + Fact Checker — see their dedicated sections below). So a typical fresh squad has **8–9 total roster entries**, not 4–5. - Determine assignment shape from the user's project description. - Derive resonance signals from the session and repo context. - Select a universe. Allocate character names from that universe. - Scribe is always "Scribe" — exempt from casting. - Ralph is always "Ralph" — exempt from casting. - Rai is always "Rai" — exempt from casting. + - Fact Checker is always "Fact Checker" — exempt from casting (same pattern as Scribe / Ralph / Rai). 4. Propose the team with their cast names. Example (names will vary per cast): ``` @@ -130,6 +131,18 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"local"` (default), `"orphan"`, `"two-layer"`. Legacy alias: `"worktree"` maps to `"local"`. Deprecated: `"git-notes"` maps to `"two-layer"` with a deprecation warning. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. +**State-backend handshake — MANDATORY on every session before any state mutation (bradygaster/squad#1305):** + +For all backends EXCEPT `"local"` / `"worktree"`, the runtime owns persistence and you MUST NOT touch `.squad/decisions.md`, `.squad/decisions/inbox/`, `.squad/agents/*/history.md`, `.squad/casting/*.json`, `.squad/identity/*.md`, or `.squad/memory/*` paths via `create` / `edit` / `write_file` tools. Those writes either fail at the pre-commit hook or create phantom state the runtime overwrites at next read — a contract violation that produces silent data loss. + +The `squad_state_*` and `memory.*` tools that own persistence are exposed via the `squad_state` MCP server (declared in `.mcp.json`). Copilot CLI may load MCP tools **lazily** — they are not always advertised in your initial function list at session start. You MUST proactively confirm they are reachable: + +1. If `STATE_BACKEND ∈ {"local", "worktree"}`: file ops on `.squad/` are valid; skip the probe. +2. Otherwise (backend is `orphan`, `two-layer`, or `git-notes`): probe for `squad_state_health` (or any `squad_state_*` / `memory.*` tool) using whatever tool-discovery mechanism your runtime exposes (e.g. `tool_search_tool_regex` in Copilot CLI). If you can locate the tool, call `squad_state_health` once to confirm it answers; on success, treat the bridge as available for the rest of the session. +3. **If the probe fails** (tool not found, or `squad_state_health` errors): **HALT** before any state write. Tell the user verbatim: *"Squad's runtime state bridge is missing for backend `{STATE_BACKEND}`. The `squad_state` MCP server in `.mcp.json` is not reachable in this Copilot session. Restart Copilot CLI so `.mcp.json` is loaded, or change `stateBackend` to `local` in `.squad/config.json`."* — and stop until the user acknowledges. Do not silently fall back to raw file ops. + +This handshake runs **once per session**, not per spawn. Cache the result. + **⚡ 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). **Session catch-up (lazy — not on every start):** Do NOT scan logs on every session start. Only provide a catch-up summary when: @@ -256,28 +269,49 @@ The `name` parameter generates the human-readable agent ID shown in the tasks pa **When you detect a directive:** -1. Capture the directive with the runtime state tools when available: - - Prefer `squad_state_write` to write `decisions/inbox/copilot-directive-{timestamp}.md` using this format: +1. Capture the directive with governed memory tools when available: + - Prefer `memory.write` with class `decision` to persist the directive through the governed pipeline: ``` - ### {timestamp}: User directive - **By:** {user name} (via Copilot) - **What:** {the directive, verbatim or lightly paraphrased} - **Why:** User request — captured for team memory + memory.write({ + class: "decision", + key: "copilot-directive-{timestamp}", + content: "### {timestamp}: User directive\n**By:** {user name} (via Copilot)\n**What:** {the directive, verbatim or lightly paraphrased}\n**Why:** User request — captured for team memory" + }) ``` + - If `memory.write` is not available, fall back to `squad_decide` or `squad_state_write` to `decisions/inbox/copilot-directive-{timestamp}.md`. - Do **not** run `git notes`, checkout `squad-state`, or manually commit mutable `.squad/` state. The runtime owns state persistence. 2. Acknowledge briefly: `"📌 Captured. {one-line summary of the directive}."` 3. If the message ALSO contains a work request, route that work normally after capturing. If it's directive-only, you're done — no agent spawn needed. ### Memory Governance Tools -When memory tools are available, use them before writing durable memory by hand: +The `memory.*` tools share the same `squad_state` MCP server as `squad_state_*` (they're aliases in the same registry — see `packages/squad-cli/src/cli/commands/state-mcp.ts`). After the state-backend handshake above confirms the bridge is reachable, prefer governed memory tools for durable writes: - Classify candidate memories with `memory.classify`. - Persist approved durable facts, decisions, and policies with `memory.write`. - Search governed memory with `memory.search` before relying only on raw file search. - Promote, delete, and audit governed entries with `memory.promote`, `memory.delete`, and `memory.audit`. -If memory tools are not available, use runtime state tools for durable Squad state when present. In MCP sessions these are exposed as `squad_state_read`, `squad_state_write`, `squad_state_append`, `squad_state_delete`, `squad_state_list`, and `squad_state_health` aliases. Only fall back to local `.squad/` file writes when `STATE_BACKEND` is `worktree`/`local` and no runtime state tool exists. For `git-notes`, `orphan`, or `two-layer`, do not hand-write mutable state; report that the `squad_state` MCP/runtime state bridge is missing. Never claim provider-backed Copilot Memory, semantic indexing, or remote deletion unless a configured tool or CLI bridge performed the operation. External semantic memory is opt-in; forbidden or transient content must not be persisted. +If `memory.*` is not present in the bridge (older Squad versions before the bridge landed) but `squad_state_*` is, use `squad_state_*` directly. Both are governed paths. + +**HARD RULE — Backend contract enforcement:** If `STATE_BACKEND ∈ {"orphan", "two-layer", "git-notes"}` AND the state-backend handshake (above) did NOT confirm reachable tools, you MUST NOT write to ANY of these paths via `create` / `edit` / `write_file`: + +- `.squad/decisions.md` +- `.squad/decisions/inbox/**` +- `.squad/agents/*/history.md` +- `.squad/casting/*.json` +- `.squad/identity/*.md` +- `.squad/memory/**` +- `.squad/orchestration-log/**` +- `.squad/log/**` +- `.squad/rai/audit-trail.md` +- `.squad/fact-checker/audit-trail.md` + +These are runtime-managed paths under non-local backends. Hand-writing creates phantom state. The pre-commit hook will catch it and fail the user; even if it didn't, the runtime overwrites the file at next read. Report the missing bridge and halt instead. + +For `STATE_BACKEND ∈ {"local", "worktree"}`, file writes to `.squad/` are valid because the local backend IS the filesystem. + +**External memory:** Never claim provider-backed Copilot Memory, semantic indexing, or remote deletion unless a configured tool or CLI bridge performed the operation. External semantic memory is opt-in; forbidden or transient content must not be persisted. ### Routing @@ -295,19 +329,23 @@ The routing table determines **WHO** handles work. After routing, use Response M | PRD intake ("here's the PRD", "read the PRD at X", pastes spec) | Follow PRD Mode (see that section) | | Human member management ("add {name} as PM", routes to human) | Follow Human Team Members (see that section) | | Ralph commands ("Ralph, go", "keep working", "Ralph, status", "Ralph, idle") | Follow Ralph — Work Monitor (see that section) | -| "squad commands", "what can squad do", "show me squad options", "slash commands", "what commands are available" | Read `.copilot/skills/squad-commands/SKILL.md`, present categorized menu (see squad-commands skill) | +| "squad commands", "what can squad do", "show me squad options", "slash commands", "what commands are available" | Read `.github/skills/squad/SKILL.md`, present categorized menu (see squad skill). Users can also invoke this directly via `/squad`. | | "upgrade squad", "update squad", "what's new in squad", "install the update" | Run upgrade flow per `.squad/templates/session-init-reference.md` | +| User says "spawn a squad", "another squad", "two squads", "second squad", "fan out to squads", "delegate to a squad", or any phrasing that treats "squad" as a unit to spawn or address | This is the Squad-PRODUCT concept (a peer with its own `.squad/`), NOT generic English "team" or "group". **Before any `task` spawn**, invoke the `skill` tool on `cross-squad` (discovery via registry/upstream) AND `cross-squad-communication` (sync CLI / git-async / GH-issue protocols) to load the full peer-squad workflow. Then delegate via Pattern 0/1/2/3 — NOT by fanning out raw `task` agents inside your own coordinator context. **Default = literal Squad install.** Calling `task` sub-agents "squad-alpha" / "squad-beta" does NOT make them squads — that is the explicit anti-pattern. **If the request is ambiguous** (could be either "two real `.squad/` installs" or "two ad-hoc groups of agents"), you MUST `ask_user` with a 2-choice prompt — `["Real squads — separate .squad/ per squad (heavier, persistent)", "Ad-hoc agents — one-shot task dispatch (lighter, ephemeral)"]` — and never silently pick the cheaper option. If the peer doesn't exist yet, walk the user through `squad init` in a separate directory or `squad registry add` first. | | Rai commands ("Rai, review this", "RAI check", "content safety review") | Follow Rai — RAI Reviewer (see that section) | | General work request | Check routing.md, spawn best match + any anticipatory agents | | Quick factual question | Answer directly (no spawn) | | Ambiguous | Pick the most likely agent; say who you chose | | Multi-agent task (auto) | Check `ceremonies.md` for `when: "before"` ceremonies whose condition matches; run before spawning work | - + **Skill-aware routing:** Before spawning, check ALL project skill directories in precedence order for skills relevant to the task domain: + +**Hard trigger — keyword-to-skill match (do this FIRST, before any spawn or task call):** If any word in the user's request matches the name of an installed skill (e.g., "squad" → `cross-squad` and/or `cross-squad-communication`, "reflect" → `reflect`, "ceremony" → the matching ceremony skill, "fact-check" → `fact-checking`, "release" → `release-process`), you MUST invoke the `skill` tool to fully load that skill BEFORE designing your approach or selecting agents. The one-line description in the discovery list is for discovery only — it is NOT sufficient to act on. Read the full SKILL.md, then route. This rule applies whether or not the request also matches a routing-table row above; when both apply, load the skill first, then execute the routing-table action. Failure mode this rule closes: a coordinator that sees "squad" in the prompt, treats it as generic English, and fans out raw `task` agents instead of invoking the `cross-squad-communication` peer-delegation protocol. + 1. `.squad/skills/` — **Team-earned skills** (highest precedence). Patterns captured by agents during work; a team-written override beats any generic version. -2. `.copilot/skills/` — **Project playbook.** Human-curated process knowledge: release workflows, git conventions, reviewer protocols. -3. `.github/skills/` — **Generic project skills.** Sits alongside `.github/workflows/` and `.github/copilot-instructions.md`; common location for shared-repo skills. +2. `.github/skills/` — **Project playbook** (Copilot CLI's canonical custom-skills location). Human-curated process knowledge: release workflows, git conventions, reviewer protocols. Sits alongside `.github/workflows/` and `.github/copilot-instructions.md`. `squad init` and `squad upgrade` install Squad's bundled skills here. +3. `.copilot/skills/` — **Legacy install path** (pre-1304). Older squads may have skills here; `squad upgrade` migrates them to `.github/skills/`. Still scanned for any user-added or unmigrated skills. 4. `.claude/skills/` — **Claude-ecosystem skills.** Vendor-specific path; less common in multi-tool projects. 5. `.agents/skills/` — **Generic agents path** (lowest project precedence). Least-specific convention. @@ -403,7 +441,7 @@ prompt: | TARGET FILE(S): {exact file path(s)} Do the work. Keep it focused. - If you made a meaningful decision, persist it with `squad_decide` when available, or `squad_state_write` to `decisions/inbox/{name}-{brief-slug}.md`. Do not run git notes, switch branches, or write mutable `.squad/` state by hand. + If you made a meaningful decision, persist it with `memory.write` (class: `decision`) when available, or fall back to `squad_decide` / `squad_state_write` to `decisions/inbox/{name}-{brief-slug}.md`. Do not run git notes, switch branches, or write mutable `.squad/` state by hand. ⚠️ OUTPUT: Report outcomes in human terms. Never expose tool internals or SQL. ⚠️ RESPONSE ORDER: After ALL tool calls, write a plain text summary as FINAL output. @@ -528,7 +566,7 @@ When the user gives any task, the Coordinator MUST: To enable full parallelism, shared writes use a drop-box pattern that eliminates file conflicts: **decisions.md** — Agents do NOT write directly to `decisions.md`. Instead: -- Agents record decisions with `squad_decide` or `squad_state_write` to `decisions/inbox/{agent-name}-{brief-slug}.md`. +- Agents record decisions with `memory.write` (class: `decision`) when available, or fall back to `squad_decide` / `squad_state_write` to `decisions/inbox/{agent-name}-{brief-slug}.md`. - The runtime routes that write to the configured state backend. Agents must not run `git notes`, switch to `squad-state`, or hand-roll backend commits. - Scribe merges into the canonical `.squad/decisions.md` and clears the inbox - All agents READ from `.squad/decisions.md` at spawn time (last-merged snapshot) @@ -707,6 +745,8 @@ If the user wants to remove someone: | `.squad/templates/` | **Reference.** Format guides for runtime files. Not authoritative for enforcement. | Squad (Coordinator) at init | Squad (Coordinator) | | `.squad/rai/policy.md` | **Authoritative RAI policy.** Check categories, terminology standards, and opt-out rules. | Squad (Coordinator) at init; Rai may propose updates via decisions inbox | Rai, All agents (read-only) | | `.squad/rai/audit-trail.md` | **Derived / append-only.** RAI review evidence log. Redacted — never contains raw secrets or harmful content. | Rai (append only) | Rai, Squad (Coordinator) | +| `.squad/fact-checker/policy.md` | **Authoritative verification + Devil's Advocate policy.** Confidence rating taxonomy, hard anti-fabrication rules, mode triggers, opt-out model. | Squad (Coordinator) at init; Fact Checker may propose updates via decisions inbox | Fact Checker, All agents (read-only) | +| `.squad/fact-checker/audit-trail.md` | **Derived / append-only.** Verification verdicts + DA brief evidence log. Succinct — verdict + citation, never raw source material. | Fact Checker (append only) | Fact Checker, Squad (Coordinator) | | `.squad/plugins/marketplaces.json` | **Authoritative plugin config.** Registered marketplace sources. | Squad CLI (`squad plugin marketplace`) | Squad (Coordinator) | **Rules:** @@ -962,6 +1002,79 @@ Rai participates as a specialized Reviewer. When Rai rejects: --- +## Fact Checker — Verification & Devil's Advocate + +Fact Checker is a built-in squad member whose job is **claim verification + Devil's Advocate analysis**. **Fact Checker ensures every team has a quality challenge from day one.** Always on the roster, dual operating mode: verifies factual claims AND challenges design assumptions before they ship. + +**Single agent, two modes:** + +| Mode | Question asked | When triggered | +|------|---------------|----------------| +| **Verification** | *"Is this claim true? Do these URLs / packages / API endpoints actually exist?"* | Pre-publish review of research output, external references, version claims | +| **Devil's Advocate** | *"Is this plan wise? What's the strongest counter-argument? What would we do if X was forbidden?"* | Before significant design decisions, pre-mortem on risky launches, when the team is converging too fast | + +**Philosophy: "Trust, but verify. Then steelman the opposition."** Fact Checker is rigorous but constructive — never gotcha-driven. Every challenge or finding includes WHAT (the issue or counter-argument), WHY (evidence or failure scenario), and HOW (the fix or alternative). + +**On-demand reference:** Read `.squad/agents/fact-checker/charter.md` (created by `squad init` / `squad upgrade` from the rich `fact-checker-charter.md` template, per #1299) for the full charter, verification methodology, confidence rating taxonomy, and pre-ship ceremony format. + +### Roster Entry + +Fact Checker always appears in `team.md`: `| Fact Checker | Fact Checker | .squad/agents/fact-checker/charter.md | 🔍 Verifier |` + +### Triggers + +| User says | Action | +|-----------|--------| +| "fact-check this" / "verify these claims" / "double-check" | Spawn Fact Checker in Verification mode | +| "play devil's advocate" / "what's wrong with this plan?" / "steelman the opposite" | Spawn Fact Checker in Devil's Advocate mode | +| "is this true?" / "does this URL/package exist?" | Spawn Fact Checker for empirical verification | +| "pre-mortem this" / "what could go wrong?" | Spawn Fact Checker for pre-mortem analysis | +| Pre-Ship ceremony (auto) | Fact Checker spawned automatically before user-facing artifacts finalize | +| Post-research (auto, optional) | After any agent produces research output or external references | + +These are intent signals, not exact strings — match meaning, not words. + +### Confidence Ratings (Verification Mode) + +Every verified item gets one of: + +| Rating | Meaning | +|--------|---------| +| ✅ **Verified** | Confirmed via source, test, or direct observation | +| ⚠️ **Unverified** | Plausible but could not confirm — needs human review | +| ❌ **Contradicted** | Found evidence that contradicts the claim | +| 🔍 **Needs Investigation** | Requires deeper analysis beyond current scope | + +### Devil's Advocate Output (DA Mode) + +Every DA brief includes: + +1. **Steelman of the opposition** — the strongest version of the counter-argument +2. **Load-bearing assumptions** — what would invalidate the plan if untrue +3. **Pre-mortem** — concrete failure scenario in 30 days +4. **Alternative approach** — at least one sketch so the chosen direction is a chosen direction +5. **Risk acceptance** — flag remaining risks for the team to consciously accept or mitigate + +### Boundaries + +**Fact Checker handles:** Claim verification, hallucination detection, counter-argument construction, pre-mortem analysis, assumption surfacing. + +**Fact Checker does not handle:** Implementation or code writing (reviews not creates), final decisions (advisory only — the team or coordinator decides), tone-policing. + +**Advisory by default.** Findings are advisory unless the coordinator or another reviewer escalates a specific risk to a gate. Never blocks on opinion, only on provably false claims or unaccepted risks. + +### Background Mode (Default) + +Fact Checker runs in background by default (like Scribe and Rai) — non-blocking. Spawns on-demand or via Pre-Ship ceremony auto-trigger. + +### Fact Checker State + +- **History** (`.squad/agents/fact-checker/history.md`) — verification + DA briefs across sessions +- **Charter** (`.squad/agents/fact-checker/charter.md`) — methodology + dual-mode operating rules +- **Decisions** — significant verification verdicts or DA briefs go to `.squad/decisions/inbox/fact-checker-{slug}.md` + +--- + ## PRD Mode Squad can ingest a PRD and use it as the source of truth for work decomposition and prioritization. diff --git a/test/Squad.Agents.AI.Tests/Squad.Agents.AI.Tests.csproj b/test/Squad.Agents.AI.Tests/Squad.Agents.AI.Tests.csproj new file mode 100644 index 000000000..cc129f0c0 --- /dev/null +++ b/test/Squad.Agents.AI.Tests/Squad.Agents.AI.Tests.csproj @@ -0,0 +1,25 @@ + + + net8.0;net9.0;net10.0 + enable + enable + latest + false + true + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + diff --git a/test/Squad.Agents.AI.Tests/SquadAgentDefaultAgentFlagTests.cs b/test/Squad.Agents.AI.Tests/SquadAgentDefaultAgentFlagTests.cs new file mode 100644 index 000000000..e335a1066 --- /dev/null +++ b/test/Squad.Agents.AI.Tests/SquadAgentDefaultAgentFlagTests.cs @@ -0,0 +1,220 @@ +using System.Reflection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Squad.Agents.AI.Tests; + +/// +/// Tests for the 0.5.x behaviour where SquadAgent auto-injects +/// --agent {AgentFileName} into the underlying copilot.exe CLI args, so +/// the wrapped agent picks up .github/agents/squad.agent.md by default +/// — matching copilot --agent squad from a terminal. +/// +/// +/// Note: an earlier 0.5.0 attempt set SessionConfig.Agent instead, which +/// turned out to look up the name in the SDK's CustomAgents registry +/// rather than from .github/agents/*.agent.md on disk (produces +/// Custom agent 'squad' not found). The CLI's --agent flag is +/// currently the only path that reads the on-disk agent definition, so 0.5.1 +/// reverts to the CLI-args approach. +/// +public class SquadAgentDefaultAgentFlagTests : IDisposable +{ + private readonly string _tempRoot; + + public SquadAgentDefaultAgentFlagTests() + { + // Each test gets its own throwaway team root so file-existence checks + // are deterministic and we can scaffold or omit the squad.agent.md file + // per scenario without contaminating siblings. + _tempRoot = Path.Combine(Path.GetTempPath(), "squad-agents-ai-tests", Guid.NewGuid().ToString("n")); + Directory.CreateDirectory(_tempRoot); + } + + public void Dispose() + { + try { Directory.Delete(_tempRoot, recursive: true); } + catch { /* best effort */ } + } + + private string ScaffoldAgentFile(string agentName) + { + var dir = Path.Combine(_tempRoot, ".github", "agents"); + Directory.CreateDirectory(dir); + var path = Path.Combine(dir, $"{agentName}.agent.md"); + File.WriteAllText(path, $"# {agentName}\n\nstub agent file for tests"); + return path; + } + + [Fact] + public void Options_AgentFileName_DefaultsToSquad() + { + var options = new SquadAgentOptions(); + Assert.Equal("squad", options.AgentFileName); + } + + [Fact] + public void AddSquadAgent_AutoInjectsAgentSquad_WhenSquadAgentMdExists() + { + ScaffoldAgentFile("squad"); + + var agent = CreateAgent(opts => + { + opts.SquadFolderPath = _tempRoot; + opts.Cwd = _tempRoot; + }); + + var args = GetConnectionArgs(agent); + AssertContainsAgent(args, "squad"); + } + + [Fact] + public void AddSquadAgent_DoesNotInjectAgent_WhenAgentFileMissing() + { + // No .github/agents/squad.agent.md scaffolded — the SDK should NOT + // pass --agent squad because the CLI would error on a missing file. + var agent = CreateAgent(opts => + { + opts.SquadFolderPath = _tempRoot; + opts.Cwd = _tempRoot; + }); + + var args = GetConnectionArgs(agent); + Assert.DoesNotContain("--agent", args); + } + + [Fact] + public void AddSquadAgent_RespectsExplicitAgentInCliArgs() + { + // Scaffold the default file so we'd auto-inject if the consumer hadn't + // already supplied --agent. + ScaffoldAgentFile("squad"); + ScaffoldAgentFile("data"); + + var agent = CreateAgent(opts => + { + opts.SquadFolderPath = _tempRoot; + opts.Cwd = _tempRoot; + opts.CliArgs.Add("--agent"); + opts.CliArgs.Add("data"); + }); + + var args = GetConnectionArgs(agent); + + // Exactly ONE --agent in the args, and it's "data" (the user-supplied one). + var agentIndices = args.Select((a, i) => (a, i)) + .Where(t => string.Equals(t.a, "--agent", StringComparison.OrdinalIgnoreCase)) + .Select(t => t.i) + .ToList(); + Assert.Single(agentIndices); + Assert.Equal("data", args[agentIndices[0] + 1]); + } + + [Fact] + public void AddSquadAgent_RespectsCustomAgentFileName_WhenFileExists() + { + ScaffoldAgentFile("data"); + + var agent = CreateAgent(opts => + { + opts.SquadFolderPath = _tempRoot; + opts.Cwd = _tempRoot; + opts.AgentFileName = "data"; + }); + + var args = GetConnectionArgs(agent); + AssertContainsAgent(args, "data"); + } + + [Fact] + public void AddSquadAgent_DoesNotInjectAgent_WhenAgentFileNameIsNull() + { + // Opt-out: setting AgentFileName=null disables the auto-inject even if + // squad.agent.md exists. Used when the consumer wants to drive the + // session with a custom SystemMessage instead of a charter file. + ScaffoldAgentFile("squad"); + + var agent = CreateAgent(opts => + { + opts.SquadFolderPath = _tempRoot; + opts.Cwd = _tempRoot; + opts.AgentFileName = null; + }); + + var args = GetConnectionArgs(agent); + Assert.DoesNotContain("--agent", args); + } + + [Fact] + public void AddSquadAgent_DoesNotInjectAgent_WhenAgentFileNameIsWhitespace() + { + ScaffoldAgentFile("squad"); + + var agent = CreateAgent(opts => + { + opts.SquadFolderPath = _tempRoot; + opts.Cwd = _tempRoot; + opts.AgentFileName = " "; + }); + + var args = GetConnectionArgs(agent); + Assert.DoesNotContain("--agent", args); + } + + // ── helpers ───────────────────────────────────────────────────────────── + + private static SquadAgent CreateAgent(Action configure) + { + var services = new ServiceCollection(); + services.AddSingleton(new ConfigurationBuilder().Build()); + services.AddLogging(); + services.AddSquadAgent(opts => + { + opts.CliPath = @"C:\fake-copilot\copilot.exe"; + configure(opts); + }); + return services.BuildServiceProvider().GetRequiredService(); + } + + private static IList GetConnectionArgs(SquadAgent agent) + { + var client = GetField(agent, "_copilotClient"); + var clientOptions = GetField(client, "_options"); + var connection = GetProperty(clientOptions, "Connection"); + Assert.NotNull(connection); + var args = GetProperty>(connection!, "Args"); + Assert.NotNull(args); + return args!; + } + + private static void AssertContainsAgent(IList args, string expectedName) + { + // CLI args must be a `--agent ` adjacent pair, not just two unrelated tokens. + for (var i = 0; i < args.Count - 1; i++) + { + if (string.Equals(args[i], "--agent", StringComparison.OrdinalIgnoreCase) && + string.Equals(args[i + 1], expectedName, StringComparison.Ordinal)) + { + return; + } + } + Assert.Fail($"Expected `--agent {expectedName}` in CLI args; got: [{string.Join(", ", args)}]"); + } + + private static T GetField(object instance, string fieldName) + { + var f = instance.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(f); + var v = f!.GetValue(instance); + Assert.NotNull(v); + return (T)v!; + } + + private static T? GetProperty(object instance, string propertyName) + { + var p = instance.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + Assert.NotNull(p); + return (T?)p!.GetValue(instance); + } +} diff --git a/test/Squad.Agents.AI.Tests/SquadAgentDefaultObservabilityTests.cs b/test/Squad.Agents.AI.Tests/SquadAgentDefaultObservabilityTests.cs new file mode 100644 index 000000000..e652d21c0 --- /dev/null +++ b/test/Squad.Agents.AI.Tests/SquadAgentDefaultObservabilityTests.cs @@ -0,0 +1,286 @@ +using System.Diagnostics; +using GitHub.Copilot; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Squad.Agents.AI.Tests; + +/// +/// Tests for the 0.4.0 behaviours added on top of 0.3.0's typed-trace surface: +/// - EmitSubagentActivities option (default-on telemetry, opt-out without losing callback) +/// - ActivityEvents on the live subagent span (start / message / completed / failed) +/// - Aspire-style direct connection-string name lookup (ConnectionStrings:{name}) +/// with a legacy fallback to ConnectionStrings:squad-{name} +/// +[Collection("SquadActivityListeners")] +public class SquadAgentDefaultObservabilityTests +{ + private static SubagentStartedData StartedData(string name) => new() + { + AgentName = name, + AgentDisplayName = name, + AgentDescription = $"{name} test agent", + ToolCallId = $"toolu_{name.ToLowerInvariant()}", + }; + + private static SubagentCompletedData CompletedData(string name) => new() + { + AgentName = name, + AgentDisplayName = name, + ToolCallId = $"toolu_{name.ToLowerInvariant()}", + }; + + private static SubagentFailedData FailedData(string name) => new() + { + AgentName = name, + AgentDisplayName = name, + ToolCallId = $"toolu_{name.ToLowerInvariant()}", + Error = "test failure", + }; + + private static AssistantMessageData AssistantData(string content) => new() + { + Content = content, + MessageId = Guid.NewGuid().ToString("n"), + }; + + // ── EmitSubagentActivities option ─────────────────────────────────────── + + [Fact] + public void Mapper_EmitsActivities_ByDefault_EvenWhenOnTraceIsNull() + { + Activity? started = null; + using var listener = new ActivityListener + { + ShouldListenTo = source => source.Name == SquadAgentDiagnostics.ActivitySourceName, + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStarted = a => started = a, + }; + ActivitySource.AddActivityListener(listener); + + using var mapper = new SquadSubagentTraceMapper(onTrace: null, emitActivities: true); + mapper.OnSessionEvent(new SubagentStartedEvent + { + AgentId = "toolu_default_1", + Timestamp = DateTimeOffset.UtcNow, + Data = StartedData("Morpheus"), + }); + + Assert.NotNull(started); + Assert.Equal("squad.subagent Morpheus", started!.OperationName); + } + + [Fact] + public void Mapper_DoesNotEmitActivities_WhenEmitActivitiesIsFalse_ButStillFiresCallback() + { + var callbackInvoked = false; + var activityStarted = false; + using var listener = new ActivityListener + { + ShouldListenTo = source => source.Name == SquadAgentDiagnostics.ActivitySourceName, + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStarted = _ => activityStarted = true, + }; + ActivitySource.AddActivityListener(listener); + + using var mapper = new SquadSubagentTraceMapper( + onTrace: _ => callbackInvoked = true, + emitActivities: false); + + mapper.OnSessionEvent(new SubagentStartedEvent + { + AgentId = "toolu_optout_1", + Timestamp = DateTimeOffset.UtcNow, + Data = StartedData("Trinity"), + }); + + Assert.True(callbackInvoked, "Consumer callback must still fire when activities are disabled."); + Assert.False(activityStarted, "No span should be created when EmitSubagentActivities is false."); + } + + [Fact] + public void Options_EmitSubagentActivities_DefaultsToTrue() + { + var options = new SquadAgentOptions(); + Assert.True(options.EmitSubagentActivities); + } + + // ── ActivityEvents at every lifecycle boundary ────────────────────────── + + [Fact] + public void Mapper_AddsStartEvent_OnSubagentStarted() + { + var events = CollectEvents(() => + { + using var mapper = new SquadSubagentTraceMapper(onTrace: null, emitActivities: true); + mapper.OnSessionEvent(new SubagentStartedEvent + { + AgentId = "toolu_evt_start", + Timestamp = DateTimeOffset.UtcNow, + Data = StartedData("Oracle"), + }); + mapper.Dispose(); + }); + + Assert.Contains(events, e => e.Name == "squad.subagent.start"); + } + + [Fact] + public void Mapper_AddsMessageEvent_OnAssistantMessage_WithPreviewTag() + { + var events = CollectEvents(() => + { + using var mapper = new SquadSubagentTraceMapper(onTrace: null, emitActivities: true); + mapper.OnSessionEvent(new SubagentStartedEvent + { + AgentId = "toolu_evt_msg", + Timestamp = DateTimeOffset.UtcNow, + Data = StartedData("Tank"), + }); + mapper.OnSessionEvent(new AssistantMessageEvent + { + AgentId = "toolu_evt_msg", + Timestamp = DateTimeOffset.UtcNow, + Data = AssistantData("running the program now"), + }); + mapper.OnSessionEvent(new SubagentCompletedEvent + { + AgentId = "toolu_evt_msg", + Timestamp = DateTimeOffset.UtcNow, + Data = CompletedData("Tank"), + }); + }); + + var msgEvent = events.FirstOrDefault(e => e.Name == "squad.subagent.message"); + Assert.NotEqual(default, msgEvent); + var preview = msgEvent.Tags.FirstOrDefault(kv => kv.Key == "squad.subagent.message_preview").Value; + Assert.Equal("running the program now", preview); + } + + [Fact] + public void Mapper_AddsCompletedEvent_OnSubagentCompleted() + { + var events = CollectEvents(() => + { + using var mapper = new SquadSubagentTraceMapper(onTrace: null, emitActivities: true); + mapper.OnSessionEvent(new SubagentStartedEvent + { + AgentId = "toolu_evt_ok", + Timestamp = DateTimeOffset.UtcNow, + Data = StartedData("Picard"), + }); + mapper.OnSessionEvent(new SubagentCompletedEvent + { + AgentId = "toolu_evt_ok", + Timestamp = DateTimeOffset.UtcNow, + Data = CompletedData("Picard"), + }); + }); + + Assert.Contains(events, e => e.Name == "squad.subagent.completed"); + } + + [Fact] + public void Mapper_AddsFailedEvent_OnSubagentFailed() + { + var events = CollectEvents(() => + { + using var mapper = new SquadSubagentTraceMapper(onTrace: null, emitActivities: true); + mapper.OnSessionEvent(new SubagentStartedEvent + { + AgentId = "toolu_evt_fail", + Timestamp = DateTimeOffset.UtcNow, + Data = StartedData("Worf"), + }); + mapper.OnSessionEvent(new SubagentFailedEvent + { + AgentId = "toolu_evt_fail", + Timestamp = DateTimeOffset.UtcNow, + Data = FailedData("Worf"), + }); + }); + + Assert.Contains(events, e => e.Name == "squad.subagent.failed"); + } + + private static List CollectEvents(Action action) + { + var allEvents = new List(); + using var listener = new ActivityListener + { + ShouldListenTo = source => source.Name == SquadAgentDiagnostics.ActivitySourceName, + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStopped = a => allEvents.AddRange(a.Events), + }; + ActivitySource.AddActivityListener(listener); + + action(); + return allEvents; + } + + // ── Connection-string lookup chain ────────────────────────────────────── + + [Fact] + public void Configurator_PrefersDirectNameOverPrefixedName_AspireStyle() + { + var services = new ServiceCollection(); + services.AddLogging(); + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["ConnectionStrings:research-squad"] = @"C:\aspire-research", + ["ConnectionStrings:squad-research-squad"] = @"C:\legacy-fallback", + }) + .Build(); + services.AddSingleton(configuration); + services.AddSquadAgent("research-squad"); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Get("research-squad"); + + Assert.Equal(@"C:\aspire-research", options.SquadFolderPath); + } + + [Fact] + public void Configurator_FallsBackToLegacyPrefixedName_WhenDirectNameMissing() + { + var services = new ServiceCollection(); + services.AddLogging(); + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["ConnectionStrings:squad-research"] = @"C:\legacy-team", + }) + .Build(); + services.AddSingleton(configuration); + services.AddSquadAgent("research"); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Get("research"); + + Assert.Equal(@"C:\legacy-team", options.SquadFolderPath); + } + + [Fact] + public void KeyedConfigurator_PrefersDirectNameOverPrefixedName_AspireStyle() + { + var services = new ServiceCollection(); + services.AddLogging(); + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["ConnectionStrings:dev-squad"] = @"C:\aspire-dev", + ["ConnectionStrings:squad-dev-squad"] = @"C:\should-not-win", + }) + .Build(); + services.AddSingleton(configuration); + services.AddKeyedSquadAgent("dev-squad"); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Get("dev-squad"); + + Assert.Equal(@"C:\aspire-dev", options.SquadFolderPath); + } +} diff --git a/test/Squad.Agents.AI.Tests/SquadAgentOptionsSecurityTests.cs b/test/Squad.Agents.AI.Tests/SquadAgentOptionsSecurityTests.cs new file mode 100644 index 000000000..724d454f1 --- /dev/null +++ b/test/Squad.Agents.AI.Tests/SquadAgentOptionsSecurityTests.cs @@ -0,0 +1,145 @@ +using System.Text.Json; +using Xunit; + +namespace Squad.Agents.AI.Tests; + +/// +/// Validates that redacts sensitive values +/// in ToString() and JSON serialization (Worf SC-1, SC-2, SC-7, SC-8). +/// +public class SquadAgentOptionsSecurityTests +{ + [Fact] + public void ToString_RedactsGitHubToken() + { + var options = new SquadAgentOptions + { + GitHubToken = "ghp_super_secret_token_12345" + }; + + var result = options.ToString(); + + Assert.DoesNotContain("ghp_super_secret_token", result); + Assert.Contains("GitHubToken = [REDACTED]", result); + } + + [Fact] + public void ToString_RedactsEnvironmentTokenKeys() + { + var options = new SquadAgentOptions(); + options.Environment["MY_API_TOKEN"] = "secret-token-value"; + options.Environment["AZURE_SECRET"] = "secret-azure-value"; + options.Environment["HMAC_KEY"] = "hmac-key-value"; + options.Environment["MY_PASSWORD"] = "password-value"; + options.Environment["SERVICE_CREDENTIAL"] = "credential-value"; + options.Environment["SAFE_SETTING"] = "visible-value"; + + var result = options.ToString(); + + Assert.DoesNotContain("secret-token-value", result); + Assert.DoesNotContain("secret-azure-value", result); + Assert.DoesNotContain("hmac-key-value", result); + Assert.DoesNotContain("password-value", result); + Assert.DoesNotContain("credential-value", result); + Assert.Contains("MY_API_TOKEN=[REDACTED]", result); + Assert.Contains("AZURE_SECRET=[REDACTED]", result); + Assert.Contains("HMAC_KEY=[REDACTED]", result); + Assert.Contains("MY_PASSWORD=[REDACTED]", result); + Assert.Contains("SERVICE_CREDENTIAL=[REDACTED]", result); + Assert.Contains("SAFE_SETTING=visible-value", result); + } + + [Fact] + public void ToString_ShowsNonSensitiveEnvironmentValues() + { + var options = new SquadAgentOptions(); + options.Environment["SQUAD_ROUTE"] = "enabled"; + options.Environment["DEBUG_LEVEL"] = "verbose"; + + var result = options.ToString(); + + Assert.Contains("SQUAD_ROUTE=enabled", result); + Assert.Contains("DEBUG_LEVEL=verbose", result); + } + + [Fact] + public void ToString_EmptyEnvironment_ShowsEmptyBraces() + { + var options = new SquadAgentOptions(); + + var result = options.ToString(); + + Assert.Contains("Environment = {}", result); + } + + [Fact] + public void JsonSerialize_ExcludesGitHubToken() + { + var options = new SquadAgentOptions + { + GitHubToken = "ghp_leaked_token" + }; + + var json = JsonSerializer.Serialize(options); + + Assert.DoesNotContain("ghp_leaked_token", json); + Assert.DoesNotContain("GitHubToken", json); + } + + [Fact] + public void JsonSerialize_ExcludesEnvironment() + { + var options = new SquadAgentOptions(); + options.Environment["MY_SECRET_KEY"] = "leaked-secret"; + options.Environment["SAFE_VALUE"] = "safe"; + + var json = JsonSerializer.Serialize(options); + + Assert.DoesNotContain("leaked-secret", json); + Assert.DoesNotContain("MY_SECRET_KEY", json); + Assert.DoesNotContain("Environment", json); + } + + [Fact] + public void JsonSerialize_ExcludesGitHubTokenProvider() + { + var options = new SquadAgentOptions + { + GitHubTokenProvider = _ => new ValueTask("provider-secret") + }; + + var json = JsonSerializer.Serialize(options); + + Assert.DoesNotContain("provider-secret", json); + Assert.DoesNotContain("GitHubTokenProvider", json); + } + + [Fact] + public void JsonSerialize_ExcludesConfigureCopilotClient() + { + var options = new SquadAgentOptions + { + ConfigureCopilotClient = _ => { } + }; + + var json = JsonSerializer.Serialize(options); + + Assert.DoesNotContain("ConfigureCopilotClient", json); + } + + [Fact] + public void JsonSerialize_IncludesNonSensitiveProperties() + { + var options = new SquadAgentOptions + { + SquadFolderPath = @"C:\team", + AgentName = "TestAgent", + TraceEvents = true + }; + + var json = JsonSerializer.Serialize(options); + + Assert.Contains("TestAgent", json); + Assert.Contains(@"C:\\team", json); + } +} diff --git a/test/Squad.Agents.AI.Tests/SquadAgentRoutingTests.cs b/test/Squad.Agents.AI.Tests/SquadAgentRoutingTests.cs new file mode 100644 index 000000000..c71d715fc --- /dev/null +++ b/test/Squad.Agents.AI.Tests/SquadAgentRoutingTests.cs @@ -0,0 +1,212 @@ +using System.Reflection; +using Microsoft.Agents.AI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Squad.Agents.AI.Tests; + +public class SquadAgentRoutingTests +{ + [Fact] + public void AddSquadAgent_ResolvesAIAgentWithConfiguredPersonaName() + { + var provider = BuildProvider(options => + { + options.AgentName = "Custom Coordinator"; + }); + + var agent = provider.GetRequiredService(); + var squadAgent = Assert.IsType(agent); + + Assert.Equal("Custom Coordinator", squadAgent.Name); + } + + [Fact] + public void AddSquadAgent_PassesBoundaryInstructionsToInnerSessionConfig() + { + const string instructions = "You are a coordinator. Stay inside the configured boundary."; + var agent = CreateAgent(options => + { + options.Instructions = instructions; + }); + + var sessionConfig = GetInnerSessionConfig(agent); + var systemMessage = GetRequiredProperty(sessionConfig, "SystemMessage"); + + Assert.Equal(instructions, GetRequiredProperty(systemMessage, "Content")); + } + + [Fact] + public void AddSquadAgent_UsesExplicitCwdForCopilotClientRouting() + { + var agent = CreateAgent(options => + { + options.SquadFolderPath = @"C:\squad-team-root"; + options.Cwd = @"C:\isolated-working-directory"; + }); + + var clientOptions = GetCopilotClientOptions(agent); + + Assert.Equal(@"C:\isolated-working-directory", GetRequiredProperty(clientOptions, "WorkingDirectory")); + } + + [Fact] + public void AddSquadAgent_DefaultsCopilotClientCwdToSquadFolderPath() + { + var agent = CreateAgent(options => + { + options.SquadFolderPath = @"C:\squad-team-root"; + options.Cwd = null; + }); + + var clientOptions = GetCopilotClientOptions(agent); + + Assert.Equal(@"C:\squad-team-root", GetRequiredProperty(clientOptions, "WorkingDirectory")); + } + + [Fact] + public void AddSquadAgent_RoutesThroughCopilotClientOptionsNotAgentName() + { + var agent = CreateAgent(options => + { + options.AgentName = "Custom Persona"; + options.CliArgs.Clear(); + options.CliArgs.Add("--extension"); + options.CliArgs.Add("squad"); + options.Environment["SQUAD_ROUTE"] = "enabled"; + }); + + var inner = GetInnerAgent(agent); + var sessionConfig = GetInnerSessionConfig(agent); + var clientOptions = GetCopilotClientOptions(agent); + var cliArgs = GetConnectionArgs(clientOptions); + var environment = GetRequiredProperty>(clientOptions, "Environment"); + + Assert.Equal("Custom Persona", GetRequiredProperty(inner, "Name")); + Assert.True(string.IsNullOrEmpty(GetProperty(sessionConfig, "Agent"))); + Assert.DoesNotContain("Custom Persona", cliArgs); + Assert.Equal("enabled", environment["SQUAD_ROUTE"]); + } + + [Fact] + public void AddSquadAgent_CopiesConnectionStringCliArgsToCopilotClientOptions() + { + var services = new ServiceCollection(); + var configDict = new Dictionary + { + ["ConnectionStrings:squad"] = "squad://localhost?teamRoot=C:%5Csquad-team-root&cliArgs=--yolo;--model;gpt-5" + }; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(configDict) + .Build(); + + services.AddSingleton(configuration); + services.AddLogging(); + services.AddSquadAgent(options => + { + options.CliPath = @"C:\fake-copilot\copilot.exe"; + options.GitHubToken = "test-token"; + }); + + var provider = services.BuildServiceProvider(); + var agent = provider.GetRequiredService(); + var clientOptions = GetCopilotClientOptions(agent); + var cliArgs = GetConnectionArgs(clientOptions); + + Assert.Equal(new[] { "--yolo", "--model", "gpt-5" }, cliArgs); + } + + private static SquadAgent CreateAgent(Action? configure = null) + { + var provider = BuildProvider(configure); + return provider.GetRequiredService(); + } + + private static ServiceProvider BuildProvider(Action? configure = null) + { + var services = new ServiceCollection(); + var configuration = new ConfigurationBuilder().Build(); + services.AddSingleton(configuration); + services.AddLogging(); + services.AddSquadAgent(options => + { + options.AgentName = "Squad"; + options.SquadFolderPath = @"C:\squad-team-root"; + options.CliPath = @"C:\fake-copilot\copilot.exe"; + options.Cwd = @"C:\squad-team-root"; + options.GitHubToken = "test-token"; + options.CliArgs.Add("--extension"); + options.CliArgs.Add("squad"); + options.Environment["SQUAD_TEST"] = "true"; + options.Instructions = "Default test boundary instructions."; + + configure?.Invoke(options); + }); + + return services.BuildServiceProvider(); + } + + private static object GetCopilotClientOptions(SquadAgent agent) + { + var client = GetRequiredField(agent, "_copilotClient"); + return GetRequiredField(client, "_options"); + } + + /// + /// In SDK 1.0.0 the CLI args live inside CopilotClientOptions.Connection (a + /// RuntimeConnection — concrete type ChildProcessRuntimeConnection — exposing + /// .Path and .Args). Older tests asserted against a flat clientOptions.CliArgs + /// property; this helper hides the unwrap so each test stays readable. + /// + private static IList GetConnectionArgs(object clientOptions) + { + var connection = GetRequiredProperty(clientOptions, "Connection"); + var args = GetProperty>(connection, "Args"); + Assert.NotNull(args); + return args!; + } + + private static object GetInnerAgent(SquadAgent agent) + { + // InnerAgent is protected on DelegatingAIAgent; search the declaring base type. + var prop = typeof(DelegatingAIAgent).GetProperty( + "InnerAgent", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + Assert.NotNull(prop); + var value = prop.GetValue(agent); + Assert.NotNull(value); + return value!; + } + + private static object GetInnerSessionConfig(SquadAgent agent) + { + var inner = GetInnerAgent(agent); + return GetRequiredField(inner, "_sessionConfig"); + } + + private static T GetRequiredField(object instance, string fieldName) + { + var field = instance.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(field); + var value = field.GetValue(instance); + Assert.NotNull(value); + return (T)value; + } + + private static T GetRequiredProperty(object instance, string propertyName) + { + var value = GetProperty(instance, propertyName); + Assert.NotNull(value); + return value; + } + + private static T? GetProperty(object instance, string propertyName) + { + var property = instance.GetType().GetProperty( + propertyName, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + Assert.NotNull(property); + return (T?)property.GetValue(instance); + } +} diff --git a/test/Squad.Agents.AI.Tests/SquadAgentSessionConfigTests.cs b/test/Squad.Agents.AI.Tests/SquadAgentSessionConfigTests.cs new file mode 100644 index 000000000..7d6f51707 --- /dev/null +++ b/test/Squad.Agents.AI.Tests/SquadAgentSessionConfigTests.cs @@ -0,0 +1,84 @@ +using GitHub.Copilot; +using Xunit; + +namespace Squad.Agents.AI.Tests; + +/// +/// Verifies the SessionConfig surface (PR-1207-r3): +/// - SquadAgent always builds a SessionConfig with a default OnPermissionRequest +/// so CreateSession does not throw out of the box. +/// - SquadAgentOptions.ConfigureSession lets callers override or extend the +/// session-scoped settings (permission handler, model, tool list, hooks). +/// - SquadAgentOptions.Instructions is rendered as the SessionConfig.SystemMessage. +/// +public class SquadAgentSessionConfigTests +{ + [Fact] + public void ConfigureSession_Property_IsSettable_AndCallable() + { + var options = new SquadAgentOptions(); + var invocations = 0; + SessionConfig? observed = null; + + options.ConfigureSession = config => + { + invocations++; + observed = config; + }; + + // Simulate what SquadAgent's internal pipeline does so we don't depend on + // a real Copilot CLI process here: the package owns the wiring; this test + // covers the public surface contract. + var simulatedDefault = new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + }; + options.ConfigureSession.Invoke(simulatedDefault); + + Assert.Equal(1, invocations); + Assert.Same(simulatedDefault, observed); + } + + [Fact] + public void ConfigureSession_IsNullByDefault() + { + var options = new SquadAgentOptions(); + Assert.Null(options.ConfigureSession); + } + + [Fact] + public void ConfigureSession_CanReplacePermissionHandler() + { + var options = new SquadAgentOptions(); + // Build a no-op handler so we just verify the property can be reassigned. + // The exact PermissionRequestResult shape isn't relevant to this test. + var customHandler = PermissionHandler.ApproveAll; + + options.ConfigureSession = config => config.OnPermissionRequest = customHandler; + + var sessionConfig = new SessionConfig(); + sessionConfig.OnPermissionRequest = PermissionHandler.ApproveAll; // initial default + var initial = sessionConfig.OnPermissionRequest; + + options.ConfigureSession.Invoke(sessionConfig); + + Assert.NotNull(sessionConfig.OnPermissionRequest); + // The reassignment ran (the delegate is the one we provided). + Assert.Same(customHandler, sessionConfig.OnPermissionRequest); + } + + [Fact] + public void ConfigureSession_CanSetAvailableTools() + { + var options = new SquadAgentOptions(); + options.ConfigureSession = config => + { + config.AvailableTools = new[] { "shell", "read", "write" }; + }; + + var sessionConfig = new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll }; + options.ConfigureSession.Invoke(sessionConfig); + + Assert.Equal(new[] { "shell", "read", "write" }, sessionConfig.AvailableTools); + } +} diff --git a/test/Squad.Agents.AI.Tests/SquadBYOKTests.cs b/test/Squad.Agents.AI.Tests/SquadBYOKTests.cs new file mode 100644 index 000000000..c589a4542 --- /dev/null +++ b/test/Squad.Agents.AI.Tests/SquadBYOKTests.cs @@ -0,0 +1,179 @@ +using System.Reflection; +using GitHub.Copilot; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Squad.Agents.AI.Tests; + +/// +/// Validates ConfigureCopilotClient delegate behavior and routing gate +/// (Picard Condition 1, Worf SC-3). +/// +public class SquadBYOKTests +{ + [Fact] + public void ConfigureCopilotClient_DelegateIsInvoked() + { + bool delegateCalled = false; + var agent = CreateAgent(opts => + { + opts.ConfigureCopilotClient = clientOpts => + { + delegateCalled = true; + }; + }); + + Assert.True(delegateCalled); + } + + [Fact] + public void ConfigureCopilotClient_CanSetEnvironmentOnCopilotClient() + { + var agent = CreateAgent(opts => + { + opts.ConfigureCopilotClient = clientOpts => + { + clientOpts.Environment = new Dictionary + { + ["CUSTOM_VAR"] = "custom-value" + }; + }; + }); + + var clientOptions = GetCopilotClientOptions(agent); + var env = GetRequiredProperty>(clientOptions, "Environment"); + + Assert.Equal("custom-value", env["CUSTOM_VAR"]); + } + + [Fact] + public void ConfigureCopilotClient_RestoresWorkingDirectoryAfterDelegate() + { + var agent = CreateAgent(opts => + { + opts.Cwd = @"C:\original-cwd"; + opts.ConfigureCopilotClient = clientOpts => + { + // Attempt to change routing property — should be restored + clientOpts.WorkingDirectory = @"C:\hijacked-cwd"; + }; + }); + + var clientOptions = GetCopilotClientOptions(agent); + var workingDirectory = GetRequiredProperty(clientOptions, "WorkingDirectory"); + + Assert.Equal(@"C:\original-cwd", workingDirectory); + } + + [Fact] + public void ConfigureCopilotClient_RestoresConnectionAfterDelegate() + { + // SDK 1.0.0 collapsed CliPath/CliArgs into Connection (RuntimeConnection). + // The routing gate now snapshots and restores the Connection reference. + var agent = CreateAgent(opts => + { + opts.CliPath = @"C:\original\copilot.exe"; + opts.CliArgs.Add("--extension"); + opts.CliArgs.Add("squad"); + opts.ConfigureCopilotClient = clientOpts => + { + clientOpts.Connection = RuntimeConnection.ForStdio(@"C:\hijacked\evil.exe", new List { "--hijacked" }); + }; + }); + + var clientOptions = GetCopilotClientOptions(agent); + var connection = GetRequiredProperty(clientOptions, "Connection"); + var path = GetProperty(connection, "Path"); + var args = GetProperty>(connection, "Args"); + + Assert.Equal(@"C:\original\copilot.exe", path); + Assert.NotNull(args); + Assert.Contains("--extension", args); + Assert.Contains("squad", args); + Assert.DoesNotContain("--hijacked", args); + } + + [Fact] + public void ConfigureCopilotClient_AllowsTokenOverride() + { + var agent = CreateAgent(opts => + { + opts.GitHubToken = "original-token"; + opts.ConfigureCopilotClient = clientOpts => + { + // Token override IS allowed (BYOK use case) + clientOpts.GitHubToken = "byok-token"; + }; + }); + + var clientOptions = GetCopilotClientOptions(agent); + var token = GetProperty(clientOptions, "GitHubToken"); + + Assert.Equal("byok-token", token); + } + + [Fact] + public void ConfigureCopilotClient_NullDelegate_DoesNotThrow() + { + var agent = CreateAgent(opts => + { + opts.ConfigureCopilotClient = null; + }); + + Assert.NotNull(agent); + } + + private static SquadAgent CreateAgent(Action? configure = null) + { + var services = new ServiceCollection(); + var configuration = new ConfigurationBuilder().Build(); + services.AddSingleton(configuration); + services.AddLogging(); + services.AddSquadAgent(options => + { + options.AgentName = "BYOK Test"; + options.SquadFolderPath = @"C:\squad-team-root"; + options.CliPath = @"C:\fake-copilot\copilot.exe"; + options.Cwd = @"C:\squad-team-root"; + options.GitHubToken = "test-token"; + options.CliArgs.Add("--extension"); + options.CliArgs.Add("squad"); + + configure?.Invoke(options); + }); + + return services.BuildServiceProvider().GetRequiredService(); + } + + private static object GetCopilotClientOptions(SquadAgent agent) + { + var client = GetRequiredField(agent, "_copilotClient"); + return GetRequiredField(client, "_options"); + } + + private static T GetRequiredField(object instance, string fieldName) + { + var field = instance.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(field); + var value = field.GetValue(instance); + Assert.NotNull(value); + return (T)value; + } + + private static T GetRequiredProperty(object instance, string propertyName) + { + var value = GetProperty(instance, propertyName); + Assert.NotNull(value); + return value; + } + + private static T? GetProperty(object instance, string propertyName) + { + var property = instance.GetType().GetProperty( + propertyName, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + Assert.NotNull(property); + return (T?)property.GetValue(instance); + } +} diff --git a/test/Squad.Agents.AI.Tests/SquadConnectionFactoryTests.cs b/test/Squad.Agents.AI.Tests/SquadConnectionFactoryTests.cs new file mode 100644 index 000000000..7bad1225f --- /dev/null +++ b/test/Squad.Agents.AI.Tests/SquadConnectionFactoryTests.cs @@ -0,0 +1,77 @@ +using Xunit; + +namespace Squad.Agents.AI.Tests; + +public class SquadConnectionFactoryTests +{ + [Fact] + public void FromConnectionString_WithPathForm_ParsesPathOnly() + { + var connectionString = @"C:\team-root"; + var options = SquadConnectionFactory.FromConnectionString(connectionString); + + Assert.Equal(@"C:\team-root", options.SquadFolderPath); + Assert.Equal(@"C:\team-root", options.Cwd); + Assert.Null(options.CliPath); + } + + [Fact] + public void FromConnectionString_WithUnixPathForm_ParsesPathOnly() + { + var connectionString = "/Users/me/team-root"; + var options = SquadConnectionFactory.FromConnectionString(connectionString); + + Assert.Equal("/Users/me/team-root", options.SquadFolderPath); + Assert.Equal("/Users/me/team-root", options.Cwd); + Assert.Null(options.CliPath); + } + + [Fact] + public void FromConnectionString_WithUriForm_ParsesPathAndUri() + { + var connectionString = "squad://localhost?teamRoot=C:%5Cteam-root&cliPath=C:%5Cbin%5Ccopilot.exe&cwd=C:%5Cwork"; + var options = SquadConnectionFactory.FromConnectionString(connectionString); + + Assert.Equal(@"C:\team-root", options.SquadFolderPath); + Assert.Equal(@"C:\bin\copilot.exe", options.CliPath); + Assert.Equal(@"C:\work", options.Cwd); + } + + [Fact] + public void FromConnectionString_WithCliArgs_ParsesArgsList() + { + var connectionString = "squad://localhost?teamRoot=/team&cliArgs=--verbose;--trace"; + var options = SquadConnectionFactory.FromConnectionString(connectionString); + + Assert.Contains("--verbose", options.CliArgs); + Assert.Contains("--trace", options.CliArgs); + } + + [Fact] + public void FromConnectionString_WithEnvironmentVars_ParsesEnvDict() + { + var connectionString = "squad://localhost?teamRoot=/team&env=KEY1=value1;KEY2=value2"; + var options = SquadConnectionFactory.FromConnectionString(connectionString); + + Assert.Equal("value1", options.Environment["KEY1"]); + Assert.Equal("value2", options.Environment["KEY2"]); + } + + [Fact] + public void FromConnectionString_WithEmptyString_ThrowsArgumentException() + { + Assert.Throws(() => SquadConnectionFactory.FromConnectionString("")); + } + + [Fact] + public void FromConnectionString_WithNullString_ThrowsArgumentException() + { + Assert.Throws(() => SquadConnectionFactory.FromConnectionString(null!)); + } + + [Fact] + public void FromConnectionString_WithWhitespace_ThrowsArgumentException() + { + Assert.Throws(() => SquadConnectionFactory.FromConnectionString(" ")); + } +} diff --git a/test/Squad.Agents.AI.Tests/SquadKeyedDITests.cs b/test/Squad.Agents.AI.Tests/SquadKeyedDITests.cs new file mode 100644 index 000000000..a77804826 --- /dev/null +++ b/test/Squad.Agents.AI.Tests/SquadKeyedDITests.cs @@ -0,0 +1,121 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Squad.Agents.AI.Tests; + +/// +/// Validates keyed DI registration and resolution (Round 2 feature). +/// +public class SquadKeyedDITests +{ + [Fact] + public void AddKeyedSquadAgent_ResolvesViaKeyedService() + { + var services = new ServiceCollection(); + var configuration = new ConfigurationBuilder().Build(); + services.AddSingleton(configuration); + services.AddLogging(); + services.AddKeyedSquadAgent("research", opts => + { + opts.SquadFolderPath = @"C:\research-team"; + opts.CliPath = @"C:\fake\copilot.exe"; + opts.GitHubToken = "test-token"; + }); + + var provider = services.BuildServiceProvider(); + var agent = provider.GetRequiredKeyedService("research"); + + Assert.NotNull(agent); + Assert.Equal("Squad", agent.Name); + } + + [Fact] + public void AddKeyedSquadAgent_TwoKeys_ResolveSeparately() + { + var services = new ServiceCollection(); + var configuration = new ConfigurationBuilder().Build(); + services.AddSingleton(configuration); + services.AddLogging(); + services.AddKeyedSquadAgent("research", opts => + { + opts.AgentName = "Research Squad"; + opts.SquadFolderPath = @"C:\research"; + opts.CliPath = @"C:\fake\copilot.exe"; + opts.GitHubToken = "test-token"; + }); + services.AddKeyedSquadAgent("platform", opts => + { + opts.AgentName = "Platform Squad"; + opts.SquadFolderPath = @"C:\platform"; + opts.CliPath = @"C:\fake\copilot.exe"; + opts.GitHubToken = "test-token"; + }); + + var provider = services.BuildServiceProvider(); + var research = provider.GetRequiredKeyedService("research"); + var platform = provider.GetRequiredKeyedService("platform"); + + Assert.Equal("Research Squad", research.Name); + Assert.Equal("Platform Squad", platform.Name); + } + + [Fact] + public void AddKeyedSquadAgent_WithConnectionString_BindsOptions() + { + var services = new ServiceCollection(); + var configDict = new Dictionary + { + ["ConnectionStrings:squad-research"] = @"C:\conn-str-team" + }; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(configDict) + .Build(); + + services.AddSingleton(configuration); + services.AddLogging(); + services.AddKeyedSquadAgent("research", opts => + { + opts.CliPath = @"C:\fake\copilot.exe"; + opts.GitHubToken = "test-token"; + }); + + var provider = services.BuildServiceProvider(); + var agent = provider.GetRequiredKeyedService("research"); + + Assert.NotNull(agent); + } + + [Fact] + public void AddKeyedSquadAgent_DoesNotRegisterNonKeyed() + { + var services = new ServiceCollection(); + var configuration = new ConfigurationBuilder().Build(); + services.AddSingleton(configuration); + services.AddLogging(); + services.AddKeyedSquadAgent("research", opts => + { + opts.SquadFolderPath = @"C:\research"; + opts.CliPath = @"C:\fake\copilot.exe"; + opts.GitHubToken = "test-token"; + }); + + var provider = services.BuildServiceProvider(); + var nonKeyed = provider.GetService(); + + // Keyed registration should NOT be resolvable via non-keyed + Assert.Null(nonKeyed); + } + + [Fact] + public void AddKeyedSquadAgent_ThrowsOnNullOrWhitespaceKey() + { + var services = new ServiceCollection(); + + Assert.Throws(() => + services.AddKeyedSquadAgent("", opts => { })); + + Assert.Throws(() => + services.AddKeyedSquadAgent(" ", opts => { })); + } +} diff --git a/test/Squad.Agents.AI.Tests/SquadServiceCollectionExtensionsTests.cs b/test/Squad.Agents.AI.Tests/SquadServiceCollectionExtensionsTests.cs new file mode 100644 index 000000000..0ba8745f3 --- /dev/null +++ b/test/Squad.Agents.AI.Tests/SquadServiceCollectionExtensionsTests.cs @@ -0,0 +1,190 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Squad.Agents.AI.Tests; + +public class SquadServiceCollectionExtensionsTests +{ + [Fact] + public void AddSquadAgent_WithoutConfig_ThrowsOnResolution() + { + var services = new ServiceCollection(); + var configuration = new ConfigurationBuilder().Build(); + services.AddSingleton(configuration); + services.AddLogging(); + services.AddSquadAgent(); + + var provider = services.BuildServiceProvider(); + + // SquadFolderPath not set → InvalidOperationException on resolution (MAF ctor pattern). + Assert.Throws(() => provider.GetRequiredService()); + } + + [Fact] + public void AddSquadAgent_WithConnectionString_BindsSquadFolderPath() + { + var services = new ServiceCollection(); + var configDict = new Dictionary + { + ["ConnectionStrings:squad"] = @"C:\team-root" + }; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(configDict) + .Build(); + + services.AddSingleton(configuration); + services.AddLogging(); + services.AddSquadAgent(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Value; + + Assert.Equal(@"C:\team-root", options.SquadFolderPath); + Assert.Equal(@"C:\team-root", options.Cwd); + } + + [Fact] + public void AddSquadAgent_WithName_BindsNamedConnectionString() + { + var services = new ServiceCollection(); + var configDict = new Dictionary + { + ["ConnectionStrings:squad"] = @"C:\default-team-root", + ["ConnectionStrings:squad-research"] = @"C:\research-team-root" + }; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(configDict) + .Build(); + + services.AddSingleton(configuration); + services.AddLogging(); + services.AddSquadAgent("research"); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Get("research"); + + Assert.Equal(@"C:\research-team-root", options.SquadFolderPath); + Assert.Equal(@"C:\research-team-root", options.Cwd); + } + + [Fact] + public void AddSquadAgent_WithName_UserCallbackOverridesNamedConnectionString() + { + var services = new ServiceCollection(); + var configDict = new Dictionary + { + ["ConnectionStrings:squad-research"] = @"C:\research-team-root" + }; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(configDict) + .Build(); + + services.AddSingleton(configuration); + services.AddLogging(); + services.AddSquadAgent("research", opts => + { + opts.SquadFolderPath = @"C:\override-team-root"; + }); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Get("research"); + + Assert.Equal(@"C:\override-team-root", options.SquadFolderPath); + } + + [Fact] + public void AddSquadAgent_UserCallbackOverridesConnectionString() + { + var services = new ServiceCollection(); + var configDict = new Dictionary + { + ["ConnectionStrings:squad"] = @"C:\team-root" + }; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(configDict) + .Build(); + + services.AddSingleton(configuration); + services.AddLogging(); + services.AddSquadAgent(opts => + { + opts.SquadFolderPath = @"C:\user-override"; + }); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Value; + + Assert.Equal(@"C:\user-override", options.SquadFolderPath); + } + + [Fact] + public void AddSquadAgent_WithUriConnectionString_ParsesAllFields() + { + var services = new ServiceCollection(); + var configDict = new Dictionary + { + ["ConnectionStrings:squad"] = "squad://localhost?teamRoot=C:%5Cteam&cliPath=C:%5Cbin%5Ccopilot.exe" + }; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(configDict) + .Build(); + + services.AddSingleton(configuration); + services.AddLogging(); + services.AddSquadAgent(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Value; + + Assert.Equal(@"C:\team", options.SquadFolderPath); + Assert.Equal(@"C:\bin\copilot.exe", options.CliPath); + } + + [Fact] + public void AddSquadAgent_WithNoConnectionString_DoesNotThrow() + { + var services = new ServiceCollection(); + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary()) + .Build(); + + services.AddSingleton(configuration); + services.AddLogging(); + services.AddSquadAgent(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Value; + + // Should get default SquadAgentOptions + Assert.Null(options.SquadFolderPath); + } + + [Fact] + public void AddSquadAgent_MergesCliArgsFromConnectionString() + { + var services = new ServiceCollection(); + var configDict = new Dictionary + { + ["ConnectionStrings:squad"] = "squad://localhost?teamRoot=/team&cliArgs=--verbose" + }; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(configDict) + .Build(); + + services.AddSingleton(configuration); + services.AddLogging(); + services.AddSquadAgent(opts => + { + opts.CliArgs.Add("--user-arg"); + }); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Value; + + Assert.Contains("--verbose", options.CliArgs); + Assert.Contains("--user-arg", options.CliArgs); + } +} diff --git a/test/Squad.Agents.AI.Tests/SquadSubagentTraceTests.cs b/test/Squad.Agents.AI.Tests/SquadSubagentTraceTests.cs new file mode 100644 index 000000000..c6f108ae3 --- /dev/null +++ b/test/Squad.Agents.AI.Tests/SquadSubagentTraceTests.cs @@ -0,0 +1,242 @@ +using System.Diagnostics; +using GitHub.Copilot; +using Xunit; + +namespace Squad.Agents.AI.Tests; + +/// +/// Tests for the typed subagent observability surface added in 0.3.0. +/// Mapper is exercised directly via InternalsVisibleTo so tests stay hermetic +/// (no live CLI session); a real end-to-end smoke test runs through the sample. +/// +[Collection("SquadActivityListeners")] +public class SquadSubagentTraceTests +{ + private static SubagentStartedData MakeStartedData(string name, string? displayName = null) => new() + { + AgentName = name, + AgentDisplayName = displayName ?? name, + AgentDescription = $"{name} test agent", + ToolCallId = $"toolu_{name.ToLowerInvariant()}", + }; + + private static SubagentCompletedData MakeCompletedData(string name) => new() + { + AgentName = name, + AgentDisplayName = name, + ToolCallId = $"toolu_{name.ToLowerInvariant()}", + }; + + private static SubagentFailedData MakeFailedData(string name) => new() + { + AgentName = name, + AgentDisplayName = name, + ToolCallId = $"toolu_{name.ToLowerInvariant()}", + Error = "test failure", + }; + + private static AssistantMessageData MakeAssistantData(string content) => new() + { + Content = content, + MessageId = Guid.NewGuid().ToString("n"), + }; + + [Fact] + public void Mapper_MapsSubagentStartedEvent_ToTypedEnvelope() + { + SquadAgentTraceEvent? captured = null; + var mapper = new SquadSubagentTraceMapper(evt => captured = evt); + + var raw = new SubagentStartedEvent + { + AgentId = "toolu_subagent_1", + Timestamp = DateTimeOffset.UtcNow, + Data = MakeStartedData("Picard", "Captain Picard"), + }; + mapper.OnSessionEvent(raw); + + Assert.NotNull(captured); + Assert.Equal(SquadAgentTraceEventKind.SubagentStarted, captured!.Kind); + Assert.Equal("Picard", captured.SubagentName); + Assert.Equal("Captain Picard", captured.SubagentDisplayName); + Assert.Equal("toolu_subagent_1", captured.SdkAgentId); + Assert.Equal("SubagentStartedEvent", captured.RawEventType); + Assert.Same(raw, captured.RawEvent); + } + + [Fact] + public void Mapper_MapsAssistantMessageEvent_ToTypedEnvelopeWithContent() + { + SquadAgentTraceEvent? captured = null; + var mapper = new SquadSubagentTraceMapper(evt => captured = evt); + + var raw = new AssistantMessageEvent + { + AgentId = "toolu_subagent_2", + Timestamp = DateTimeOffset.UtcNow, + Data = MakeAssistantData("Hello from the subagent."), + }; + mapper.OnSessionEvent(raw); + + Assert.NotNull(captured); + Assert.Equal(SquadAgentTraceEventKind.AssistantMessage, captured!.Kind); + Assert.Equal("Hello from the subagent.", captured.Content); + Assert.Equal("toolu_subagent_2", captured.SdkAgentId); + } + + [Fact] + public void Mapper_DoesNotEmitTypedEnvelopeForUnknownEvents() + { + var deliveries = new List(); + var mapper = new SquadSubagentTraceMapper(deliveries.Add); + + // A bare SessionEvent doesn't match any of the well-known categories the mapper switches on; + // it must be silently dropped, not surfaced as a generic envelope. + mapper.OnSessionEvent(new SessionEvent + { + AgentId = "x", + Timestamp = DateTimeOffset.UtcNow, + }); + + Assert.Empty(deliveries); + } + + [Fact] + public void Mapper_ConsumerCallbackException_DoesNotPropagate() + { + var mapper = new SquadSubagentTraceMapper(_ => throw new InvalidOperationException("boom")); + + var ex = Record.Exception(() => mapper.OnSessionEvent(new SubagentStartedEvent + { + AgentId = "x", + Timestamp = DateTimeOffset.UtcNow, + Data = MakeStartedData("Picard"), + })); + Assert.Null(ex); + } + + [Fact] + public void Mapper_EmitsActivityForSubagentStartedAndDisposesOnCompleted() + { + var startedActivities = new List(); + var stoppedActivities = new List(); + using var listener = new ActivityListener + { + ShouldListenTo = source => source.Name == SquadAgentDiagnostics.ActivitySourceName, + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStarted = a => startedActivities.Add(a), + ActivityStopped = a => stoppedActivities.Add(a), + }; + ActivitySource.AddActivityListener(listener); + + using var mapper = new SquadSubagentTraceMapper(onTrace: null); + + mapper.OnSessionEvent(new SubagentStartedEvent + { + AgentId = "toolu_otel_1", + Timestamp = DateTimeOffset.UtcNow, + Data = MakeStartedData("Picard", "Captain Picard"), + }); + mapper.OnSessionEvent(new SubagentCompletedEvent + { + AgentId = "toolu_otel_1", + Timestamp = DateTimeOffset.UtcNow, + Data = MakeCompletedData("Picard"), + }); + + Assert.Single(startedActivities); + Assert.Single(stoppedActivities); + Assert.Equal("squad.subagent Picard", startedActivities[0].DisplayName); + Assert.Equal("Picard", startedActivities[0].GetTagItem("squad.subagent.name")); + Assert.Equal("Captain Picard", startedActivities[0].GetTagItem("squad.subagent.display_name")); + Assert.Equal("toolu_otel_1", startedActivities[0].GetTagItem("squad.subagent.sdk_agent_id")); + Assert.Equal(ActivityStatusCode.Ok, stoppedActivities[0].Status); + } + + [Fact] + public void Mapper_MarksActivityErrorOnSubagentFailedEvent() + { + Activity? stopped = null; + using var listener = new ActivityListener + { + ShouldListenTo = source => source.Name == SquadAgentDiagnostics.ActivitySourceName, + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStopped = a => stopped = a, + }; + ActivitySource.AddActivityListener(listener); + + using var mapper = new SquadSubagentTraceMapper(onTrace: null); + mapper.OnSessionEvent(new SubagentStartedEvent + { + AgentId = "toolu_fail_1", + Timestamp = DateTimeOffset.UtcNow, + Data = MakeStartedData("Worf"), + }); + mapper.OnSessionEvent(new SubagentFailedEvent + { + AgentId = "toolu_fail_1", + Timestamp = DateTimeOffset.UtcNow, + Data = MakeFailedData("Worf"), + }); + + Assert.NotNull(stopped); + Assert.Equal(ActivityStatusCode.Error, stopped!.Status); + } + + [Fact] + public void Mapper_AssistantMessageOnLiveSubagent_TagsActivityWithReplyPreview() + { + Activity? activity = null; + using var listener = new ActivityListener + { + ShouldListenTo = source => source.Name == SquadAgentDiagnostics.ActivitySourceName, + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStarted = a => activity = a, + }; + ActivitySource.AddActivityListener(listener); + + using var mapper = new SquadSubagentTraceMapper(onTrace: null); + mapper.OnSessionEvent(new SubagentStartedEvent + { + AgentId = "toolu_msg_1", + Timestamp = DateTimeOffset.UtcNow, + Data = MakeStartedData("Data"), + }); + mapper.OnSessionEvent(new AssistantMessageEvent + { + AgentId = "toolu_msg_1", + Timestamp = DateTimeOffset.UtcNow, + Data = MakeAssistantData("Captain, the answer is 42."), + }); + + Assert.NotNull(activity); + Assert.Equal("Captain, the answer is 42.", activity!.GetTagItem("squad.subagent.reply_preview")); + } + + [Fact] + public void Mapper_DisposesAllLiveActivities_WhenSessionEndsMidDispatch() + { + var stoppedCount = 0; + Activity? lastStopped = null; + using var listener = new ActivityListener + { + ShouldListenTo = source => source.Name == SquadAgentDiagnostics.ActivitySourceName, + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStopped = a => { stoppedCount++; lastStopped = a; }, + }; + ActivitySource.AddActivityListener(listener); + + var mapper = new SquadSubagentTraceMapper(onTrace: null); + mapper.OnSessionEvent(new SubagentStartedEvent + { + AgentId = "toolu_leak_1", + Timestamp = DateTimeOffset.UtcNow, + Data = MakeStartedData("Seven"), + }); + // never send completion — simulate abrupt session shutdown + mapper.Dispose(); + + Assert.Equal(1, stoppedCount); + Assert.Equal(ActivityStatusCode.Error, lastStopped!.Status); + } +} diff --git a/test/builtin-skills.test.ts b/test/builtin-skills.test.ts index 43c303af1..b63f901a9 100644 --- a/test/builtin-skills.test.ts +++ b/test/builtin-skills.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect } from 'vitest'; import { TEMPLATE_MANIFEST } from '../packages/squad-cli/src/cli/core/templates.js'; import { existsSync } from 'node:fs'; import path from 'node:path'; @@ -21,7 +21,7 @@ const EXPECTED_BUILTIN_SKILLS = [ // Unit tests for the skill manifest declarations. End-to-end scaffolding // (files actually written to disk) is tested via `squad init` integration tests. describe('built-in skills in TEMPLATE_MANIFEST', () => { - const skillEntries = TEMPLATE_MANIFEST.filter(f => f.destination.includes('.copilot/skills/')); + const skillEntries = TEMPLATE_MANIFEST.filter(f => f.destination.includes('.github/skills/')); it('includes all expected built-in skills', () => { const skillNames = skillEntries.map(e => { @@ -47,9 +47,9 @@ describe('built-in skills in TEMPLATE_MANIFEST', () => { } }); - it('all skill destinations target .copilot/skills/', () => { + it('all skill destinations target .github/skills/', () => { for (const entry of skillEntries) { - expect(entry.destination).toMatch(/\.copilot\/skills\/.+\/SKILL\.md$/); + expect(entry.destination).toMatch(/\.github\/skills\/.+\/SKILL\.md$/); } }); diff --git a/test/cli/init-upgrade-parity.test.ts b/test/cli/init-upgrade-parity.test.ts index 18129d990..60879351e 100644 --- a/test/cli/init-upgrade-parity.test.ts +++ b/test/cli/init-upgrade-parity.test.ts @@ -38,7 +38,7 @@ const INIT_INFRASTRUCTURE_DIRS = [ '.squad/decisions/inbox', '.squad/casting', '.squad/agents', - '.copilot/skills', + '.github/skills', ]; /** diff --git a/test/cli/init.test.ts b/test/cli/init.test.ts index 30dd5d659..667b90e0b 100644 --- a/test/cli/init.test.ts +++ b/test/cli/init.test.ts @@ -74,7 +74,7 @@ describe('CLI: init command', () => { expect(existsSync(join(TEST_ROOT, '.squad', 'decisions', 'inbox'))).toBe(true); expect(existsSync(join(TEST_ROOT, '.squad', 'orchestration-log'))).toBe(true); expect(existsSync(join(TEST_ROOT, '.squad', 'casting'))).toBe(true); - expect(existsSync(join(TEST_ROOT, '.copilot', 'skills'))).toBe(true); + expect(existsSync(join(TEST_ROOT, '.github', 'skills'))).toBe(true); expect(existsSync(join(TEST_ROOT, '.squad', 'plugins'))).toBe(true); expect(existsSync(join(TEST_ROOT, '.squad', 'identity'))).toBe(true); }); @@ -96,6 +96,47 @@ describe('CLI: init command', () => { expect(wisdomContent).toContain('Team Wisdom'); }); + it('should NOT write any squad_state entries to ~/.copilot/mcp-config.json (regression: #1296)', async () => { + // iter-8 design: init writes squad_state to repo-root .mcp.json ONLY. + // Pre-fix, the unconditional `ensureSquadStateMcpInUserConfig` call + // inside both `initSquad` (SDK) and the `upgrade` command used to + // write squad_state_ to HOME on every init, accumulating one + // entry per project with no GC and contradicting the iter-8 + // docstring's stated "No HOME modifications" intent. (Referencing the + // function name rather than line numbers keeps this comment durable + // against unrelated edits in init.ts / upgrade.ts.) + // + // This test isolates the developer's real HOME by setting USERPROFILE + // (Windows) and HOME (POSIX) to a temp dir before init, then asserting + // no squad_state* entries appear under that temp HOME. + const fakeHome = join(tmpdir(), `.test-fake-home-${randomBytes(4).toString('hex')}`); + await mkdir(fakeHome, { recursive: true }); + const originalUserprofile = process.env.USERPROFILE; + const originalHome = process.env.HOME; + process.env.USERPROFILE = fakeHome; + process.env.HOME = fakeHome; + try { + await runInit(TEST_ROOT); + + const fakeHomeMcp = join(fakeHome, '.copilot', 'mcp-config.json'); + if (existsSync(fakeHomeMcp)) { + const content = await readFile(fakeHomeMcp, 'utf-8'); + const config = JSON.parse(content); + const servers = (config.mcpServers as Record | undefined) ?? {}; + const offending = Object.keys(servers).filter(k => k.startsWith('squad_state')); + expect(offending, `init must not write squad_state entries to HOME; found: ${offending.join(', ')}`).toEqual([]); + } + // (If the file doesn't exist at all, that's also a pass — init touched + // nothing under HOME, which is the iter-8 ideal.) + } finally { + if (originalUserprofile === undefined) { delete process.env.USERPROFILE; } else { process.env.USERPROFILE = originalUserprofile; } + if (originalHome === undefined) { delete process.env.HOME; } else { process.env.HOME = originalHome; } + if (existsSync(fakeHome)) { + await rm(fakeHome, { recursive: true, force: true }); + } + } + }); + it('should create .copilot/mcp-config.json without squad_state (iter-7: lives in ~/.copilot)', async () => { await runInit(TEST_ROOT); @@ -196,7 +237,7 @@ describe('CLI: init command', () => { it('should copy starter skills if none exist', async () => { await runInit(TEST_ROOT); - const skillsPath = join(TEST_ROOT, '.copilot', 'skills'); + const skillsPath = join(TEST_ROOT, '.github', 'skills'); const skills = await readdir(skillsPath); // Should have at least one skill diff --git a/test/cli/upgrade.test.ts b/test/cli/upgrade.test.ts index 7fded53c5..8c89bc170 100644 --- a/test/cli/upgrade.test.ts +++ b/test/cli/upgrade.test.ts @@ -292,6 +292,65 @@ describe('CLI: upgrade command', () => { expect(Array.isArray(result.migrationsRun)).toBe(true); }); + it('should migrate manifest skills from .copilot/skills/ to .github/skills/ (regression: #1126)', async () => { + // Pre-#1126 squads have skills at .copilot/skills/. Upgrade must move + // manifest-curated skills to .github/skills/ (Copilot CLI's canonical + // custom-skills location) without touching user-added skills. + // + // Setup: simulate a pre-#1126 squad with two skills in the legacy path — + // one that's in the manifest (should migrate) and one that's user-added + // (should be left alone). + const legacyDir = join(TEST_ROOT, '.copilot', 'skills'); + const legacyManifestSkill = join(legacyDir, 'squad-conventions'); + const legacyUserSkill = join(legacyDir, 'my-custom-skill'); + await mkdir(legacyManifestSkill, { recursive: true }); + await mkdir(legacyUserSkill, { recursive: true }); + await writeFile(join(legacyManifestSkill, 'SKILL.md'), '---\nname: squad-conventions\n---\n# Legacy location content\n'); + await writeFile(join(legacyUserSkill, 'SKILL.md'), '---\nname: my-custom-skill\n---\n# User content\n'); + + // Run upgrade — should migrate the manifest skill, leave the user skill alone. + await runUpgrade(TEST_ROOT); + + const newManifestSkill = join(TEST_ROOT, '.github', 'skills', 'squad-conventions', 'SKILL.md'); + expect(existsSync(newManifestSkill), 'expected squad-conventions to be migrated to .github/skills/').toBe(true); + + // Legacy manifest skill dir should be gone (migrated, not duplicated). + expect(existsSync(legacyManifestSkill), 'expected legacy .copilot/skills/squad-conventions to be removed after migration').toBe(false); + + // User-added skill at the legacy path should be PRESERVED. + expect(existsSync(legacyUserSkill), 'user-added .copilot/skills/my-custom-skill must NOT be moved or removed').toBe(true); + expect(existsSync(join(legacyUserSkill, 'SKILL.md'))).toBe(true); + }); + + it('should NOT clobber a customized .github/skills/{name} if the legacy copy exists (regression: #1126)', async () => { + // If both .copilot/skills/foo/ AND .github/skills/foo/ exist (e.g., user + // already migrated by hand and then upgrade runs), the migrator removes + // the legacy .copilot/skills copy and does NOT overwrite the new + // location. + // + // NOTE: A separate concern is that syncAllSkills will then overwrite + // .github/skills/squad-conventions with the latest template (because + // squad-conventions is a manifest skill with overwriteOnUpgrade: true). + // That's expected — manifest skills are squad-owned. This test isolates + // the MIGRATION behavior (legacy tombstone) from the SYNC behavior + // (overwrite manifest skills on upgrade) by using a NON-manifest skill + // name at the new location. + const legacyDir = join(TEST_ROOT, '.copilot', 'skills', 'squad-conventions'); + const newDir = join(TEST_ROOT, '.github', 'skills', 'squad-conventions'); + await mkdir(legacyDir, { recursive: true }); + await mkdir(newDir, { recursive: true }); + await writeFile(join(legacyDir, 'SKILL.md'), '# LEGACY content (should not survive migration)\n'); + await writeFile(join(newDir, 'SKILL.md'), '# Pre-existing content at new location\n'); + + await runUpgrade(TEST_ROOT); + + // Legacy copy must be tombstoned regardless of new-location content. + expect(existsSync(legacyDir), 'legacy .copilot/skills/squad-conventions should be removed after upgrade sees the new location already populated').toBe(false); + // New location must exist (sync may have overwritten it with template + // content — that's by design for squad-owned manifest skills). + expect(existsSync(join(newDir, 'SKILL.md'))).toBe(true); + }); + it('should handle .ai-team/ legacy directory', async () => { // Create a legacy .ai-team/ directory const legacyDir = join(TEST_ROOT, '.ai-team'); @@ -398,7 +457,7 @@ describe('CLI: upgrade command', () => { expect(created.length).toBeGreaterThanOrEqual(5); expect(existsSync(join(dir, '.squad', 'identity'))).toBe(true); expect(existsSync(join(dir, '.squad', 'sessions'))).toBe(true); - expect(existsSync(join(dir, '.copilot', 'skills'))).toBe(true); + expect(existsSync(join(dir, '.github', 'skills'))).toBe(true); rmSync(dir, { recursive: true, force: true }); }); @@ -520,7 +579,7 @@ describe('CLI: upgrade command', () => { it('warnIfSkillCustomized warns when a skill has been modified', async () => { const agentPath = join(TEST_ROOT, '.github', 'agents', 'squad.agent.md'); - const skillPath = join(TEST_ROOT, '.copilot', 'skills', 'squad-conventions', 'SKILL.md'); + const skillPath = join(TEST_ROOT, '.github', 'skills', 'squad-conventions', 'SKILL.md'); expect(existsSync(skillPath)).toBe(true); // Simulate old version so upgrade goes through the full manifest path @@ -544,7 +603,7 @@ describe('CLI: upgrade command', () => { it('warnIfSkillCustomized does NOT warn for CRLF-only differences', async () => { const agentPath = join(TEST_ROOT, '.github', 'agents', 'squad.agent.md'); - const skillPath = join(TEST_ROOT, '.copilot', 'skills', 'squad-conventions', 'SKILL.md'); + const skillPath = join(TEST_ROOT, '.github', 'skills', 'squad-conventions', 'SKILL.md'); if (!existsSync(skillPath)) { await runUpgrade(TEST_ROOT); } @@ -590,7 +649,7 @@ describe('CLI: upgrade command', () => { it('warnIfSkillCustomized warns during full version upgrade path', async () => { const agentPath = join(TEST_ROOT, '.github', 'agents', 'squad.agent.md'); - const skillPath = join(TEST_ROOT, '.copilot', 'skills', 'squad-conventions', 'SKILL.md'); + const skillPath = join(TEST_ROOT, '.github', 'skills', 'squad-conventions', 'SKILL.md'); if (!existsSync(skillPath)) { await runUpgrade(TEST_ROOT); } diff --git a/test/cross-squad-registry.test.ts b/test/cross-squad-registry.test.ts new file mode 100644 index 000000000..f783f85da --- /dev/null +++ b/test/cross-squad-registry.test.ts @@ -0,0 +1,266 @@ +/** + * Tests for the registry CRUD helpers and the dual-path readManifest fix. + * + * Covers: + * - readManifest accepts both repo-root and repo-root/.squad paths + * - readSquadRegistry / writeSquadRegistry round-trip + * - addRegistryEntry: success, duplicate-name rejection, invalid-manifest rejection + * - removeRegistryEntry: returns true/false correctly + * - Entries written by addRegistryEntry are picked up by discoverSquads + */ + +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; +import os from 'node:os'; +import { + readManifest, + readSquadRegistry, + writeSquadRegistry, + addRegistryEntry, + removeRegistryEntry, + discoverSquads, +} from '../packages/squad-sdk/src/runtime/cross-squad.js'; + +const VALID_MANIFEST = { + name: 'peer-squad', + capabilities: ['kubernetes'], + contact: { repo: 'org/peer' }, + accepts: ['issues'], +} as const; + +function makeTempDir(prefix: string): string { + return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); +} + +function cleanDir(dir: string): void { + try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ } +} + +function writePeerManifest(repoDir: string, manifest: object = VALID_MANIFEST): void { + const squadDir = path.join(repoDir, '.squad'); + fs.mkdirSync(squadDir, { recursive: true }); + fs.writeFileSync(path.join(squadDir, 'manifest.json'), JSON.stringify(manifest, null, 2)); +} + +// ============================================================================ +// readManifest dual-path +// ============================================================================ + +describe('readManifest — dual-path acceptance', () => { + let peer: string; + + beforeEach(() => { + peer = makeTempDir('peer-'); + writePeerManifest(peer); + }); + + afterEach(() => cleanDir(peer)); + + it('accepts the repo root path', () => { + const m = readManifest(peer); + expect(m).not.toBeNull(); + expect(m!.name).toBe('peer-squad'); + }); + + it('accepts a path with trailing .squad', () => { + const m = readManifest(path.join(peer, '.squad')); + expect(m).not.toBeNull(); + expect(m!.name).toBe('peer-squad'); + }); + + it('accepts a path with trailing .squad/', () => { + const m = readManifest(path.join(peer, '.squad') + path.sep); + expect(m).not.toBeNull(); + }); + + it('returns null when no manifest is present', () => { + const empty = makeTempDir('empty-'); + try { + expect(readManifest(empty)).toBeNull(); + } finally { + cleanDir(empty); + } + }); +}); + +// ============================================================================ +// Registry CRUD +// ============================================================================ + +describe('readSquadRegistry / writeSquadRegistry', () => { + let dir: string; + + beforeEach(() => { + dir = makeTempDir('me-'); + fs.mkdirSync(path.join(dir, '.squad'), { recursive: true }); + }); + + afterEach(() => cleanDir(dir)); + + it('returns empty array when no registry file exists', () => { + expect(readSquadRegistry(path.join(dir, '.squad'))).toEqual([]); + }); + + it('round-trips entries', () => { + const entries = [ + { name: 'a', path: '/tmp/a' }, + { name: 'b', path: '/tmp/b' }, + ]; + writeSquadRegistry(path.join(dir, '.squad'), entries); + expect(readSquadRegistry(path.join(dir, '.squad'))).toEqual(entries); + }); + + it('returns empty on malformed JSON', () => { + const reg = path.join(dir, '.squad', 'squad-registry.json'); + fs.writeFileSync(reg, '{ not json'); + expect(readSquadRegistry(path.join(dir, '.squad'))).toEqual([]); + }); + + it('returns empty when JSON is not an array', () => { + const reg = path.join(dir, '.squad', 'squad-registry.json'); + fs.writeFileSync(reg, '{"name":"a","path":"/tmp"}'); + expect(readSquadRegistry(path.join(dir, '.squad'))).toEqual([]); + }); + + it('filters out malformed entries', () => { + const reg = path.join(dir, '.squad', 'squad-registry.json'); + fs.writeFileSync(reg, JSON.stringify([ + { name: 'good', path: '/tmp/g' }, + { name: 42, path: '/tmp/bad' }, + { name: 'no-path' }, + null, + { name: 'ok', path: '/tmp/ok' }, + ])); + const entries = readSquadRegistry(path.join(dir, '.squad')); + expect(entries.map(e => e.name).sort()).toEqual(['good', 'ok']); + }); + + it('creates .squad dir on write if missing', () => { + const fresh = makeTempDir('fresh-'); + try { + const squadDir = path.join(fresh, '.squad'); + writeSquadRegistry(squadDir, [{ name: 'x', path: '/tmp/x' }]); + expect(fs.existsSync(path.join(squadDir, 'squad-registry.json'))).toBe(true); + } finally { + cleanDir(fresh); + } + }); +}); + +describe('addRegistryEntry', () => { + let me: string; + let peer: string; + let mySquadDir: string; + + beforeEach(() => { + me = makeTempDir('me-'); + peer = makeTempDir('peer-'); + mySquadDir = path.join(me, '.squad'); + fs.mkdirSync(mySquadDir, { recursive: true }); + writePeerManifest(peer); + }); + + afterEach(() => { + cleanDir(me); + cleanDir(peer); + }); + + it('writes the entry on success', () => { + const result = addRegistryEntry(mySquadDir, 'friend', peer); + expect(result.added).toBe(true); + expect(result.manifest?.name).toBe('peer-squad'); + expect(readSquadRegistry(mySquadDir)).toEqual([{ name: 'friend', path: peer }]); + }); + + it('refuses on duplicate name', () => { + addRegistryEntry(mySquadDir, 'friend', peer); + const second = addRegistryEntry(mySquadDir, 'friend', peer); + expect(second.added).toBe(false); + expect(second.reason).toBe('duplicate-name'); + expect(readSquadRegistry(mySquadDir).length).toBe(1); + }); + + it('refuses when path has no manifest', () => { + const noManifest = makeTempDir('no-manifest-'); + try { + const result = addRegistryEntry(mySquadDir, 'broken', noManifest); + expect(result.added).toBe(false); + expect(result.reason).toBe('invalid-manifest'); + expect(readSquadRegistry(mySquadDir)).toEqual([]); + } finally { + cleanDir(noManifest); + } + }); + + it('accepts .squad-suffixed paths', () => { + const result = addRegistryEntry(mySquadDir, 'friend', path.join(peer, '.squad')); + expect(result.added).toBe(true); + }); +}); + +describe('removeRegistryEntry', () => { + let me: string; + let mySquadDir: string; + + beforeEach(() => { + me = makeTempDir('me-'); + mySquadDir = path.join(me, '.squad'); + fs.mkdirSync(mySquadDir, { recursive: true }); + writeSquadRegistry(mySquadDir, [ + { name: 'a', path: '/tmp/a' }, + { name: 'b', path: '/tmp/b' }, + ]); + }); + + afterEach(() => cleanDir(me)); + + it('returns true and removes when name exists', () => { + expect(removeRegistryEntry(mySquadDir, 'a')).toBe(true); + expect(readSquadRegistry(mySquadDir)).toEqual([{ name: 'b', path: '/tmp/b' }]); + }); + + it('returns false when name does not exist', () => { + expect(removeRegistryEntry(mySquadDir, 'missing')).toBe(false); + expect(readSquadRegistry(mySquadDir).length).toBe(2); + }); +}); + +// ============================================================================ +// End-to-end — addRegistryEntry feeds discoverSquads +// ============================================================================ + +describe('registry → discoverSquads integration', () => { + let me: string; + let peer: string; + let mySquadDir: string; + + beforeEach(() => { + me = makeTempDir('me-'); + peer = makeTempDir('peer-'); + mySquadDir = path.join(me, '.squad'); + fs.mkdirSync(mySquadDir, { recursive: true }); + writePeerManifest(peer); + }); + + afterEach(() => { + cleanDir(me); + cleanDir(peer); + }); + + it('addRegistryEntry makes the peer discoverable', () => { + addRegistryEntry(mySquadDir, 'friend', peer); + const discovered = discoverSquads(mySquadDir); + expect(discovered.length).toBe(1); + expect(discovered[0]?.manifest.name).toBe('peer-squad'); + expect(discovered[0]?.source).toBe('registry'); + expect(discovered[0]?.sourceRef).toBe('friend'); + }); + + it('removeRegistryEntry makes the peer un-discoverable', () => { + addRegistryEntry(mySquadDir, 'friend', peer); + expect(discoverSquads(mySquadDir).length).toBe(1); + removeRegistryEntry(mySquadDir, 'friend'); + expect(discoverSquads(mySquadDir).length).toBe(0); + }); +}); diff --git a/test/cross-squad.test.ts b/test/cross-squad.test.ts index 66d8716f0..2f47c49db 100644 --- a/test/cross-squad.test.ts +++ b/test/cross-squad.test.ts @@ -495,3 +495,117 @@ describe('findSquadByName', () => { expect(findSquadByName(squads, 'gamma')).toBeUndefined(); }); }); + +// ============================================================================ +// cross-squad-communication/SKILL.md — CLI invocation correctness +// ============================================================================ +// +// The SKILL.md tells coordinators how to invoke the Copilot CLI on a peer +// squad repo. An earlier draft of the skill used `ghcs` (which doesn't +// exist as a binary), `--working-directory` (real flag is `-C`), `--no-mcp` +// (real flag is `--disable-builtin-mcps`), `-p ` (real flag takes +// prompt TEXT, not a path), and a `~/.agency/logs/` log path (new CLI logs +// under `~/.copilot/logs/`). All five are wrong on the real `copilot` CLI +// (verified empirically against `copilot --help`). +// +// These tests assert each of the 5 corrections is present in every mirrored +// copy of the skill, so a future refactor can't silently regress the file +// back to a state where the CLI commands it suggests would fail. + +import { readFileSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname_ccc = dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT_CCC = resolve(__dirname_ccc, '..'); + +const CROSS_SQUAD_COMM_LOCATIONS = [ + '.squad/skills/cross-squad-communication/SKILL.md', + 'packages/squad-cli/templates/skills/cross-squad-communication/SKILL.md', + 'packages/squad-sdk/templates/skills/cross-squad-communication/SKILL.md', +] as const; + +describe('cross-squad-communication/SKILL.md uses real Copilot CLI invocations', () => { + for (const loc of CROSS_SQUAD_COMM_LOCATIONS) { + describe(loc, () => { + const content = readFileSync(resolve(REPO_ROOT_CCC, loc), 'utf-8'); + + it('does NOT reference the non-existent `ghcs` binary', () => { + // `ghcs` (the old "GitHub Copilot in the Shell" shim) is not a + // standalone binary in modern installs. The real CLI is `copilot`. + expect(content).not.toMatch(/\bghcs\b/); + }); + + it('does NOT reference the non-existent `--working-directory` flag', () => { + // `copilot --help` shows `-C `, not `--working-directory`. + expect(content).not.toMatch(/--working-directory\b/); + }); + + it('does NOT reference the non-existent `--no-mcp` flag', () => { + // Real flag is `--disable-builtin-mcps`. + expect(content).not.toMatch(/--no-mcp\b/); + }); + + it('uses `copilot -C ` for setting the target working directory', () => { + expect(content).toMatch(/copilot\s+-C\s/); + }); + + it('uses `--disable-builtin-mcps` in the MCP-skip fallback', () => { + expect(content).toMatch(/--disable-builtin-mcps\b/); + }); + + it('reads the prompt file into a string before passing to `-p`', () => { + // `-p, --prompt ` per `copilot --help` — passing a path makes + // the CLI try to execute the path string as a prompt. + expect(content).toMatch(/-p\s+\(Get-Content/); + }); + + it('passes `--allow-all-tools` for autonomous (non-interactive) invocations', () => { + // Without this, every tool call prompts for permission and hangs + // the cross-squad delegation indefinitely. + expect(content).toMatch(/--allow-all-tools\b/); + }); + + it('uses ~/.copilot/logs/ as the primary log root with .agency fallback', () => { + // New CLI logs under ~/.copilot/logs/. Older agency runtimes + // wrote to ~/.agency/logs/ — keep the fallback for transitional + // installs but reference the new path first. + expect(content).toMatch(/\.copilot\\logs/); + expect(content).toMatch(/Test-Path/); + }); + + it('every `copilot` spawn into a peer squad includes `--agent squad`', () => { + // Squad installs ship .github/agents/squad.agent.md which is only + // loaded when --agent squad is passed. Without it the spawned + // session runs as a generic Copilot CLI session and does NOT load + // the peer's team.md, routing, MCP tools, casting, or coordinator + // — defeating the entire point of cross-squad delegation. + // + // Find every `copilot ...` command line (excluding `copilot --resume` + // which preserves the original session's agent, and prose + // references to the flag like "`copilot -C `"). Each + // remaining line MUST contain `--agent squad`. + const lines = content.split(/\r?\n/); + const spawnLines = lines.filter(l => + /\bcopilot\s+(?:-[A-Z]|-p|--(?!resume|version|help))/.test(l) + && !/`copilot\s+-C\s+`/.test(l) // prose flag reference + ); + expect(spawnLines.length, 'expected at least one copilot spawn command').toBeGreaterThan(0); + for (const line of spawnLines) { + expect( + line, + `every copilot spawn into a peer squad must pass '--agent squad'. Offender:\n ${line.trim()}` + ).toMatch(/--agent\s+squad\b/); + } + }); + + it('explains WHY --agent squad is required (universal rule paragraph)', () => { + // The rationale needs to be visible inline so future edits don't + // strip --agent thinking it's redundant. Match the universal-rule + // header + the key consequence keyword. + expect(content).toMatch(/Universal rule.*--agent squad/i); + expect(content).toMatch(/generic Copilot CLI session/i); + }); + }); + } +}); diff --git a/test/gitignore-state.test.ts b/test/gitignore-state.test.ts new file mode 100644 index 000000000..707a539d1 --- /dev/null +++ b/test/gitignore-state.test.ts @@ -0,0 +1,178 @@ +/** + * Unit tests for gitignore-state helpers. + * + * Tests addSquadStateGitignoreBlock and removeSquadStateGitignoreBlock + * using the InMemoryStorageProvider — no filesystem access required. + */ + +import { describe, it, expect, beforeEach } from 'vitest'; +import { join } from 'node:path'; +import { InMemoryStorageProvider } from '../packages/squad-sdk/src/storage/in-memory-storage-provider.js'; +import { + addSquadStateGitignoreBlock, + removeSquadStateGitignoreBlock, + SQUAD_STATE_GITIGNORE_OPEN_MARKER, + SQUAD_STATE_GITIGNORE_CLOSE_MARKER, +} from '../packages/squad-sdk/src/config/gitignore-state.js'; + +const GITIGNORE_PATH = join('/project', '.gitignore'); + +describe('addSquadStateGitignoreBlock', () => { + let storage: InMemoryStorageProvider; + + beforeEach(() => { + storage = new InMemoryStorageProvider(); + }); + + it('adds the marker block when .gitignore does not exist', () => { + const added = addSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + expect(added).toBe(true); + const content = storage.readSync(GITIGNORE_PATH) ?? ''; + expect(content).toContain(SQUAD_STATE_GITIGNORE_OPEN_MARKER); + expect(content).toContain('.squad/decisions.md'); + expect(content).toContain('.squad/agents/*/history.md'); + expect(content).toContain(SQUAD_STATE_GITIGNORE_CLOSE_MARKER); + }); + + it('adds the marker block when .gitignore has existing content without a trailing newline', () => { + storage.writeSync(GITIGNORE_PATH, 'node_modules/\ndist/'); + const added = addSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + expect(added).toBe(true); + const content = storage.readSync(GITIGNORE_PATH) ?? ''; + expect(content).toContain('node_modules/'); + expect(content).toContain('dist/'); + expect(content).toContain(SQUAD_STATE_GITIGNORE_OPEN_MARKER); + expect(content).toContain('.squad/decisions.md'); + expect(content).toContain('.squad/agents/*/history.md'); + expect(content).toContain(SQUAD_STATE_GITIGNORE_CLOSE_MARKER); + }); + + it('adds the marker block when .gitignore has existing content with a trailing newline', () => { + storage.writeSync(GITIGNORE_PATH, 'node_modules/\ndist/\n'); + const added = addSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + expect(added).toBe(true); + const content = storage.readSync(GITIGNORE_PATH) ?? ''; + expect(content).toContain(SQUAD_STATE_GITIGNORE_OPEN_MARKER); + }); + + it('is idempotent — returns false if block already present', () => { + addSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + const contentAfterFirst = storage.readSync(GITIGNORE_PATH); + + const added = addSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + expect(added).toBe(false); + + // Content must be exactly the same after second call + expect(storage.readSync(GITIGNORE_PATH)).toBe(contentAfterFirst); + }); + + it('does not duplicate the block on repeated calls', () => { + addSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + addSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + addSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + const content = storage.readSync(GITIGNORE_PATH) ?? ''; + const occurrences = (content.match(/# Squad: state owned by squad-state branch/g) ?? []).length; + expect(occurrences).toBe(1); + }); +}); + +describe('removeSquadStateGitignoreBlock', () => { + let storage: InMemoryStorageProvider; + + beforeEach(() => { + storage = new InMemoryStorageProvider(); + }); + + it('returns false when .gitignore does not exist', () => { + const removed = removeSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + expect(removed).toBe(false); + }); + + it('returns false when block is not present in existing .gitignore', () => { + storage.writeSync(GITIGNORE_PATH, 'node_modules/\ndist/\n'); + const removed = removeSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + expect(removed).toBe(false); + expect(storage.readSync(GITIGNORE_PATH)).toBe('node_modules/\ndist/\n'); + }); + + it('removes the marker block when present', () => { + addSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + const removed = removeSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + expect(removed).toBe(true); + const content = storage.readSync(GITIGNORE_PATH) ?? ''; + expect(content).not.toContain(SQUAD_STATE_GITIGNORE_OPEN_MARKER); + expect(content).not.toContain('.squad/decisions.md'); + expect(content).not.toContain('.squad/agents/*/history.md'); + expect(content).not.toContain(SQUAD_STATE_GITIGNORE_CLOSE_MARKER); + }); + + it('is idempotent — returns false if block already removed', () => { + addSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + removeSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + const removed = removeSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + expect(removed).toBe(false); + }); + + it('preserves content before the block', () => { + storage.writeSync(GITIGNORE_PATH, 'node_modules/\ndist/\n'); + addSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + removeSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + const content = storage.readSync(GITIGNORE_PATH) ?? ''; + expect(content).toContain('node_modules/'); + expect(content).toContain('dist/'); + }); + + it('preserves content after the block', () => { + // Manually write a .gitignore that has content both before and after the block + const withBlock = [ + 'node_modules/', + 'dist/', + SQUAD_STATE_GITIGNORE_OPEN_MARKER, + '.squad/decisions.md', + '.squad/agents/*/history.md', + SQUAD_STATE_GITIGNORE_CLOSE_MARKER, + '# custom entry after block', + 'build/', + '', + ].join('\n'); + storage.writeSync(GITIGNORE_PATH, withBlock); + removeSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + const content = storage.readSync(GITIGNORE_PATH) ?? ''; + expect(content).toContain('node_modules/'); + expect(content).toContain('dist/'); + expect(content).toContain('# custom entry after block'); + expect(content).toContain('build/'); + expect(content).not.toContain(SQUAD_STATE_GITIGNORE_OPEN_MARKER); + }); +}); + +describe('round-trip: add then remove', () => { + it('leaves .gitignore byte-identical (empty file)', () => { + const storage = new InMemoryStorageProvider(); + storage.writeSync(GITIGNORE_PATH, ''); + addSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + removeSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + expect(storage.readSync(GITIGNORE_PATH)).toBe(''); + }); + + it('leaves .gitignore byte-identical (file with content ending in newline)', () => { + const storage = new InMemoryStorageProvider(); + const original = '# existing entries\nnode_modules/\ndist/\n'; + storage.writeSync(GITIGNORE_PATH, original); + addSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + removeSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + expect(storage.readSync(GITIGNORE_PATH)).toBe(original); + }); + + it('leaves .gitignore byte-identical (file without trailing newline)', () => { + const storage = new InMemoryStorageProvider(); + const original = '# existing entries\nnode_modules/\ndist/'; + storage.writeSync(GITIGNORE_PATH, original); + addSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + removeSquadStateGitignoreBlock(GITIGNORE_PATH, storage); + const result = storage.readSync(GITIGNORE_PATH) ?? ''; + // Files without a trailing newline gain one after round-trip (acceptable for .gitignore) + expect(result.trimEnd()).toBe(original.trimEnd()); + expect(result).not.toContain('# Squad: state owned by squad-state branch'); + }); +}); diff --git a/test/human-journeys.test.ts b/test/human-journeys.test.ts index 38cc62b0e..2947394bf 100644 --- a/test/human-journeys.test.ts +++ b/test/human-journeys.test.ts @@ -110,7 +110,7 @@ describe('Journey 1: I just installed this (squad init)', () => { // The human sees: a .squad/ directory was created expect(existsSync(join(tempDir, '.squad'))).toBe(true); - expect(existsSync(join(tempDir, '.copilot', 'skills'))).toBe(true); + expect(existsSync(join(tempDir, '.github', 'skills'))).toBe(true); expect(existsSync(join(tempDir, '.squad', 'identity'))).toBe(true); expect(existsSync(join(tempDir, '.squad', 'ceremonies.md'))).toBe(true); }); diff --git a/test/init-scaffolding.test.ts b/test/init-scaffolding.test.ts index 3b180a812..d39ce62ab 100644 --- a/test/init-scaffolding.test.ts +++ b/test/init-scaffolding.test.ts @@ -428,3 +428,73 @@ describe('squad.agent.md template handling (#730)', () => { expect(result.createdFiles.length).toBeGreaterThan(0); }); }); + +// ─── .gitignore marker block for two-layer/orphan backends (#1228) ───────── + +describe('.gitignore state-backend marker block — initSquad()', () => { + beforeEach(async () => { + if (existsSync(TEST_ROOT)) { + await rm(TEST_ROOT, { recursive: true, force: true }); + } + await mkdir(TEST_ROOT, { recursive: true }); + gitInit(TEST_ROOT); + }); + + afterEach(async () => { + if (existsSync(TEST_ROOT)) { + await rm(TEST_ROOT, { recursive: true, force: true }); + } + }); + + it('initSquad({stateBackend: "two-layer"}) adds the marker block to .gitignore', async () => { + await initSquad({ ...sdkOptions(TEST_ROOT), stateBackend: 'two-layer' }); + + const gitignorePath = join(TEST_ROOT, '.gitignore'); + expect(existsSync(gitignorePath)).toBe(true); + const content = await readFile(gitignorePath, 'utf-8'); + expect(content).toContain('# Squad: state owned by squad-state branch (two-layer/orphan backend)'); + expect(content).toContain('.squad/decisions.md'); + expect(content).toContain('.squad/agents/*/history.md'); + expect(content).toContain('# /Squad: state owned by squad-state branch'); + }); + + it('initSquad({stateBackend: "orphan"}) adds the marker block to .gitignore', async () => { + await initSquad({ ...sdkOptions(TEST_ROOT), stateBackend: 'orphan' }); + + const gitignorePath = join(TEST_ROOT, '.gitignore'); + expect(existsSync(gitignorePath)).toBe(true); + const content = await readFile(gitignorePath, 'utf-8'); + expect(content).toContain('# Squad: state owned by squad-state branch (two-layer/orphan backend)'); + expect(content).toContain('.squad/decisions.md'); + expect(content).toContain('.squad/agents/*/history.md'); + }); + + it('initSquad({stateBackend: "local"}) does NOT add the marker block to .gitignore', async () => { + await initSquad({ ...sdkOptions(TEST_ROOT), stateBackend: 'local' }); + + const gitignorePath = join(TEST_ROOT, '.gitignore'); + expect(existsSync(gitignorePath)).toBe(true); + const content = await readFile(gitignorePath, 'utf-8'); + expect(content).not.toContain('# Squad: state owned by squad-state branch'); + expect(content).not.toContain('.squad/decisions.md'); + expect(content).not.toContain('.squad/agents/*/history.md'); + }); + + it('initSquad() with no stateBackend does NOT add the marker block', async () => { + await initSquad(sdkOptions(TEST_ROOT)); + + const gitignorePath = join(TEST_ROOT, '.gitignore'); + expect(existsSync(gitignorePath)).toBe(true); + const content = await readFile(gitignorePath, 'utf-8'); + expect(content).not.toContain('# Squad: state owned by squad-state branch'); + }); + + it('initSquad with two-layer is idempotent — second call does not duplicate marker block', async () => { + await initSquad({ ...sdkOptions(TEST_ROOT), stateBackend: 'two-layer' }); + await initSquad({ ...sdkOptions(TEST_ROOT), stateBackend: 'two-layer' }); + + const content = await readFile(join(TEST_ROOT, '.gitignore'), 'utf-8'); + const occurrences = (content.match(/# Squad: state owned by squad-state branch/g) ?? []).length; + expect(occurrences).toBe(1); + }); +}); diff --git a/test/init-sdk.test.ts b/test/init-sdk.test.ts index f37d0248a..77854b425 100644 --- a/test/init-sdk.test.ts +++ b/test/init-sdk.test.ts @@ -142,8 +142,8 @@ describe('squad init --sdk flag', () => { // Assert: .squad/decisions/inbox/ exists expect(existsSync(join(tempDir, '.squad', 'decisions', 'inbox'))).toBe(true); - // Assert: .copilot/skills/ exists - expect(existsSync(join(tempDir, '.copilot', 'skills'))).toBe(true); + // Assert: .github/skills/ exists + expect(existsSync(join(tempDir, '.github', 'skills'))).toBe(true); // Assert: .squad/identity/ exists expect(existsSync(join(tempDir, '.squad', 'identity'))).toBe(true); diff --git a/test/init.test.ts b/test/init.test.ts index 63b883ac5..1548fef64 100644 --- a/test/init.test.ts +++ b/test/init.test.ts @@ -8,7 +8,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { mkdir, rm, readFile, writeFile } from 'fs/promises'; import { join } from 'path'; import { existsSync } from 'fs'; -import { initSquad } from '@bradygaster/squad-sdk/config'; +import { initSquad, MANIFEST_SKILL_NAMES } from '@bradygaster/squad-sdk/config'; import { onboardAgent, addAgentToConfig } from '@bradygaster/squad-sdk/agents'; import type { InitOptions, InitAgentSpec } from '@bradygaster/squad-sdk/config'; import type { OnboardOptions } from '@bradygaster/squad-sdk/agents'; @@ -136,7 +136,159 @@ describe('Squad Initialization', () => { expect(existsSync(join(TEST_ROOT, '.squad', 'agents'))).toBe(true); expect(existsSync(join(TEST_ROOT, '.squad', 'casting'))).toBe(true); expect(existsSync(join(TEST_ROOT, '.squad', 'decisions'))).toBe(true); - expect(existsSync(join(TEST_ROOT, '.copilot', 'skills'))).toBe(true); + expect(existsSync(join(TEST_ROOT, '.github', 'skills'))).toBe(true); + // bradygaster/squad#1126 regression — skills must NOT live at the legacy + // .copilot/skills path (invisible to all Copilot surfaces). Fresh init + // creates only the canonical .github/skills location. + expect(existsSync(join(TEST_ROOT, '.copilot', 'skills')), '.copilot/skills must NOT be created by fresh init (#1126)').toBe(false); + }); + + it('should install Squad-bundled skills at .github/skills/{name}/SKILL.md (#1126)', async () => { + // #1126: skills at .copilot/skills/ are invisible to all GitHub Copilot + // surfaces (cloud agent, CLI outside Squad, VS Code extension, @copilot + // coding agent). The canonical project-skills location per the official + // Agent Skills spec is .github/skills/. + // + // This test asserts: + // 1. squad-conventions (a manifest-curated bundled skill) lands at + // .github/skills/squad-conventions/SKILL.md + // 2. The legacy .copilot/skills/squad-conventions location is NOT + // created in a fresh init + const agents: InitAgentSpec[] = [{ name: 'lead', role: 'lead' }]; + const options: InitOptions = { + teamRoot: TEST_ROOT, + projectName: 'Test Project', + agents + }; + + await initSquad(options); + + const canonical = join(TEST_ROOT, '.github', 'skills', 'squad-conventions', 'SKILL.md'); + const legacy = join(TEST_ROOT, '.copilot', 'skills', 'squad-conventions', 'SKILL.md'); + expect(existsSync(canonical), 'expected squad-conventions at .github/skills/').toBe(true); + expect(existsSync(legacy), 'must NOT install to legacy .copilot/skills/ (#1126)').toBe(false); + }); + + it('should seed .squad/fact-checker/{policy,audit-trail}.md (regression: bradygaster/squad#1299)', async () => { + // Fact Checker is an always-on built-in (#789 + #1254, single agent dual + // operating mode: Verification + Devil's Advocate). It must get the + // same first-class state-dir treatment as Rai: policy.md + audit-trail.md + // under .squad/fact-checker/. Without these, fact-checker is "a name on + // disk with a 21-line placeholder" (verbatim user feedback, 2026-06-13). + const agents: InitAgentSpec[] = [ + { name: 'lead', role: 'lead' }, + { name: 'fact-checker', role: 'fact-checker', displayName: 'Fact Checker' }, + ]; + const options: InitOptions = { + teamRoot: TEST_ROOT, + projectName: 'Test Project', + agents + }; + + await initSquad(options); + + const policyPath = join(TEST_ROOT, '.squad', 'fact-checker', 'policy.md'); + const auditPath = join(TEST_ROOT, '.squad', 'fact-checker', 'audit-trail.md'); + expect(existsSync(policyPath), 'expected .squad/fact-checker/policy.md to be seeded').toBe(true); + expect(existsSync(auditPath), 'expected .squad/fact-checker/audit-trail.md to be seeded').toBe(true); + + const policy = await readFile(policyPath, 'utf-8'); + // Policy must declare both operating modes and the hard anti-fabrication rules. + expect(policy).toMatch(/Verification/i); + expect(policy).toMatch(/Devil['']s Advocate/i); + expect(policy).toMatch(/Confidence|✅|⚠️|❌|🔍/); + expect(policy).toMatch(/anti.?fabrication|never invent|never cite/i); + + const audit = await readFile(auditPath, 'utf-8'); + // Audit trail must be append-only and start empty (no entries yet). + expect(audit).toMatch(/Audit Trail/i); + expect(audit).toMatch(/append.?only/i); + }); + + it('should use the rich fact-checker-charter.md template for built-in agents at init (#1299)', async () => { + // Before #1299, both Rai and fact-checker got a 478-byte generic stub + // charter from generateCharter(). The rich charter templates + // (Rai-charter.md, fact-checker-charter.md) only got used by + // `squad upgrade`'s ensureBuiltinAgents path. This made both built-ins + // effectively "names on disk" until upgrade was run. Init now reads + // the rich template if it exists. + const agents: InitAgentSpec[] = [ + { name: 'fact-checker', role: 'fact-checker', displayName: 'Fact Checker' }, + ]; + const options: InitOptions = { + teamRoot: TEST_ROOT, + projectName: 'Test Project', + agents + }; + + await initSquad(options); + + const charterPath = join(TEST_ROOT, '.squad', 'agents', 'fact-checker', 'charter.md'); + expect(existsSync(charterPath)).toBe(true); + const charter = await readFile(charterPath, 'utf-8'); + // Must contain rich-charter markers, not the generic stub. + // Generic stub has "Responsibilities", "Work Style", "Project Context" + // sections and ~500 bytes. Rich charter has Verification Methodology, + // Confidence Ratings, Devil's Advocate, etc., and is several KB. + expect(charter.length).toBeGreaterThan(1000); + expect(charter).toMatch(/Verification Methodology|## Verification/i); + expect(charter).toMatch(/Confidence Ratings|✅ Verified/i); + // Must NOT contain the generic stub boilerplate. + expect(charter).not.toMatch(/^## Work Style$/m); + }); + + it('should use the rich Rai-charter.md template at init (companion to fact-checker fix, #1299)', async () => { + // Same template-lookup logic must benefit Rai too. Before #1299, Rai's + // charter.md was a 478-byte generic stub even though Rai-charter.md + // (4525 bytes) shipped in templates/. + const agents: InitAgentSpec[] = [ + { name: 'Rai', role: 'Rai', displayName: 'Rai' }, + ]; + const options: InitOptions = { + teamRoot: TEST_ROOT, + projectName: 'Test Project', + agents + }; + + await initSquad(options); + + const charterPath = join(TEST_ROOT, '.squad', 'agents', 'Rai', 'charter.md'); + expect(existsSync(charterPath)).toBe(true); + const charter = await readFile(charterPath, 'utf-8'); + expect(charter.length).toBeGreaterThan(1000); + // Rich Rai charter mentions RAI policy + audit-trail paths. + expect(charter).toMatch(/\.squad\/rai\/policy\.md/); + expect(charter).toMatch(/\.squad\/rai\/audit-trail\.md/); + }); + + it('should install every manifest-curated skill (regression: bradygaster/squad#1289, #1264)', async () => { + // Sanity check: every skill listed in MANIFEST_SKILL_NAMES must end up + // installed under .copilot/skills/. The prior v0.10.0 install path + // silently skipped skills whose source dir was missing from the SDK + // templates dir; this test exists to ensure that regression cannot + // happen again (the loop now throws on drift, but a missing + // SKILL.md after install would still indicate a deeper issue). + // + // The expected list comes from the same MANIFEST_SKILL_NAMES export + // the production install loop reads — so the test cannot fall out + // of sync when a new skill is added. Sanity-asserted to be non-empty + // so a future accidental empty export doesn't make this test pass + // trivially. + expect(MANIFEST_SKILL_NAMES.length).toBeGreaterThan(0); + + const agents: InitAgentSpec[] = [{ name: 'lead', role: 'lead' }]; + const options: InitOptions = { + teamRoot: TEST_ROOT, + projectName: 'Test Project', + agents + }; + + await initSquad(options); + + for (const skill of MANIFEST_SKILL_NAMES) { + const skillPath = join(TEST_ROOT, '.github', 'skills', skill, 'SKILL.md'); + expect(existsSync(skillPath), `expected ${skill}/SKILL.md to be installed`).toBe(true); + } }); it('should create .gitattributes for merge drivers', async () => { @@ -157,6 +309,61 @@ describe('Squad Initialization', () => { expect(content).toContain('.squad/decisions.md merge=union'); }); + it('should install the squad-help disambiguation skill (regression: #1297 / supersedes squad name collision)', async () => { + const agents: InitAgentSpec[] = [{ name: 'lead', role: 'lead' }]; + const options: InitOptions = { + teamRoot: TEST_ROOT, + projectName: 'Test Project', + agents + }; + + await initSquad(options); + + const skillPath = join(TEST_ROOT, '.github', 'skills', 'squad-help', 'SKILL.md'); + expect(existsSync(skillPath)).toBe(true); + const content = await readFile(skillPath, 'utf-8'); + expect(content).toContain('name: "squad-help"'); + expect(content).not.toMatch(/^name:\s*"?squad"?\s*$/m); + expect(content).toContain('agent_type="Squad"'); + expect(content).toContain('custom agent'); + }); + + it('should install the squad slash-command skill with user-invocable: true (regression: /squad must appear)', async () => { + const agents: InitAgentSpec[] = [{ name: 'lead', role: 'lead' }]; + const options: InitOptions = { + teamRoot: TEST_ROOT, + projectName: 'Test Project', + agents + }; + + await initSquad(options); + + const skillPath = join(TEST_ROOT, '.github', 'skills', 'squad', 'SKILL.md'); + expect(existsSync(skillPath)).toBe(true); + const content = await readFile(skillPath, 'utf-8'); + expect(content).toMatch(/^user-invocable:\s*true\s*$/m); + expect(content).toMatch(/^name:\s*"?squad"?\s*$/m); + expect(content).toContain('Menu Presentation Rules'); + }); + + it('should install cross-squad-communication skill (companion to cross-squad — #5)', async () => { + const agents: InitAgentSpec[] = [{ name: 'lead', role: 'lead' }]; + const options: InitOptions = { + teamRoot: TEST_ROOT, + projectName: 'Test Project', + agents + }; + + await initSquad(options); + + const skillPath = join(TEST_ROOT, '.github', 'skills', 'cross-squad-communication', 'SKILL.md'); + expect(existsSync(skillPath)).toBe(true); + const content = await readFile(skillPath, 'utf-8'); + expect(content).toContain('cross-squad-communication'); + expect(content).toContain('Pattern 0'); + expect(content).toContain('Pattern 2'); + }); + it('should create initial decisions.md', async () => { const agents: InitAgentSpec[] = [{ name: 'lead', role: 'lead' }]; const options: InitOptions = { diff --git a/test/mcp-spec-init.test.ts b/test/mcp-spec-init.test.ts index 7fa719689..8778c63ff 100644 --- a/test/mcp-spec-init.test.ts +++ b/test/mcp-spec-init.test.ts @@ -84,6 +84,16 @@ describe('resolveSquadStateMcpSpec (iter-7: 2-tier resolver)', () => { expect(spec.command).toBe('npx'); }); + it('short-circuits for versions with build metadata (+ suffix) — returns @insider (#1204)', async () => { + const spec = await resolveSquadStateMcpSpec('0.10.0+local.1234', { + publishedCheck: async () => { + throw new Error('publishedCheck should not be called for build metadata version'); + }, + }); + expect(spec.source).toBe('insider'); + expect(spec.args[1]).toBe('@bradygaster/squad-cli@insider'); + }); + it('uses the real npm-registry probe by default when publishedCheck is not injected', async () => { mockIsPublished.mockResolvedValue(false); const spec = await resolveSquadStateMcpSpec('0.9.6-preview.99999'); @@ -124,3 +134,36 @@ describe('init.ts uses resolveSquadStateMcpSpec (asymmetry fix)', () => { expect(src).toMatch(/from ['"]\.\/mcp-spec\.js['"]/); }); }); + +describe('isLocalOrUnpublishedVersion guard (#1204)', () => { + // Import the guard function directly + let isLocalOrUnpublishedVersion: (version: string) => boolean; + + beforeEach(async () => { + const mod = await import('../packages/squad-cli/src/cli/core/upgrade.js'); + isLocalOrUnpublishedVersion = mod.isLocalOrUnpublishedVersion; + }); + + it('returns true for empty string', () => { + expect(isLocalOrUnpublishedVersion('')).toBe(true); + }); + + it('returns true for 0.0.0 placeholder', () => { + expect(isLocalOrUnpublishedVersion('0.0.0')).toBe(true); + }); + + it('returns true for 0.0.0-development sentinel', () => { + expect(isLocalOrUnpublishedVersion('0.0.0-development')).toBe(true); + }); + + it('returns true for versions with + build metadata (local builds)', () => { + expect(isLocalOrUnpublishedVersion('0.10.0+local.1234')).toBe(true); + expect(isLocalOrUnpublishedVersion('1.0.0+build.42')).toBe(true); + }); + + it('returns false for normal published versions', () => { + expect(isLocalOrUnpublishedVersion('0.10.0')).toBe(false); + expect(isLocalOrUnpublishedVersion('0.9.6-preview.42')).toBe(false); + expect(isLocalOrUnpublishedVersion('1.0.0-rc.1')).toBe(false); + }); +}); diff --git a/test/presets.test.ts b/test/presets.test.ts index 9d6b705ed..71153f6fa 100644 --- a/test/presets.test.ts +++ b/test/presets.test.ts @@ -331,6 +331,206 @@ describe('applyPreset()', () => { const results = applyPreset('nope', '/tmp/anywhere'); expect(results[0]!.status).toBe('error'); }); + + // Regression tests for bradygaster/squad#1288 — applyPreset must wire the + // preset agents into team.md, routing.md, and the casting state files so + // the coordinator's mode-switch check sees a populated ## Members table + // and skips Init Mode. + + it('wires preset agents into team.md ## Members (#1288)', () => { + const homeDir = join(TMP, 'apply-team'); + process.env['SQUAD_HOME'] = homeDir; + + scaffold('apply-team/presets/starter/agents/dev', 'apply-team/presets/starter/agents/qa'); + writeFile('apply-team/presets/starter/preset.json', JSON.stringify({ + name: 'starter', + version: '1.0.0', + description: 'Starter preset', + agents: [ + { name: 'dev', role: 'developer' }, + { name: 'qa', role: 'reviewer' }, + ], + })); + writeFile('apply-team/presets/starter/agents/dev/charter.md', '# Dev'); + writeFile('apply-team/presets/starter/agents/qa/charter.md', '# QA'); + + const squadDir = join(TMP, 'target-team'); + const agentsDir = join(squadDir, 'agents'); + mkdirSync(agentsDir, { recursive: true }); + + applyPreset('starter', agentsDir); + + const teamMd = readFileSync(join(squadDir, 'team.md'), 'utf-8'); + expect(teamMd).toContain('## Members'); + expect(teamMd).toContain('| dev | developer |'); + expect(teamMd).toContain('| qa | reviewer |'); + expect(teamMd).toContain('`.squad/agents/dev/charter.md`'); + expect(teamMd).toContain('`.squad/agents/qa/charter.md`'); + }); + + it('merges preset agents into an existing team.md without duplicating rows (#1288)', () => { + const homeDir = join(TMP, 'apply-team-merge'); + process.env['SQUAD_HOME'] = homeDir; + + scaffold('apply-team-merge/presets/starter/agents/qa'); + writeFile('apply-team-merge/presets/starter/preset.json', JSON.stringify({ + name: 'starter', + version: '1.0.0', + description: 'Starter preset', + agents: [{ name: 'qa', role: 'reviewer' }], + })); + writeFile('apply-team-merge/presets/starter/agents/qa/charter.md', '# QA'); + + const squadDir = join(TMP, 'target-team-merge'); + const agentsDir = join(squadDir, 'agents'); + mkdirSync(agentsDir, { recursive: true }); + // Pre-existing team.md from a prior squad init/cast with one member + writeFileSync(join(squadDir, 'team.md'), [ + '# Squad Team', + '', + '## Coordinator', + '', + '| Name | Role | Notes |', + '|------|------|-------|', + '| Squad | Coordinator | Routes work. |', + '', + '## Members', + '', + '| Name | Role | Charter | Status |', + '|------|------|---------|--------|', + '| Picard | Lead | `.squad/agents/picard/charter.md` | ✅ Active |', + '', + '## Project Context', + '', + '- **Project:** Existing', + '', + ].join('\n')); + + applyPreset('starter', agentsDir); + + const teamMd = readFileSync(join(squadDir, 'team.md'), 'utf-8'); + expect(teamMd).toContain('| Picard | Lead |'); + expect(teamMd).toContain('| qa | reviewer |'); + expect(teamMd).toContain('## Project Context'); // section after Members preserved + + // Idempotency: a second apply must not duplicate the qa row + applyPreset('starter', agentsDir); + const teamMdAfter = readFileSync(join(squadDir, 'team.md'), 'utf-8'); + const qaCount = (teamMdAfter.match(/\| qa \| reviewer \|/g) ?? []).length; + expect(qaCount).toBe(1); + }); + + it('writes casting registry.json, history.json, and policy.json (#1288)', () => { + const homeDir = join(TMP, 'apply-casting'); + process.env['SQUAD_HOME'] = homeDir; + + scaffold('apply-casting/presets/starter/agents/dev'); + writeFile('apply-casting/presets/starter/preset.json', JSON.stringify({ + name: 'starter', + version: '1.0.0', + description: 'Starter preset', + agents: [{ name: 'dev', role: 'developer' }], + })); + writeFile('apply-casting/presets/starter/agents/dev/charter.md', '# Dev'); + + const squadDir = join(TMP, 'target-casting'); + const agentsDir = join(squadDir, 'agents'); + mkdirSync(agentsDir, { recursive: true }); + + applyPreset('starter', agentsDir); + + const registry = JSON.parse(readFileSync(join(squadDir, 'casting', 'registry.json'), 'utf-8')); + expect(registry.agents).toHaveProperty('dev'); + expect(registry.agents.dev.persistent_name).toBe('dev'); + expect(registry.agents.dev.universe).toBe('preset:starter'); + expect(registry.agents.dev.status).toBe('active'); + + const history = JSON.parse(readFileSync(join(squadDir, 'casting', 'history.json'), 'utf-8')); + expect(Object.keys(history.assignment_cast_snapshots).length).toBeGreaterThan(0); + const firstSnapshot = Object.values<{ agents: string[]; universe: string }>( + history.assignment_cast_snapshots, + )[0]!; + expect(firstSnapshot.agents).toContain('dev'); + expect(firstSnapshot.universe).toBe('preset:starter'); + expect(history.universe_usage_history.length).toBeGreaterThan(0); + + const policy = JSON.parse(readFileSync(join(squadDir, 'casting', 'policy.json'), 'utf-8')); + expect(policy.universe_allowlist).toContain('*'); + expect(policy.max_capacity).toBeGreaterThan(0); + }); + + it('preserves built-in role status labels in team.md (Scribe/Ralph/Rai/Fact Checker) — review on #1293', () => { + // A preset that happens to ship one of the always-on built-ins (Scribe, + // Ralph, Rai, Fact Checker) must produce the same Status cell that a + // fresh `squad init` cast would — '📋 Silent', '🔄 Monitor', '🛡️ RAI', + // '🔍 Verifier' respectively — NOT '✅ Active'. Pre-fix, memberRow() + // hardcoded '✅ Active' for every preset agent, which made + // preset-scaffolded teams visually disagree with cast-scaffolded teams + // for the same roster. + const homeDir = join(TMP, 'apply-status-roles'); + process.env['SQUAD_HOME'] = homeDir; + + scaffold( + 'apply-status-roles/presets/builtins/agents/scribe', + 'apply-status-roles/presets/builtins/agents/ralph', + 'apply-status-roles/presets/builtins/agents/rai', + 'apply-status-roles/presets/builtins/agents/fact-checker', + 'apply-status-roles/presets/builtins/agents/dev', + ); + writeFile('apply-status-roles/presets/builtins/preset.json', JSON.stringify({ + name: 'builtins', + version: '1.0.0', + description: 'Preset that ships built-in roles', + agents: [ + { name: 'scribe', role: 'Session Logger' }, + { name: 'ralph', role: 'Work Monitor' }, + { name: 'rai', role: 'RAI Reviewer' }, + { name: 'fact-checker', role: 'Fact Checker' }, + { name: 'dev', role: 'developer' }, + ], + })); + for (const a of ['scribe', 'ralph', 'rai', 'fact-checker', 'dev']) { + writeFile(`apply-status-roles/presets/builtins/agents/${a}/charter.md`, `# ${a}`); + } + + const squadDir = join(TMP, 'target-status-roles'); + const agentsDir = join(squadDir, 'agents'); + mkdirSync(agentsDir, { recursive: true }); + + applyPreset('builtins', agentsDir); + + const teamMd = readFileSync(join(squadDir, 'team.md'), 'utf-8'); + expect(teamMd).toContain('| scribe | Session Logger | `.squad/agents/scribe/charter.md` | 📋 Silent |'); + expect(teamMd).toContain('| ralph | Work Monitor | `.squad/agents/ralph/charter.md` | 🔄 Monitor |'); + expect(teamMd).toContain('| rai | RAI Reviewer | `.squad/agents/rai/charter.md` | 🛡️ RAI |'); + expect(teamMd).toContain('| fact-checker | Fact Checker | `.squad/agents/fact-checker/charter.md` | 🔍 Verifier |'); + // Regular agent still gets ✅ Active. + expect(teamMd).toContain('| dev | developer | `.squad/agents/dev/charter.md` | ✅ Active |'); + }); + + it('appends routing rows for preset agents to routing.md (#1288)', () => { + const homeDir = join(TMP, 'apply-routing'); + process.env['SQUAD_HOME'] = homeDir; + + scaffold('apply-routing/presets/starter/agents/dev'); + writeFile('apply-routing/presets/starter/preset.json', JSON.stringify({ + name: 'starter', + version: '1.0.0', + description: 'Starter preset', + agents: [{ name: 'dev', role: 'developer' }], + })); + writeFile('apply-routing/presets/starter/agents/dev/charter.md', '# Dev'); + + const squadDir = join(TMP, 'target-routing'); + const agentsDir = join(squadDir, 'agents'); + mkdirSync(agentsDir, { recursive: true }); + + applyPreset('starter', agentsDir); + + const routing = readFileSync(join(squadDir, 'routing.md'), 'utf-8'); + expect(routing).toContain('## Work Type → Agent'); + expect(routing).toContain('| developer | dev |'); + }); }); // ============================================================================ diff --git a/test/repl-ux-fixes.test.ts b/test/repl-ux-fixes.test.ts index d72967626..570fd9de2 100644 --- a/test/repl-ux-fixes.test.ts +++ b/test/repl-ux-fixes.test.ts @@ -105,8 +105,8 @@ describe('#596 — Init creates complete .squad/ directory', () => { expect(existsSync(join(squadDir, 'plugins'))).toBe(true); expect(existsSync(join(squadDir, 'identity'))).toBe(true); - // Skills now live in .copilot/skills/ (not .squad/skills/) - expect(existsSync(join(tmpRoot, '.copilot', 'skills'))).toBe(true); + // Skills now live in .github/skills/ (not .squad/skills/) + expect(existsSync(join(tmpRoot, '.github', 'skills'))).toBe(true); // Required files expect(existsSync(join(squadDir, 'ceremonies.md'))).toBe(true); diff --git a/test/squad-agent-roster.test.ts b/test/squad-agent-roster.test.ts new file mode 100644 index 000000000..e576efd86 --- /dev/null +++ b/test/squad-agent-roster.test.ts @@ -0,0 +1,70 @@ +/** + * Regression tests for bradygaster/squad#1299 — the canonical squad.agent.md + * template must explicitly instruct the coordinator to roster all four + * always-on built-in agents: Scribe, Ralph, Rai, Fact Checker. + * + * Without explicit roster-entry instructions, the coordinator model omits + * agents from team.md during first-time casting even when the agent + * directory was correctly created on disk by squad init. + */ + +import { describe, it, expect } from 'vitest'; +import { readFileSync, existsSync } from 'node:fs'; +import path from 'node:path'; + +const REPO_ROOT = path.resolve(__dirname, '..'); + +const TEMPLATE_TARGETS = [ + '.squad-templates/squad.agent.md', + 'templates/squad.agent.md.template', + 'packages/squad-cli/templates/squad.agent.md.template', + 'packages/squad-sdk/templates/squad.agent.md.template', +]; + +describe('squad.agent.md.template — always-on roster instructions (#1299)', () => { + for (const rel of TEMPLATE_TARGETS) { + describe(rel, () => { + const fullPath = path.join(REPO_ROOT, rel); + if (!existsSync(fullPath)) { + it.skip(`(file does not exist in this checkout: ${rel})`, () => {}); + return; + } + const content = readFileSync(fullPath, 'utf-8'); + + it('mentions all four always-on built-ins in the initial team-size guidance', () => { + // The casting flow's "Determine team size" line must name all four + // always-on built-ins so the coordinator does not silently omit them. + const teamSizeLine = content.match(/Determine team size[^\n]+/); + expect(teamSizeLine, 'no "Determine team size" line found').toBeTruthy(); + expect(teamSizeLine![0]).toContain('Scribe'); + expect(teamSizeLine![0]).toContain('Ralph'); + expect(teamSizeLine![0]).toContain('Rai'); + expect(teamSizeLine![0]).toContain('Fact Checker'); + }); + + it('has a dedicated ## Fact Checker section with roster-entry instructions', () => { + expect(content).toMatch(/^##\s+Fact Checker\b/m); + // Must explicitly tell the coordinator that Fact Checker goes on the + // roster — same pattern as Rai's "always appears in team.md" line. + expect(content).toMatch(/Fact Checker[^\n]*always appears in[^\n]*team\.md/i); + }); + + it('declares Fact Checker as dual operating mode (Verification + Devil\'s Advocate)', () => { + // The single-agent dual-mode design is the source-of-truth from + // bradygaster/squad#789 + #1254. The template must reinforce it so + // the coordinator does not split them. + const factSection = content.split(/^##\s+Fact Checker\b/m)[1] ?? ''; + const beforeNextSection = factSection.split(/^##\s+/m)[0] ?? ''; + expect(beforeNextSection).toMatch(/Verification/i); + expect(beforeNextSection).toMatch(/Devil['']s Advocate/i); + expect(beforeNextSection).toMatch(/dual operating mode/i); + }); + + it('preserves the existing Ralph + Rai always-on sections', () => { + expect(content).toMatch(/^##\s+Ralph\b/m); + expect(content).toMatch(/^##\s+Rai\b/m); + expect(content).toMatch(/Rai[^\n]*always appears in[^\n]*team\.md/i); + }); + }); + } +}); diff --git a/test/state-backend-handshake.test.ts b/test/state-backend-handshake.test.ts new file mode 100644 index 000000000..ed7613512 --- /dev/null +++ b/test/state-backend-handshake.test.ts @@ -0,0 +1,91 @@ +/** + * Regression tests for bradygaster/squad#1305 — the canonical squad.agent.md + * template must instruct the coordinator to probe for squad_state and memory + * tools before mutating state on non-local backends, and must hard-refuse + * writes when the bridge isn't reachable. + * + * Background: Copilot CLI loads MCP server tools lazily — they're not + * always advertised in the initial function list. The pre-1305 prompt said + * "when memory tools are available, use them" which models interpreted as + * "if listed" instead of "after probing". That led to a real incident where + * a coordinator session against a two-layer backend wrote .squad/decisions.md + * via raw create/edit tools, hit the pre-commit hook, and treated it as a + * git problem instead of a contract violation. + */ + +import { describe, it, expect } from 'vitest'; +import { readFileSync, existsSync } from 'node:fs'; +import path from 'node:path'; + +const REPO_ROOT = path.resolve(__dirname, '..'); + +const TEMPLATE_TARGETS = [ + '.squad-templates/squad.agent.md', + 'templates/squad.agent.md.template', + 'packages/squad-cli/templates/squad.agent.md.template', + 'packages/squad-sdk/templates/squad.agent.md.template', +]; + +describe('squad.agent.md.template — state-backend handshake (#1305)', () => { + for (const rel of TEMPLATE_TARGETS) { + describe(rel, () => { + const fullPath = path.join(REPO_ROOT, rel); + if (!existsSync(fullPath)) { + it.skip(`(file does not exist in this checkout: ${rel})`, () => {}); + return; + } + const content = readFileSync(fullPath, 'utf-8'); + + it('declares the mandatory state-backend handshake section', () => { + // The handshake must be flagged as MANDATORY and explicitly mention + // every session and pre-state-mutation timing — soft language was + // the original failure mode. + expect(content).toMatch(/State-backend handshake[^\n]*MANDATORY/i); + expect(content).toMatch(/every session/i); + expect(content).toMatch(/before any state mutation/i); + }); + + it('instructs the coordinator to PROBE for squad_state_health on non-local backends', () => { + // The probe is the load-bearing behavioral instruction. The pre-1305 + // prompt said "when memory tools are available" which models read as + // "if listed in my tool block"; the post-1305 prompt explicitly says + // to probe via tool-discovery (e.g. tool_search_tool_regex). + expect(content).toMatch(/squad_state_health/); + expect(content).toMatch(/tool_search_tool_regex|tool-discovery/i); + }); + + it('instructs the coordinator to HALT when the probe fails', () => { + // Halt is the load-bearing safety instruction. Without it, models + // silently fall back to raw file ops and violate the backend contract. + expect(content).toMatch(/HALT/i); + // Must tell the user how to fix it — restart Copilot CLI or change + // stateBackend to local. + expect(content).toMatch(/Restart Copilot CLI/i); + expect(content).toMatch(/stateBackend.*local/i); + }); + + it('declares a HARD RULE forbidding raw file writes to runtime-owned paths under non-local backends', () => { + expect(content).toMatch(/HARD RULE/i); + // The forbidden-paths list must include the high-traffic state files. + expect(content).toMatch(/\.squad\/decisions\.md/); + expect(content).toMatch(/\.squad\/decisions\/inbox/); + expect(content).toMatch(/\.squad\/agents\/\*\/history\.md/); + // And must call out the create/edit/write_file tools by name so the + // model maps the rule to its actual function inventory. Match each + // tool name with separate assertions so dropping any one of them + // (e.g. forgetting `write_file`) fails the test — the previous + // single-regex form had | precedence so the shorter alternative + // `create\s*/\s*edit` could pass without `write_file`. + expect(content).toMatch(/\bcreate\b/); + expect(content).toMatch(/\bedit\b/); + expect(content).toMatch(/\bwrite_file\b/); + }); + + it('keeps the local/worktree carve-out explicit (file ops valid for local backends)', () => { + // The rule applies ONLY to non-local backends. Local-backend users + // must still be able to use create/edit/write_file on .squad/. + expect(content).toMatch(/local.*worktree|local.*backend.*valid/i); + }); + }); + } +}); diff --git a/test/template-sync.test.ts b/test/template-sync.test.ts index f4d5e674c..81d776367 100644 --- a/test/template-sync.test.ts +++ b/test/template-sync.test.ts @@ -242,3 +242,144 @@ describe('casting-policy.json content parity', () => { } }); }); + +// --------------------------------------------------------------------------- +// 5. Squad-spawning routing guard (regression for the "spawn a squad" hole) +// --------------------------------------------------------------------------- + +describe('squad.agent.md squad-spawning guidance', () => { + // A coordinator reading squad.agent.md saw the user say "spawn two squads + // of designers and devs" and fanned out raw `task` agents inside its own + // context instead of treating "squad" as the Squad-PRODUCT concept and + // routing through the cross-squad / cross-squad-communication skills. + // Two surgical edits to squad.agent.md (a routing-table row + a hard + // keyword-to-skill trigger paragraph) close the hole; assert both are + // present in every mirrored copy so a future refactor cannot silently + // drop either. + + for (const loc of SQUAD_AGENT_LOCATIONS) { + describe(loc, () => { + const content = readFile(loc); + + it('has a routing-table row for "spawn a squad" / "another squad" / "two squads"', () => { + // Match the row independent of exact wording so phrasing tweaks + // are allowed; what matters is that ALL three trigger phrases + // appear in a single routing-table row (lines starting with `|`). + const rowRegex = /^\|.*spawn a squad.*another squad.*two squads.*\|.*$/im; + expect(content, 'expected routing row containing all three trigger phrases').toMatch(rowRegex); + }); + + it('the squad-spawning row references the cross-squad skill(s) as a precondition', () => { + // The action cell must instruct the coordinator to load + // cross-squad (and ideally cross-squad-communication) BEFORE + // any `task` spawn. Match on co-occurrence of "cross-squad" + // and "skill" on the same row. + const row = content + .split(/\r?\n/) + .find(l => /^\|.*spawn a squad.*another squad.*two squads/i.test(l)); + expect(row, 'spawn-a-squad row should exist').toBeDefined(); + expect(row, 'row should mention cross-squad skill').toMatch(/cross-squad/i); + expect(row, 'row should invoke the skill tool').toMatch(/skill/i); + }); + + it('has a Hard trigger — keyword-to-skill match paragraph in Skill-aware routing', () => { + // Both keywords must appear ("Hard trigger" + "keyword-to-skill") + // — together they uniquely identify the guard paragraph and rule + // out an accidental match against the routing-table row. + expect(content).toMatch(/Hard trigger/i); + expect(content).toMatch(/keyword.to.skill match/i); + }); + + it('the hard-trigger paragraph names the cross-squad mapping as a worked example', () => { + // Defends against a future edit that keeps the paragraph header + // but drops the concrete "squad → cross-squad" example, which + // is what makes the rule unambiguous for a coordinator. + const tail = content.slice(content.toLowerCase().indexOf('hard trigger')); + expect(tail).toMatch(/["']?squad["']?\s*→\s*`?cross-squad/); + }); + + it('the routing row instructs the coordinator to ask_user when the request is ambiguous', () => { + // Real-world failure mode (2026-06-13): even AFTER the row + + // hard-trigger were in place, a coordinator silently picked + // ad-hoc `task` fan-out instead of bootstrapping real squads + // because "two squads of engineers for a 30-line app felt + // disproportionate". The fix is an explicit "you MUST ask_user + // when ambiguous" clause with no escape hatch — and a callout + // that calling task agents "squad-alpha" doesn't make them + // squads. Both must be present on the squad-spawning row. + const row = content + .split(/\r?\n/) + .find(l => /^\|.*spawn a squad.*another squad.*two squads/i.test(l)); + expect(row, 'spawn-a-squad row should exist').toBeDefined(); + expect(row, 'row should reference ask_user for ambiguous requests').toMatch(/ask_user/i); + expect(row, 'row should call out the "naming != being" anti-pattern').toMatch(/anti-pattern/i); + }); + }); + } +}); + +// --------------------------------------------------------------------------- +// 6. cross-squad/SKILL.md must have the Disambiguation section +// --------------------------------------------------------------------------- + +const CROSS_SQUAD_SKILL_LOCATIONS = [ + '.squad/skills/cross-squad/SKILL.md', + 'packages/squad-cli/templates/skills/cross-squad/SKILL.md', + 'packages/squad-sdk/templates/skills/cross-squad/SKILL.md', +] as const; + +describe('cross-squad/SKILL.md disambiguation rule', () => { + // Folded into PR #1307 after a real-world failure (2026-06-13): the routing + // row alone wasn't enough — once the coordinator loaded the skill, its + // generic eager-execution / parallel-fan-out doctrine pulled it back to + // `task` fan-out anyway. The dedicated Disambiguation section makes the + // squad-vs-ad-hoc rule unmistakable inside the skill itself, with explicit + // default-behavior table, ask_user protocol, and anti-patterns. + + for (const loc of CROSS_SQUAD_SKILL_LOCATIONS) { + describe(loc, () => { + const content = readFile(loc); + + it('has a "Disambiguation" section', () => { + expect(content).toMatch(/##\s+Disambiguation/i); + }); + + it('declares the default-behaviour rule (literal Squad install when user says "squad")', () => { + // The default must be the literal interpretation. Any future + // edit that flips the default to ad-hoc fan-out would be a + // regression — that's exactly the failure mode this section + // exists to prevent. + const tail = content.slice(content.toLowerCase().indexOf('disambiguation')); + expect(tail).toMatch(/default behaviour|default behavior/i); + expect(tail).toMatch(/real.*Squad install|literal reference to a Squad install/i); + }); + + it('requires ask_user on ambiguous requests (no silent downgrade)', () => { + const tail = content.slice(content.toLowerCase().indexOf('disambiguation')); + expect(tail).toMatch(/ask_user/); + // Must explicitly forbid silent downgrades — the failure mode + // was "coordinator silently picked the cheaper option". + expect(tail).toMatch(/never silently/i); + }); + + it('lists the "calling task agents squad-alpha doesn\'t make them squads" anti-pattern', () => { + const tail = content.slice(content.toLowerCase().indexOf('disambiguation')); + // The anti-pattern is the strongest defense against the + // "name it a squad to look compliant" failure mode. + expect(tail).toMatch(/anti-pattern/i); + expect(tail).toMatch(/squad-alpha|squad-beta|naming.*does(n't| not)/i); + }); + + it('frontmatter declares squad-spawning trigger phrases', () => { + // Copilot CLI itself ignores triggers: at the loader level + // (verified via sdk/index.js decompile), but the squad + // skill-aware-routing system uses natural-language matching + // against frontmatter + content. Documenting the trigger + // phrases here helps that matcher fire on the right prompts. + expect(content).toMatch(/^triggers:/m); + expect(content).toMatch(/spawn a squad|spawn N squads/); + expect(content).toMatch(/another squad|two squads of/); + }); + }); + } +}); diff --git a/test/tools.test.ts b/test/tools.test.ts index e555289e3..905bc65c4 100644 --- a/test/tools.test.ts +++ b/test/tools.test.ts @@ -856,7 +856,7 @@ describe('squad_skill handler', () => { resultType: 'success', }); - const skillFile = path.join(projectRoot, '.copilot', 'skills', 'typescript-refactoring', 'SKILL.md'); + const skillFile = path.join(projectRoot, '.github', 'skills', 'typescript-refactoring', 'SKILL.md'); expect(fs.existsSync(skillFile)).toBe(true); const content = fs.readFileSync(skillFile, 'utf-8'); @@ -951,7 +951,7 @@ describe('squad_skill handler', () => { } ); - const skillFile = path.join(projectRoot, '.copilot', 'skills', 'test-skill', 'SKILL.md'); + const skillFile = path.join(projectRoot, '.github', 'skills', 'test-skill', 'SKILL.md'); const content = fs.readFileSync(skillFile, 'utf-8'); expect(content).toContain('**Confidence:** medium'); }); diff --git a/test/upgrade-state-backend.test.ts b/test/upgrade-state-backend.test.ts index 8accf3194..093129cab 100644 --- a/test/upgrade-state-backend.test.ts +++ b/test/upgrade-state-backend.test.ts @@ -151,4 +151,111 @@ describe('squad upgrade --state-backend migration', () => { expect(occurrences).toBe(1); expect(JSON.parse(raw).stateBackend).toBe('two-layer'); }); + + // ── .gitignore marker block (#1228) ────────────────────────────────────── + + it('local → two-layer: adds .gitignore marker block', { timeout: 30_000 }, async () => { + dir = mkRepo('local'); + await migrateStateBackend(dir, 'two-layer'); + + const gitignore = fs.readFileSync(path.join(dir, '.gitignore'), 'utf-8'); + expect(gitignore).toContain('# Squad: state owned by squad-state branch (two-layer/orphan backend)'); + expect(gitignore).toContain('.squad/decisions.md'); + expect(gitignore).toContain('.squad/agents/*/history.md'); + expect(gitignore).toContain('# /Squad: state owned by squad-state branch'); + }); + + it('local → orphan: adds .gitignore marker block', { timeout: 30_000 }, async () => { + dir = mkRepo('local'); + await migrateStateBackend(dir, 'orphan'); + + const gitignore = fs.readFileSync(path.join(dir, '.gitignore'), 'utf-8'); + expect(gitignore).toContain('# Squad: state owned by squad-state branch (two-layer/orphan backend)'); + expect(gitignore).toContain('.squad/decisions.md'); + expect(gitignore).toContain('.squad/agents/*/history.md'); + }); + + it('two-layer → local: removes .gitignore marker block', { timeout: 30_000 }, async () => { + dir = mkRepo('two-layer'); + // Pre-seed a .gitignore with the marker block + const gitignorePath = path.join(dir, '.gitignore'); + fs.writeFileSync(gitignorePath, [ + '# Squad: ignore runtime state (logs, inbox, sessions)', + '.squad/orchestration-log/', + '# Squad: state owned by squad-state branch (two-layer/orphan backend)', + '.squad/decisions.md', + '.squad/agents/*/history.md', + '# /Squad: state owned by squad-state branch', + '', + ].join('\n')); + + await migrateStateBackend(dir, 'local'); + + const gitignore = fs.readFileSync(gitignorePath, 'utf-8'); + expect(gitignore).not.toContain('# Squad: state owned by squad-state branch'); + expect(gitignore).not.toContain('.squad/decisions.md'); + expect(gitignore).not.toContain('.squad/agents/*/history.md'); + // Other entries should be preserved + expect(gitignore).toContain('.squad/orchestration-log/'); + }); + + it('orphan → local: removes .gitignore marker block', { timeout: 30_000 }, async () => { + dir = mkRepo('orphan'); + const gitignorePath = path.join(dir, '.gitignore'); + fs.writeFileSync(gitignorePath, [ + '.squad/orchestration-log/', + '# Squad: state owned by squad-state branch (two-layer/orphan backend)', + '.squad/decisions.md', + '.squad/agents/*/history.md', + '# /Squad: state owned by squad-state branch', + '', + ].join('\n')); + + await migrateStateBackend(dir, 'local'); + + const gitignore = fs.readFileSync(gitignorePath, 'utf-8'); + expect(gitignore).not.toContain('# Squad: state owned by squad-state branch'); + expect(gitignore).toContain('.squad/orchestration-log/'); + }); + + it('two-layer → orphan: no-op on .gitignore (block already present)', { timeout: 30_000 }, async () => { + dir = mkRepo('two-layer'); + const gitignorePath = path.join(dir, '.gitignore'); + fs.writeFileSync(gitignorePath, [ + '# Squad: state owned by squad-state branch (two-layer/orphan backend)', + '.squad/decisions.md', + '.squad/agents/*/history.md', + '# /Squad: state owned by squad-state branch', + '', + ].join('\n')); + const contentBefore = fs.readFileSync(gitignorePath, 'utf-8'); + + await migrateStateBackend(dir, 'orphan'); + + const contentAfter = fs.readFileSync(gitignorePath, 'utf-8'); + // Block is still present + expect(contentAfter).toContain('# Squad: state owned by squad-state branch'); + // Not duplicated + const occurrences = (contentAfter.match(/# Squad: state owned by squad-state branch \(two-layer\/orphan backend\)/g) ?? []).length; + expect(occurrences).toBe(1); + }); + + it('.gitignore round-trip: local→two-layer→local leaves file byte-identical', { timeout: 30_000 }, async () => { + dir = mkRepo('local'); + const gitignorePath = path.join(dir, '.gitignore'); + const existingContent = '# my project\nnode_modules/\ndist/\n'; + fs.writeFileSync(gitignorePath, existingContent); + + await migrateStateBackend(dir, 'two-layer'); + // Migrate back to local but this time need to create fresh repo with same config + // We need to start a new repo already set to two-layer then go back + const twoLayerDir = mkRepo('two-layer'); + const twoLayerGitignorePath = path.join(twoLayerDir, '.gitignore'); + fs.writeFileSync(twoLayerGitignorePath, existingContent); + await migrateStateBackend(twoLayerDir, 'local'); + // After round-trip: the marker block should not be present + const finalContent = fs.readFileSync(twoLayerGitignorePath, 'utf-8'); + expect(finalContent).not.toContain('# Squad: state owned by squad-state branch'); + try { fs.rmSync(twoLayerDir, { recursive: true, force: true }); } catch { /* best-effort */ } + }); }); From ffcd439980c99cdec038b72b1cca964deeddda46 Mon Sep 17 00:00:00 2001 From: Tamir Dresher Date: Tue, 23 Jun 2026 15:23:29 +0300 Subject: [PATCH 3/7] =?UTF-8?q?docs:=20SWE-bench=20Lite=20technical=20repo?= =?UTF-8?q?rt=20=E2=80=94=20198/300=20(66.0%)=20#1=20on=20leaderboard=20(#?= =?UTF-8?q?1373)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: add SWE-bench Lite technical report (198/300 = 66.0%) * fix: reconcile timeout numbers, add Limitations section, link public data repo Addresses Brady's review: - Clarify 31 timeouts total (20 no-patch + 11 partial-patch errors) - Remove wording inconsistency between Key Parameters and Error Analysis - Add Limitations & Attribution section (model vs orchestration) - Link to public tamirdresher/squad-swe-bench for full reproduction Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Brady Gaster <41929050+bradygaster@users.noreply.github.com> --- .../blog/033-swe-bench-lite-results.md | 285 ++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 docs/src/content/blog/033-swe-bench-lite-results.md diff --git a/docs/src/content/blog/033-swe-bench-lite-results.md b/docs/src/content/blog/033-swe-bench-lite-results.md new file mode 100644 index 000000000..e4c2adb18 --- /dev/null +++ b/docs/src/content/blog/033-swe-bench-lite-results.md @@ -0,0 +1,285 @@ +--- +title: "Squad Achieves #1 on SWE-bench Lite: 66% with Multi-Agent Orchestration" +date: 2026-06-23 +author: "Tamir Dresher" +wave: null +tags: [squad, benchmarks, swe-bench, multi-agent, results] +status: published +hero: "Squad v0.9.6 resolves 198 out of 300 real-world GitHub issues on SWE-bench Lite — a 66% resolve rate that would place it #1 on the leaderboard. Here's how multi-agent orchestration outperforms single-agent systems." +--- + +Squad v0.9.6 just completed its first official run on [SWE-bench Lite](https://www.swebench.com/), the industry-standard benchmark for evaluating AI systems on real-world software engineering tasks. The result: **198 out of 300 issues resolved (66.0%)**, which would place Squad at **#1 on the SWE-bench Lite leaderboard**. + +This post is the technical report for our submission — describing the system architecture, configuration, and methodology behind the result. + +## What is SWE-bench? + +SWE-bench Lite is a curated subset of 300 real GitHub issues from 12 popular open-source Python repositories (Django, Sympy, Matplotlib, Scikit-learn, etc.). Each task requires the system to: + +1. Read a GitHub issue description +2. Analyze the relevant codebase +3. Generate a patch that fixes the issue +4. Pass the repository's existing test suite + +This isn't code generation in a vacuum — it's real-world bug fixing against real test suites, in repositories with hundreds of thousands of lines of code. + +## Results Summary + +| Metric | Value | +|--------|-------| +| **Resolved** | 198 / 300 (66.0%) | +| **Patches generated** | 280 / 300 (93.3%) | +| **Patch apply errors** | 38 (12.7%) | +| **Unresolved (tests fail)** | 44 (14.7%) | +| **No generation** | 20 (6.7%) | + +### Leaderboard Context + +| Rank | System | Score | +|------|--------|-------| +| 🥇 | **Squad v0.9.6** | **66.0%** | +| 🥈 | Claude Opus 4.6 | 62.7% | +| 🥉 | MiniMax M2.5 | 56.3% | +| 4 | OpenAI GPT-5 | 54.3% | +| 5 | Claude Haiku 4.5 | 54.3% | + +### Per-Repository Breakdown + +| Repository | Resolved | Total | Rate | +|-----------|----------|-------|------| +| mwaskom/seaborn | 3 | 4 | 75.0% | +| django/django | 84 | 114 | 73.7% | +| pytest-dev/pytest | 12 | 17 | 70.6% | +| sphinx-doc/sphinx | 11 | 16 | 68.8% | +| astropy/astropy | 4 | 6 | 66.7% | +| pallets/flask | 2 | 3 | 66.7% | +| matplotlib/matplotlib | 15 | 23 | 65.2% | +| sympy/sympy | 48 | 77 | 62.3% | +| scikit-learn/scikit-learn | 14 | 23 | 60.9% | +| pylint-dev/pylint | 3 | 6 | 50.0% | +| pydata/xarray | 2 | 5 | 40.0% | +| psf/requests | 0 | 6 | 0.0% | + +## System Architecture + +Squad is a multi-agent orchestration framework built on [GitHub Copilot CLI](https://github.com/features/copilot). Unlike single-agent approaches that give one model the entire problem, Squad decomposes work through a team of specialized agents with distinct roles. + +### The Team + +For SWE-bench, Squad used its standard team configuration — no special tuning for the benchmark: + +``` +🏗️ Picard — Lead Architecture decisions, code review, routing +💻 Data — Code Expert C#, Go, .NET, Python — implementation +📋 Scribe — Session Logger Memory, decisions, context sharing +``` + +### How It Works + +```mermaid +graph TD + A[SWE-bench Issue] --> B[Squad Coordinator] + B --> C{Route to specialist} + C --> D[Data - Code Expert] + D --> E[Analyze codebase] + E --> F[Identify root cause] + F --> G[Generate patch] + G --> H[Commit fix] +``` + +1. **Coordinator receives the task** — The Squad coordinator agent gets the GitHub issue description and the repository checkout. + +2. **Routing** — The coordinator reads `routing.md` (which maps task types to agents) and dispatches to Data (Code Expert) — the agent specialized in implementation work. + +3. **Agent context loading** — Data receives: + - Its charter (role definition, boundaries, capabilities) + - Team decisions (architectural choices, conventions) + - History (learnings from past tasks) + - The full problem statement + +4. **Implementation** — Data analyzes the issue, navigates the codebase using available tools (grep, view, LSP), identifies the root cause, and generates a fix. + +5. **Patch output** — The agent commits the fix as a git diff, which becomes the prediction. + +### Why Multi-Agent Beats Single-Agent + +The key insight is **separation of concerns**: + +- The **Coordinator** handles routing logic, team state, and decisions — it never writes code. +- The **Code Expert** focuses purely on implementation — it doesn't need to manage its own context or decide what to work on. +- **Charters** prevent agents from drifting — Data knows exactly what it's responsible for and what's out of scope. +- **Decisions memory** accumulates patterns across tasks — even within a single benchmark run, the team builds institutional knowledge. + +This mirrors how effective human engineering teams work: specialists with clear ownership, guided by shared architectural decisions. + +## Configuration + +```yaml +# Squad SWE-bench runner configuration +model: gpt-4o +agent: squad +mode: autopilot (--yolo) +max_autopilot_continues: 50 +timeout_seconds: 1800 # 30 minutes per task +workers: 4 # parallel task execution +total_runtime: ~21 hours +``` + +### Key Parameters + +- **Model: gpt-4o** — Used for all agents (coordinator, lead, and code expert). No model mixing. +- **Autopilot mode** — Agents work without human intervention. The `--yolo` flag prevents confirmation prompts. +- **50 continuation limit** — Prevents infinite loops. Most tasks complete in 10-20 continuations. +- **30-minute timeout** — Generous but bounded. 31 tasks hit the timeout; 20 produced no patch at all, 11 produced partial patches that failed to apply cleanly. +- **4 parallel workers** — Processes 4 issues concurrently. Each worker gets its own git worktree. + +## Methodology + +### Pass@1 Submission + +Each instance is attempted exactly once. No retries, no best-of-k selection, no evaluation feedback loops. + +### No Test Knowledge + +The system does NOT use: +- `PASS_TO_PASS` tests (tests that should continue passing) +- `FAIL_TO_PASS` tests (tests that should start passing after the fix) +- `hints_text` field (human hints about the solution) + +### No Web Browsing + +Squad agents do not have web browsing capabilities. They work purely on: +- The local repository checkout +- The issue description provided by SWE-bench +- Their built-in knowledge from training + +### Evaluation + +Evaluation used the official `swebench.harness.run_evaluation` Docker harness: + +```bash +docker run --rm \ + -v /workspace/predictions.json:/workspace/predictions.json \ + -v /var/run/docker.sock:/var/run/docker.sock \ + swebench-eval \ + --dataset_name princeton-nlp/SWE-bench_Lite \ + --predictions_path /workspace/predictions.json \ + --run_id squad_v1 \ + --max_workers 4 +``` + +The harness: +1. Checks out each repository at the correct commit +2. Applies the predicted patch +3. Runs the repository's test suite +4. Verifies that previously-failing tests now pass +5. Verifies that previously-passing tests still pass + +## Integrity Verification + +We performed a 12-point integrity check on the results: + +1. ✅ All 300 SWE-bench Lite instances present in predictions +2. ✅ Report math checks out (198 + 44 + 38 + 20 = 300) +3. ✅ 300 unique instance_ids, all 12 repos represented +4. ✅ No `.squad/` or `.github/agents/` contamination in patches +5. ✅ All 198 resolved IDs have non-empty patches +6. ✅ Worker logs confirm Squad Team Mode active (sampled) +7. ✅ Data subagent confirmed spawned across sampled tasks +8. ✅ Per-repo rates are internally consistent +9. ✅ Evaluation via official swebench Docker harness +10. ✅ No test knowledge used (PASS_TO_PASS, FAIL_TO_PASS) +11. ✅ No hints field used +12. ✅ No web browsing capabilities available to agents + +## Error Analysis + +### Timeout Cases (31/300, 10.3%) + +31 tasks exceeded the 30-minute timeout. Of these: +- 20 produced no patch at all (the "no generation" category) +- 11 produced partial patches that failed to apply cleanly (included in the 38 "patch apply errors") + +Tasks that timed out typically involved: +- Very large codebases requiring extensive navigation +- Complex multi-file changes where the agent explored many approaches +- Issues requiring deep domain knowledge the model lacked + +### Patch Apply Errors (38/300, 12.7%) + +Generated diffs that couldn't cleanly apply to the target commit. Common causes: +- Line number offsets from generated vs actual file state +- Fuzzy matching failures on complex edits +- Patches generated against a slightly different file state + +### Unresolved — Tests Fail (44/300, 14.7%) + +Patches that applied cleanly but didn't fix the issue. These represent cases where the agent: +- Fixed a symptom but not the root cause +- Made a partial fix that didn't satisfy all test cases +- Misunderstood the issue requirements + +### No Generation (20/300, 6.7%) + +Tasks where the agent couldn't produce a patch within the time limit. All 20 overlap with the timeout cases above — no task failed to generate a patch for non-timeout reasons. + +## Limitations & Attribution + +### Model vs. Orchestration + +Squad's 66% result uses GPT-4o as the base model. An important question: how much of this performance comes from the model vs. the multi-agent orchestration? + +What we know: +- **GPT-4o single-agent** scores in the low-to-mid 40s% on SWE-bench Lite (based on public leaderboard entries for single-agent GPT-4o systems) +- **Squad + GPT-4o** achieved 66% — a ~20+ point improvement over the same base model used alone +- **Claude Opus 4.6 single-agent** (a stronger model) achieves 62.7% — still below Squad + GPT-4o + +This suggests the multi-agent architecture contributes meaningfully, but we cannot claim the full 66% is due to orchestration alone. A rigorous ablation study — running the same tasks with GPT-4o in a single-agent configuration using the same tools and timeout — is planned as a follow-up. + +### Other Limitations + +- **Single model tested** — We haven't yet run Squad with other base models (Claude, o3). It's possible the improvement varies by model. +- **Python-only benchmark** — SWE-bench Lite contains only Python repositories. Squad's performance on other languages is untested. +- **Cost not reported** — We haven't published per-task API costs, which matter for practical comparison. + +## What's Next + +- **Single-agent baseline** — Run GPT-4o without Squad orchestration as an ablation +- **Formal leaderboard submission** via sb-cli +- **SWE-bench Verified** run (500 instances, harder tasks) +- **Full SWE-bench** run (2,294 instances) +- **Model comparison** — Testing with Claude, o3, and other models as the base LLM +- **Team configuration experiments** — Does adding a dedicated Tester agent improve results? + +## Reproducing the Results + +All artifacts — predictions, evaluation report, worker logs, runner code, and agent configuration — are publicly available: + +**📦 [github.com/tamirdresher/squad-swe-bench](https://github.com/tamirdresher/squad-swe-bench)** + +The repository contains: + +``` +benchmarks/swe-bench/ +├── squad_swebench_runner.py # Main orchestrator +├── config.yaml # Configuration +├── squad-scaffold/ # Agent charters & team config +│ └── .squad/ +│ ├── team.md +│ ├── routing.md +│ └── agents/ +├── output/ +│ ├── predictions.json # 300-task predictions +│ └── squad-v1.squad_v1.json # Evaluation report +└── submission/ # Leaderboard submission package +``` + +## About Squad + +Squad is a multi-agent orchestration framework that turns AI coding assistants into coordinated engineering teams. Each agent has a persistent identity, charter, and memory — enabling the kind of specialization and institutional knowledge that makes human teams effective. + +Learn more at [bradygaster.github.io/squad](https://bradygaster.github.io/squad/). + +--- + +*This post serves as the technical report for Squad's SWE-bench Lite leaderboard submission (June 2026).* From 0d9e538e2cfa5051d37259318ebc1833200bc38e Mon Sep 17 00:00:00 2001 From: Tamir Dresher Date: Mon, 29 Jun 2026 20:14:26 +0300 Subject: [PATCH 4/7] fix: add tools: ['*'] to agent front-matter for agency compatibility (#1415) When running Squad through Microsoft's agency wrapper (agency cp), agents receive only 3 built-in tools instead of the full toolset. This happens because agency serializes a missing tools: key as tools: [], which Copilot CLI interprets as 'no tools allowed.' The wildcard tools: ['*'] explicitly means 'all tools' and is understood by both agency and raw Copilot CLI across all surfaces. Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/agents/squad.agent.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 0ec9b9448..8a6ce5d79 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -1,6 +1,7 @@ --- name: Squad description: "Your AI team. Describe what you're building, get a team of specialists that live in your repo." +tools: ['*'] --- From 25eae44b53bb2842f15bb2cd71c97a0ad7d6e598 Mon Sep 17 00:00:00 2001 From: Brady Gaster <41929050+bradygaster@users.noreply.github.com> Date: Mon, 29 Jun 2026 23:24:34 -0700 Subject: [PATCH 5/7] =?UTF-8?q?chore:=20promote=20dev=20=E2=86=92=20main?= =?UTF-8?q?=20(v0.11.0)=20(#1422)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: auto-scaffold Fact Checker agent during init and cast (#1222) The Fact Checker role landed in v0.10.0 (#789) with catalog entry, charter template, skill, AGENT_TEMPLATES map entry, and template manifest entry — but was never wired into the user-facing onboarding flow. Users running 'squad init' got Scribe/Ralph/Rai but never saw Fact Checker as a default or cast option. This mirrors how Rai was wired: - init.ts: adds 'fact-checker' to the default agents: array passed to sdkInitSquad() - cast.ts: adds factCheckerMember(), factCheckerCharter(), hasFactChecker branches in castTeam(), and the roster banner line Smoke-tested locally: 'squad init' in a clean repo now produces .squad/agents/fact-checker/charter.md alongside scribe/ralph/Rai. Closes #1222 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(upgrade): auto-scaffold Rai + Fact Checker on squad upgrade (#1222) Extends the #1222 fix to the third code path. \squad upgrade\ was intentionally silent on agents (preserves user state). For users upgrading from v0.9.x or earlier (no Rai) or v0.10.0 (no fact-checker), this means they'd never get the built-in agents unless they re-ran \squad init\ (which would overwrite other state). Adds \nsureBuiltinAgents()\ to \ unEnsureChecks()\. Idempotent — only scaffolds when the agent directory is absent. Never overwrites existing charters or history files. Sources content from the shipped \ emplates/{Rai,fact-checker}-charter.md\ templates (already present via TEMPLATE_MANIFEST). Scribe and Ralph are intentionally NOT scaffolded by upgrade — they predate this fix in every squad, and their charters are inlined in cast.ts (no shipped template file). Smoke tested locally: - Set up a simulated v0.9.4 squad (scribe + ralph only) - Ran \squad upgrade\ → 'scaffolded 2 built-in agent(s): Rai, fact-checker' - Ran upgrade again → no re-scaffold (idempotent) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(permissions): use 'approve-once' for Copilot CLI v1.0.54+ contract (#1192) The Copilot CLI post-v1.0.54 changed the permission handler contract to expect 'approve-once' instead of 'approved'. Update the handler, type definition, and error hint to match the new contract. Closes #1191 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: Squad.Agents.AI - Microsoft Agent Framework adapter for the Squad CLI (#1207) * feat: Squad.Agents.AI community NuGet for MAF integration Squad CLI as Microsoft.Extensions.AI IChatClient, composing GitHub.Copilot.SDK via AsAIAgent() from Microsoft.Agents.AI.GitHub.Copilot 1.7.0-preview. Closes Track A of the Q1-Q7 design lock (see tamresearch1 .squad/decisions.md Decisions 441, 443, 444, 447). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: add PR body for #3 * fix(SquadAgent): inherit AIAgent (was IChatClient force-cast) - SquadAgent now properly inherits from Microsoft.Agents.AI.AIAgent - Removed (IChatClient)(object)agent force-cast - Overrides all AIAgent abstract members (CreateSessionCoreAsync, RunCoreAsync, etc.) - DI registration now registers AIAgent (not IChatClient) - README updated to use AIAgent.RunAsync API - No more abstraction inversion; AIAgent is the correct layer Fixes the architectural error identified by Tamir. * docs(SquadAgent): rewrite README — prerequisites, Hello World, troubleshooting, preview callout Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(SquadAgent): GitHubTokenProvider callback + redact options ToString Adds async token provider pattern for production scenarios (KeyVault/MSI integration). - GitHubTokenProvider property takes precedence over GitHubToken - GitHubToken marked [JsonIgnore] to prevent serialization leaks - SquadAgentOptions.ToString() redacts GitHubToken field - Updated CreateCopilotClient to resolve token from provider first Mitigates P0 #3: token leakage via ILogger structured-log calls, IOptions snapshots, and serializers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(SquadAgent): document GitHubTokenProvider callback for production token management Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(SquadAgent): bind ConnectionStrings__squad via IConfigureOptions + add smoke tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(README): replace removed WithTeamRoot with positional teamRoot ctor WithTeamRoot was deleted in commit 35767c90 in favor of mandatory positional teamRoot constructor argument on AddSquad. The Aspire example in the Squad.Agents.AI README still showed the old fluent API, which would now fail at compile time for anyone copy-pasting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(squad-agents-ai): add routing integration tests Closes the routing-verification gap identified during squad-squad onboarding: the API surface existed but routing semantics weren't functionally tested. New tests verify persona pass-through, boundary-instruction injection on first turn, WorkingDirectory isolation (Decision 452a), and CopilotClientOptions-based routing (Decision 447). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci(squad-agents-ai): add .NET build/test/pack workflow PR #3 CI was Node/docs-only — adding the .NET gate so green actually reflects the package code. Matrix on ubuntu + windows, restore/build/test/pack, uploads TestResults and nupkg artifacts. Closes the build-verification gap identified during squad-squad onboarding (see .squad/decisions.md adoption record, 2026-06-02). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci(squad-agents-ai): NuGet publish workflow + Dependabot config - .github/workflows/squad-agents-ai-release.yml: workflow_dispatch and tag-driven publish to nuget.org with --skip-duplicate idempotency, fail-fast on missing NUGET_API_KEY secret, optional GitHub Release on tag - .github/dependabot.yml: nuget (src + test) + github-actions, weekly, M.A.AI major allowed, OpenTelemetry major deferred (per Decision 602) Closes the release-pipeline + supply-chain-tracking gaps identified during squad-squad onboarding. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(squad-agents-ai): release-ready docs + .csproj packaging metadata - README updates / XML docs on public surface - CHANGELOG.md with [0.1.0-preview] - 2026-06-02 entry - .csproj: Description, RepositoryUrl, Authors, PackageTags, PackageReadmeFile - Verified via dotnet pack — .nupkg contains README, LICENSE, xml-docs Closes the docs-readiness gap for v0.1-preview publish. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci(squad-agents-ai): switch release triggers to dev/main branch-driven Per Tamir's release-strategy directive (decisions.md 2026-06-02): - dev merges → prerelease publish (suffix scheme mirrors Squad CLI) - main merges → stable publish - workflow_dispatch retained as manual escape hatch - tag-driven trigger removed (branches are the source of truth) Version derivation pattern adapted from the Squad CLI's existing release workflow. --skip-duplicate retained for idempotent reruns. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs+fix: PR #3 review pass — hygiene, XML docs, cliArgs, multi-named connections - Rewrite the package README and PR validation flow around the AIAgent surface and ambient Copilot authentication. - Keep public docs free of internal process references and remove obsolete deferral language. - Preserve connection-string cliArgs through CopilotClientOptions and cover the behavior with a routing test. - Add named connection-string lookup via AddSquadAgent("name") using ConnectionStrings:squad-{name}. - Document the new public overloads and verify the package builds without warnings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: Round 2 — keyed DI, BYOK delegate, routing gate, security hardening - Add ConfigureCopilotClient delegate on SquadAgentOptions for BYOK - Add routing gate: snapshot/restore Cwd/CliPath/CliArgs after delegate (Picard C1) - Add 4 AddKeyedSquadAgent overloads with .NET 8+ keyed DI - Fix Environment credential leak: [JsonIgnore] on Environment, GitHubTokenProvider, ConfigureCopilotClient - ToString() redacts token-pattern keys (TOKEN/KEY/SECRET/HMAC/PASSWORD/CREDENTIAL) - Add 21 new tests (43 total): security redaction, keyed DI, BYOK routing gate - Update README: streaming, keyed DI, BYOK, security sections Complies with: Picard C1-C4, Worf SC-1 through SC-8. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: add Squad.Agents.AI sample app demonstrating DI, keyed DI, BYOK, and streaming - samples/squad-agents-ai-sample/Program.cs: four runnable flows Flow 1 -- AddSquadAgent + RunAsync (basic DI) Flow 2 -- AddKeyedSquadAgent x2 + GetRequiredKeyedService Flow 3 -- ConfigureCopilotClient delegate (BYOK token + env var injection) Flow 4 -- RunStreamingAsync with await foreach token-by-token output - samples/squad-agents-ai-sample/Squad.Agents.AI.Sample.csproj: net10.0, project reference to src/Squad.Agents.AI, Microsoft.Extensions.Hosting 10.0.0 - samples/squad-agents-ai-sample/README.md: prerequisites, run commands, per-flow walkthrough, troubleshooting table - .github/workflows/squad-agents-ai-ci.yml: adds paths trigger and restore + build steps for the sample (no run step -- requires live CLI) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(Squad.Agents.AI): co-locate sample under src/ and consolidate README - Moves the sample app from samples/squad-agents-ai-sample/ to src/Squad.Agents.AI/samples/Squad.Agents.AI.Sample/ so it lives alongside the package it demonstrates. - Folds the sample's standalone README into the package README, giving consumers one canonical doc for both the API and the runnable demo. - Adds to Squad.Agents.AI.csproj so the library's wildcard glob does not pick up Program.cs in the co-located samples subdirectory. - Updates the .csproj project reference, and CI workflow paths to match the new layout. - Verified end-to-end: dotnet build, dotnet test (43/43 passing), and a sample sanity-check run (clear CLI-not-found error, no stack trace). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(Squad.Agents.AI): remove outdated draft PR body file The standalone pr-body.md was an early draft authored before the live PR description took its final shape. The PR body on GitHub is the canonical source; this file is dead weight and would confuse maintainers reviewing the diff. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(Squad.Agents.AI): address PR #1207 reviewer feedback (12 items) - Snapshot CliArgs by value, not reference, so in-place mutation by SDK consumers is also caught by the routing guard. - Validate name/connectionName/serviceKey is non-empty in all DI registration overloads; previously null/whitespace produced invalid connection-string keys. - Replace ghp_-prefixed placeholder in the sample with a clearly-fake token to avoid tripping secret-scanning and to remove a real-token lookalike. - Remove brittle 'PR #3' references from README and CHANGELOG; describe the feature without tying to a specific PR thread. - Update NuGet metadata and README links to point to bradygaster/squad (canonical repo) instead of the tamirdresher fork. - Multi-target the test project to match the package's target framework set so CI exercises every framework the package ships against. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(Squad.Agents.AI): adopt DelegatingAIAgent base + MAF ctor pattern (PR #1207 r2) Addresses westey-m's review feedback: - Extend Microsoft.Agents.AI.DelegatingAIAgent — drops ~70 lines of manual Core* overrides; pass-through is provided by the base class. - Adopt MAF constructor pattern: \(string squadFolderPath, SquadAgentOptions? options = null, ILoggerFactory? loggerFactory = null)\. Required settings on the constructor, options optional, ILoggerFactory stays on ctor for DI injection. - Routing-guard, IAsyncDisposable, and security posture preserved. - Add Squad.Agents.AI.slnx solution for easy IDE open. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Reno (Copilot) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Co-authored-by: tamirdresher * ci(Squad.Agents.AI): switch NuGet publish to Trusted Publishing (OIDC) (#1240) Replace the long-lived NUGET_API_KEY repo secret with NuGet/login@v1 OIDC token exchange (1-hour API key) per the modern Trusted Publishing flow: https://learn.microsoft.com/nuget/nuget-org/trusted-publishing Why --- - No long-lived credentials stored in the repo. - Token is scoped to this workflow + repo + branch via the OIDC subject claim and the Trusted Publishing policy registered on nuget.org. - Eliminates the chicken-and-egg between needing admin to set NUGET_API_KEY and needing the package live to validate the workflow. What changed ------------ - Add `id-token: write` to the publish job (required for OIDC). - Drop the `Verify NuGet API key` step. - Drop the `--api-key ` reference to secrets.NUGET_API_KEY. - Add `NuGet/login@v1` step that exchanges the OIDC token for a short-lived API key, exposed via `steps.nuget-login.outputs.NUGET_API_KEY`. - Add a fail-fast check for the new `vars.NUGET_USER` repository variable (non-sensitive; the nuget.org profile name that performs the exchange). - Update the file header documentation to reflect the new flow and link to the Trusted Publishing setup page. Required configuration before first publish ------------------------------------------- 1. Create the `Squad` organization on nuget.org and add owners. 2. Configure a Trusted Publishing policy at https://www.nuget.org/account/trusted-publishing owned by the Squad org: Repository Owner: bradygaster Repository: squad Workflow File: squad-agents-ai-release.yml Environment: (empty) 3. Set repository variable NUGET_USER (Settings → Secrets and variables → Actions → Variables) to a Squad-org-member's nuget.org profile name (NOT email). Variable, not secret — the username is non-sensitive. Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(Squad.Agents.AI): expose SessionConfig + default OnPermissionRequest (#1252) Squad.Agents.AI 0.1.0-preview.2 calls `CopilotClient.AsAIAgent(instructions, name)` which leaves `SessionConfig.OnPermissionRequest` unset. The first call to `SquadAgent.CreateSessionAsync()` therefore throws: System.ArgumentException: An OnPermissionRequest handler is required when creating a session. For example, to allow all permissions, use CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll }); There was no public way for consumers to fix it from the outside — `ConfigureCopilotClient` only exposes `CopilotClientOptions`, not the per-session `SessionConfig` that owns the permission handler. What changed ------------ - `SquadAgent` now switches to the `AsAIAgent(client, sessionConfig, ...)` overload and constructs a `SessionConfig` with: - `OnPermissionRequest = PermissionHandler.ApproveAll` (sensible default for a host-process Squad adapter; the host already chose to instantiate Squad and is responsible for sandboxing). - `WorkingDirectory` defaulting to the resolved `Cwd` / `SquadFolderPath`. - `SystemMessage = new SystemMessageConfig { Content = Instructions }` when `SquadAgentOptions.Instructions` is set. - `SquadAgentOptions.ConfigureSession: Action?` is new — runs after Squad applies its defaults so a consumer can swap in a stricter permission handler, pin the model, restrict tools, etc. Tests ----- - New `SquadAgentSessionConfigTests` (4 cases) exercising the new surface: `ConfigureSession` is settable, runs against the live SessionConfig, can replace the permission handler, and can set `AvailableTools`. - All existing 43 tests still pass per TFM (141 total across net8/9/10). Verified -------- - The end-to-end consumer smoke test in `C:\Users\tamirdresher\source\repos\squad-agents-ai-consume-test` previously had to fall back to the raw SDK because of the missing handler. With this change, `SquadAgent.CreateSessionAsync()` returns successfully and `RunAsync` drives real 3-turn conversations against `.squad/`-init'd teams. Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Squad.Agents.AI 0.2.0: bump to MAF 1.10.0-rc1 / SDK 1.0.0 GA — file-IO works (#1259) * Squad.Agents.AI: bump to MAF 1.10.0-rc1 / SDK 1.0.0 GA — file-IO now works Microsoft.Agents.AI.GitHub.Copilot bumped from 1.7.0-preview to 1.10.0-rc1, which transitively brings GitHub.Copilot.SDK 1.0.0 GA. SDK 1.0.0 reshaped CopilotClientOptions and SessionConfig (the breaking namespace + property renames are mirrored below), but in return Copilot CLI 1.0.61 talks to the SDK over the new ACP-style protocol — meaning the agent's view/grep/powershell tools finally work end-to-end without 'permission errors' or 'content exclusion policy' hallucinations. SDK 1.0.0 API migrations applied: * Namespace GitHub.Copilot.SDK -> GitHub.Copilot. * CopilotClientOptions.Cwd -> WorkingDirectory. * CopilotClientOptions.CliPath + .CliArgs collapsed into a single CopilotClientOptions.Connection (RuntimeConnection). We now build the Connection via RuntimeConnection.ForStdio(CliPath, CliArgs) only when the consumer supplied either a custom CLI path or extra CLI args; otherwise the SDK's default child-process connection is used and the bundled copilot.exe (downloaded by the SDK's build/ targets) is invoked. * SessionConfig.ConfigDir -> SessionConfig.ConfigDirectory. * PermissionRequestHandler is no longer a named delegate type; we use type inference where the test code referenced it. Public Squad.Agents.AI surface is intentionally unchanged: SquadAgentOptions still exposes Cwd, CliPath, CliArgs, ConfigureSession, ConfigureCopilotClient. We translate to the SDK 1.0.0 shape internally. Routing gate (Picard Condition 1 / Worf SC-3) updated to snapshot and restore WorkingDirectory + Connection instead of the old Cwd / CliPath / CliArgs trio. A delegate that REPLACES Connection (e.g. via RuntimeConnection.ForStdio(...)) is reverted, mirroring the previous CliPath / CliArgs hijack tests. Verified end-to-end against a real .squad-initialised team root: the coordinator successfully reads .squad/team.md and enumerates every cast member with their role. The dotnet test suite passes 46/46 across net8.0, net9.0, and net10.0 (-1 vs baseline because the old CliPath and CliArgs hijack tests collapsed into a single Connection hijack test, which is the right granularity for SDK 1.0.0). Version bumped 0.1.0-preview -> 0.2.0 to surface the MAF/SDK transitive bump. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address Copilot review feedback (#1259) - Remove duplicate 'using GitHub.Copilot' in SquadAgentSessionConfigTests (regex replace inadvertently doubled the directive when migrating from the old GitHub.Copilot.SDK namespace). - Drop hardcoded teamRoot override I left behind in the sample Program.cs during the CLI-not-found debug session. The sample now correctly reads SQUAD_TEAM_ROOT (or falls back to CWD) as documented. - Fix mismatched comment in SquadAgent: the SDK-protocol section now correctly references --allow-all (matching the flag actually injected in CreateCopilotClient), not the narrower --allow-all-tools. - Detect more existing permission-opening flags before injecting our default --allow-all so a host that opts in via --allow-all-paths, --allow-all-urls, or the omnibus --yolo no longer gets --allow-all prepended on top. Comparison is now case-insensitive. Updated the connection-string test that asserted the old over-eager behavior. Also add .vs/, *.user, *.userprefs to .gitignore so VS solution junk doesn't surface in git status. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address pre-existing Copilot review comments from PR #1212 (#1258) - Workflow permissions: add issues: write to squad-pr-nudge, squad-impact, and squad-repo-health workflows that call issues.createComment - Logic bug: fix ahead_by → behind_by in pr-nudge stale branch check - Logic bug: fix PR_LABELS fallback producing string instead of null - Script fix: check legacy statuses for failure/error in checkCIStatus() - Script fix: truncation row column count mismatch in pr-readiness.mjs - Script fix: validate JSON.parse result is array before using as labels - Script fix: isNodeBuiltin now validates node: prefix against known builtins - YAML escaping: use JSON.stringify for skill descriptions in apm.yml Closes #1213 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: expose memory tools via MCP + route writes through governed pipeline (#1251) - Expose memory.classify, memory.write, memory.search, memory.promote, memory.delete, memory.audit through the squad_state MCP server (#1244) - Pin squad_state to user-level ~/.copilot/mcp-config.json during init/upgrade for external `copilot -p` mode compatibility (#1247) - Update squad.agent.md directive-capture and decision-recording instructions to route through memory.write instead of raw squad_state_write to the drop-box (#1246) Closes #1244 Closes #1247 Closes #1246 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(skills): update path references from .squad/skills/ to .copilot/skills/ (#1260) The CLI and SDK write skills to .copilot/skills/ by default, but docs still referenced .squad/skills/. Update all documentation to use the canonical .copilot/skills/ path and add a note about legacy fallback. Closes #1241 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(init): prompt to add @copilot as a team member during squad init (#1262) Add an interactive prompt during squad init that asks users if they want to add @copilot (the GitHub Copilot coding agent) as an autonomous team member. If accepted, adds the Coding Agent section to team.md and copies copilot-instructions.md into the project. Non-interactive mode skips silently with a hint to run `squad copilot enable` later. Closes #1147 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: align skills path + state-backend upgrade flow (#1241, #1226) (#1249) - Update all docs references from .squad/skills/ to .copilot/skills/ - Note that both paths are scanned at read time but .copilot/skills/ is write default - List all 6 git hooks (add pre-commit and post-commit to docs) - Correct 'read-only reference' claim about migrated files - Add recovery section for pre-commit hook refusal scenarios Closes #1241 Closes #1226 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Squad.Agents.AI 0.3.0: first-class subagent observability (#1265) Adds a typed OnSubagentTrace callback + OpenTelemetry ActivitySource so consumers can see subagent dispatch (the coordinator's 'task' tool spawning specialist sub-agents and their replies) without writing their own polymorphic dispatch over the raw GitHub.Copilot SessionEvent hierarchy. The previous 0.2.x surface required consumers to write the same boilerplate the aspire-squad-resource demo had to (CopilotSessionTraceMapper.cs): switch on every event subtype, unwrap subagent context, manage Activity lifetime. 0.3.0 makes that the SDK's job. New public surface: * SquadAgentOptions.OnSubagentTrace (Action) Set this to subscribe to subagent lifecycle (Selected / Started / Completed / Failed), assistant messages from coordinator AND subagents, tool start/complete, and SessionIdle. Setting OnSubagentTrace implicitly turns on SessionConfig.IncludeSubAgentStreamingEvents so subagent replies actually flow up to the parent session (otherwise they stay inside the subagent session and never reach the callback). * SquadAgentTraceEvent record — typed envelope (Kind, RawEventType, Timestamp, SdkAgentId, SubagentName, SubagentDisplayName, ToolCallId, Content, Success, RawEvent). Carries the original SessionEvent on RawEvent for advanced consumers but exposes everything else through neutral primitive types so the callback signature has no transitive dependency on GitHub.Copilot.SDK. * SquadAgentTraceEventKind enum — categorises the SessionEvent into the well-known cases that downstream observability surfaces want. * SquadAgentDiagnostics.ActivitySourceName ('Microsoft.Agents.AI.Squad') + SquadAgentDiagnostics.ActivitySource — one Activity per subagent dispatch is opened on SubagentStartedEvent and disposed on the matching SubagentCompletedEvent / SubagentFailedEvent, tagged with squad.subagent.name, squad.subagent.display_name, squad.subagent.sdk_agent_id, and squad.subagent.reply_preview (a short truncated copy of the subagent's assistant message). Hosts that .AddSource(SquadAgentDiagnostics.ActivitySourceName) on their OpenTelemetry tracer get these spans in their backend — the Aspire dashboard renders them in the trace view automatically. Internal: * SquadSubagentTraceMapper — wires SessionEvent -> SquadAgentTraceEvent and the Activity lifecycle. Held by SquadAgent and disposed during DisposeAsync to drain any subagent activities that never received a matching Completed event (e.g. session ended mid-dispatch). * InternalsVisibleTo=Squad.Agents.AI.Tests so the mapper can be unit tested directly without spinning a real CLI session. Tests: 54/54 passing across net8, net9, net10 (+8 new tests for the observability surface including Activity lifetime, tag propagation, mid-session disposal, and consumer-callback exception isolation). Verified end-to-end against the tamresearch1 Star Trek squad: the OnSubagentTrace callback observed two parallel subagent dispatches (Picard, Data), captured each of their replies attributed to the right SdkAgentId, and the matching 'squad.subagent Picard' / 'squad.subagent Data' OTel spans opened and closed cleanly with the reply tagged on each span. Version bumped 0.2.0 -> 0.3.0 to signal the new public surface. Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(lockfile): sync package-lock with package.json after v0.10.0 bump (#1266) After the v0.10.0 stable release on 2026-06-07, the root package-lock.json still recorded packages/squad-cli@0.9.6-preview.15 and packages/squad-sdk@0.9.6-preview.13 for the workspace entries. The package.json files were updated correctly during the release, but the lockfile workspace metadata was not regenerated. This causes `npm ci --ignore-scripts` to fail in the `sdk-exports-validation` CI job whenever package.json versions diverge from these stale lockfile entries (which happens on every build that runs `scripts/bump-build.mjs`). Confirmed on PR #1257. This commit regenerates only the workspace version metadata (`packages/squad-cli` and `packages/squad-sdk`) — no dependency trees are touched. Verified locally on Windows + Node v23.5.0: - `npm ci --ignore-scripts` at repo root: ✅ exit 0 (was already passing on dev because root package.json/lockfile match; failure mode is the workspace-entry mismatch surfacing under specific build conditions) - `npm install --ignore-scripts`: no further drift produced - Full `npm test` suite: 6535 passed / 134 failed / 60 skipped — the 134 failures are all pre-existing Windows file-locking flakiness (EBUSY/ENOTEMPTY/hook-timeout); identical failure mode and similar count on the immediate pre-merge commit cc37a2f7 (125 failures pre-merge, 134 post-merge — diff is within flake noise). - Targeted re-run of the 5 "newly failing" files in isolation: 93/93 passing — confirms the bulk-run failures are concurrency-induced flakes, not regressions from the recent merges (#1251, #1258, #1260, #1262, #1249). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(bump-build): also update package-lock workspace entries (#1267) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: release pipeline version pinning (#1203, #1204) (#1250) * fix: release pipeline version pinning (#1203, #1204) - Lower SDK dependency floor from >=0.10.0 to >=0.9.0 so the CLI tarball resolves against the last published SDK when current version isn't yet on the registry (Closes #1203) - Add isLocalOrUnpublishedVersion guard so local dev builds and versions with build metadata (+) fall back to @insider instead of writing unresolvable version strings into MCP config (Closes #1204) - Extend resolveSquadStateMcpSpec to short-circuit for build-metadata versions - Add CI step to verify SDK dependency is resolvable before CLI publish Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: reset accidentally-bumped version 0.10.0-build.2 -> 0.10.0 Pre-publish version guard rejects -build.N suffixes (release pipeline policy). The bump was made by an unintended local 'npm run build' run before commit. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher Co-authored-by: Copilot * chore: include CHANGELOG.md in published npm tarball (#1257) * chore: include CHANGELOG.md in published npm tarball Add CHANGELOG.md to the files array in both squad-cli and squad-sdk package.json files so changelogs are included in published npm tarballs. This enables offline what's-new prompts and removes the need for GitHub API calls to show release notes. Closes #1171 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: reset accidentally-bumped versions to 0.10.0 Pre-publish version guard rejects -build.N suffixes (release pipeline policy). Both SDK and CLI package.json had -build.4 from a local 'npm run build' run before commit. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher Co-authored-by: Copilot * Squad.Agents.AI 0.4.0: default-on subagent observability + Aspire-style connection-string lookup (#1271) Two small but high-impact changes that remove ~30 lines of boilerplate from every consumer (Aspire and otherwise) and make the OpenTelemetry story self-explanatory. ## 1. EmitSubagentActivities (default true) — telemetry independent of callback Today, the per-subagent OpenTelemetry Activity emission is a side-effect of setting `OnSubagentTrace`. A host that just wants `squad.subagent {Name}` spans in their backend has to wire a callback they don't need. `Microsoft.Agents.AI.Squad` is silent until then. 0.4.0 makes activity emission the default: * New `SquadAgentOptions.EmitSubagentActivities` (defaults to `true`). * `SquadAgent` installs `SquadSubagentTraceMapper` whenever `EmitSubagentActivities || OnSubagentTrace != null`, so spans flow with zero extra wiring. * `OnSubagentTrace` becomes a pure customisation hook (logging, dashboards, metrics) — independent of telemetry. Set `EmitSubagentActivities = false` to opt out of built-in spans when you want to handle telemetry yourself. Plus richer span shape: every lifecycle phase is now an `ActivityEvent` on the live subagent span (visible as annotated markers on the timeline in Aspire / Jaeger / etc.): * `squad.subagent.start` — on SubagentStarted * `squad.subagent.message` — on AssistantMessage (with message_preview tag) * `squad.subagent.completed` — on SubagentCompleted * `squad.subagent.failed` — on SubagentFailed Net effect for a consumer: builder.Services.AddOpenTelemetry() .WithTracing(t => t.AddSource(SquadAgentDiagnostics.ActivitySourceName)); builder.Services.AddSquadAgent(o => o.SquadFolderPath = "/team"); …and the dashboard lights up. No callback wiring, no Activity.Current?.AddEvent plumbing in the host. ## 2. Aspire-style connection-string lookup (with legacy fallback) Aspire injects connection strings under the literal resource name — e.g. an AppHost that calls `builder.AddSquad("research-squad", ...)` exposes `ConnectionStrings:research-squad` to the consumer. The 0.3.0 SDK only looked at `ConnectionStrings:squad-research-squad` (prefixed), so Aspire consumers had to manually call `Configuration.GetConnectionString(name)`, parse the URI, and feed `SquadFolderPath` into the configure callback themselves. 0.4.0 tries the literal name first and falls back to the legacy prefixed form: | Style | Example | Lookup | |--------------------------------------|---------------------------------------------|--------------------------------------------| | Aspire-style direct (tried first) | `AddSquadAgent("research-squad")` | `ConnectionStrings:research-squad` | | Legacy prefixed fallback | `AddSquadAgent("research")` | `ConnectionStrings:squad-research` | Both work. Existing consumers using `ConnectionStrings:squad-{name}` continue unchanged; new Aspire consumers get the natural one-line registration. ## Tests 54 → 64 tests, all passing on net8.0/9.0/10.0. New `SquadAgentDefaultObservabilityTests` covers: * Default-on activity emission (without consumer callback) * Opt-out path (`EmitSubagentActivities = false`) — span suppression + callback still fires * Each ActivityEvent name (`start` / `message` / `completed` / `failed`) * Connection-string precedence: Aspire-direct preferred, prefixed fallback used, same rule applies for keyed registrations Existing `SquadSubagentTraceTests` and the new class share an `[Collection("SquadActivityListeners")]` so they run serially — process-global `ActivityListener` state caused cross-test pollution otherwise. ## Files * `src/Squad.Agents.AI/SquadAgentOptions.cs` — new `EmitSubagentActivities` property + reworked `OnSubagentTrace` XML doc to clarify independence. * `src/Squad.Agents.AI/SquadAgent.cs` — install trace mapper when telemetry OR callback is requested. * `src/Squad.Agents.AI/SquadSubagentTraceMapper.cs` — accept `emitActivities` flag; gate `StartActivity`/`Dispose` on it; add `ActivityEvent` annotations at every lifecycle boundary. * `src/Squad.Agents.AI/SquadAgentOptionsConfigurator.cs` — accept a list of candidate connection-string names; first non-empty wins. * `src/Squad.Agents.AI/SquadServiceCollectionExtensions.cs` — new `GetConnectionStringNames` returns `[name, "squad-"+name]` so both Aspire and legacy conventions resolve. * `src/Squad.Agents.AI/Squad.Agents.AI.csproj` — bump 0.3.0 → 0.4.0. * `src/Squad.Agents.AI/README.md` — new "Subagent observability" section, updated "Aspire / configuration path" section, two new option rows in the Key Options table. ## Backward compatibility Fully backward compatible. The two-arg `SquadSubagentTraceMapper` constructor defaults `emitActivities` to `true`, `EmitSubagentActivities` defaults to `true`, and the legacy `ConnectionStrings:squad-{name}` lookup still resolves. Net change for an existing consumer that had OnSubagentTrace set: nothing (mapper runs in both 0.3.0 and 0.4.0 because OnSubagentTrace is non-null). Net change for a consumer that did NOT set OnSubagentTrace but did AddSource: they now get spans they always asked for. Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Squad.Agents.AI 0.5.0: auto-inject --agent squad (CLI parity by default) (#1275) * Squad.Agents.AI 0.5.0: auto-inject --agent squad (CLI parity by default) The whole point of SquadAgent is to wrap a Squad coordinator team — but the 0.4.x SDK launched the underlying copilot.exe with the CLI's built-in generic agent. The coordinator therefore had no instructions to eager-execute, fan out, or dispatch via the task tool, so it role-played responses inline. Concretely: this SDK call builder.Services.AddSquadAgent(o => o.SquadFolderPath = teamRoot); did NOT behave the same as running copilot --agent squad interactively against the same team root. Consumers had to remember to add `opts.CliArgs.Add(""--agent""); opts.CliArgs.Add(""squad"");` themselves, which is an SDK leak — the class is literally called SquadAgent. 0.5.0 makes --agent squad the SDK default: * New `SquadAgentOptions.AgentFileName` (defaults to `""squad""`). * On client construction, SquadAgent looks for `{teamRoot}/.github/agents/{AgentFileName}.agent.md`. If it exists, `--agent {AgentFileName}` is auto-prepended to the CLI args. * If the file is missing (folder not Squad-initialized), the inject is silently skipped and a Debug log line explains why. The CLI then starts with its default agent, which is what 0.4.x did anyway. * If the consumer already supplied `--agent X` in `CliArgs`, the explicit value wins and we do NOT add a second one. * Set `AgentFileName = null` (or whitespace) to opt out entirely. Net effect: SquadAgent.RunAsync now matches `copilot --agent squad` for any Squad-initialized team root, without the consumer doing anything. ## Tests 64 -> 71 tests, all passing on net8.0/9.0/10.0. New `SquadAgentDefaultAgentFlagTests` (uses a per-test temp dir to scaffold or omit the agent file deterministically): * Default AgentFileName is ""squad"" * Auto-inject when squad.agent.md exists * No inject when the file is missing (graceful degradation) * Explicit --agent in CliArgs wins (no second --agent added) * Custom AgentFileName=""data"" injects --agent data when data.agent.md exists * AgentFileName=null opts out entirely * AgentFileName=whitespace opts out entirely Backward compatibility: existing routing tests use a non-existent `C:\squad-team-root` path, so the file-existence check silently skips the inject — those tests continue to pass with no changes. ## Files * `src/Squad.Agents.AI/SquadAgentOptions.cs` — new `AgentFileName` property with XML doc covering the default, the opt-out, and the not-yet-initialized fallback. * `src/Squad.Agents.AI/SquadAgent.cs` — auto-inject logic in `CreateCopilotClient` (after the `--allow-all` block, before `options.CliArgs` are appended) with file-existence + already-supplied guards and a Debug log when the file is missing. * `src/Squad.Agents.AI/Squad.Agents.AI.csproj` — bump 0.4.0 -> 0.5.0. * `src/Squad.Agents.AI/README.md` — new ""Coordinator agent selection"" section with the precedence table; `AgentFileName` row added to Key Options table. * `+test/Squad.Agents.AI.Tests/SquadAgentDefaultAgentFlagTests.cs` Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * amend: use SessionConfig.Agent instead of --agent CLI args GitHub.Copilot SDK's SessionConfigBase exposes an Agent (string) property that is the first-class equivalent of the Copilot CLI's --agent flag. It discovers and loads .github/agents/{name}.agent.md exactly the same way the CLI does, but without us having to munge CliArgs. Switch the 0.5.0 default-coordinator-agent implementation: - SquadAgent now sets sessionConfig.Agent = options.AgentFileName (default ""squad"") right after constructing the SessionConfig, before ConfigureSession runs. - Drop the --agent CliArgs hack (we no longer need to detect ""did the consumer already pass --agent?"" because ConfigureSession naturally wins over our default). - Tests now assert against sessionConfig.Agent via reflection over the inner DelegatingAIAgent — exactly what consumers using ConfigureSession would see. - README ""Coordinator agent selection"" section reworded to say ""sets SessionConfig.Agent"" instead of ""auto-adds --agent"". 71/71 tests still pass on net8.0/9.0/10.0. The fifth new test (ConfigureSession_CanOverrideAutoSetAgent) explicitly proves the ConfigureSession callback can replace the auto-set value, which is the clean override path now that --agent CliArgs is gone. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Squad.Agents.AI 0.5.1: fix 0.5.0 regression — use --agent CLI flag (not SessionConfig.Agent) (#1277) 0.5.0 (#1275) replaced the previous --agent CliArgs approach with sessionConfig.Agent = options.AgentFileName, on the theory that the SDK property was the first-class equivalent of the CLI's --agent flag. It is not. SessionConfig.Agent looks up the name in the SDK's CustomAgents registry (programmatic agent definitions, never populated by SquadAgent), NOT in .github/agents/*.agent.md files on disk. The result was a runtime error on every RunAsync call against a Squad-initialised team: Communication error with Copilot CLI: Request session.create failed with message: Custom agent 'squad' not found Verified at GitHub.Copilot.SDK 1.0.0: * SessionConfigBase.Agent (string) — selects from CustomAgents * SessionConfigBase.CustomAgents (IList) — programmatically defined inline agents (Name, Prompt, Tools, Skills, Model, etc.). Empty by default. * The CLI's --agent flag is currently the only path that reads .github/agents/{name}.agent.md on disk. 0.5.1 reverts to the original CliArgs implementation: * SquadAgent now auto-prepends '--agent {AgentFileName}' to combinedCliArgs (back to what 0.5.0 originally proposed before the SessionConfig.Agent detour). * The file-existence check at {teamRoot}/.github/agents/{name}.agent.md still gates the inject so non-Squad-initialized folders degrade gracefully (no --agent passed -> CLI uses default agent). * The 'consumer already supplied --agent in CliArgs' guard is back so the SDK does not add a duplicate. Tests: * New SquadAgentDefaultAgentFlagTests covers all seven cases via reflection over Connection.Args (the CLI-args path the SDK actually uses): default 'squad' value, auto-inject when file exists, no inject when missing, explicit --agent wins, custom AgentFileName works, AgentFileName=null/whitespace opts out. * The older SquadAgentDefaultAgentTests (which targeted SessionConfig.Agent) is removed since that property does NOT do what we wanted. 71/71 tests passing on net8.0/9.0/10.0. Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add squad registry add/list/remove for discovery-only peer squads (#1291) Closes #1290. Adds CLI surface for managing .squad/squad-registry.json, symmetric to squad upstream. Registry entries are discovery-only (visible to squad discover and squad delegate) but do NOT trigger inheritance of the peer squad's skills/decisions/wisdom/routing. Previously users had to hand-edit .squad/squad-registry.json — even the squad discover empty-state hint told them to "create a squad-registry.json" manually. This adds proper commands: squad registry add # validates manifest, refuses duplicate squad registry list # show all registered peers squad registry remove # remove by name Also fixes a subtle path-semantics confusion: readManifest() now accepts BOTH the repo root AND a path with a trailing .squad segment. The docs and SKILL.md showed the .squad-suffixed form but the code previously joined .squad/manifest.json onto whatever you gave it, so the suffixed form silently failed empirical reproduction (discover returned nothing). Test coverage: 18 new tests in cross-squad-registry.test.ts covering the dual-path readManifest fix, registry round-trip, add/list/remove behavior including duplicate-name and invalid-manifest rejection, and end-to-end integration with discoverSquads. Also updates: - cross-squad SKILL.md (canonical + 2 template mirrors) to document the registry vs upstream distinction explicitly - squad discover empty-state hint to mention squad registry add - squad help text with the new commands - SDK exports for the new registry helpers + RegistryEntry/ AddRegistryEntryResult types End-to-end verified locally: - squad registry add (both repo-root and .squad-suffixed paths) - squad registry list (rich output) - squad registry remove (success + missing-name warning) - squad discover picks up registry entries with source=registry - 50/50 tests pass (32 existing + 18 new) - npm run build clean Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): stop writing squad_state to ~/.copilot/mcp-config.json on every init/upgrade (#1296) (#1298) * fix(cli): stop writing squad_state to ~/.copilot/mcp-config.json on every init/upgrade (#1296) squad init (init.ts:408) and squad upgrade (upgrade.ts:738) unconditionally called ensureSquadStateMcpInUserConfig, writing squad_state_ to ~/.copilot/mcp-config.json keyed by a stable project-path hash. Each new squad init accumulated another entry in HOME with no garbage collection. This contradicted the explicit iter-8 design intent documented at packages/squad-cli/src/cli/core/mcp-root.ts:1-27, which says iter-8 stops writing to HOME and writes squad_state ONLY to repo-root .mcp.json. The repo-root .mcp.json writes (init.ts:403 / upgrade.ts:728) already cover all documented Copilot CLI launch modes - copilot and copilot -p both walk up from cwd to find .mcp.json. Out-of-tree copilot -p invocations should use --additional-mcp-config @.mcp.json (already documented at init.ts:494). Changes: * Removed the unconditional ensureSquadStateMcpInUserConfig call from init.ts:408 and upgrade.ts:738. Replaced both with comments explaining iter-8 + #1296. * Removed the now-unused import from both files. * Kept the function definition at mcp-root.ts:178-228 - a future squad doctor --mcp-prune cleanup helper may want to inspect HOME. Tests: * New regression test in test/cli/init.test.ts: "should NOT write any squad_state entries to ~/.copilot/mcp-config.json (#1296)". Isolates the developer's real HOME by setting USERPROFILE/HOME to a temp dir before init, asserts no squad_state* keys appear under temp HOME. * All 40 existing init tests still pass. npm run lint clean. Closes #1296 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: comments reference actual Copilot CLI version (≥1.0.59), drop hardcoded test line numbers Reviewer follow-ups on #1298 (closes #1296): 1. The comments in init.ts/upgrade.ts/mcp-root.ts referenced 'Copilot CLI 5.3+' as the version that auto-loads .mcp.json. The real shipping CLI is at 1.0.62 (5.3 was a typo'd projection from review). Updated all 5 occurrences to '≥1.0.59' — the lowest version where the .mcp.json walk-up behavior is documented. 2. The init.test.ts regression comment hard-coded line numbers (init.ts:408, upgrade.ts:738) that will go stale on any unrelated edit to those files. Rewrote the comment to identify the call by function name (ensureSquadStateMcpInUserConfig) instead — durable against re-orderings. Verified: regression: #1296 test still passes (1/16 in init.test). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(init): post-init package.json script tip includes --agent squad The post-init tip showing how to add a non-interactive `squad:copilot` script to package.json was: "squad:copilot": "copilot --additional-mcp-config @.mcp.json" This omits `--agent squad`, so users who copy/paste it get a generic Copilot CLI session that doesn't load the Squad coordinator, team.md, casting, or MCP-wired memory/state tools — only the additional MCP config gets loaded. Same underlying issue surfaced in the cross-squad- communication SKILL.md sweep (squad/wire-cross-squad-skill commit 6b0eac21): anywhere we spawn `copilot` into a Squad-initialised repo, we must pass `--agent squad`. Single-line fix: "squad:copilot": "copilot --agent squad --additional-mcp-config @.mcp.json" Verified: a fresh `squad init` smoke test now prints the corrected tip line verbatim. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(sdk): wire team.md/routing.md/casting state on \squad preset apply\ (#1288) (#1293) * fix(sdk): wire team.md/routing.md/casting state on `squad preset apply` (#1288) `squad preset apply ` only copied agent charters into .squad/agents/. It left .squad/team.md `## Members` empty, .squad/routing.md missing `## Work Type -> Agent` rows, and never created .squad/casting/registry.json, history.json, or policy.json. Net result: the coordinator's mode-switch check saw an empty Members table and treated every session as Init Mode, proposing to re-scaffold the team the user already applied. This change adds a merge-friendly scaffold module (packages/squad-sdk/src/presets/scaffold.ts) that runs after charters are copied and: * writes/updates team.md `## Members` (creates from scratch if missing; appends new rows to an existing table while preserving the surrounding Coordinator / Project Context sections; idempotent on repeat apply) * writes/updates routing.md `## Work Type -> Agent` (creates or appends) * writes/merges casting/registry.json (universe = `preset:`) * appends a snapshot to casting/history.json + a universe_usage_history entry * creates casting/policy.json with defaults only if missing (never clobbers) Agents with `status: 'error'` are excluded from wiring; agents with `status: 'skipped'` (already exist in target) ARE wired so the team reflects user intent. Scaffolder failure is reported as a synthetic error result without masking the per-agent install results. Verified: * npm run lint passes * test/presets.test.ts: 28/28 pass including 4 new regression tests - wires preset agents into team.md ## Members (#1288) - merges preset agents into an existing team.md without duplicating rows - writes casting registry.json, history.json, and policy.json (#1288) - appends routing rows for preset agents to routing.md (#1288) Out of scope (tracked separately): deduplicating these writers with the equivalent fresh-write versions in packages/squad-cli/src/cli/core/cast.ts. Closes #1288 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(presets): role-aware Status cell + non-colliding sentinel for synthetic scaffold error Two reviewer follow-ups on #1293 (closes #1288): 1. Members-table Status was hardcoded to '✅ Active' for every preset agent. Presets that ship one of the always-on built-ins (Scribe, Ralph, Rai, Fact Checker) would render with the wrong status label compared to a fresh cast: '✅ Active' instead of '📋 Silent' / '🔄 Monitor' / '🛡️ RAI' / '🔍 Verifier'. Added a small statusForRole() helper that mirrors the role→status mapping in cast.ts:652-655 (case-insensitive role matching to tolerate preset authors who lowercase the role string). Built-in role names get their canonical labels; everything else falls back to '✅ Active'. Added a regression test asserting the labels for a preset that ships scribe/ralph/rai/fact-checker + one regular agent. 2. Synthetic scaffold-failure result row used 'agent: presetName' for its 'agent' field. If the preset itself happens to include an agent literally named after the preset ('squad preset apply geektime' on a preset whose roster has a 'geektime' agent), the consumer of PresetApplyResult[] could not distinguish the synthetic scaffold- level error from a real per-agent install error. Replaced with the angle-bracketed sentinel '' (which validateName() rejects, so it can never collide with a real agent name) and moved the preset name into the human-readable reason string so consumers don't lose that context. Verified: 29/29 preset tests pass (28 existing + 1 new role-status test). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(docs): tell coordinator to roster Fact Checker on first-time cast (#1299) (#1300) * fix(docs): tell coordinator to roster Fact Checker on first-time cast (#1299) squad init correctly creates .squad/agents/fact-checker/ on disk (per merged PR #1223). But when the user opens copilot --agent squad and the coordinator runs first-time casting, it OMITS Fact Checker from the team.md ## Members table while including Scribe, Ralph, and Rai. Root cause: .squad-templates/squad.agent.md had two gaps: 1. Line 56 said "team size (typically 4-5 + Scribe)" — naming only Scribe 2. Rai had a dedicated ## Rai section with explicit "Rai always appears in team.md" instruction — Fact Checker had no equivalent section So the model added Rai (because instructed to) but had no instruction to add Fact Checker, even though the agent dir was scaffolded on disk. Fix: * Update team-size line to name all 4 always-on built-ins: Scribe + Ralph + Rai + Fact Checker * Add full ## Fact Checker — Verification & Devil's Advocate section mirroring the Rai pattern: roster-entry instruction, dual operating mode (per #789 + #1254), trigger phrase table, confidence ratings, DA brief structure, boundaries, state location Sync via sync-templates.mjs --sync propagates squad.agent.md changes to all 4 mirror targets: .squad-templates/, templates/, packages/squad-cli/ templates/, packages/squad-sdk/templates/, .github/agents/. Tests: new test/squad-agent-roster.test.ts runs against all 4 template targets and asserts: * The "Determine team size" line names all 4 built-ins * A ## Fact Checker section exists with "always appears in team.md" * The section declares dual operating mode (anchors #789 + #1254 design so a future PR can't accidentally split Fact Checker and Devil's Advocate again — cf. closed PR #1294) * Existing Ralph + Rai sections still present 16/16 pass. Closes #1299 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * doc(squad.agent.md): clarify Fact Checker is exempt from casting + correct on-demand reference path Reviewer follow-ups on #1300: 1. Team-size phrasing — the line read 'typically 4-5 + Scribe + Ralph + Rai + Fact Checker' which a model could parse as arithmetic (4-5 + 4 = 8-9, but it could also collapse). Rewrote it to make the composition explicit: '4-5 cast (user-domain) agents + 4 always-on built-ins = 8-9 total roster entries'. 2. Cast-exemption parity — Scribe, Ralph, and Rai each have an explicit 'exempt from casting' bullet but Fact Checker did not. Added the matching bullet right after Rai's. 3. Bad on-demand reference path — the FC section pointed at '.squad/templates/fact-checker-charter.md'. That file IS shipped (TEMPLATE_MANIFEST destination 'templates/fact-checker-charter.md') but only AFTER 'squad init' or 'squad upgrade' has populated .squad/templates/. A reader of squad.agent.md on an un-initialized repo (or in .github/agents/ on the cloud agent surface) would follow a dead link. Repointed to the '.squad/agents/fact-checker/charter.md' instance that ensureBuiltinAgents creates as part of the same init/upgrade path — that's where the rich charter actually lives at runtime per #1299 + #1301. All 4 mirrored copies re-synced via scripts/sync-templates.mjs. fact-checker-role.test.ts: 8/8 pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(sdk): plumb Fact Checker like Rai — rich charter at init + .squad/fact-checker/ state dir (#1299 deep) (#1301) * fix(sdk): plumb Fact Checker like Rai — rich charter at init + .squad/fact-checker/ state dir (#1299 deep) PR #1300 fixed the documentation gap so the coordinator knows to roster Fact Checker. This PR fixes the structural gap behind it. Per user testing 2026-06-13: even after #1300 the actual agent on disk was still "a name on disk with a 21-line placeholder". Three structural problems: 1. squad init never used the rich {role}-charter.md templates. Both Rai and fact-checker got 478-byte generic stubs from generateCharter(). Rich templates only ran via squad upgrade's ensureBuiltinAgents path. 2. fact-checker had no state dir. Rai gets .squad/rai/{policy.md, audit-trail.md} via init.ts lines 879-941. fact-checker had nothing equivalent. 3. fact-checker-charter.md was only in packages/squad-cli/templates/ — missing from .squad-templates/ (canonical source) AND packages/ squad-sdk/templates/. SDK init's getSDKTemplatesDir() resolves to the SDK templates dir, so even if init tried to read the rich charter, the file wasn't there. Fix (4 parts): Part 1 - Rich charter at init (benefits BOTH Rai and fact-checker): * SDK init.ts agent loop now looks up {templatesDir}/{role}-charter.md for each agent and uses that as charter.md content if it exists. Falls back to generateCharter() for user-defined agents. * Result: fresh squad init produces .squad/agents/Rai/charter.md at 4525 bytes (full Rai charter) and fact-checker/charter.md at 3024 bytes (full FC charter). Previously both were 478-byte stubs. Part 2 - .squad/fact-checker/ state dir mirroring .squad/rai/: * New block in init.ts (right after the Rai seeding) creates .squad/fact-checker/policy.md (from templates/fact-checker-policy.md or inline fallback) and audit-trail.md. * New .squad-templates/fact-checker-policy.md (~6KB) is the canonical authority for dual-mode operating rules per #789 + #1254: - Mode 1 Verification: ✅/⚠️/❌/🔍 confidence rating taxonomy - Mode 2 Devil's Advocate: required brief structure - Hard anti-fabrication rules - Advisory by default with narrow blocking exceptions - Audit trail rules (succinct, never raw source) Part 3 - Fix .squad-templates/ distribution gap: * Copied fact-checker-charter.md into .squad-templates/ so sync-templates.mjs propagates it to all 4 mirror targets including packages/squad-sdk/templates/. This unblocks Part 1. Part 4 - Plumbing: * .gitattributes: .squad/fact-checker/audit-trail.md merge=union * TEMPLATE_MANIFEST: fact-checker-policy.md * squad.agent.md Files Catalog: 2 new rows for FC state files Tests: 3 new regression tests in test/init.test.ts (28/28 pass total). npm run lint clean. Composability: This PR builds on #1300 (which adds the ## Fact Checker section to squad.agent.md and the team-size line fix). Both PRs modify squad.agent.md in disjoint regions and merge in either order. Full plumbing requires BOTH to land. Closes #1299 (deep fix; #1300 was the surface fix) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(sdk): lowercase fallback for rich-charter template lookup + sync .github/agents Two reviewer follow-ups on #1301 (#1299 deep): 1. Case-sensitive FS bug in rich-charter lookup The lookup tried '\-charter.md' and '\-charter.md' only. For Rai (role='Rai', name='Rai') this becomes 'Rai-charter.md', but the actual file shipped lowercase ('rai-charter.md'). On Windows the lookup succeeded because the filesystem is case-insensitive; on Linux CI it silently missed and fell back to the 478-byte generic stub — exactly the regression #1299 was trying to fix. Reproduced by 'should use the rich Rai-charter.md template at init' failing with 'expected 476 to be greater than 1000' on GitHub Actions. Add toLowerCase() candidates after the exact-case ones. De-dupe via a Set so we don't double-stat when role and name are already lowercase (fact-checker case). Guard each candidate against blank keys. 2. Template-sync parity The canonical .squad-templates/squad.agent.md gained two Fact Checker rows in the Files Catalog but the mirrored .github/agents/squad.agent.md copy was never re-synced, so the template-sync.test.ts byte-for-byte parity check would have fired. Run 'node scripts/sync-templates.mjs --sync' to regenerate. Verified: vitest 'rich Rai-charter' passes locally after the fix (previously failing on Linux CI run 27464079078). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(skills): rename squad disambiguation skill to squad-help (supersedes #1297) (#1302) * fix(skills): rename disambiguation skill 'squad' -> 'squad-help' (supersedes #1297) PR #1297 added a disambiguation skill named 'squad' so models calling skill(Squad) would get a redirect. After local end-to-end testing on 2026-06-13: skill ships to disk correctly but never shows up in Copilot CLI's /skills list. Root cause (verified against Copilot CLI source 1.0.62-2 app.js): 1. Copilot CLI's skill schema is {name, description, source, baseDir, allowedTools, pluginName, pluginVersion} (line 989). Frontmatter fields triggers:, domain:, confidence:, license: are silently ignored. 2. Skill loader returns {skills, warnings, errors} (line 4427). Skills that fail to load are reported as errors. 3. A skill named 'squad' collides with the Copilot agent named 'Squad' (registered at .github/agents/squad.agent.md). The agent wins; the skill is hidden from /skills. Fix: * Rename 'squad' -> 'squad-help' (avoids the agent-name collision; still descriptive enough for natural-language match when user says 'how do I use squad' or 'squad help') * SKILL.md content: name: 'squad-help', removed unused triggers:/domain:/ confidence:/source:/license: fields, added allowedTools: [], rewrote description: to be self-explanatory, added explicit note that /squad slash command does NOT exist (slash commands are CLI built-ins, not auto-mapped from skills) * MANIFEST_SKILL_NAMES in sdk-init.ts: 'squad' -> 'squad-help' * New TEMPLATE_MANIFEST entry in templates.ts for squad-help (so squad upgrade also propagates the skill - that code path uses TEMPLATE_MANIFEST instead of MANIFEST_SKILL_NAMES) Tests: new test asserts .copilot/skills/squad-help/SKILL.md exists with right frontmatter; explicit regression guard against re-introducing name: 'squad'. 26/26 init tests pass. npm run lint clean. Supersedes #1297. Out of scope (separate issue worth filing): squad upgrade synced only 10 of 16 installed skills - TEMPLATE_MANIFEST (used by upgrade) is out of sync with MANIFEST_SKILL_NAMES (used by init). Skills from PRs #1292 + #1295 (tiered-memory, iterative-retrieval, reflect, cross-squad, cross-squad-communication) have entries in MANIFEST_SKILL_NAMES but not TEMPLATE_MANIFEST. Follow-up will fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(skill): add confidence + domain frontmatter to squad-help Reviewer follow-up on #1302: the isSkillContent() classifier in sharing/consult.ts:990 requires BOTH name: AND confidence: in the frontmatter to recognize a file as a skill. Without confidence:, squad-help would not be detected as a skill in cross-squad merge / share / promote flows — it would be misclassified as a generic markdown decision. The Copilot CLI itself silently ignores custom frontmatter fields (per sdk/index.js decompile — only name/description/allowedTools/ user-invocable are read), so adding confidence: high and domain: squad-onboarding is safe at the CLI surface and necessary at the SDK surface. Applied identically to all 3 mirrored copies of squad-help/SKILL.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(prompt): coordinator must probe for squad_state/memory tools before mutating state (closes #1305) (#1306) * fix(prompt): coordinator must probe for squad_state/memory tools before mutating state on non-local backends (closes #1305) Symptom: a coordinator session against a Squad with stateBackend two-layer wrote .squad/decisions.md and history files via raw create/edit tools, never calling any squad_state or memory tool. The pre-commit hook caught the contract violation; the agent treated it as a 'git problem' instead of the symptom it was. Root cause: two failure modes stacked. Mechanical (Copilot CLI): MCP server tools are loaded lazily, not always advertised in the model's initial function list. Squad cannot fix this server-side; out of scope. Behavioral (squad.agent.md): pre-1305 prompt said 'when memory tools are available, use them' which models read as 'if listed' instead of 'after probing'. No hard refusal clause when the agent is about to violate the state-backend contract. Two changes to .squad-templates/squad.agent.md (synced to 4 mirror targets): 1. New 'State-backend handshake' section (MANDATORY, every session): - Skip for local/worktree backends - For orphan/two-layer/git-notes: probe via tool_search_tool_regex for squad_state_health; call it to confirm the bridge answers - On probe fail: HALT and tell the user verbatim how to fix (restart Copilot CLI or change stateBackend to local) 2. Replaced soft 'if not available' language with a HARD RULE in Memory Governance Tools section. Lists 10 forbidden paths (decisions.md, decisions/inbox, agents history, casting JSON, identity, memory, orchestration-log, log, rai+fact-checker audit-trail) that MUST NOT be written via create/edit/write_file on non-local backends when the bridge isn't reachable. Preserved the local/worktree carve-out. Also clarified that memory.* and squad_state_* share the same MCP server (same registry in packages/squad-cli/src/cli/commands/state- mcp.ts), so models stop treating them as separate availability checks. Tests: new test/state-backend-handshake.test.ts asserts 5 invariants against all 4 template mirror targets (20 tests total): handshake mandatory + every-session timing + squad_state_health probe + HALT remediation + HARD RULE with forbidden paths + local-backend carve- out. 20/20 pass. npm run lint clean. Out of scope: server-side MCP tool preload (Copilot CLI feature request), skill reinforcement in init-mode/agent-conduct (small follow-up). Closes #1305 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: tighten handshake-rule regex (split into 3 separate assertions) Reviewer follow-up on #1306: the regex '/create.*edit.*write_file|create\s*\/\s*edit/i' has '|' operator precedence ambiguity — the alternation binds at the top, so the shorter branch 'create\s*/\s*edit' could match the rule paragraph even if 'write_file' was missing entirely. The test was meant to ensure all three tool names appear in the HARD RULE list; this fix replaces it with three separate \b-anchored assertions, one per tool, so dropping any one fails the test. 20/20 handshake tests still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * feat(skills): make /squad a real slash command (user-invocable: true + rename squad-commands → squad) (#1303) * feat(skills): make /squad a real slash command — rename squad-commands to squad with user-invocable:true Users want /squad in Copilot CLI to show Squad's command catalog. Verified via Copilot CLI 1.0.62-2 source (sdk/index.js line 2618): getLoadedSkills().filter(e => e.userInvocable) .map(e => ({name: \/\\, isSkill: true, skill: e})) Any skill with frontmatter user-invocable: true is auto-registered as a slash command at /. The previous skill 'squad-commands' had no user-invocable field (Copilot CLI defaults to false), so /squad-commands never existed and users had no slash entrypoint. Changes: * Renamed squad-commands -> squad (canonical .squad/skills/squad/) * Frontmatter rewritten to Copilot CLI schema: - name: squad (was squad-commands) - user-invocable: true (the load-bearing change) - description: self-explanatory for natural-language match - allowedTools: [] - Removed unused fields: domain:, confidence:, source:, triggers: * Body updated to reference /squad as primary invocation * MANIFEST_SKILL_NAMES updated * TEMPLATE_MANIFEST updated (so squad upgrade syncs it too) * Removed stale squad-commands template dirs from both packages Tests: new test asserts user-invocable: true frontmatter AND name: squad (both are load-bearing for the slash command to register). 26/26 init tests pass. npm run lint clean. Composability: * Disjoint from #1292 (skills bundling) - both touch MANIFEST_SKILL_NAMES but this is a rename not an add * Disjoint from #1302 (squad-help disambiguation) - that one covers the skill(Squad) misdirect, this one covers the /squad slash UX Out of scope (future): machine-wide install (~/.copilot/skills/squad/) so /squad init works in folders without .squad/ yet. Requires a squad install --global-skill command + 'no .squad yet' menu branch. Will file separate follow-up. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(squad.agent.md): update squad-commands → squad skill xref Follow-up to reviewer comment on #1303: when this PR renamed '.copilot/skills/squad-commands/SKILL.md' to '.copilot/skills/squad/SKILL.md' (to make /squad a real user-invocable slash command), the routing-table entry inside squad.agent.md still pointed at the old path. Coordinators loading squad.agent.md from a freshly upgraded squad would have followed a dead reference and reported 'skill not found'. Updated the row in all 5 mirrored copies (.squad-templates, .github/agents, packages/squad-cli/templates, packages/squad-sdk/templates, templates/). Also added a hint that users can invoke the skill directly via /squad, since that is now the documented surface for the same content. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(skills): install bundled skills to .github/skills/ (closes #1126) (#1304) * fix(skills): install bundled skills to .github/skills/ (closes #1126) Per the official Agent Skills spec docs (GitHub, VS Code), the canonical project-level custom-skills location is .github/skills/. The legacy .copilot/skills/ is invisible to all Copilot surfaces except Squad itself - cloud agent, CLI outside Squad, VS Code extension, @copilot coding agent all ignore it. Changes: * squad init writes bundled skills to .github/skills/{name}/SKILL.md * squad upgrade does the same AND migrates legacy .copilot/skills/{name}/ -> .github/skills/{name}/ for manifest skills only (user-added skills at .copilot/skills/ are preserved). Tombstones empty legacy dir. * TEMPLATE_MANIFEST destinations: 10 skill entries retargeted * ENSURE_DIRECTORIES: .copilot/skills -> .github/skills * squad.agent.md narrative: 5-path scan order now lists .github/skills as primary, .copilot/skills as legacy. Personal scope unchanged. * All user-facing docs updated (README, spawn-reference, squad-commands skill, release-process skill, build.ts skill creation paths, SDK type-comment paths). Migration semantics in upgrade.ts: * Move-only-if-new-location-empty: legacy at .copilot/skills/, new location empty -> move + tombstone legacy * Tombstone-on-collision: both locations exist -> remove legacy, new wins (then syncAllSkills overwrites manifest skills per overwriteOnUpgrade=true semantics) * Preserve user-added: skills NOT in TEMPLATE_MANIFEST stay at .copilot/skills/ untouched * All best-effort with try/catch - disk failures do not block upgrade Tests (188/188 pass): * New: init.test.ts asserts canonical path + legacy NOT created * New: upgrade.test.ts asserts manifest migration + user-skill preservation + collision tombstoning * Updated: 8 existing test files retargeted to .github/skills NOT changed (intentional): * .copilot/skills/ stays in coordinator skill-discovery scan order for backward compat with user-added skills * ~/.copilot/skills/ (personal scope) unchanged - that's Copilot CLI's official personal-skills location * Runtime skill-loader tests (skill-source, skills-export-import, tools, skill-script-loader) unchanged - those test loader behavior which still supports .copilot/skills/ as a scan path Closes #1126 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: squad_skill writes to .github/skills, sync .github/agents copy, align issue refs Reviewer follow-ups on #1304 (closes #1126): 1. squad_skill tool implementation matches its description now Description says skills live at '.github/skills/{name}/SKILL.md' but the handler was still writing to '.copilot/skills/' — every successful 'write' shipped to a different path than what the tool documented (and what squad init/upgrade now install to). Fixed the handler so: - write operations go to '.github/skills/{name}/SKILL.md' - read operations check .github/skills first, then fall back to .copilot/skills (legacy) and .squad/skills (in-repo team skills), in that precedence — so users with un-migrated existing skills can still read them. 2. .github/agents/squad.agent.md re-synced from .squad-templates The canonical template had the corrected routing-table reference ('.github/skills/squad-commands/SKILL.md') but the .github/agents mirror copy was never re-synced. The byte-for-byte template-sync parity test would have fired on next CI. Ran 'node scripts/sync-templates.mjs --sync'. 3. Align issue references: #1304 → #1126 in code/docs/tests This PR closes #1126; #1304 is the PR number. The migrator docstring, the init.ts comment, and the two upgrade.test.ts cases all called the regression '#1304' — confusing for anyone digging into git blame for the canonical issue. Renamed to #1126 where the references describe the bug origin (kept #1304 only where the comment specifically describes 'the PR that implemented it'). 4. Migrator docstring corrected to match reality Old docstring claimed 'skips skills already present at the new location with the same content' but the implementation never compares content — it unconditionally tombstones the legacy copy when the new location exists. Rewrote the doc paragraph to describe what the code actually does (preserves the new-location copy verbatim). Verified locally: ✓ vitest test/cli/upgrade.test.ts -t 'migrate manifest skills|should NOT clobber' ✓ vitest test/tools.test.ts -t squad_skill Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(tools): squad_skill writes go to .github/skills now (CI fix) CI failure on commit 9de50ded: 'squad_skill handler > should write skill file' and 'should default confidence to medium' both still asserted '.copilot/skills/{name}/SKILL.md' as the write destination. With squad_skill's handler now writing to '.github/skills/' (the canonical Copilot CLI custom-skills location, per the fix in the same commit), those assertions need to be updated. The handler's read-fallback chain still finds .copilot/skills/ and .squad/skills/ for legacy installs, but the write target is fixed. Verified locally: 5/5 squad_skill tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(init): update squad-help + /squad slash test paths to .github/skills/ (cascade fix on top of #1126) After #1303 + #1302 landed first (with their tests asserting .copilot/skills/), the rebase of #1304 (which moves manifest skills to .github/skills/) leaves the test paths pointing at the old location. Update them so the rebased branch's CI is green. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * feat(sdk): bundle cross-squad-communication skill (#5 cross-squad protocols) (#1295) * feat(sdk): bundle cross-squad-communication skill (#5 cross-squad protocols) PR #1291 added squad registry add/list/remove and the cross-squad/SKILL.md for peer discovery. But discovery is only half the story — once a peer is known, agents need to know HOW to actually exchange info with it. This change ports cross-squad-communication from tamirdresher/squad-skills into the bundled skills so a fresh squad init produces a coordinator that knows the four communication patterns: * Pattern 0: Synchronous CLI session (--working-directory targeting peer) * Pattern 1: Read-only metadata scan (team.md / decisions.md) * Pattern 2: Async git-based request/response (.squad/cross-squad/...) * Pattern 3: Issue-based delegation (gh issue with squad:cross-squad label) Plus decision tree for pattern choice, anti-patterns, request/response YAML schemas, and validation status. Changes: * .squad/skills/cross-squad-communication/SKILL.md (379 lines, canonical source). sync-skill-templates.mjs propagates to both packages templates. * MANIFEST_SKILL_NAMES in packages/squad-sdk/src/config/init.ts grows by 1 entry: cross-squad-communication. Now 11 entries. * cross-squad/SKILL.md gets a one-paragraph "Companion skill" note pointing to cross-squad-communication for protocol details. Together: cross-squad answers "who?" (registry discovery), cross-squad-communication answers "how?" (the 4 patterns). The original plugin documented validation against specific internal Microsoft repos. Examples in this version use generic names (platform-squad, research-squad) so they are meaningful to upstream users. Protocol mechanics are unchanged. Frontmatter source: attributes the port. Test: new "should install cross-squad-communication skill" in init.test.ts asserts SKILL.md is at install path and content contains expected pattern names. 26/26 init tests pass. npm run lint clean. Composes with #1291: * squad init → both skills bundled * squad registry add ../peer → peer discoverable * coordinator: "ask peer X for their team members" → uses Pattern 1 by reading team.md from the registered peer Note on overlap with #1292: PR #1292 (skills bundling fix) adds 4 entries to MANIFEST_SKILL_NAMES; this PR adds 1. Disjoint additions to the same array; either can land first. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: TEMPLATE_MANIFEST entry for cross-squad-communication + pattern numbering + honest source/validation Five reviewer follow-ups on #1295: 1. MANIFEST_SKILL_NAMES added 'cross-squad-communication' but TEMPLATE_MANIFEST didn't — 'squad upgrade' would skip the skill even though 'squad init' installed it. Added the missing entry to TEMPLATE_MANIFEST so upgrade syncs it like every other manifest skill. (Same drift bug pattern noted on #1292/#1303.) 2-3. Both SKILL.md files (companion 'cross-squad' and full 'cross-squad-communication') said the skill covers 'the four communication patterns (sync CLI, async git-based, issue-based)' but only listed three — Pattern 1 (read-only knowledge query) was missing from the intro list. Rewrote both intro lines to spell out all four: Pattern 0 (sync CLI), Pattern 1 (read-only), Pattern 2 (git-based async), Pattern 3 (issue-based). 4. The doc had 'Pattern 4: Cross-Repo Dependency Scan' which broke the 'four numbered patterns 0-3' framing — Pattern 4 isn't a communication protocol, it's a one-off analysis utility for discovering how two repos relate. Moved it to a clearly labeled '### Appendix' section with a note explaining why it's not a numbered pattern. 5. Frontmatter 'source:' claimed the skill was 'validated against two production squad instances' but the Validation Status section said Patterns 2 and 3 were 'untested end-to-end'. Reworded 'source:' to acknowledge that only Pattern 0 (and partially Pattern 1) are end-to-end-validated; Patterns 2/3 are documented from design and need live validation before production use. Also softened the same overclaim in the Validation Status prose (replaced 'validated against two production' with 'drafted against two prototype' + explicit acknowledgement that 2/3 are design-only). All 3 mirrored copies of cross-squad-communication/SKILL.md and 3 copies of cross-squad/SKILL.md kept in sync (canonical = .squad/, mirrors = packages/squad-cli/templates/, packages/squad-sdk/templates/). Verified: 31/31 init + builtin-skills tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(skill): cross-squad-communication uses real Copilot CLI invocations (ghcs→copilot, real flags) The cross-squad-communication SKILL.md as drafted instructed coordinators to use a non-existent CLI shape. Verified empirically against `copilot --help` (v1.0.62): - `ghcs` is not a binary on a clean install; the real CLI is `copilot` (resolved to C:\ProgramData\global-npm\copilot.ps1 on a default node install). - `--working-directory ` does not exist. Real flag is `-C ` (git-style "change directory first"). - `--no-mcp` does not exist. Real flag is `--disable-builtin-mcps`. - `-p, --prompt ` takes the prompt STRING, not a path. Earlier examples passed `-p \` (a path), which makes the CLI try to execute the path string as a prompt. Read the file with `Get-Content -Raw` first. - `--allow-all-tools` was missing from autonomous (non-interactive) invocations — without it every tool call prompts for permission and hangs the cross-squad delegation indefinitely. - Log directory was `~/.agency/logs/` (older `agency` runtime). New CLI logs under `~/.copilot/logs/`. Switched to a `Test-Path`-guarded fallback so both transitional and clean installs resolve correctly. All 14 call-site occurrences in cross-squad-communication/SKILL.md updated (3 mirrored copies). Mirrors verified byte-identical. New regression test in test/cross-squad.test.ts asserts each of the 8 correctness properties (ghcs/—working-directory/—no-mcp absent; `copilot -C`, `--allow-all-tools`, `--disable-builtin-mcps`, `-p (Get-Content`, and `.copilot\\logs + Test-Path` present) for every mirror. 8 × 3 mirrors = 24 new assertions; 56/56 cross-squad tests pass. Catches a fairly large embarrassment: had this skill landed as drafted, every fresh `squad init` coordinator would have run a totally bogus CLI command on the first delegation attempt. Credit to the peer-squad `read this skill, ran into errors, asked why` loop for surfacing it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(skill): every cross-squad copilot spawn passes --agent squad A peer-squad delegation that runs the Copilot CLI without --agent squad gets a generic Copilot CLI session — the spawned process never loads .github/agents/squad.agent.md, the peer's team.md, MCP tools, casting, or coordinator behaviour. The whole point of cross-squad delegation collapses: the answer comes from an off-the-shelf model, not from the peer's Squad. All 7 example command lines in cross-squad-communication/SKILL.md (Pattern 0 Options A/B/C, Liveness Protocol MCP-skip retry, and the two Anti-Pattern examples) now include --agent squad. Also added a new top-of-Patterns "Universal rule: every copilot spawn into a peer squad MUST pass --agent squad" section that: - States the rule once, prominently. - Explains the failure mode (generic session, no team.md / MCP / coordinator loaded) so future edits don't strip the flag thinking it's redundant. - Notes the only exception (--resume preserves original session agent). - Generalises the rule beyond cross-squad: "anywhere else you spawn copilot into a Squad-initialised repo — e.g., post-init tips and any automation that invokes the CLI on a squadified folder." Also added a precondition note to Pattern 0 Requirements: the target repo must be Squad-initialised (.squad/config.json + .github/agents/squad.agent.md present) so --agent squad resolves. Verified empirically: copilot --help (v1.0.62) confirms --agent is the real flag name. squad delegate (CLI command) was audited and does NOT need this fix — it uses `gh issue create`, not `copilot`, so the issue body lands in the peer repo's GitHub Issues for async pickup, not via direct CLI spawn. Regression tests in test/cross-squad.test.ts: - 'every copilot spawn into a peer squad includes --agent squad' — scans every copilot command line (excluding --resume / prose references) and asserts each one has --agent squad. Tells you the exact offending line on failure. - 'explains WHY --agent squad is required (universal rule paragraph)' — defends against a future edit that strips the rationale. - 2 new assertions × 3 mirror locations = 6 new tests. - 62/62 cross-squad tests pass. All 3 mirrored copies of cross-squad-communication/SKILL.md kept in sync (canonical = .squad/, mirrors = packages/squad-cli/templates/, packages/squad-sdk/templates/). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(coordinator): route "spawn a squad" / "another squad" prompts through the cross-squad skill (#1307) * fix(coordinator): route "spawn a squad" / "another squad" prompts through the cross-squad skill A coordinator initialised by `squad init` saw prompts like "spawn two squads of designers and devs" and fanned out raw `task` agents inside its own context, treating "squad" as generic English for "team / group". It never invoked the bundled `cross-squad` or `cross-squad-communication` skills, so the peer-squad delegation protocol (registry / manifest / sync CLI / git-async / GH-issue patterns) was bypassed entirely. Two structural holes in squad.agent.md allowed this: 1. The Routing table had no row mapping "spawn a squad" phrasing to the Squad-PRODUCT concept (only "upgrade squad" / "squad commands" rows covered Squad-as-a-product vocabulary). 2. The Skill-aware-routing block was process discipline ("check skill directories by domain relevance") with no hard "if the user's word matches a skill name, MUST load the skill" trigger. This fix: - Adds a new routing-table row for the squad-spawning vocabulary ("spawn a squad", "another squad", "two squads", "second squad", "fan out to squads", "delegate to a squad"). Action: invoke the skill tool on cross-squad AND cross-squad-communication BEFORE any task spawn, then delegate via Pattern 0/1/2/3. - Adds a "Hard trigger — keyword-to-skill match" paragraph at the top of the Skill-aware-routing block. If any word in the user's request matches an installed skill name (squad → cross-squad, reflect → reflect, ceremony → matching ceremony skill, fact-check → fact-checking, release → release-process), the coordinator MUST invoke the skill tool to fully load that skill before designing its approach. Includes a "failure mode this rule closes" pointer so the guard survives future paraphrasing. - Strengthens cross-squad/SKILL.md with a Read-this-FIRST callout above the existing Context paragraph, so even a coordinator that skips the routing-table row still hits the trigger when it does eventually load the skill. - Adds a regression test (template-sync.test.ts) that asserts the row + the hard-trigger paragraph + the worked example are present in every mirrored copy of squad.agent.md (5 locations). All 4 mirrors re-synced via `scripts/sync-templates.mjs --sync`. Verified: 223/223 template-sync tests pass. Composability: disjoint from #1292/#1293/#1295/#1298/#1300/#1301/#1302/ #1303/#1304/#1306 — only touches squad.agent.md mirrors + cross-squad/ SKILL.md + the template-sync test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(coordinator): strengthen squad-spawn disambiguation — ask_user on ambiguity + anti-patterns Real-world failure (2026-06-13): even AFTER the routing-table row and hard-trigger paragraph from b5d05fb6 landed, a peer-squad coordinator *still* did ad-hoc `task` fan-out for "spawn two squads of engineers and QAs". Self-diagnosis surfaced four contributing failure modes: 1. Prior-session anchoring (saw earlier `reviews/squad-alpha/` folders and matched the pattern without re-evaluating user intent). 2. Ambiguous wording, lazy interpretation (silently picked the cheaper option instead of asking). 3. Coordinator doctrine biases toward `task` fan-out (the existing Eager Execution / Parallel Fan-Out section pulled the coordinator back even after it had loaded `cross-squad`). 4. Cost/overhead instinct ("two real squads for a 30-line app feels disproportionate" — judged silently instead of surfacing the trade-off). The original PR #1307 fix closed modes 1 and 3 mechanically (forces the skill to load) but left modes 2 and 4 open (didn't dictate what to DO with that knowledge). This commit closes them: A. squad.agent.md routing row — added two explicit clauses: - "**Default = literal Squad install.** Calling `task` sub-agents 'squad-alpha' / 'squad-beta' does NOT make them squads — that is the explicit anti-pattern." - "**If the request is ambiguous** ... you MUST `ask_user` with a 2-choice prompt — and never silently pick the cheaper option." No escape hatch. The coordinator can no longer rationalise the downgrade as a judgment call. B. cross-squad/SKILL.md — added a full `## Disambiguation: 'squad' vs ad-hoc agents` section with: - Default-behaviour table mapping common phrasings to expected coordinator actions (real squads vs ad-hoc agents vs ambiguous). - ask_user 2-choice protocol verbatim (heavier/persistent vs lighter/ephemeral) so the coordinator has the exact prompt shape. - Four named anti-patterns drawn directly from the observed failure: * Naming task agents "squad-alpha" doesn't make them squads * Prior-session anchoring (pattern is a hint, not a contract) * Silent cheaper-option pick (judgment call belongs to the user) * Loading the skill but doing task fan-out anyway (disambiguation rule OVERRIDES generic fan-out doctrine when "squad" was the trigger) - Sharpened `description:` so the squad skill-aware-router has better natural-language hooks. - `triggers:` frontmatter array (Copilot CLI ignores `triggers:` per sdk/index.js decompile, but the squad coordinator's skill-aware routing system uses natural-language matching against frontmatter + content, so documenting the phrases here helps that matcher fire). C. Regression tests in test/template-sync.test.ts: - Routing row must mention `ask_user` + "anti-pattern" (new × 5 mirrors = 5 assertions). - cross-squad/SKILL.md must have `## Disambiguation` section, default-behaviour rule, ask_user requirement, the squad-alpha anti-pattern, and triggers: frontmatter (5 new × 3 mirrors = 15 assertions). - 20 new assertions total; 243/243 template-sync tests pass. cross-squad/SKILL.md mirrored to packages/squad-cli/templates/skills/ and packages/squad-sdk/templates/skills/ (byte-identical). squad.agent.md re-synced to all 4 mirrors via scripts/sync-templates.mjs. Composability: still disjoint from all other open PRs. Pure additions to two files (squad.agent.md row, cross-squad/SKILL.md content) plus mirrors + tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(sdk,cli): bundle missing skills on init + strip fabricated tiered-memory provenance (#1289, #1264) (#1292) * fix(sdk,cli): bundle missing skills on init + strip fabricated provenance from tiered-memory (#1289, #1264) Two regressions shipped in v0.10.0 caused several skills to silently never reach users: * #1289 — squad-commands + squad-version-check were in MANIFEST_SKILL_NAMES but missing from packages/squad-sdk/templates/skills/. The install loop silently skipped missing source dirs (storage.existsSync check, no log), so every squad init produced an install with these two absent and the `squad commands` trigger phrase was a dead no-op for every v0.10.0 user. * #1264 — tiered-memory, iterative-retrieval, reflect existed in templates but were never added to MANIFEST_SKILL_NAMES. tiered-memory/SKILL.md also claimed confidence: high with fabricated tamirdresher/tamresearch1 measurement data, and referenced a non-existent docs/tiered-memory-guide.md. * PR #1291 updated cross-squad/SKILL.md but never added it to the manifest. This change: * Adds 5 missing skill dirs to .squad/skills/ (canonical source): squad-commands, squad-version-check, tiered-memory, iterative-retrieval, reflect. sync-skill-templates.mjs (which runs in prebuild) propagates them to both packages/squad-cli/templates/skills/ and packages/squad-sdk/templates/skills/. * Grows MANIFEST_SKILL_NAMES from 10 → 14 entries: adds tiered-memory, iterative-retrieval, reflect, cross-squad. * Replaces the silent skip in sdk-init.ts with a thrown error that names the missing skill(s) and points at sync-skill-templates.mjs. This is what would have surfaced #1289 at build time instead of in user installs. * Rewrites tiered-memory/SKILL.md to be honest about status: - frontmatter: confidence: design + source: design proposal - prominent Status callout linking to #1264 for the runtime gap - removed fabricated measurement table - removed reference to non-existent docs/tiered-memory-guide.md - References section points only to real issues (#1264, #686, #600) * Adds a regression test in test/init.test.ts that asserts every manifest-curated skill ends up installed at .copilot/skills/{name}/SKILL.md. Verified: * npm run lint — passes (after build) * test/init.test.ts — 26/26 pass including new regression test * Targeted suites (test/init, test/cli/init, test/sdk) — 292/292 pass Out of scope (tracked separately): * tiered-memory runtime backing (.squad/memory/hot|cold|wiki/ scaffolding, Scribe promotion, tier-aware spawn template) — follow-up in #1264 * cross-squad-communication plugin port from tamirdresher/squad-skills — separate PR Closes #1289 Refs #1264 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: precheck drift, end-user remediation, TEMPLATE_MANIFEST sync, frontmatter, single-source test Five reviewer follow-ups on #1292 (closes #1289, refs #1264): 1. (1292-a) Drift guard ran the copy loop FIRST and threw at the end if any source dir was missing — leaving a partial skills install on disk. Refactored to a pre-check pass that walks MANIFEST_SKILL_NAMES once for existence, throws if any source is missing, then runs the copy loop only if everything is present. Result: no partial state when a packaging bug ships. 2. (1292-b) Remediation text told the user to 'run scripts/sync-skill-templates.mjs from the squad repo root', which doesn't exist in an installed @bradygaster/squad-sdk tarball. Rewrote the message to target end users (try squad upgrade, report a bug if it persists) and kept the contributor hint as a parenthetical aside. 3. (1292-c) MANIFEST_SKILL_NAMES grew to 14 entries (squad-commands, squad-version-check, tiered-memory, iterative-retrieval, reflect, cross-squad added across PRs), but TEMPLATE_MANIFEST in packages/squad-cli/src/cli/core/templates.ts had only 10 — so squad upgrade would skip the new ones. Added matching entries for tiered-memory/iterative-retrieval/reflect/cross-squad (the 4 not already covered by other PRs). 4. (1292-d) packages/squad-cli/templates/skills/squad-version-check/ SKILL.md (and the 2 mirrors) shipped without ANY YAML frontmatter — just a freeform '**Confidence:**' Markdown line. isSkillContent() in consult.ts requires '---\\nname:...\\nconfidence:' front-matter to classify the file as a skill, so squad-version- check was misclassified in share / promote / merge flows. Added proper frontmatter (name, description, allowedTools, confidence, domain, source) to all 3 mirrors. 5. (1292-e) The regression test duplicated the MANIFEST_SKILL_NAMES list literal — adding a new skill would have required two edits to stay green. Exported MANIFEST_SKILL_NAMES from sdk/config and updated the test to import + iterate the same array the install loop reads. Future skill additions only need one edit. Verified: 26/26 init tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * docs: add feature pages for preset, cross-squad discover/delegate, and coordinator-as-agent export (#1274) Closes part of #1272 — covers 3 of the 11 undocumented v0.10 user-facing features identified in the docs-gap audit. Files added: docs/src/content/docs/features/preset.md (205 lines) docs/src/content/docs/features/cross-squad-discover.md (158 lines) docs/src/content/docs/features/coordinator-as-agent-export.md (237 lines) Source verification: - preset.md sourced from packages/squad-cli/src/cli/commands/preset.ts - cross-squad-discover.md sourced from packages/squad-cli/src/cli/commands/cross-squad.ts and packages/squad-sdk/src/runtime/cross-squad.ts - coordinator-as-agent-export.md sourced from PR #1180 body and packages/squad-sdk/src/repo-native/ scope Style matches existing feature pages (loop.md, plugins.md format). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: add feature pages for remaining 8 v0.10 features (tiered-memory, reflect, error-recovery, teams-comms, fleet-dispatch, mcp-frontmatter, dual-mode, skill-security-scanner) + export-import --repo/--branch (#1276) * docs: add feature pages for remaining 8 undocumented v0.10 features Closes the rest of #1272 — covers the 8 user-facing features that had no dedicated docs page after PR #1274 (which covered preset, cross-squad discover, and coordinator-as-agent export). New pages (8): docs/features/tiered-memory.md (hot/cold/wiki memory model) docs/features/reflect.md (in-session learning skill) docs/features/error-recovery.md (failure recovery skill) docs/features/teams-comms.md (Microsoft Teams adapter) docs/features/fleet-dispatch.md (/fleet hybrid dispatch) docs/features/mcp-frontmatter.md (--mcp-frontmatter flag) docs/features/dual-mode-deployment.md (SQUAD_POD_ID, dual-mode capabilities) docs/features/skill-security-scanner.md (markdown-aware skill scanner) Updated (1): docs/features/export-import.md (added --repo / --branch sections) Source verification: - tiered-memory.md ← packages/squad-cli/templates/skills/tiered-memory/SKILL.md - reflect.md ← packages/squad-cli/templates/skills/reflect/SKILL.md - error-recovery.md ← packages/squad-cli/templates/skills/error-recovery/SKILL.md - teams-comms.md ← packages/squad-sdk/src/platform/comms-teams.ts + changeset - fleet-dispatch.md ← packages/squad-cli/src/cli/commands/watch/capabilities/fleet-dispatch.ts - mcp-frontmatter.md ← packages/squad-cli/src/cli-entry.ts:346 flag + init flow - dual-mode-deployment.md ← packages/squad-sdk/src/ralph/capabilities.ts - skill-security-scanner.md ← scripts/security-review.mjs + changeset - export-import.md edits ← packages/squad-cli/src/cli/commands/import.ts:817 Style matches existing feature pages (loop.md, plugins.md, preset.md format). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cspell): whitelist PKCE/MSAL/AKIA/runas + intentional 'typoo' example CI failure on docs-quality: cspell flagged 9 words across 3 of the new feature pages added by this PR. All 9 are legitimate technical terms or intentional content (not typos): - PKCE (RFC 7636 — OAuth Proof Key for Code Exchange) — 5 hits in teams-comms.md - MSAL (Microsoft Authentication Library) — 1 hit in teams-comms.md - AKIA (AWS access key ID prefix) — 1 hit in skill-security-scanner.md - runas (Windows runas elevation primitive) — 1 hit in skill-security-scanner.md - 'typoo' — an INTENTIONAL typo in an error-recovery example showing 'don't retry the user's typo by adding another character' Added all 5 to cspell.json. Verified locally: npx cspell --no-progress --dot 'docs/src/content/**/*.md' 'README.md' → 0 issues across 166 files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * docs(state-backends): document pre-commit hook, post-upgrade flow, and recovery (#1227) - Correct hook list (4 -> 6) in state-backends.md - Correct misleading claim that working-tree files remain as read-only reference (they're deleted after migration per migrate-backend.ts:218-235) - Add Steady-state safety net section explaining pre-commit + post-commit hooks - Add troubleshooting entry for refusing to commit two-layer state with recovery flow - Add post-upgrade verification checklist to upgrading.md Closes #1226 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: clarify that .squad/casting/* files are identity, not two-layer state (#1230) * docs: clarify that .squad/casting/* files are identity, not two-layer state .squad/casting/* files (policy.json, registry.json, history.json) are AUTHORITATIVE IDENTITY files that should be committed to 'main' alongside team.md and routing.md. They define the agent universe, name registry, and usage history - fundamental team identity, not runtime mutable state. This change adds clarifying documentation to: - .gitignore: explicit NOTE that casting/* MUST NOT be ignored - .gitattributes: explicit NOTE that casting/* is identity, not append-only - init.ts: enhanced comments explaining casting/* classification This resolves confusion where casting files were misclassified as two-layer runtime state. Per squad.agent.md (the source of truth), casting files are 'Authoritative' and belong on main. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(changeset): add patch changeset for the casting=identity doc clarifications Policy Gates noticed the init.ts touch (pure comment additions clarifying that .squad/casting/* is identity, not mutable state) and asked for a changeset. Adding one — patch bump. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher * fix(state-backend): conditional .gitignore entries for two-layer/orphan backends (#1229) Adds .squad/decisions.md and .squad/agents/*/history.md to .gitignore (within a marker-delimited block) when stateBackend is 'two-layer' or 'orphan'. Removes the block when switching back to 'local'. Defense-in-depth complement to the pre-commit hook installed by squad upgrade --state-backend. - New helper module: packages/squad-sdk/src/config/gitignore-state.ts with addSquadStateGitignoreBlock + removeSquadStateGitignoreBlock - initSquad calls helpers based on options.stateBackend (adds stateBackend to InitOptions in SDK) - CLI runInit passes stateBackend to sdkInitSquad - migrate-backend calls helpers on backend transitions - Unit tests for helpers (add/remove idempotency, round-trip) - Integration tests for init and migration paths Closes #1228 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(sdk): thread reasoningEffort through agent spawning pipeline (#1148) * feat(sdk): thread reasoningEffort through agent spawning pipeline Add support for configuring reasoning effort level per-agent through: - Charter markdown: **Reasoning Effort:** field in ## Model section - Config persistence: defaultReasoningEffort and agentReasoningEffortOverrides in .squad/config.json - SDK builders: reasoningEffort on defineAgent() and defineDefaults() - Layered resolution: resolveReasoningEffort() with priority hierarchy matching model resolution - Lifecycle manager: SpawnAgentOptions.reasoningEffortOverride passes through to SquadSessionConfig - Fan-out pipeline: AgentSpawnConfig.reasoningEffortOverride passes through to createSession() The value 'auto' is treated as a sentinel meaning 'let the SDK/API decide' and resolves to undefined. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(sdk): clamp reasoningEffort to model's supported range Add clampReasoningEffort() that caps the requested effort to the highest level the model supports. This prevents API errors when a user requests e.g. 'xhigh' on a model that only supports up to 'high' (like GPT-5.5). resolveReasoningEffort() now accepts an optional supportedEfforts array (from listModels()) and auto-clamps the result. Behavior: - xhigh on GPT-5.5 [low,medium,high] -> high (clamped down) - xhigh on GPT-5.2-codex [low,medium,high,xhigh] -> xhigh (unchanged) - xhigh on gpt-4.1 (no effort support) -> undefined (stripped) - max and xhigh treated as equivalent rank Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(sdk): address review feedback on reasoning effort - Validate reasoning effort at parse time: charter compiler now rejects invalid values and normalizes 'auto' to undefined early, preventing invalid strings from reaching SquadSessionConfig - Validate readReasoningEffort() against allowed set (low/medium/high/xhigh/auto), matching the per-agent override validation for consistency - Add isValid() guard in resolveReasoningEffort() so invalid values at any layer (spawn override, charter, config) fall through gracefully - Remove duplicate extractReasoningEffort() regex in lifecycle.ts; use compileCharterFull() output instead (single source of truth) - Add plain-object check after JSON.parse in write functions to prevent corruption when config.json contains non-object JSON (e.g. array/number) - Fix changeset docs to match actual priority order: per-agent config > global config > spawn override > charter > undefined - Add tests for invalid inputs at all resolver layers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(coordinator): teach coordinator about reasoning effort Add Per-Agent Reasoning Effort section to squad.agent.md so the coordinator knows to use reasoningEffort as a session parameter instead of switching to different model variants (e.g. using claude-opus-4.7-1m-internal with xhigh effort instead of switching to claude-opus-4.7-xhigh). Includes: - Layered resolution instructions matching the SDK implementation - Config persistence commands (always use xhigh, per-agent, clear) - Explicit rule: same model + different effort, NOT different model - Updated spawn output format showing effort annotation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(sdk): address second review round on reasoning effort - Centralize VALID_REASONING_EFFORTS as exported const from config/models.ts; charter-compiler and builders import it instead of maintaining duplicates - Validate reasoning effort in lifecycle before setting on SquadSessionConfig: reject auto and invalid values so they never reach the SDK - Validate in fan-out fallback path: invalid reasoningEffortOverride values are filtered out instead of passed through to createSession - Fix clampReasoningEffort to clamp UP to model's minimum when requested effort is below the supported range (e.g. low on a model that only supports [medium] returns medium instead of undefined) - Validate write APIs: writeReasoningEffort and writeAgentReasoningEffortOverrides now reject invalid values instead of persisting them to config.json - Add test for clamp-up behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(sdk): address review nits on reasoning effort Follow-up to @tamirdresher's review on PR #1148: - clampReasoningEffort: document the deliberate upward clamp (JSDoc and inline comment) so a request below a model's minimum isn't mistaken for a bug. - writeReasoningEffort: warn and preserve the existing preference on invalid input instead of silently clearing it (only null clears). - writeAgentReasoningEffortOverrides: warn about dropped invalid entries instead of discarding them silently. - charter parsing: warn on an invalid Reasoning Effort value so charter authors get feedback instead of a silent drop. - Consolidate the reasoning-effort union on the canonical SquadReasoningEffort type: builders/types.ts references it and VALID_REASONING_EFFORTS is tied to it via satisfies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Alan Lai Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * deps: Bump xunit from 2.9.2 to 2.9.3 (#1237) --- updated-dependencies: - dependency-name: xunit dependency-version: 2.9.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(test): anchor docs fence-parity regex to line start (#1319) The fence-parity check used /\\\/g which matched triple-backticks anywhere in file content — including inside table cells and inline code spans that document fence syntax itself. Reproducer: docs/src/content/docs/features/skill-security-scanner.md L69 contains a table cell that shows the fence-syntax escape with four-backtick quoting (\\\\ \\\ \\\\). The old regex matched 3 backtick-triples from that one cell plus 2 from the real fenced bash block at L82/L91 = 5 total (odd) → failing parity check. Fix: anchor both regexes to line start with /^\\\/gm so only real fence delimiters (at column 0) are counted or matched. - Line-count check: /\\\/g → /^\\\/gm - Block-extraction regex: /\\\[\s\S]*?\\\/g → /^\\\[\s\S]*?^\\\/gm This unblocks PRs #1316, #1317, #1234 which are all failing CI on dev. Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * deps: Bump Microsoft.Extensions.Configuration and 8 others (#1317) Bumps Microsoft.Extensions.Configuration from 10.0.0 to 10.0.9 Bumps Microsoft.Extensions.Configuration.Json from 10.0.0 to 10.0.9 Bumps Microsoft.Extensions.DependencyInjection from 10.0.0 to 10.0.9 Bumps Microsoft.Extensions.DependencyInjection.Abstractions from 10.0.8 to 10.0.9 Bumps Microsoft.Extensions.Hosting.Abstractions from 10.0.8 to 10.0.9 Bumps Microsoft.Extensions.Logging from 10.0.0 to 10.0.9 Bumps Microsoft.Extensions.Logging.Abstractions from 10.0.8 to 10.0.9 Bumps Microsoft.Extensions.Options from 10.0.8 to 10.0.9 Bumps Microsoft.NET.Test.Sdk from 17.12.0 to 17.14.1 --- updated-dependencies: - dependency-name: Microsoft.Extensions.Configuration dependency-version: 10.0.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoft-packages - dependency-name: Microsoft.Extensions.Configuration.Json dependency-version: 10.0.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoft-packages - dependency-name: Microsoft.Extensions.DependencyInjection dependency-version: 10.0.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoft-packages - dependency-name: Microsoft.Extensions.DependencyInjection.Abstractions dependency-version: 10.0.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoft-packages - dependency-name: Microsoft.Extensions.Hosting.Abstractions dependency-version: 10.0.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoft-packages - dependency-name: Microsoft.Extensions.Logging dependency-version: 10.0.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoft-packages - dependency-name: Microsoft.Extensions.Logging.Abstractions dependency-version: 10.0.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoft-packages - dependency-name: Microsoft.Extensions.Options dependency-version: 10.0.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoft-packages - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 17.14.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: microsoft-packages ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci(dependabot): add npm ecosystem coverage for JS workspaces (#1320) The repo is a JS-first monorepo — Squad CLI, Squad SDK, and the Astro docs site all ship npm packages — but dependabot.yml had zero npm monitoring (only NuGet and GitHub Actions). Added npm entries for the four real package directories: / — root workspace (vitest, typescript, shared tooling) /packages/squad-cli — Squad CLI package /packages/squad-sdk — Squad SDK package /docs — Astro/Starlight docs site Excluded samples/ and test-fixtures/ (scaffolding/fixtures, not published packages). Each entry mirrors the existing NuGet convention: weekly Monday 08:00 UTC, open-pull-requests-limit 5, labels [dependencies, squad:belanna], commit-message prefix 'deps', minor/patch grouped to keep noise low. Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * deps: Bump GitHub.Copilot.SDK from 1.0.0 to 1.0.1 (#1316) --- updated-dependencies: - dependency-name: GitHub.Copilot.SDK dependency-version: 1.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci: bump the github-actions group across 1 directory with 8 updates (#1234) Bumps the github-actions group with 8 updates in the / directory: | Package | From | To | | --- | --- | --- | | [actions/checkout](https://github.com/actions/checkout) | `4` | `6` | | [actions/setup-dotnet](https://github.com/actions/setup-dotnet) | `4` | `5` | | [actions/upload-artifact](https://github.com/actions/upload-artifact) | `4` | `7` | | [actions/setup-node](https://github.com/actions/setup-node) | `4` | `6` | | [peter-evans/create-issue-from-file](https://github.com/peter-evans/create-issue-from-file) | `5` | `6` | | [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) | `3` | `5` | | [actions/deploy-pages](https://github.com/actions/deploy-pages) | `4` | `5` | | [actions/github-script](https://github.com/actions/github-script) | `7` | `9` | Updates `actions/checkout` from 4 to 6 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v6) Updates `actions/setup-dotnet` from 4 to 5 - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v4...v5) Updates `actions/upload-artifact` from 4 to 7 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v7) Updates `actions/setup-node` from 4 to 6 - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v4...v6) Updates `peter-evans/create-issue-from-file` from 5 to 6 - [Release notes](https://github.com/peter-evans/create-issue-from-file/releases) - [Commits](https://github.com/peter-evans/create-issue-from-file/compare/v5...v6) Updates `actions/upload-pages-artifact` from 3 to 5 - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v5) Updates `actions/deploy-pages` from 4 to 5 - [Release notes](https://github.com/actions/deploy-pages/releases) - [Commits](https://github.com/actions/deploy-pages/compare/v4...v5) Updates `actions/github-script` from 7 to 9 - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7...v9) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/deploy-pages dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/github-script dependency-version: '9' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/setup-dotnet dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/setup-node dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-pages-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: peter-evans/create-issue-from-file dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: Bump xunit.runner.visualstudio from 2.8.2 to 3.1.5 (#1238) --- updated-dependencies: - dependency-name: xunit.runner.visualstudio dependency-version: 3.1.5 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: bump the minor-patch group in /docs with 7 updates (#1330) Bumps the minor-patch group in /docs with 7 updates: | Package | From | To | | --- | --- | --- | | [@astrojs/rss](https://github.com/withastro/astro/tree/HEAD/packages/astro-rss) | `4.0.17` | `4.0.18` | | [@astrojs/sitemap](https://github.com/withastro/astro/tree/HEAD/packages/integrations/sitemap) | `3.7.1` | `3.7.3` | | [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.2.1` | `4.3.1` | | [sharp](https://github.com/lovell/sharp) | `0.34.5` | `0.35.1` | | [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.2.1` | `4.3.1` | | [@playwright/test](https://github.com/microsoft/playwright) | `1.58.2` | `1.61.0` | | [pagefind](https://github.com/Pagefind/pagefind) | `1.4.0` | `1.5.2` | Updates `@astrojs/rss` from 4.0.17 to 4.0.18 - [Release notes](https://github.com/withastro/astro/releases) - [Changelog](https://github.com/withastro/astro/blob/main/packages/astro-rss/CHANGELOG.md) - [Commits](https://github.com/withastro/astro/commits/@astrojs/rss@4.0.18/packages/astro-rss) Updates `@astrojs/sitemap` from 3.7.1 to 3.7.3 - [Release notes](https://github.com/withastro/astro/releases) - [Changelog](https://github.com/withastro/astro/blob/main/packages/integrations/sitemap/CHANGELOG.md) - [Commits](https://github.com/withastro/astro/commits/@astrojs/sitemap@3.7.3/packages/integrations/sitemap) Updates `@tailwindcss/vite` from 4.2.1 to 4.3.1 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/@tailwindcss-vite) Updates `sharp` from 0.34.5 to 0.35.1 - [Release notes](https://github.com/lovell/sharp/releases) - [Commits](https://github.com/lovell/sharp/compare/v0.34.5...v0.35.1) Updates `tailwindcss` from 4.2.1 to 4.3.1 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/tailwindcss) Updates `@playwright/test` from 1.58.2 to 1.61.0 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.58.2...v1.61.0) Updates `pagefind` from 1.4.0 to 1.5.2 - [Release notes](https://github.com/Pagefind/pagefind/releases) - [Changelog](https://github.com/Pagefind/pagefind/blob/main/CHANGELOG.md) - [Commits](https://github.com/Pagefind/pagefind/compare/v1.4.0...v1.5.2) --- updated-dependencies: - dependency-name: "@astrojs/rss" dependency-version: 4.0.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@astrojs/sitemap" dependency-version: 3.7.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@tailwindcss/vite" dependency-version: 4.3.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: sharp dependency-version: 0.35.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: tailwindcss dependency-version: 4.3.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@playwright/test" dependency-version: 1.61.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: pagefind dependency-version: 1.5.2 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: Bump Microsoft.Extensions.AI from 10.6.0 to 10.7.0 (#1331) --- updated-dependencies: - dependency-name: Microsoft.Extensions.AI dependency-version: 10.7.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: microsoft-packages ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: bump eslint-plugin-n from 17.24.0 to 18.1.0 (#1336) Bumps [eslint-plugin-n](https://github.com/eslint-community/eslint-plugin-n) from 17.24.0 to 18.1.0. - [Release notes](https://github.com/eslint-community/eslint-plugin-n/releases) - [Changelog](https://github.com/eslint-community/eslint-plugin-n/blob/master/CHANGELOG.md) - [Commits](https://github.com/eslint-community/eslint-plugin-n/compare/v17.24.0...v18.1.0) --- updated-dependencies: - dependency-name: eslint-plugin-n dependency-version: 18.1.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: bump astro from 5.18.0 to 6.4.7 in /docs (#1332) Bumps [astro](https://github.com/withastro/astro/tree/HEAD/packages/astro) from 5.18.0 to 6.4.7. - [Release notes](https://github.com/withastro/astro/releases) - [Changelog](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md) - [Commits](https://github.com/withastro/astro/commits/astro@6.4.7/packages/astro) --- updated-dependencies: - dependency-name: astro dependency-version: 6.4.7 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump flask in /test-fixtures/dogfood/python-flask (#1341) Bumps [flask](https://github.com/pallets/flask) from 2.3.0 to 3.1.3. - [Release notes](https://github.com/pallets/flask/releases) - [Changelog](https://github.com/pallets/flask/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/flask/compare/2.3.0...3.1.3) --- updated-dependencies: - dependency-name: flask dependency-version: 3.1.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump black in /test-fixtures/dogfood/python-flask (#1340) Bumps [black](https://github.com/psf/black) from 23.3.0 to 26.3.1. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/23.3.0...26.3.1) --- updated-dependencies: - dependency-name: black dependency-version: 26.3.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump pytest in /test-fixtures/dogfood/mixed-lang/backend (#1339) Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.3.0 to 9.0.3. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.3.0...9.0.3) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(docs): migrate to astro 6 content collection config (#1345) - Move docs/src/content/config.ts -> docs/src/content.config.ts (Astro 6 requires the content config at src/content.config.ts, not inside src/content/) - Replace deprecated ViewTransitions import with ClientRouter from astro:transitions (also removed in Astro 6) - Update generated .astro/ type stubs to match new collection layout Fixes the LegacyContentConfigError introduced by PR #1332 (astro 6.4.7 bump). dev CI has been failing since 19:35 UTC 2026-06-15 at: https://github.com/bradygaster/squad/actions/runs/27571236144 Note: the markdown.remarkPlugins / rehypePlugins deprecation warning visible in the build output is intentionally NOT addressed in this PR (separate concern). Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(deps-dev): bump vite in /samples/streaming-chat (#1344) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.3.1 to 7.3.5. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v7.3.5/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v7.3.5/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 7.3.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump esbuild, tsx and vitest in /samples/streaming-chat (#1343) Bumps [esbuild](https://github.com/evanw/esbuild) to 0.28.1 and updates ancestor dependencies [esbuild](https://github.com/evanw/esbuild), [tsx](https://github.com/privatenumber/tsx) and [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest). These dependencies need to be updated together. Updates `esbuild` from 0.27.3 to 0.28.1 - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md) - [Commits](https://github.com/evanw/esbuild/compare/v0.27.3...v0.28.1) Updates `tsx` from 4.21.0 to 4.22.4 - [Release notes](https://github.com/privatenumber/tsx/releases) - [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs) - [Commits](https://github.com/privatenumber/tsx/compare/v4.21.0...v4.22.4) Updates `vitest` from 3.2.4 to 4.1.9 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/HEAD/packages/vitest) --- updated-dependencies: - dependency-name: esbuild dependency-version: 0.28.1 dependency-type: indirect - dependency-name: tsx dependency-version: 4.22.4 dependency-type: direct:development - dependency-name: vitest dependency-version: 4.1.9 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: bump typedoc-plugin-markdown from 4.11.0 to 4.12.0 in /packages/squad-sdk (#1329) * deps: bump typedoc-plugin-markdown in /packages/squad-sdk Bumps [typedoc-plugin-markdown](https://github.com/typedoc2md/typedoc-plugin-markdown/tree/HEAD/packages/typedoc-plugin-markdown) from 4.11.0 to 4.12.0. - [Release notes](https://github.com/typedoc2md/typedoc-plugin-markdown/releases) - [Changelog](https://github.com/typedoc2md/typedoc-plugin-markdown/blob/main/packages/typedoc-plugin-markdown/CHANGELOG.md) - [Commits](https://github.com/typedoc2md/typedoc-plugin-markdown/commits/typedoc-plugin-markdown@4.12.0/packages/typedoc-plugin-markdown) --- updated-dependencies: - dependency-name: typedoc-plugin-markdown dependency-version: 4.12.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore: sync lockfile for typedoc-plugin-markdown 4.12.0 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * deps: bump markdown-it from 14.1.1 to 14.2.0 (#1346) Bumps [markdown-it](https://github.com/markdown-it/markdown-it) from 14.1.1 to 14.2.0. - [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md) - [Commits](https://github.com/markdown-it/markdown-it/compare/14.1.1...14.2.0) --- updated-dependencies: - dependency-name: markdown-it dependency-version: 14.2.0 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: bump the minor-patch group across 1 directory with 13 updates (#1348) * deps: bump the minor-patch group across 1 directory with 13 updates Bumps the minor-patch group with 12 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@playwright/test](https://github.com/microsoft/playwright) | `1.58.2` | `1.61.0` | | [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.57.2` | `8.61.1` | | [eslint](https://github.com/eslint/eslint) | `10.1.0` | `10.5.0` | | [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) | `0.21.0` | `0.22.1` | | [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `19.2.4` | `19.2.7` | | [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.14` | `19.2.17` | | [typedoc](https://github.com/TypeStrong/TypeDoc) | `0.28.18` | `0.28.19` | | [@opentelemetry/exporter-metrics-otlp-grpc](https://github.com/open-telemetry/opentelemetry-js) | `0.57.2` | `0.219.0` | | [@opentelemetry/exporter-trace-otlp-grpc](https://github.com/open-telemetry/opentelemetry-js) | `0.57.2` | `0.219.0` | | [@opentelemetry/sdk-node](https://github.com/open-telemetry/opentelemetry-js) | `0.57.2` | `0.219.0` | | [@opentelemetry/semantic-conventions](https://github.com/open-telemetry/opentelemetry-js) | `1.28.0` | `1.41.1` | | [ws](https://github.com/websockets/ws) | `8.20.0` | `8.21.0` | Updates `@playwright/test` from 1.58.2 to 1.61.0 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.58.2...v1.61.0) Updates `@typescript-eslint/eslint-plugin` from 8.57.2 to 8.61.1 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.1/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.57.2 to 8.61.1 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.1/packages/parser) Updates `eslint` from 10.1.0 to 10.5.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/compare/v10.1.0...v10.5.0) Updates `markdownlint-cli2` from 0.21.0 to 0.22.1 - [Changelog](https://github.com/DavidAnson/markdownlint-cli2/blob/main/CHANGELOG.md) - [Commits](https://github.com/DavidAnson/markdownlint-cli2/compare/v0.21.0...v0.22.1) Updates `react` from 19.2.4 to 19.2.7 - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react) Updates `@types/react` from 19.2.14 to 19.2.17 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) Updates `@types/react` from 19.2.14 to 19.2.17 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) Updates `typedoc` from 0.28.18 to 0.28.19 - [Release notes](https://github.com/TypeStrong/TypeDoc/releases) - [Changelog](https://github.com/TypeStrong/typedoc/blob/master/CHANGELOG.md) - [Commits](https://github.com/TypeStrong/TypeDoc/compare/v0.28.18...v0.28.19) Updates `@opentelemetry/exporter-metrics-otlp-grpc` from 0.57.2 to 0.219.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/experimental/v0.57.2...experimental/v0.219.0) Updates `@opentelemetry/exporter-trace-otlp-grpc` from 0.57.2 to 0.219.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/experimental/v0.57.2...experimental/v0.219.0) Updates `@opentelemetry/sdk-node` from 0.57.2 to 0.219.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/experimental/v0.57.2...experimental/v0.219.0) Updates `@opentelemetry/semantic-conventions` from 1.28.0 to 1.41.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/v1.28.0...semconv/v1.41.1) Updates `ws` from 8.20.0 to 8.21.0 - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/8.20.0...8.21.0) --- updated-dependencies: - dependency-name: "@playwright/test" dependency-version: 1.61.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@typescript-eslint/eslint-plugin" dependency-version: 8.61.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@typescript-eslint/parser" dependency-version: 8.61.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: eslint dependency-version: 10.5.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: markdownlint-cli2 dependency-version: 0.22.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: react dependency-version: 19.2.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@types/react" dependency-version: 19.2.17 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@types/react" dependency-version: 19.2.17 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: typedoc dependency-version: 0.28.19 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@opentelemetry/exporter-metrics-otlp-grpc" dependency-version: 0.219.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@opentelemetry/exporter-trace-otlp-grpc" dependency-version: 0.219.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@opentelemetry/sdk-node" dependency-version: 0.219.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@opentelemetry/semantic-conventions" dependency-version: 1.41.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: ws dependency-version: 8.21.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch ... Signed-off-by: dependabot[bot] * fix(otel): migrate Resource constructor to resourceFromAttributes for OTel JS SDK 2.x @opentelemetry/resources 2.x removed the Resource class constructor. Replace all uses of \ ew Resource({...})\ with \ esourceFromAttributes({...})\ and update variable bindings accordingly. Fixes the TypeError: Resource is not a constructor CI failure blocking the minor-patch dependency bump (#1348). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: add changeset for OTel Resource API migration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: bump @types/node to 25.9.3 (cli + sdk) (#1349) Replaces #1321 and #1326. Removes unused @ts-expect-error directive in cli-entry.ts that is no longer needed under @types/node 25 typings. Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: adapt CLI to ink 7 (#1350) * chore: adapt CLI to ink 7 Replaces #1322 and #1335. Updates Escape/backspace/delete keyboard handling to ink 7's API; adjusts paste-detection debounce so the visual clear happens synchronously inside discreteUpdates; adds key.escape guard to disabled path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: add changeset for ink 7 adaptation * fix: move setHistory to synchronous flush, fix workspace integrity * fix: restore dehoisted opentelemetry packages in lockfile --------- Co-authored-by: tamirdresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * deps: bump vscode-jsonrpc from 8.2.1 to 9.0.0 in /packages/squad-cli (#1327) Bumps [vscode-jsonrpc](https://github.com/Microsoft/vscode-languageserver-node/tree/HEAD/jsonrpc) from 8.2.1 to 9.0.0. - [Release notes](https://github.com/Microsoft/vscode-languageserver-node/releases) - [Commits](https://github.com/Microsoft/vscode-languageserver-node/commits/release/client/9.0.0/jsonrpc) --- updated-dependencies: - dependency-name: vscode-jsonrpc dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: upgrade to TypeScript 6 (cli + sdk + root) and align @typescript-eslint (#1351) - Bump typescript from ^5.7.0 to ^6.0.3 in root, packages/squad-cli, packages/squad-sdk - Bump @typescript-eslint/parser from ^8.57.1 to ^8.61.1 in root (aligns with plugin; v8.61.1 supports typescript >=4.8.4 <6.1.0) - Add \\ ypes\\: [\\ ode\\] to compilerOptions in packages/squad-cli/tsconfig.json and packages/squad-sdk/tsconfig.json; TypeScript 6 requires explicit Node type declarations - Update lockfile (npm install) - Add .changeset/typescript-6.md Closes #1325 Closes #1328 Closes #1334 Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: upgrade vitest to v4 (coordinated with @vitest/coverage-v8 v4) (#1352) * deps: bump @vitest/coverage-v8 from 3.2.4 to 4.1.9 Bumps [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) from 3.2.4 to 4.1.9. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.9/packages/coverage-v8) --- updated-dependencies: - dependency-name: "@vitest/coverage-v8" dependency-version: 4.1.9 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore: bump vitest from v3 to v4 to resolve peer-dep mismatch with @vitest/coverage-v8 v4 Dependabot #1337 bumped @vitest/coverage-v8 to ^4.1.9 but left vitest at ^3.0.0, causing ERESOLVE in CI. This commit aligns both packages at ^4.1.9 across root and all sample workspaces that referenced ^3.0.0. Vitest v4 API adaptations required: - test/cli/loop.test.ts: add vi.clearAllMocks() to afterEach — vitest 4 no longer resets vi.fn() call history on vi.restoreAllMocks(); the 'skips gh copilot preflight when agentCmd is provided' test was seeing stale execFile call counts from the prior test. - test/cli/watch-capabilities.test.ts: FSStorageProvider mock changed from vi.fn(() => mockStorage) to vi.fn(function() { return mockStorage; }) — vitest 4 spying rewrite requires 'function' keyword (not arrow function) for mocks used as constructors with 'new'. No changes to non-test, non-package-version code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(test): vitest 4 strict-import + constructor-mock fixes vitest 4 esbuild rejects duplicate imports (parseHistory was imported twice in squad-state-gaps.test.ts). vitest 4 vi.fn(arrow) is not callable as a constructor, so the CopilotClient mock needed a function() wrapper that assigns to his. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(test): vitest 4 constructor-mock fixes for client/session-traces/integration Same pattern as adapter-client.test.ts: vitest 4 vi.fn(arrow) is not callable as a constructor (no [[Construct]] internal slot). All three files mock CopilotClient and instantiate it with 'new', so they need a function() wrapper that assigns to 'this'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * deps: bump vscode-jsonrpc from 8.2.1 to 9.0.0 in /packages/squad-sdk (#1324) * deps: bump vscode-jsonrpc from 8.2.1 to 9.0.0 in /packages/squad-sdk Bumps [vscode-jsonrpc](https://github.com/Microsoft/vscode-languageserver-node/tree/HEAD/jsonrpc) from 8.2.1 to 9.0.0. - [Release notes](https://github.com/Microsoft/vscode-languageserver-node/releases) - [Commits](https://github.com/Microsoft/vscode-languageserver-node/commits/release/client/9.0.0/jsonrpc) --- updated-dependencies: - dependency-name: vscode-jsonrpc dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore: sync lockfile + add changeset for vscode-jsonrpc 9 in sdk Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * deps: bump sharp from 0.35.1 to 0.35.2 in /docs in the minor-patch group (#1367) Bumps the minor-patch group in /docs with 1 update: [sharp](https://github.com/lovell/sharp). Updates `sharp` from 0.35.1 to 0.35.2 - [Release notes](https://github.com/lovell/sharp/releases) - [Commits](https://github.com/lovell/sharp/compare/v0.35.1...v0.35.2) --- updated-dependencies: - dependency-name: sharp dependency-version: 0.35.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: bump the minor-patch group with 2 updates (#1360) Bumps the minor-patch group with 2 updates: [ink](https://github.com/vadimdemedes/ink) and [@opentelemetry/semantic-conventions](https://github.com/open-telemetry/opentelemetry-js). Updates `ink` from 7.0.6 to 7.1.0 - [Release notes](https://github.com/vadimdemedes/ink/releases) - [Commits](https://github.com/vadimdemedes/ink/compare/v7.0.6...v7.1.0) Updates `@opentelemetry/semantic-conventions` from 1.28.0 to 1.41.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/v1.28.0...semconv/v1.41.1) --- updated-dependencies: - dependency-name: ink dependency-version: 7.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@opentelemetry/semantic-conventions" dependency-version: 1.41.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: Bump GitHub.Copilot.SDK from 1.0.1 to 1.0.3 (#1359) --- updated-dependencies: - dependency-name: GitHub.Copilot.SDK dependency-version: 1.0.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: bump hono from 4.12.22 to 4.12.26 (#1355) Bumps [hono](https://github.com/honojs/hono) from 4.12.22 to 4.12.26. - [Release notes](https://github.com/honojs/hono/releases) - [Commits](https://github.com/honojs/hono/compare/v4.12.22...v4.12.26) --- updated-dependencies: - dependency-name: hono dependency-version: 4.12.26 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(coordinator): restore always-on inline-dispatch gate (#1378) The Squad coordinator began doing domain work itself instead of dispatching to roster agents in v0.10.0 (worked in v0.9.4). Root cause: #1035 (afe78188) relocated the concrete inline-dispatch gate and dispatch mechanics out of the always-on coordinator prompt into lazy-loaded reference files, leaving only a soft "inline is last-resort" one-liner. The model then treated inline execution as a peer escape hatch. Fix (3 minimal edits to canonical .squad-templates/squad.agent.md, synced to all 5 mirrors via sync-templates): - Client Compatibility: explicit Inline-dispatch gate (inline allowed ONLY in Direct Mode or when no spawn tool exists) - How to Spawn an Agent: one-line STOP gate at artifact-emit time - Re-inlined ~5-line VS Code runSubagent micro-playbook Direct/Lightweight modes preserved. Net +3 canonical lines; final 817 stays ~153 lines under the pre-#1035 size, so the shrink goal holds. The .github/agents copy diff is larger (+24) because sync-templates also reconciled pre-existing canonical->installed drift. Adds deterministic regression test (red 8/8 -> green 8/8) asserting the gate/STOP-gate/micro-playbook across canonical + all 5 copies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(deps): bump flask in /test-fixtures/dogfood/mixed-lang/backend (#1375) Bumps [flask](https://github.com/pallets/flask) from 2.3.0 to 3.1.3. - [Release notes](https://github.com/pallets/flask/releases) - [Changelog](https://github.com/pallets/flask/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/flask/compare/2.3.0...3.1.3) --- updated-dependencies: - dependency-name: flask dependency-version: 3.1.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump pytest in /test-fixtures/dogfood/python-flask (#1376) Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.3.0 to 9.0.3. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.3.0...9.0.3) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(docs): rephrase 'fenced code block' row in skill-security-scanner table (#1312) CI failure on dev tip (567f4475, Squad CI run 27488323855): test/docs-build.test.ts > 'all code blocks are properly fenced (even count of backticks)' → expected 1 to be 0 test/docs-build.test.ts > 'code blocks contain language specification or valid content' → expected 1 to be greater than 1 Root cause: a 4-backtick table cell intended to display a literal triple-backtick: | Inside a fenced code block (\\\\ \\\ \\\\) | Suppressed | made the regex /\\\/g see 5 triple-backtick occurrences across the file (instead of the 2 from the real bash example), tripping both the even-fence check and the line-count > 1 check. Rephrased the table to say 'three backticks' / 'single backtick' in prose — no embedded delimiters, no need to defend against the markdown fence escape mechanism. Reads cleaner anyway. Verified locally: 22/22 docs-build tests pass; npm run build in docs/ completes (171 files emitted; pagefind indexes 168 pages, 6911 words). Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(Squad.Agents.AI): update README header to 0.5.1 and multi-target (#1354) The header still says the package is `0.1.0-preview` and targets `net10.0`. Actual: `0.5.1` and `net8.0;net9.0;net10.0` since the 0.2.0 multi-target work landed. No content changes elsewhere - just the header line. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci(release): stop publishing on test-only dep bumps (#1342) Remove test/Squad.Agents.AI.Tests/** from the push paths filter so that test-dependency updates (e.g. xunit bumps) no longer trigger a NuGet publish. The tests still run as part of the workflow — they just no longer act as a publish trigger. Closes #1338 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: bump actions/checkout from 6 to 7 in the github-actions group (#1358) Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 6 to 7 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix: respect SQUAD_HOME env var, implement SQUAD_PERSONAL_DIR, fix Windows loop preflight (#1382) * fix: respect SQUAD_HOME env var, implement SQUAD_PERSONAL_DIR, fix Windows loop preflight - capabilities.ts: use resolveSquadHome() instead of hardcoded ~/.squad/ (#1280) - comms-teams.ts: use resolveSquadHome() for Teams OAuth token storage (#1279) - resolution.ts: implement SQUAD_PERSONAL_DIR env var override (#1278) - loop.ts: add shell flag for Windows copilot.ps1/.cmd resolution (#1372) - init.ts: link to personal squad teamRoot when available (#1010, #984) Closes #1280, Closes #1279, Closes #1278, Closes #1372, Closes #1010, Closes #984 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address PR review feedback - teamRoot, directory validation, JSDoc, tests - init.ts: use resolveGlobalSquadPath() for teamRoot instead of personal-squad dir - resolution.ts: validate SQUAD_PERSONAL_DIR is a directory, not just exists - comms-teams.ts: update JSDoc to reflect resolveSquadHome() usage - capabilities.ts: update JSDoc search order to document SQUAD_HOME support - test: add SQUAD_PERSONAL_DIR env var test cases Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: fix Teams home resolution mocks Closes #1280 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: brady gaster Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: fix platform paths and explain global squad.agent.md (#1381) * docs: fix platform paths and explain global squad.agent.md - Fix #1284: consult.ts JSDoc @param now shows macOS/Linux/Windows paths and documents SQUAD_HOME override - Fix #1283: consult-mode.md 'The Problem' section now shows macOS path alongside Linux - Fix #1282: personal-squad guide now explains that squad.agent.md in the global config dir is a template, not a live Copilot instruction file - Fix #1281: personal-squad SKILL.md templates correctly separate macOS and Linux paths instead of incorrectly labeling ~/.config/ as both Closes #1284, Closes #1283, Closes #1282, Closes #1281 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: fix paths to match implementation and correct squad.agent.md claim - consult.ts JSDoc: use personal-squad (not .squad), remove incorrect SQUAD_HOME reference (that env var is for resolveSquadHome, not getPersonalSquadRoot) - consult-mode.md: use personal-squad path, not .squad - personal-squad.md: clarify that global squad.agent.md is a reference copy, not a master template used by squad init (init uses the packaged squad.agent.md.template) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: add changeset for path fixes Closes #1281 Closes #1282 Closes #1283 Closes #1284 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: brady gaster Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: CLI and upgrade bug fixes (#1050, #1048, #1047, #1052, #1029, #1353) (#1383) * fix: CLI and upgrade bug fixes - Show externalize/internalize in squad --help (Closes #1050) - squad build writes to externalized state dir (Closes #1048) - squad new agent adds agent to config agents array (Closes #1047) - squad upgrade backs up customized squad.agent.md, adds --dry-run (Closes #1052) - Explicit @agent mentions bypass direct-response handler (Closes #1029) - state-mcp uses lazy init to avoid blocking multi-MCP startup (Closes #1353) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address PR review feedback on mention detection and patterns - Require word boundary before @ in routing and direct-response (no email false positives) - Fix logic: known agent AND not coordinator (was incorrectly OR) - Narrow agentsArrayPattern to skip routing-rule string arrays - Normalize template content to LF before comparison in upgrade - Add tests for @agent mention routing behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: repair CI test regressions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: brady gaster Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(sdk): export gitignore-state helpers; bump Squad.Agents.AI to 0.5.5 (#1387) migrate-backend.ts imports addSquadStateGitignoreBlock/removeSquadStateGitignoreBlock from @bradygaster/squad-sdk, but src/index.ts never re-exported them, breaking the CLI TypeScript build (TS2305). Re-export both helpers (and their marker constants) so the CLI compiles. Also bump the stale Squad.Agents.AI NuGet version 0.5.1 -> 0.5.5 so a republish pins to the fixed CLI. Unblocks a stable release carrying the #1378 inline-dispatch-gate fix. Refs #1386 Co-authored-by: brady gaster Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(tracing): capture task-tool dispatch + tool requests for subagent OTel (#1384) Adds a "subagent dispatched" envelope to the SquadSubagentTraceMapper so consumers and OTel backends see the LLM-supplied persona identity from the moment the coordinator invokes the `task` tool — instead of waiting for the later SubagentStartedEvent (which only carries the catalog `agent_type`, e.g. always "general-purpose"). Mapper / public surface: - Add `SubagentDispatched` to `SquadAgentTraceEventKind` and four persona properties on `SquadAgentTraceEvent` (`DispatchedPersonaName/Description/AgentType/Prompt`). - Rewrite mapper to key live activities by `ToolCallId` (primary) and maintain a `SdkAgentId → ToolCallId` lookup so `AssistantMessageEvent`s (which only carry `AgentId`) can find the right span. Activities are opened on the task-dispatch event and labelled with the persona name from the start, then augmented on `SubagentStartedEvent`. - Surface `AssistantMessageData.ToolRequests[].Name` on the typed envelope as `RequestedToolNames`, and add a `squad.subagent.tool_requests` ActivityEvent so tool-only assistant turns (empty Content) are visible on the OTel span as "called gh, view, grep" instead of a blank marker. Sample: - Add Flow 5 demonstrating both the typed `OnSubagentTrace` callback and an `ActivityListener` for OTel. - Update Flow 1 to use the same handler with the new tool-name surfacing and a short `ShortId` formatter so parallel subagents are visually distinct. - Fix Flow 3 (BYOK): merge the parent process env with custom vars to avoid the `Assertion failed: ncrypto::CSPRNG(nullptr, 0)` Node crash on Windows. SDK: - When forwarding `options.Environment` to the Copilot client, merge with the parent process env (instead of replacing it) so the native CLI inherits SYSTEMROOT/PATH/TEMP and doesn't crash on crypto init. Tests (82 total, all pass on net8.0/net9.0/net10.0): - 11 new tests covering task-dispatch parsing, `subagent_type` alias, bare-arguments fallback, `ToolRequests` extraction, the `squad.subagent.tool_requests` ActivityEvent, ToolCallId-keyed activity lifecycle, parallel dispatches, missing-arguments path, and the `sessionConfig.Agent = "Squad"` routing assertion update. Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: escape args for cmd.exe shell on Windows (DEP0190) (#1388) * fix: escape args for cmd.exe shell on Windows (DEP0190) When execFile is called with shell: true on Windows, Node concatenates args with spaces but does NOT quote them. This causes multi-word prompts (e.g. the -p/--message arg) to be split by cmd.exe, resulting in 'The system cannot find the file specified' errors. Changes: - Add escapeForCmd() and escapeArgs() to agent-spawn.ts that properly wrap args containing spaces/metacharacters in double quotes - Apply escaping in spawnWithTimeout() and spawnAgent() before passing to execFile - Migrate monitor-email, monitor-teams, retro, and decision-hygiene capabilities to use the shared agent-spawn module instead of duplicated inline buildAgentCommand/spawnWithTimeout functions - Fix preflight checks to use IS_WINDOWS instead of hardcoded shell:true Also resolves the Node DEP0190 deprecation warning about passing args to a child process with shell option true being a security risk, since args are now properly escaped before concatenation. Fixes bradygaster/squad#TBD Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: use gh auth token instead of gh auth status for auth checks gh auth status returns a non-zero exit code when ANY account in the keyring has an invalid/stale token, even if the active account (e.g. via GH_TOKEN env var) is perfectly fine. This causes squad watch and other commands to incorrectly report 'gh CLI not authenticated'. Switch to gh auth token which only checks the active account and returns the token on success. Fixes the false-negative auth check when users have multiple gh accounts with a stale keyring entry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: use -p flag instead of --message for copilot CLI The copilot CLI accepts -p/--prompt for non-interactive mode, not --message. The --message flag doesn't exist, causing 'unknown option' errors when capabilities try to invoke copilot. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: default to --yolo when --execute is active Copilot CLI hangs in non-interactive (-p) mode without permission flags because it prompts for tool/path/url approval. Since squad watch spawns copilot headlessly, default to --yolo (equivalent to --allow-all) when execute mode is active and no explicit copilotFlags or agentCmd are set. Users can still override with --copilot-flags to use a more restrictive set of permissions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: add changeset for shell spawn fixes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(coordinator): spawn cast members as sub-sessions in Copilot App (#1385) * feat(coordinator): spawn cast members as sub-sessions in Copilot App Add SpawnBackend abstraction with TaskSpawnBackend (CLI) and SessionSpawnBackend (App) implementations. When create_session tool is available, agents spawn as sub-sessions with real-time visibility in the left nav. Zero CLI impact — behavior unchanged without the tool. Key design: - Platform detection via tool availability probe at session start - Naming: '{Name} {verb}ing {noun}' (40-char max, sentence case) - Concurrency cap: 4-5 simultaneous sub-sessions - Depth limit: max 1 (no sub-sub-sessions) - Graceful fallback: App backend fails → degrade to task tool Closes #1377 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(coordinator): address PR review - wire spawnBackend, fix detection order, add release() - Reorder platform detection: create_session > runSubagent > task > inline - Wire spawnBackend.spawn() into spawnSingle() with fallback to createSession - Both backends now require injected createSession callback (real session creation) - Add release(handle) to SpawnBackend interface for concurrency tracking - SessionSpawnBackend: proper pending/active tracking, reject (not queue) at cap - Remove unused SpawnRequest import (now used in fan-out wiring) - Add tests for spawn-backend and fan-out integration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: brady gaster Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(coordinator): address #1385 review follow-ups (#1390) Hardening fixes for the sub-session spawn backend (#1377): - App->task fallback: spawnSingle() falls back to createSession when the platform backend fails instead of failing the agent (emits session.spawn_fallback) - Slot-leak guards: registerSpawnRelease() handles 'completed' status and adds an unref'd max-lifetime safety timer - createSession timeout in both backends (createSessionTimeoutMs, default 60s) - Honest isAvailable() with injectable availabilityCheck predicate - Prompt sanitization for caller task/context (sanitizePromptValue) - Re-synced template platform-detection probe order to mirrors Adds vitest coverage for all six items. Closes #1377 Co-authored-by: brady gaster Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Rename .NET Aspire references to Aspire (#1239) * docs: rename .NET Aspire references Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Meir Blachman --------- Co-authored-by: Meir Blachman Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: simplify workflow files by removing .ai-team fallback logic (#1263) Remove dead .ai-team/ fallback code from workflow templates. The .squad/ directory is the canonical location since the rename — the fallback paths were never triggered and added unnecessary complexity. Changes: - Remove .ai-team/ fallback branches from triage, issue-assign, heartbeat, and sync-squad-labels workflows - Clean up empty if-blocks left by fallback removal - Simplify warning messages to reference only .squad/team.md - Change let → const for team/routing file paths (no longer reassigned) - Sync all template copies (.squad-templates/, packages/) Guard rail checks in squad-preview.yml and squad-promote.yml are preserved since they legitimately prevent .ai-team/ files from shipping. Closes #1167 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(routing): strip surrounding quotes from routing.md examples (#1357) parseRoutingMarkdown kept surrounding quotes on Examples cells, so a quoted example tokenized with the quote glued on and compiled to patterns that never matched, silently routing everything to fallback. Strip leading/trailing quotes so quoted and unquoted examples behave identically. Adds parse and matchRoute regression tests. Co-authored-by: duau_microsoft <107149404+duau_microsoft@users.noreply.github.com> * fix(cli): add externalize/internalize to top-level squad --help (#1232) The top-level `squad --help` command list omitted the externalize and internalize commands, leaving them undiscoverable. Add both entries (descriptions sourced verbatim from command-help.ts) plus a regression test guarding the top-level command list. Closes #1050 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * deps: bump @types/node from 25.9.4 to 26.0.0 in /packages/squad-cli (#1365) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.9.4 to 26.0.0. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 26.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: bump @types/node from 25.9.4 to 26.0.0 in /packages/squad-sdk (#1366) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.9.4 to 26.0.0. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 26.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci: bump esbuild and tsx in /samples/storage-provider-sqlite (#1380) Bumps [esbuild](https://github.com/evanw/esbuild) to 0.28.1 and updates ancestor dependency [tsx](https://github.com/privatenumber/tsx). These dependencies need to be updated together. Updates `esbuild` from 0.27.4 to 0.28.1 - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md) - [Commits](https://github.com/evanw/esbuild/compare/v0.27.4...v0.28.1) Updates `tsx` from 4.21.0 to 4.22.4 - [Release notes](https://github.com/privatenumber/tsx/releases) - [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs) - [Commits](https://github.com/privatenumber/tsx/compare/v4.21.0...v4.22.4) --- updated-dependencies: - dependency-name: esbuild dependency-version: 0.28.1 dependency-type: indirect - dependency-name: tsx dependency-version: 4.22.4 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: bump @opentelemetry/sdk-trace-base from 1.30.1 to 2.8.0 in /packages/squad-sdk (#1368) Bumps [@opentelemetry/sdk-trace-base](https://github.com/open-telemetry/opentelemetry-js) from 1.30.1 to 2.8.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/v1.30.1...v2.8.0) --- updated-dependencies: - dependency-name: "@opentelemetry/sdk-trace-base" dependency-version: 2.8.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: bump @opentelemetry/resources from 1.30.1 to 2.8.0 in /packages/squad-sdk (#1369) Bumps [@opentelemetry/resources](https://github.com/open-telemetry/opentelemetry-js) from 1.30.1 to 2.8.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/v1.30.1...v2.8.0) --- updated-dependencies: - dependency-name: "@opentelemetry/resources" dependency-version: 2.8.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: bump @github/copilot-sdk from 0.3.0 to 1.0.4 (#1361) Bumps [@github/copilot-sdk](https://github.com/github/copilot-sdk) from 0.3.0 to 1.0.4. - [Release notes](https://github.com/github/copilot-sdk/releases) - [Changelog](https://github.com/github/copilot-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/copilot-sdk/compare/v0.3.0...v1.0.4) --- updated-dependencies: - dependency-name: "@github/copilot-sdk" dependency-version: 1.0.3 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: bump @github/copilot-sdk from 0.3.0 to 1.0.4 in /packages/squad-sdk (#1371) Bumps [@github/copilot-sdk](https://github.com/github/copilot-sdk) from 0.3.0 to 1.0.4. - [Release notes](https://github.com/github/copilot-sdk/releases) - [Changelog](https://github.com/github/copilot-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/copilot-sdk/compare/v0.3.0...v1.0.4) --- updated-dependencies: - dependency-name: "@github/copilot-sdk" dependency-version: 1.0.3 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * deps: bump cspell from 9.7.0 to 10.0.1 (#1362) Bumps [cspell](https://github.com/streetsidesoftware/cspell/tree/HEAD/packages/cspell) from 9.7.0 to 10.0.1. - [Release notes](https://github.com/streetsidesoftware/cspell/releases) - [Changelog](https://github.com/streetsidesoftware/cspell/blob/main/packages/cspell/CHANGELOG.md) - [Commits](https://github.com/streetsidesoftware/cspell/commits/v10.0.1/packages/cspell) --- updated-dependencies: - dependency-name: cspell dependency-version: 10.0.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * docs(casting): add spoiler-awareness to casting/naming rules (#1196) * Add spoiler-awareness guideline to casting/naming rules Squad's casting system allocated character names from fictional universes but never screened them for plot spoilers. The existing easter-egg rule only hides the casting *rationale*, not spoiler-bearing names. A name that encodes a character's later title, role, transformation, or fate can spoil a user who is mid-way through the source material, since names appear in plain text across team.md, prompts, and logs. Adds an always-loaded Name Allocation rule in squad.agent.md and a new "Spoiler Awareness" section in casting-reference.md (with a scrubbed motivating example), propagated to all mirrors via sync-templates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(casting): restore CRLF endings on casting-reference.md to remove false diff The Spoiler Awareness edit was made on files whose committed blobs use CRLF, but local normalization rewrote them to LF. That made all 104 existing lines appear changed (122 ins / 104 del) and hid the real 18-line addition. Restoring CRLF collapses the diff to the genuine change only (18 insertions, 0 deletions). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(casting): clarify spoiler guidance wording Address review feedback on the Spoiler Awareness section: - Use standard spelling 'midway' instead of 'mid-way'. - Replace the ambiguous 'Never reorganize the casting around the spoiler' with explicit guidance: keep existing name mappings stable and only let the next/new allocation pick a different spoiler-safe character. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: add changeset for spoiler-aware casting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(prompt): slim squad.agent.md by extracting 3 sections to satellite skills (#1311) * feat(prompt): slim squad.agent.md by extracting 3 sections to satellite skills (#1308 phase 1) squad.agent.md is loaded as the agent prompt at every Copilot session start. v0.10.0-insider.1 ships it at 81 KB / 1137 lines and the v0.10 stabilisation pass alone added +10.6 KB / +114 lines. Every byte is paid at every session. Phase 1 fix: extract three low-cross-reference, high-byte sections to satellite skills the coordinator loads on demand via the skill tool — same pattern that worked for cross-squad-communication (#1295). - coordinator-init-mode — Init Mode Phase 1 + Phase 2 (5.4 KB) - coordinator-source-of-truth — Source of Truth Hierarchy (4.4 KB) - coordinator-response-mode — Response Mode Selection + Lightweight Spawn Template (3.9 KB) Result: squad.agent.md 81 KB → 70 KB (-13.9 %, -140 lines). 19 skills installed at .github/skills/ (was 16). Behaviour unchanged — each removed section is replaced with a stub naming the trigger condition and instructing the coordinator to skill(coordinator-X) before acting. Sections that stay in squad.agent.md (intentional): - Team Mode + state-backend handshake + HARD RULE — handshake must fire before any state write - Routing table — hit on every user prompt - Hard trigger keyword-to-skill match paragraph (#1307) — load-bearing - How to Spawn an Agent — referenced from every routing action - Coordinator Identity / Personal Squad / Memory Governance Tools — frequently re-read inline Wired changes: - New canonical sources at .squad/skills/coordinator-{x}/SKILL.md plus 2 template-dir mirrors - MANIFEST_SKILL_NAMES grows by 3 entries (16 → 19) - TEMPLATE_MANIFEST grows by 3 entries with ../.github/skills/ destinations (post-#1304 install location) - .squad-templates/squad.agent.md replaces each section with a stub - 4 mirrored squad.agent.md copies re-synced via scripts/sync-templates.mjs --sync Tests: 287/287 pass. The existing 'should install every manifest-curated skill (regression: bradygaster/squad#1289, #1264)' already iterates MANIFEST_SKILL_NAMES, so it automatically asserts the 3 new skills install. Smoke test: fresh squad init produces 19 skills at .github/skills/ and squad.agent.md is 70 KB. Follow-ups (separate PRs) for the still-large sections: - Routing (9.2 KB) — extract action-cell verbiage, keep trigger table - Team Mode (5.5 KB) — extract worktree-awareness sub-sections - How to Spawn (3.2 KB) — extract role-emoji catalog Target after 2-3 follow-ups: ~45 KB coordinator file. Closes #1308 (phase 1). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test+changeset: follow 'Determine team size' assertion to coordinator-init-mode skill CI failure on #1311: test/squad-agent-roster.test.ts asserted the 'Determine team size' line is in squad.agent.md.template, but after #1308 phase 1 that line moved into the coordinator-init-mode skill. Restructured the test into two describe blocks: 1. squad.agent.md.template — must still keep an Init Mode STUB that names the coordinator-init-mode satellite skill AND preserves the load-bearing eager-execution exception callout. This makes sure future edits can't accidentally drop the satellite reference. 2. coordinator-init-mode/SKILL.md (3 mirrors) — must contain the 'Determine team size' line naming all four built-ins, AND must mark each of Scribe/Ralph/Rai/Fact Checker as 'exempt from casting'. The regression coverage from #1299 follows the content. 22/22 tests pass. Also addressed the changeset review comment: clarified that .squad/skills/coordinator-*/SKILL.md are the canonical source files in the squad repo (where every other bundled skill source lives), and they're copied to .github/skills/ on install/upgrade per #1304. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: git-add the canonical .squad/skills/coordinator-*/ source dirs (review on #1311) Reviewer caught a real bug: the new coordinator-* skill source dirs existed in my worktree but had never been `git add`-ed because `.squad/` is gitignored on this repo (specific subpaths under it are exception-tracked). The template mirrors at packages/squad-{cli,sdk}/templates/skills/coordinator-*/ shipped fine, but the canonical sources at .squad/skills/coordinator-*/SKILL.md weren't in the tree — so: - The changeset claim that .squad/skills/coordinator-*/ is the canonical source was untrue on dev. - scripts/sync-skill-templates.mjs would not see the new skills when run from a fresh checkout, breaking the contributor workflow (canonical → mirrors). Sync would silently drop the 3 new skills on the next prebuild. Force-added the 3 dirs (`git add -f` because of the .squad/ ignore rule, same as every other tracked skill under .squad/skills/). Verified: `node scripts/sync-skill-templates.mjs` now lists coordinator-* among the 26 discovered skills and re-produces the 2 template mirrors byte-identical to the canonical sources. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: extractInitMode follows satellite skill + version regex tolerates pre-release tail Two narrow fixes uncovered while smoke-testing the slim squad.agent.md PR (#1311) and the 0.10.0-insider.1 release pipeline: 1. test/init-flow.test.cjs — after the Init Mode prose moved from squad.agent.md to the coordinator-init-mode satellite skill (#1308 / PR #1311), the structural assertions in 'Init Mode prompt structure (#66)' had nothing to read in the parent file. They now follow the pointer via the new readSatelliteSkill() helper (which prefers the installed copy at .github/skills//SKILL.md and falls back to the source repo template at packages/squad-sdk/templates/skills/), so the same gates (STOP/WAIT, 'Look right?', numbered confirm-before- create flow, Phase 2 trigger) keep being enforced — just against the prompt the coordinator actually loads at runtime. 2. test/version-stamping.test.cjs + index.cjs — the version regex [0-9.]+(?:-[a-z]+)? truncated pre-release versions with a numeric tail (e.g. 0.10.0-insider.1 → 0.10.0-insider, 0.10.0-build.3 → 0.10.0-build). That made the test 'upgrade detects same version' fail locally on any contributor who built the package (pkg.version gets stamped 0.10.0-build.N during build). More importantly it made the legacy index.cjs upgrade re-run the full upgrade flow for npm- installed insider users (who have 0.10.0-insider.1 on disk) instead of printing 'Already up to date'. Widened to [0-9.]+(?:-[a-z]+(?:\.[0-9]+)?)? — matches everything we already accepted plus the .N tail used by npm pre-release dist-tags and the build-time stamping script. All 133 .cjs tests now pass (was 123 pass / 10 fail before). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(docs): rephrase 'fenced code block' row in skill-security-scanner table CI failure on dev tip (567f4475, Squad CI run 27488323855): test/docs-build.test.ts > 'all code blocks are properly fenced (even count of backticks)' → expected 1 to be 0 test/docs-build.test.ts > 'code blocks contain language specification or valid content' → expected 1 to be greater than 1 Root cause: a 4-backtick table cell intended to display a literal triple-backtick: | Inside a fenced code block (\\\\ \\\ \\\\) | Suppressed | made the regex /\\\/g see 5 triple-backtick occurrences across the file (instead of the 2 from the real bash example), tripping both the even-fence check and the line-count > 1 check. Rephrased the table to say 'three backticks' / 'single backtick' in prose — no embedded delimiters, no need to defend against the markdown fence escape mechanism. Reads cleaner anyway. Verified locally: 22/22 docs-build tests pass; npm run build in docs/ completes (171 files emitted; pagefind indexes 168 pages, 6911 words). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(cli): rename hire to cast (#1394) The canonical command for adding team members is now 'cast' — we're casting agents, not hiring humans. 'hire' continues to work silently as an alias (like cls/clear in PowerShell). - squad cast (no args) → shows roster (existing behavior preserved) - squad cast --name X --role Y → launches team creation wizard - squad hire → always launches the wizard (silent alias) Updated all CLI help text, README docs, blog posts, SDK templates, and skill files. Added 'cast' to test expectations while keeping 'hire' in the recognized-commands list. Closes #1393 Co-authored-by: brady gaster Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add OSS release checklist files (#1217) * Add OSS release checklist files - CODE_OF_CONDUCT.md (Contributor Covenant v1.4) - SUPPORT.md (points to GitHub Discussions) - .github/CODEOWNERS (@bradygaster @tamirdresher) - README.md: add Requirements, License, Maintainers, Support, Contributing, Code of Conduct sections Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * feat: add 'squad preset install ' for sharing presets via repo URL (#1224) (#1225) * feat: add 'squad preset install ' for sharing presets via repo URL (#1224) Closes #1224. Adds a new subcommand that installs a single preset from a GitHub URL or local path into \\/presets//\ — the peer-to-peer preset sharing flow that was missing in v0.10.0. SDK side (squad-sdk/src/presets/index.ts): - New \installPresetFromSource(source, options)\ function - Resolves source: GitHub URL → shallow git clone --depth 1 to OS temp; local path → use as-is - Locates preset within source via 3 patterns: - dir contains preset.json → single-preset source - dir contains presets/ subdir → require --name to pick - dir IS the presets/ dir → require --name (or auto-pick if only one) - Validates preset.json before any destructive action - Copies preset.json (with optional rename) + agents/ into squad home - Cleans up temp clones in finally block (success or failure) - Exports: installPresetFromSource, InstallPresetOptions, InstallPresetResult CLI side (squad-cli/src/cli/commands/preset.ts): - New 'install' dispatcher case + presetInstall() function - Supports --name , --force - Module docstring + default usage help updated to include 'install' Supported source shapes: https://github.com/owner/repo https://github.com/owner/repo#preset-name (frag as subdir hint) https://github.com/owner/repo/tree/branch/path/... (sub-path) git\@github.com:owner/repo.git (SSH) ./local/path (single preset OR collection) Smoke tested all 6 cases locally: 1. Local single-preset → installs under manifest.name ✅ 2. Idempotent re-install fails without --force ✅ 3. --force overwrites ✅ 4. --name renames + updates manifest.name ✅ 5. Invalid source → clear error ✅ 6. GitHub URL (cloned bradygaster/squad's presets/builtin/default) ✅ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(presets): address 6 review comments — security, validation, fragment semantics, tests, help block Addresses all six review comments on bradygaster/squad#1225: 1. [security] git clone via execSync was vulnerable to shell injection because the command was built as a string. Switched to execFileSync with an argument array (no shell), so source / ref values containing ';' '&&' '|' backticks etc. can no longer be interpreted by a shell. The earlier ad-hoc whitespace/quote escaping was the wrong defence layer. 2. [security] nameHint was used in path.join without validation. A value like '../something' would have escaped the presets/ directory. Added validatePathSegment() that rejects path separators ('/' '\\'), '..', '.', null bytes. Applied both at the public installPresetFromSource() entry AND inside locatePresetWithinSource() as defence-in-depth in case future callers go direct. Also added validateSubPath() for the URL-fragment-derived subPath: rejects absolute paths and '..' segments. 3. [correctness] Fragment semantics: 'repo#some-name' (bare fragment, no slash) was being treated as a literal subPath, so it looked for /some-name/ and broke the documented /presets/some-name/ collection layout from the PR description. Restructured resolveInstallSource to return a new nameHint field alongside subPath: - Fragment WITH '/' -> literal subPath (e.g. repo#packs/team-a) - Fragment WITHOUT '/' -> preset-name HINT (e.g. repo#my-team) The nameHint is forwarded to locatePresetWithinSource without being used as a path segment itself, so the common collection layout now works as advertised. 4. [UX] --name parsing didn't validate that a value was actually provided. 'squad preset install --name' (no value) or '--name --force' (next arg is a flag) silently produced undefined or '--force' as the override and failed downstream with a confusing error. Added an early fail-fast guard with a clear usage hint. 5. [tests] Added 7 focused tests for installPresetFromSource covering the new code path: - single-preset local source (startDir/preset.json present) - collection local source + --name selection - collection source without --name throws with helpful message - --force overwrite of an existing same-name preset - --name rename + manifest.name stamping (other fields preserved) - --name path-escape attempts are rejected - empty source throws 'required' Remote (URL) branch isn't stubbed here — splitting the git-clone call into a small helper that tests can mock is a separate follow-up. 6. [docs] preset help block in command-help.ts still printed Usage: squad preset without 'install'. Updated to include 'install ', the new --name option, and a concise documentation of the fragment semantics from fix #3. Verified locally: 36/36 preset tests pass (29 existing + 7 new); 14/14 command-help tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: update adapter/client.ts for @github/copilot-sdk 1.0.4 API changes (#1403) * fix: update adapter/client.ts for @github/copilot-sdk 1.0.4 API changes - Replace removed cliPath/cliArgs/useStdio/port/cliUrl options with RuntimeConnection - Update ping() return type from timestamp: number to timestamp: string - Make SquadModelBilling.multiplier optional to match upstream ModelBilling - Replace client.on() with client.onLifecycle() for lifecycle events Unblocks Dependabot PRs #1364 and #1370 which surface these type errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: update test mocks for @github/copilot-sdk 1.0.4 API changes - Add RuntimeConnection and onLifecycle to all 4 copilot-sdk test mocks - Fix command-help.test.ts expected commands list (remove stale entries) - Regenerate package-lock.json after merging dev (OTel dep alignment) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: brady gaster Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * deps: bump @opentelemetry/sdk-metrics and sdk-trace-node to 2.8.0 (#1404) Bumps both OpenTelemetry SDK packages from 1.x to 2.8.0: - @opentelemetry/sdk-metrics: ^1.30.0 → ^2.8.0 - @opentelemetry/sdk-trace-node: ^1.30.0 → ^2.8.0 The createGauge API is still supported in SDK 2.x — no code changes needed. All OTel tests and SDK export validations pass. Closes #1364, Closes #1370 Co-authored-by: brady gaster Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): pin squad-sdk to workspace:* (fixes prerelease build crash) (#1406) * fix(cli): pin squad-sdk workspace dependency Closes #1405 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CLI SDK workspace resolution Closes #1405 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix green test suite (#1416) * Fix green test suite Align local test expectations with current personal squad paths, stabilize observer file category detection, skip unavailable docs/Aspire capabilities, and add the missing OTel context test dependency. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix observer review hardening Closes #1416 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Stage 0.11.0 release (#1420) Prepare clean 0.11.0 package versions and CHANGELOG notes for release staging. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Guard symlink test for Windows/restricted environments (#1418) * fix(test): guard observer symlink setup Handle restricted symlink creation in the observer symlink test so Windows and locked-down environments can continue the suite. Refs #1416 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address symlink test review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: allow SWE-bench docs terms Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: Tamir Dresher Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tamir Dresher Co-authored-by: Reno (Copilot) Co-authored-by: Copilot Co-authored-by: Copilot Co-authored-by: Allai Co-authored-by: Alan Lai Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: brady gaster Co-authored-by: Meir Blachman Co-authored-by: Meir Blachman Co-authored-by: Duong Au <28365324+duongau@users.noreply.github.com> Co-authored-by: duau_microsoft <107149404+duau_microsoft@users.noreply.github.com> Co-authored-by: Earl Tankard, Jr., Ph.D. <45021016+primetimetank21@users.noreply.github.com> Co-authored-by: Ohad Beltzer Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .changeset/bump-otel-sdk-2x.md | 5 + .changeset/cli-upgrade-fixes.md | 12 + .changeset/docs-platform-path-fixes.md | 6 + .changeset/feat-preset-install.md | 60 + .changeset/fix-cli-sdk-workspace-pin.md | 5 + .../fix-coordinator-inline-dispatch-gate.md | 24 + .changeset/fix-copilot-sdk-type-compat.md | 12 + .changeset/fix-dep0190-shell-spawn.md | 11 + .changeset/fix-green-suite.md | 5 + .../fix-routing-strip-example-quotes.md | 9 + .changeset/fix-sdk-export-gitignore-state.md | 11 + .changeset/fix-squad-home-env-bugs.md | 12 + .changeset/help-externalize-internalize.md | 5 + .changeset/ink7-adaptation.md | 12 + .changeset/observer-hardening.md | 5 + .changeset/otel-resource-api-migration.md | 5 + .changeset/reasoning-effort.md | 17 + .changeset/release-0.11.0.md | 6 + .changeset/rename-dotnet-aspire-to-aspire.md | 5 + .changeset/rename-hire-to-cast.md | 5 + .changeset/slim-squad-agent-md-phase1.md | 90 + .changeset/spawn-backend-followups-1377.md | 26 + .changeset/spoiler-aware-casting.md | 29 + .changeset/sub-sessions-spawn-backend.md | 22 + .changeset/types-node-25.md | 6 + .changeset/typescript-6.md | 10 + .changeset/vitest-4-upgrade.md | 6 + .changeset/vscode-jsonrpc-9-sdk.md | 5 + .copilot/skills/cli-wiring/SKILL.md | 4 +- .copilot/skills/init-mode/SKILL.md | 8 +- .github/CODEOWNERS | 4 + .github/agents/squad.agent.md | 287 +- .github/dependabot.yml | 100 +- .github/workflows/squad-agents-ai-ci.yml | 8 +- .github/workflows/squad-agents-ai-release.yml | 7 +- .github/workflows/squad-ci.yml | 22 +- .github/workflows/squad-docs-links.yml | 4 +- .github/workflows/squad-docs.yml | 8 +- .github/workflows/squad-heartbeat.yml | 10 +- .github/workflows/squad-impact.yml | 6 +- .github/workflows/squad-insider-publish.yml | 4 +- .github/workflows/squad-issue-assign.yml | 8 +- .github/workflows/squad-label-enforce.yml | 4 +- .github/workflows/squad-npm-publish.yml | 14 +- .github/workflows/squad-pr-nudge.yml | 2 +- .github/workflows/squad-pr-readiness.yml | 2 +- .github/workflows/squad-preview.yml | 4 +- .github/workflows/squad-promote.yml | 4 +- .github/workflows/squad-release.yml | 4 +- .github/workflows/squad-repo-health.yml | 24 +- .github/workflows/squad-scope-check.yml | 2 +- .github/workflows/squad-triage.yml | 6 +- .github/workflows/sync-squad-labels.yml | 4 +- .squad-templates/casting-reference.md | 18 + .squad-templates/spawn-reference.md | 57 +- .squad-templates/squad.agent.md | 251 +- .../workflows/squad-heartbeat.yml | 331 +- .../workflows/squad-issue-assign.yml | 10 +- .squad-templates/workflows/squad-triage.yml | 14 +- .../workflows/sync-squad-labels.yml | 9 +- .squad/agents/_alumni/saul/charter.md | 2 +- .squad/decisions-archive.md | 2 +- ...es-fix-coordinator-inline-dispatch-gate.md | 45 + .squad/skills/coordinator-init-mode/SKILL.md | 83 + .../skills/coordinator-response-mode/SKILL.md | 97 + .../coordinator-source-of-truth/SKILL.md | 45 + .squad/skills/personal-squad/SKILL.md | 5 +- CHANGELOG.md | 25 +- CODE_OF_CONDUCT.md | 74 + README.md | 34 +- SUPPORT.md | 13 + cspell.json | 2 +- docs/.astro/collections/blog.schema.json | 80 +- docs/.astro/collections/docs.schema.json | 80 +- docs/.astro/content.d.ts | 114 +- docs/package-lock.json | 2485 ++++----- docs/package.json | 14 +- docs/pagefind.yml | 24 +- .../{content/config.ts => content.config.ts} | 0 docs/src/content/blog/001b-meet-the-squad.md | 4 +- .../blog/014-wave-1-otel-and-aspire.md | 2 +- .../blog/022-welcome-to-the-new-squad.md | 2 +- .../src/content/docs/features/consult-mode.md | 2 +- .../docs/features/copilot-coding-agent.md | 2 +- docs/src/content/docs/features/mcp.md | 2 +- .../docs/guide/build-autonomous-agent.md | 4 +- docs/src/content/docs/guide/personal-squad.md | 8 + docs/src/content/docs/guide/sample-prompts.md | 4 +- docs/src/content/docs/sample-prompts.md | 4 +- docs/src/layouts/BaseLayout.astro | 4 +- index.cjs | 3 +- package-lock.json | 4839 +++++++---------- package.json | 33 +- packages/squad-cli/README.md | 2 +- packages/squad-cli/package.json | 16 +- packages/squad-cli/src/cli-entry.ts | 33 +- packages/squad-cli/src/cli/commands/aspire.ts | 4 +- packages/squad-cli/src/cli/commands/build.ts | 35 +- packages/squad-cli/src/cli/commands/loop.ts | 2 +- packages/squad-cli/src/cli/commands/preset.ts | 64 +- .../squad-cli/src/cli/commands/state-mcp.ts | 66 +- .../src/cli/commands/watch/agent-spawn.ts | 43 +- .../watch/capabilities/decision-hygiene.ts | 26 +- .../watch/capabilities/monitor-email.ts | 27 +- .../watch/capabilities/monitor-teams.ts | 28 +- .../cli/commands/watch/capabilities/retro.ts | 26 +- .../src/cli/commands/watch/config.ts | 6 + .../squad-cli/src/cli/commands/watch/index.ts | 4 +- .../squad-cli/src/cli/core/command-help.ts | 38 +- packages/squad-cli/src/cli/core/gh-cli.ts | 11 +- packages/squad-cli/src/cli/core/init.ts | 23 +- packages/squad-cli/src/cli/core/templates.ts | 18 + packages/squad-cli/src/cli/core/upgrade.ts | 52 +- .../src/cli/shell/components/InputPrompt.tsx | 26 +- packages/squad-cli/src/cli/shell/index.ts | 2 +- .../squad-cli/templates/casting-reference.md | 18 + .../templates/skills/cli-wiring/SKILL.md | 4 +- .../skills/coordinator-init-mode/SKILL.md | 83 + .../skills/coordinator-response-mode/SKILL.md | 97 + .../coordinator-source-of-truth/SKILL.md | 45 + .../templates/skills/init-mode/SKILL.md | 8 +- .../templates/skills/personal-squad/SKILL.md | 5 +- .../squad-cli/templates/skills/squad/SKILL.md | 4 +- .../squad-cli/templates/spawn-reference.md | 57 +- .../templates/squad.agent.md.template | 251 +- .../templates/workflows/squad-heartbeat.yml | 7 +- .../workflows/squad-issue-assign.yml | 10 +- .../templates/workflows/squad-triage.yml | 14 +- .../templates/workflows/sync-squad-labels.yml | 9 +- packages/squad-cli/tsconfig.json | 3 +- packages/squad-sdk/package.json | 30 +- packages/squad-sdk/src/adapter/client.ts | 34 +- packages/squad-sdk/src/adapter/types.ts | 6 +- .../squad-sdk/src/agents/charter-compiler.ts | 32 + packages/squad-sdk/src/agents/index.ts | 5 + packages/squad-sdk/src/agents/lifecycle.ts | 26 +- packages/squad-sdk/src/agents/onboarding.ts | 105 +- packages/squad-sdk/src/builders/index.ts | 8 + packages/squad-sdk/src/builders/types.ts | 8 + packages/squad-sdk/src/client/event-bus.ts | 1 + packages/squad-sdk/src/config/init.ts | 3 + packages/squad-sdk/src/config/models.ts | 333 ++ packages/squad-sdk/src/config/routing.ts | 26 +- packages/squad-sdk/src/config/schema.ts | 3 + .../src/coordinator/direct-response.ts | 5 + packages/squad-sdk/src/coordinator/fan-out.ts | 209 +- packages/squad-sdk/src/coordinator/index.ts | 17 + .../src/coordinator/spawn-backend.ts | 401 ++ packages/squad-sdk/src/index.ts | 1 + .../squad-sdk/src/platform/comms-teams.ts | 20 +- packages/squad-sdk/src/presets/index.ts | 330 ++ packages/squad-sdk/src/ralph/capabilities.ts | 14 +- packages/squad-sdk/src/resolution.ts | 8 + packages/squad-sdk/src/runtime/otel.ts | 12 +- .../squad-sdk/src/runtime/squad-observer.ts | 75 +- packages/squad-sdk/src/sharing/consult.ts | 6 +- .../squad-sdk/templates/casting-reference.md | 18 + .../templates/skills/cli-wiring/SKILL.md | 4 +- .../skills/coordinator-init-mode/SKILL.md | 83 + .../skills/coordinator-response-mode/SKILL.md | 97 + .../coordinator-source-of-truth/SKILL.md | 45 + .../templates/skills/init-mode/SKILL.md | 8 +- .../templates/skills/personal-squad/SKILL.md | 5 +- .../squad-sdk/templates/skills/squad/SKILL.md | 4 +- .../squad-sdk/templates/spawn-reference.md | 57 +- .../templates/squad.agent.md.template | 251 +- .../templates/workflows/squad-heartbeat.yml | 7 +- .../workflows/squad-issue-assign.yml | 10 +- .../templates/workflows/squad-triage.yml | 14 +- .../templates/workflows/sync-squad-labels.yml | 9 +- packages/squad-sdk/tsconfig.json | 3 +- samples/autonomous-pipeline/README.md | 2 +- samples/autonomous-pipeline/TEST-SCRIPT.md | 2 +- samples/autonomous-pipeline/package.json | 2 +- samples/cost-aware-router/package.json | 2 +- samples/hello-squad/package.json | 2 +- samples/hook-governance/package.json | 2 +- .../storage-provider-sqlite/package-lock.json | 1182 ++-- samples/storage-provider-sqlite/package.json | 2 +- samples/streaming-chat/package-lock.json | 1422 ++--- samples/streaming-chat/package.json | 4 +- src/Squad.Agents.AI/README.md | 2 +- src/Squad.Agents.AI/Squad.Agents.AI.csproj | 16 +- src/Squad.Agents.AI/SquadAgent.cs | 24 +- src/Squad.Agents.AI/SquadAgentDiagnostics.cs | 53 + .../SquadSubagentTraceMapper.cs | 352 +- .../samples/Squad.Agents.AI.Sample/Program.cs | 244 +- .../Squad.Agents.AI.Sample.csproj | 2 +- templates/casting-reference.md | 18 + templates/spawn-reference.md | 57 +- templates/squad.agent.md.template | 251 +- templates/workflows/squad-heartbeat.yml | 7 +- templates/workflows/squad-issue-assign.yml | 10 +- templates/workflows/squad-triage.yml | 14 +- templates/workflows/sync-squad-labels.yml | 9 +- .../mixed-lang/backend/requirements.txt | 4 +- .../dogfood/python-flask/requirements.txt | 6 +- test-fixtures/dogfood/python-flask/setup.py | 4 +- .../Squad.Agents.AI.Tests.csproj | 14 +- .../SquadAgentRoutingTests.cs | 8 +- .../SquadSubagentTraceTests.cs | 455 ++ test/adapter-client.test.ts | 12 +- test/aspire-integration.test.ts | 13 +- test/charter-compiler.test.ts | 66 + test/cli-packaging-smoke.test.ts | 1 + test/cli-shell-comprehensive.test.ts | 12 +- test/cli/command-help.test.ts | 1 - test/cli/consult.test.ts | 10 +- test/cli/loop.test.ts | 1 + test/cli/watch-capabilities.test.ts | 2 +- test/client.test.ts | 38 +- test/comms-teams-integration.test.ts | 32 +- test/coordinator-inline-dispatch-gate.test.ts | 111 + test/docs-build.test.ts | 26 +- test/fan-out.test.ts | 271 + test/human-journeys.test.ts | 2 +- test/init-flow.test.cjs | 70 +- test/integration.test.ts | 12 +- test/model-preference.test.ts | 302 + test/personal-squad-init.test.ts | 29 + test/presets.test.ts | 123 + test/routing.test.ts | 56 + test/session-traces.test.ts | 12 +- test/spawn-backend.test.ts | 187 + test/squad-agent-roster.test.ts | 80 +- test/squad-observer.test.ts | 64 +- test/state/squad-state-gaps.test.ts | 2 - test/storage-provider.test.ts | 30 +- test/version-stamping.test.cjs | 16 +- 229 files changed, 11675 insertions(+), 7381 deletions(-) create mode 100644 .changeset/bump-otel-sdk-2x.md create mode 100644 .changeset/cli-upgrade-fixes.md create mode 100644 .changeset/docs-platform-path-fixes.md create mode 100644 .changeset/feat-preset-install.md create mode 100644 .changeset/fix-cli-sdk-workspace-pin.md create mode 100644 .changeset/fix-coordinator-inline-dispatch-gate.md create mode 100644 .changeset/fix-copilot-sdk-type-compat.md create mode 100644 .changeset/fix-dep0190-shell-spawn.md create mode 100644 .changeset/fix-green-suite.md create mode 100644 .changeset/fix-routing-strip-example-quotes.md create mode 100644 .changeset/fix-sdk-export-gitignore-state.md create mode 100644 .changeset/fix-squad-home-env-bugs.md create mode 100644 .changeset/help-externalize-internalize.md create mode 100644 .changeset/ink7-adaptation.md create mode 100644 .changeset/observer-hardening.md create mode 100644 .changeset/otel-resource-api-migration.md create mode 100644 .changeset/reasoning-effort.md create mode 100644 .changeset/release-0.11.0.md create mode 100644 .changeset/rename-dotnet-aspire-to-aspire.md create mode 100644 .changeset/rename-hire-to-cast.md create mode 100644 .changeset/slim-squad-agent-md-phase1.md create mode 100644 .changeset/spawn-backend-followups-1377.md create mode 100644 .changeset/spoiler-aware-casting.md create mode 100644 .changeset/sub-sessions-spawn-backend.md create mode 100644 .changeset/types-node-25.md create mode 100644 .changeset/typescript-6.md create mode 100644 .changeset/vitest-4-upgrade.md create mode 100644 .changeset/vscode-jsonrpc-9-sdk.md create mode 100644 .github/CODEOWNERS create mode 100644 .squad/decisions/inbox/procedures-fix-coordinator-inline-dispatch-gate.md create mode 100644 .squad/skills/coordinator-init-mode/SKILL.md create mode 100644 .squad/skills/coordinator-response-mode/SKILL.md create mode 100644 .squad/skills/coordinator-source-of-truth/SKILL.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 SUPPORT.md rename docs/src/{content/config.ts => content.config.ts} (100%) create mode 100644 packages/squad-cli/templates/skills/coordinator-init-mode/SKILL.md create mode 100644 packages/squad-cli/templates/skills/coordinator-response-mode/SKILL.md create mode 100644 packages/squad-cli/templates/skills/coordinator-source-of-truth/SKILL.md create mode 100644 packages/squad-sdk/src/coordinator/spawn-backend.ts create mode 100644 packages/squad-sdk/templates/skills/coordinator-init-mode/SKILL.md create mode 100644 packages/squad-sdk/templates/skills/coordinator-response-mode/SKILL.md create mode 100644 packages/squad-sdk/templates/skills/coordinator-source-of-truth/SKILL.md create mode 100644 test/coordinator-inline-dispatch-gate.test.ts create mode 100644 test/spawn-backend.test.ts diff --git a/.changeset/bump-otel-sdk-2x.md b/.changeset/bump-otel-sdk-2x.md new file mode 100644 index 000000000..06d0af4dd --- /dev/null +++ b/.changeset/bump-otel-sdk-2x.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-sdk": patch +--- + +deps: bump @opentelemetry/sdk-metrics and @opentelemetry/sdk-trace-node from 1.x to 2.8.0 diff --git a/.changeset/cli-upgrade-fixes.md b/.changeset/cli-upgrade-fixes.md new file mode 100644 index 000000000..159088d0a --- /dev/null +++ b/.changeset/cli-upgrade-fixes.md @@ -0,0 +1,12 @@ +--- +"@bradygaster/squad-cli": patch +"@bradygaster/squad-sdk": patch +--- + +Fix CLI and upgrade bugs: +- `squad externalize` and `squad internalize` now appear in `squad --help` output (#1050) +- `squad build` writes files to the externalized state directory when applicable (#1048) +- `squad new agent` correctly adds agent definitions to `squad.config.ts` (#1047) +- `squad upgrade` backs up customized `squad.agent.md` before overwriting; supports `--dry-run` (#1052) +- Explicit `@agent` mentions bypass direct-response handler and route to named agent (#1029) +- `state-mcp` server uses lazy initialization to avoid blocking multi-MCP startup (#1353) diff --git a/.changeset/docs-platform-path-fixes.md b/.changeset/docs-platform-path-fixes.md new file mode 100644 index 000000000..d2be7342c --- /dev/null +++ b/.changeset/docs-platform-path-fixes.md @@ -0,0 +1,6 @@ +--- +"@bradygaster/squad-sdk": patch +"@bradygaster/squad-cli": patch +--- + +Fix platform-specific path examples in personal squad docs, JSDoc, and bundled skill templates. diff --git a/.changeset/feat-preset-install.md b/.changeset/feat-preset-install.md new file mode 100644 index 000000000..4c47d0c29 --- /dev/null +++ b/.changeset/feat-preset-install.md @@ -0,0 +1,60 @@ +--- +"@bradygaster/squad-cli": minor +"@bradygaster/squad-sdk": minor +--- + +feat: add `squad preset install ` for sharing presets via repo URL or local path (#1224) + +Closes #1224. Adds a new subcommand that installs a single preset from a GitHub URL or local path into `$SQUAD_HOME/presets//` — the peer-to-peer preset sharing flow that was missing in v0.10.0. + +### CLI + +```bash +# From a GitHub repo (preset at /preset.json or /presets//) +squad preset install https://github.com/tamir/my-presets#my-awesome-team + +# From a sub-path +squad preset install https://github.com/tamir/my-presets/tree/main/presets/my-awesome-team + +# From SSH URL +squad preset install git@github.com:tamir/my-presets.git#my-awesome-team + +# From a local path +squad preset install ./my-awesome-team + +# Override the installed name +squad preset install https://github.com/tamir/my-presets#my-awesome-team --name corp-team + +# Overwrite an existing preset +squad preset install --force +``` + +After install, the preset is a normal entry in `$SQUAD_HOME/presets/` — `squad preset list`, `squad preset apply `, and `squad init --preset ` all work as today. + +### Why + +The existing `squad preset init --remote` flow is for syncing your *whole* `$SQUAD_HOME` repo across machines (single-user, multi-machine use case). There was no built-in way to install just *one* preset from someone else's repo. Users had to: +1. Manually clone the repo to a temp dir +2. `cp -r /presets/ $SQUAD_HOME/presets/` +3. Then `squad preset apply ` + +…or hijack `SQUAD_HOME` (which collides with their own personal squad config). + +### What it does + +1. Resolves source — GitHub URL → shallow `git clone --depth 1` to a temp dir; local path → use as-is +2. Locates the preset within the source via 3 patterns: + - Source dir contains `preset.json` → single-preset source + - Source dir contains a `presets/` collection → require `--name` (or `#name` fragment) to pick + - Source dir IS the `presets/` dir → require `--name`, or auto-pick if only one preset +3. Validates the preset's `preset.json` manifest (name, agents[]) before any destructive action +4. Verifies `agents/` directory exists +5. Copies `preset.json` (with optional rename) + `agents/` into `$SQUAD_HOME/presets//` +6. Fail-stops if destination exists, unless `--force` is passed +7. Cleans up the temp clone whether install succeeds or fails + +### What's NOT in scope (deferred to follow-ups) + +- `squad preset uninstall` — `rm -rf $SQUAD_HOME/presets/` works for now +- `squad preset update ` to pull a fresh version from origin +- Public preset registry / discovery catalog diff --git a/.changeset/fix-cli-sdk-workspace-pin.md b/.changeset/fix-cli-sdk-workspace-pin.md new file mode 100644 index 000000000..70050760a --- /dev/null +++ b/.changeset/fix-cli-sdk-workspace-pin.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-cli": patch +--- + +Force the CLI's squad-sdk dependency through the repo workspace by using a root file dependency plus an npm override for the CLI package. This keeps fresh installs linked to the local SDK prerelease instead of pulling the stale published SDK. Closes #1405. diff --git a/.changeset/fix-coordinator-inline-dispatch-gate.md b/.changeset/fix-coordinator-inline-dispatch-gate.md new file mode 100644 index 000000000..5871a8941 --- /dev/null +++ b/.changeset/fix-coordinator-inline-dispatch-gate.md @@ -0,0 +1,24 @@ +--- +"@bradygaster/squad-cli": patch +"@bradygaster/squad-sdk": patch +--- + +Fix coordinator inline-dispatch regression: restore always-on dispatch gate in the Squad coordinator template + +**Problem** + +In v0.10.0 the Squad coordinator started doing domain work itself instead of dispatching to roster agents (it worked in v0.9.4). Root cause: commit `afe78188` (#1035, "context overflow sentinel + coordinator size reduction") relocated the concrete inline-dispatch gate and the dispatch mechanics out of the always-on coordinator prompt (`.squad-templates/squad.agent.md`) into lazy-loaded reference files (`client-compatibility-reference.md`, `spawn-reference.md`). The remaining inline one-liner ("inline work is last-resort fallback only") was too soft, and the concrete "when may I work inline?" rule + the VS Code `runSubagent` how-to were no longer in-context, so the coordinator defaulted to executing work itself. + +**Fix** + +Three minimal edits to the canonical coordinator template (`.squad-templates/squad.agent.md`), synced to all mirrors via `npm run sync-templates`: + +1. **Client Compatibility** — replaced the soft one-liner with an explicit **Inline-dispatch gate**: doing domain work inline is permitted ONLY in Direct Mode, or when NEITHER `task` NOR `runSubagent` is available; otherwise the coordinator MUST dispatch. +2. **How to Spawn an Agent** — added a one-line **STOP gate**: about to produce a domain artifact with no `task` / `runSubagent` call this turn → STOP and dispatch (exceptions: Direct Mode, or no spawn tool exists). +3. Re-inlined a ~5-line **VS Code `runSubagent` micro-playbook** so the how-to-dispatch mechanics are always-on instead of lazy-loaded. + +The legitimate Direct/Lightweight response modes are preserved (Lightweight still spawns one agent; Direct still answers from context without spawning). + +**Validation** + +New deterministic, subprocess-free regression test `test/coordinator-inline-dispatch-gate.test.ts` asserts the gate/STOP-gate/micro-playbook exist in canonical and that the gate is present in all 5 synced copies (parity). Red before the fix (8/8 failing), green after. Existing requires a `squad upgrade` for existing installs to pick up the new template. diff --git a/.changeset/fix-copilot-sdk-type-compat.md b/.changeset/fix-copilot-sdk-type-compat.md new file mode 100644 index 000000000..05fd158c7 --- /dev/null +++ b/.changeset/fix-copilot-sdk-type-compat.md @@ -0,0 +1,12 @@ +--- +"@bradygaster/squad-sdk": patch +--- + +fix: update adapter/client.ts for @github/copilot-sdk 1.0.4 API changes + +Fixes 5 TypeScript errors caused by breaking changes in `@github/copilot-sdk@1.0.4`: + +- Replace removed `cliPath`/`cliArgs`/`useStdio`/`port`/`cliUrl` options with the new `RuntimeConnection` pattern +- Update `ping()` return type from `timestamp: number` to `timestamp: string` +- Make `SquadModelBilling.multiplier` optional to match upstream `ModelBilling` +- Replace `client.on()` with `client.onLifecycle()` for session lifecycle events diff --git a/.changeset/fix-dep0190-shell-spawn.md b/.changeset/fix-dep0190-shell-spawn.md new file mode 100644 index 000000000..8cb081f75 --- /dev/null +++ b/.changeset/fix-dep0190-shell-spawn.md @@ -0,0 +1,11 @@ +--- +"@bradygaster/squad-cli": patch +--- + +Fix Windows shell spawn issues and gh auth false-negative + +- **DEP0190 fix:** Added `escapeForCmd()`/`escapeArgs()` to properly quote args when `shell: true` is used on Windows. Changed `shell: true` → `shell: IS_WINDOWS`. +- **Capability migration:** Moved monitor-email, monitor-teams, retro, decision-hygiene from inline `buildAgentCommand`/`spawnWithTimeout` to shared `agent-spawn.ts` module. +- **gh auth fix:** Use `gh auth token` instead of `gh auth status` — the latter returns non-zero when any keyring entry is stale, even if the active account works fine. +- **Copilot flag fix:** Use `-p` (correct) instead of `--message` (non-existent) for copilot CLI prompt flag. +- **Default --yolo:** When `--execute` is active and no `copilotFlags`/`agentCmd` are set, default to `--yolo` so copilot doesn't hang waiting for permission prompts. \ No newline at end of file diff --git a/.changeset/fix-green-suite.md b/.changeset/fix-green-suite.md new file mode 100644 index 000000000..7e7911126 --- /dev/null +++ b/.changeset/fix-green-suite.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-sdk": patch +--- + +Fix local test-suite failures by aligning personal squad path expectations, stabilizing observer file classification, and skipping environment-dependent docs/Aspire checks when required local dependencies are unavailable. diff --git a/.changeset/fix-routing-strip-example-quotes.md b/.changeset/fix-routing-strip-example-quotes.md new file mode 100644 index 000000000..365753800 --- /dev/null +++ b/.changeset/fix-routing-strip-example-quotes.md @@ -0,0 +1,9 @@ +--- +"@bradygaster/squad-sdk": patch +--- + +Fix: strip surrounding quotes from routing.md example phrases in `parseRoutingMarkdown` + +`parseRoutingMarkdown` split the Examples cell on commas but never stripped the surrounding quotes, so a quoted example like `"unit tests"` was stored verbatim (quotes included). When `compileRoutingRules` / `matchRoute` later tokenized it, the quote characters glued onto the boundary words (`"unit`, `tests"`), producing regex patterns that could never match — so routing tables that used quoted examples silently routed every message to the fallback agent. + +Leading/trailing quotes (`"`, `'`, `` ` ``) are now stripped from each example, so quoted and unquoted examples behave identically. Adds regression tests covering both the parse step and end-to-end `matchRoute`. diff --git a/.changeset/fix-sdk-export-gitignore-state.md b/.changeset/fix-sdk-export-gitignore-state.md new file mode 100644 index 000000000..0f23309b8 --- /dev/null +++ b/.changeset/fix-sdk-export-gitignore-state.md @@ -0,0 +1,11 @@ +--- +"@bradygaster/squad-sdk": patch +--- + +fix(sdk): export `addSquadStateGitignoreBlock` / `removeSquadStateGitignoreBlock` from the package root + +`packages/squad-cli/src/cli/commands/migrate-backend.ts` imports these helpers from +`@bradygaster/squad-sdk`, but they were never re-exported from `src/index.ts`. This broke +the CLI TypeScript build (`TS2305: has no exported member`). Re-export the two functions +(plus their marker constants) so the CLI compiles and the state-backend migration command +resolves them. Unblocks cutting a stable release that carries the #1378 inline-dispatch-gate fix. diff --git a/.changeset/fix-squad-home-env-bugs.md b/.changeset/fix-squad-home-env-bugs.md new file mode 100644 index 000000000..e5bd0da9d --- /dev/null +++ b/.changeset/fix-squad-home-env-bugs.md @@ -0,0 +1,12 @@ +--- +"@bradygaster/squad-sdk": patch +"@bradygaster/squad-cli": patch +--- + +fix: respect SQUAD_HOME in capabilities.ts and comms-teams.ts, implement SQUAD_PERSONAL_DIR env var, fix Windows shell flag in loop preflight, link personal squad during init + +- capabilities.ts: use `resolveSquadHome()` instead of hardcoded `~/.squad/` for machine-capabilities.json (#1280) +- comms-teams.ts: use `resolveSquadHome()` instead of hardcoded `~/.squad/` for Teams OAuth token storage (#1279) +- resolution.ts: implement `SQUAD_PERSONAL_DIR` env var override in `resolvePersonalSquadDir()` (#1278) +- loop.ts: add `shell: process.platform === 'win32'` to `checkCopilotCli()` execFile call (#1372) +- init.ts: set `teamRoot` in config.json to personal squad directory when one exists (#1010, #984) diff --git a/.changeset/help-externalize-internalize.md b/.changeset/help-externalize-internalize.md new file mode 100644 index 000000000..4d71e7b76 --- /dev/null +++ b/.changeset/help-externalize-internalize.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-cli": patch +--- + +Add `externalize` and `internalize` to the top-level `squad --help` command list so they are discoverable (#1050). diff --git a/.changeset/ink7-adaptation.md b/.changeset/ink7-adaptation.md new file mode 100644 index 000000000..626297867 --- /dev/null +++ b/.changeset/ink7-adaptation.md @@ -0,0 +1,12 @@ +--- +"@bradygaster/squad-cli": patch +--- + +chore: adapt CLI to ink 7.0.6 + +Updates the ink dependency from ^6.8.0 to ^7.0.6 and adapts keyboard handling in InputPrompt.tsx to ink 7's revised API: + +- key.meta no longer fires on bare Escape; guard updated to use key.escape +- useInput callbacks are now wrapped in reconciler.discreteUpdates(), which flushes React state synchronously at end of callback; rewrote Enter/paste handler to call setValue('') inside the flush rather than in a deferred timer + +Closes #1322, #1335. diff --git a/.changeset/observer-hardening.md b/.changeset/observer-hardening.md new file mode 100644 index 000000000..e71207b54 --- /dev/null +++ b/.changeset/observer-hardening.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-sdk": patch +--- + +Harden squad observer change resolution against directory events, symlinks, and filesystem scan errors. diff --git a/.changeset/otel-resource-api-migration.md b/.changeset/otel-resource-api-migration.md new file mode 100644 index 000000000..8b6b1082f --- /dev/null +++ b/.changeset/otel-resource-api-migration.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-sdk": patch +--- + +Migrate OTel Resource API to `resourceFromAttributes` for compatibility with `@opentelemetry/resources` 2.x (replaces the removed `Resource` class constructor in `packages/squad-sdk/src/runtime/otel.ts` and `test/aspire-integration.test.ts`) \ No newline at end of file diff --git a/.changeset/reasoning-effort.md b/.changeset/reasoning-effort.md new file mode 100644 index 000000000..54ed26a13 --- /dev/null +++ b/.changeset/reasoning-effort.md @@ -0,0 +1,17 @@ +--- +"@bradygaster/squad-sdk": minor +--- + +Add reasoning effort support to agent spawning pipeline + +Thread `reasoningEffort` through the full agent lifecycle: + +- **Charter**: Parse `**Reasoning Effort:**` from `## Model` section in `charter.md` +- **Config**: Read/write `defaultReasoningEffort` and `agentReasoningEffortOverrides` in `.squad/config.json` +- **Resolution**: New `resolveReasoningEffort()` with layered priority (per-agent config override > global config > spawn override > charter preference > undefined) +- **Clamping**: New `clampReasoningEffort()` caps effort to model's max supported level +- **Lifecycle**: `SpawnAgentOptions.reasoningEffortOverride` passes through to `SquadSessionConfig` +- **Fan-Out**: `AgentSpawnConfig.reasoningEffortOverride` passes through to `createSession()` +- **Builders**: `defineAgent()` and `defineDefaults()` accept `reasoningEffort: "low" | "medium" | "high" | "xhigh"` +- **Template**: Updated charter template with `**Reasoning Effort:** auto` +- **Validation**: `"auto"` and invalid values are normalized to `undefined` at parse time diff --git a/.changeset/release-0.11.0.md b/.changeset/release-0.11.0.md new file mode 100644 index 000000000..225502b45 --- /dev/null +++ b/.changeset/release-0.11.0.md @@ -0,0 +1,6 @@ +--- +"@bradygaster/squad-cli": minor +"@bradygaster/squad-sdk": minor +--- + +Stage the 0.11.0 release by cleaning the committed package versions and publishing the 0.11.0 changelog notes. diff --git a/.changeset/rename-dotnet-aspire-to-aspire.md b/.changeset/rename-dotnet-aspire-to-aspire.md new file mode 100644 index 000000000..c083185a4 --- /dev/null +++ b/.changeset/rename-dotnet-aspire-to-aspire.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-cli": patch +--- + +Rename user-facing Aspire terminology in CLI help text and command output. Aspire is a standalone OTLP dashboard, not limited to .NET apps. diff --git a/.changeset/rename-hire-to-cast.md b/.changeset/rename-hire-to-cast.md new file mode 100644 index 000000000..41eab8b42 --- /dev/null +++ b/.changeset/rename-hire-to-cast.md @@ -0,0 +1,5 @@ +--- +"squad-cli": minor +--- + +Rename `hire` command to `cast` in CLI help and documentation. The `hire` command continues to work as a silent alias (like `cls`/`clear` in PowerShell) — no deprecation, no warnings, it just does the same thing. All user-facing text now presents `cast` as the canonical verb because we're casting agents, not hiring humans. diff --git a/.changeset/slim-squad-agent-md-phase1.md b/.changeset/slim-squad-agent-md-phase1.md new file mode 100644 index 000000000..1b4b05ebf --- /dev/null +++ b/.changeset/slim-squad-agent-md-phase1.md @@ -0,0 +1,90 @@ +--- +"@bradygaster/squad-sdk": minor +"@bradygaster/squad-cli": minor +--- + +Slim squad.agent.md by extracting 3 sections to satellite skills (closes #1308 phase 1) + +## Symptom + +`squad.agent.md` is loaded as the agent prompt at every Copilot session start. As of v0.10.0-insider.1 it sits at **81 KB / 1,137 lines** and was steadily growing — the v0.10 stabilisation pass alone added +10.6 KB / +114 lines. Every byte of that file is paid at every session. + +## Phase 1 fix (this PR) + +Extract three **low-cross-reference, high-byte** sections into satellite skills the coordinator loads on demand via the `skill` tool. Same pattern that already worked for `cross-squad-communication` (#1295). + +| Extracted to satellite skill | Bytes saved in main file | +|---|---| +| `coordinator-init-mode` (Init Mode Phase 1 + Phase 2) | 5.4 KB | +| `coordinator-source-of-truth` (Source of Truth Hierarchy table) | 4.4 KB | +| `coordinator-response-mode` (Response Mode Selection + Lightweight Spawn Template) | 3.9 KB | + +### Result + +- `squad.agent.md`: **81 KB → 70 KB** (-13.9%, -140 lines) +- Three new bundled skills (`coordinator-source-of-truth`, `coordinator-response-mode`, `coordinator-init-mode`) installed alongside the existing 16 — total 19 skills at `.github/skills/` +- Behaviour unchanged: each removed section is replaced with a stub that names the trigger condition and instructs the coordinator to `skill(coordinator-X)` before acting + +## Stubs left in `squad.agent.md` + +Each extracted section keeps a short stub (4-12 lines) so the coordinator still knows the section exists and what triggers it. The full algorithm/table/template lives in the satellite skill. + +Example (Source of Truth, was 30+ lines, now 9): + +```markdown +## Source of Truth Hierarchy + +Squad files split into **authoritative** (governance, roster, charters — static) and +**derived / append-only** (decisions, history, logs — runtime-owned). The four +governing rules: + +1. **`squad.agent.md` wins** any conflict with another file. +2. **Append-only files** are never retroactively edited. +3. **Agents may only write to files in their "Who May Write" column** of the hierarchy. +4. **Only Squad (Coordinator)** records accepted decisions in `.squad/decisions.md`. + +**For the full file-by-file table** (who writes / who reads / authoritative vs derived +for `team.md`, `decisions.md`, `routing.md`, `casting/*`, `agents/{name}/*`, `rai/*`, +`fact-checker/*`, `orchestration-log/`, `log/`, `templates/`, `plugins/marketplaces.json`): +invoke the `skill` tool on **`coordinator-source-of-truth`** to load the complete reference. +``` + +## What is NOT extracted (intentional) + +These sections stay in `squad.agent.md` because they're loaded on EVERY prompt and / or are load-bearing for first-touch behaviour: + +- **Team Mode + state-backend handshake + HARD RULE** (#1306) — handshake must fire before any state write +- **Routing table** — the trigger table for every user prompt +- **Hard trigger — keyword-to-skill match** paragraph (#1307) — load-bearing for the spawn-routing fix +- **How to Spawn an Agent** — referenced from every routing-table action +- **Coordinator Identity / Personal Squad / Memory Governance Tools** — frequently re-read inline + +Follow-up issues will tackle these one at a time if/when needed. + +## Wired changes + +- New: `.squad/skills/coordinator-{source-of-truth,response-mode,init-mode}/SKILL.md` are the **canonical source files** kept in the squad repo itself (the same convention every other bundled skill follows). At install/upgrade time they are copied to `.github/skills/coordinator-{x}/SKILL.md` in the user's repo — that's the Copilot CLI custom-skills location adopted by #1304 (formerly `.copilot/skills/`). +- Mirrored at build time to `packages/squad-cli/templates/skills/` and `packages/squad-sdk/templates/skills/` via `scripts/sync-skill-templates.mjs` (same path every other manifest skill takes). +- `MANIFEST_SKILL_NAMES` in `packages/squad-sdk/src/config/init.ts` grows by 3 entries (now 19) +- `TEMPLATE_MANIFEST` in `packages/squad-cli/src/cli/core/templates.ts` grows by 3 entries with `../.github/skills/` destinations (matching the post-#1304 install location) +- `.squad-templates/squad.agent.md` replaces each extracted section with a stub +- 4 mirrored copies re-synced via `scripts/sync-templates.mjs --sync` + +## Tests + +- `test/init.test.ts > should install every manifest-curated skill (regression: bradygaster/squad#1289, #1264)` — already imports `MANIFEST_SKILL_NAMES` and iterates it, so it automatically asserts the 3 new skills install. **287/287 tests pass.** +- Smoke test: fresh `squad init` produces 19 skills at `.github/skills/`, `squad.agent.md` is 70 KB. + +## Composability + +Disjoint from all other open PRs. Pure file moves + a few line replacements + 3 manifest entries. + +## Follow-ups (separate PRs) + +The remaining high-bloat sections in `squad.agent.md` are stickier and need careful design to extract: + +- **Routing table** (9.2 KB) — needed on every prompt; can't be lazy-loaded as-is. May be slimmable by moving the action-cell verbiage into satellite skills (the trigger table stays, the action prose moves). +- **Team Mode** (5.5 KB) — contains the state-backend handshake, which MUST fire on every session. Maybe extract the worktree-awareness / casting-migration sub-sections. +- **How to Spawn an Agent** (3.2 KB) — could extract the role-emoji catalog. + +Target: get the coordinator down to **~45 KB** in 2-3 follow-up PRs. diff --git a/.changeset/spawn-backend-followups-1377.md b/.changeset/spawn-backend-followups-1377.md new file mode 100644 index 000000000..590d9d2ed --- /dev/null +++ b/.changeset/spawn-backend-followups-1377.md @@ -0,0 +1,26 @@ +--- +"@bradygaster/squad-cli": patch +"@bradygaster/squad-sdk": patch +--- + +Fix #1377 follow-ups: spawn-backend fallback, slot-leak guards, timeouts, prompt sanitization + +Addresses the six deferred review items from the #1385 review (sub-sessions in the Copilot App). All are hardening fixes to the spawn coordinator (`packages/squad-sdk/src/coordinator/`) plus a template probe-order correction. + +**Blockers** + +1. **App→task fallback (`fan-out.ts`).** When the platform `SpawnBackend` (e.g. App sub-sessions) returns `success: false` — concurrency cap, unavailable tool, transient error — `spawnSingle()` no longer throws and fails the agent. It emits a `session.spawn_fallback` event and falls through to the direct `createSession` path, honoring the template contract ("if `create_session` fails, retry with `task`"). The direct path is now extracted into a shared `spawnViaCreateSession()` helper. + +2. **Concurrency slot leak (`fan-out.ts`).** `registerSpawnRelease()` now (a) treats `completed` as a terminal status (previously only `idle`/`error`/`destroyed` released the slot), and (b) installs an unref'd max-lifetime safety timer (default 1h) that force-releases the slot if a silently-crashed sub-session emits no terminal event. The timer is cleared on normal release. + +3. **Template detection-order drift.** Re-synced the canonical `.squad-templates/squad.agent.md` probe order (`create_session` → `runSubagent` → `task` → inline) to all mirror copies, which had `task` and `runSubagent` swapped. + +**Risks** + +4. **Prompt-injection hardening (`fan-out.ts`).** `buildInitialPrompt()` now runs caller-supplied `task`/`context` through `sanitizePromptValue()` (defense-in-depth): strips control characters, neutralizes forged `**Marker:**` headers, and caps length. Not a complete prompt-injection defense, but it stops trivial structural-marker spoofing. + +5. **`createSession` timeout (`spawn-backend.ts`).** Both backends wrap the injected `createSession` call in a timeout (`createSessionTimeoutMs`, default 60s, 0 disables) so a hung factory cannot pin `pendingSpawnCount` / a concurrency slot forever. `SessionSpawnBackend`'s `finally` still decrements the pending counter on timeout. + +6. **Honest `isAvailable()` (`spawn-backend.ts`).** Both backends previously returned `true` unconditionally. They now return a real heuristic (`typeof createSession === 'function'`) and accept an injectable `availabilityCheck` predicate via options. + +Adds vitest coverage for the fallback path, `completed`-status release, the safety-timeout release, both createSession timeouts, prompt sanitization, and `isAvailable()`. diff --git a/.changeset/spoiler-aware-casting.md b/.changeset/spoiler-aware-casting.md new file mode 100644 index 000000000..f352e57a8 --- /dev/null +++ b/.changeset/spoiler-aware-casting.md @@ -0,0 +1,29 @@ +--- +"@bradygaster/squad-cli": patch +"@bradygaster/squad-sdk": patch +--- + +docs(casting): make agent name allocation spoiler-aware + +Agent names are drawn from fictional universes as easter eggs and are +shown in plain text across `team.md`, prompts, logs, and generated files. +Previously the casting rules optimized only for theme fit and uniqueness, +so a freshly set-up squad could surface a character name that encodes a +future title, role, transformation, or fate — spoiling the source +material for a user who is only part-way through it. + +This change updates the canonical naming guidance so that name allocation +now: + +- Prefers the name a character has when first introduced. +- Avoids titles or epithets a character only earns later. +- Avoids names that reveal a transformation, fate, hidden identity, or + later reveal. +- Falls back to a safer character from the same universe when unsure. +- Keeps existing name mappings stable — only the next/new allocation + picks a different spoiler-safe character, so already-named agents are + never renamed. + +Updates the `squad.agent.md` name-allocation rule and the +`casting-reference.md` reference (new "Spoiler Awareness" section), +propagated to all template copies via `scripts/sync-templates.mjs`. diff --git a/.changeset/sub-sessions-spawn-backend.md b/.changeset/sub-sessions-spawn-backend.md new file mode 100644 index 000000000..b9d1c7f9e --- /dev/null +++ b/.changeset/sub-sessions-spawn-backend.md @@ -0,0 +1,22 @@ +--- +"@bradygaster/squad-sdk": minor +"@bradygaster/squad-cli": minor +--- + +Add sub-session spawn backend for Copilot App integration + +Spawn cast members as sub-sessions when running in the Copilot App (Tauri desktop), +giving users richer UX with each squad member visible in the left navigation: + +- **SpawnBackend interface**: Thin abstraction with `TaskSpawnBackend` (CLI) and + `SessionSpawnBackend` (App) implementations +- **Detection**: `detectSpawnBackend()` probes for `create_session` tool availability + at coordinator startup; `detectSpawnPlatform()` returns the platform type +- **Session naming**: `"{Name} {verb}ing {noun}"` convention with 40-char limit + via `truncateSessionName()` and `buildSessionName()` helpers +- **Concurrency cap**: Maximum 4-5 simultaneous sub-sessions with queuing +- **Depth limit**: No sub-sub-sessions — max depth 1 +- **Fallback**: Graceful degradation to `task` tool if `create_session` fails +- **Zero CLI impact**: Behavior unchanged when `create_session` is absent +- **Template updates**: `squad.agent.md` and `spawn-reference.md` updated with + App dispatch mechanism, platform detection probe, and sub-session rules diff --git a/.changeset/types-node-25.md b/.changeset/types-node-25.md new file mode 100644 index 000000000..0dde9d78e --- /dev/null +++ b/.changeset/types-node-25.md @@ -0,0 +1,6 @@ +--- +"@bradygaster/squad-sdk": patch +"@bradygaster/squad-cli": patch +--- + +Bump @types/node from ^22.0.0 to ^25.9.3 in cli and sdk packages. Removes a no-longer-needed @ts-expect-error directive in cli-entry.ts now that process.emit is properly typed. \ No newline at end of file diff --git a/.changeset/typescript-6.md b/.changeset/typescript-6.md new file mode 100644 index 000000000..ae8a4fb80 --- /dev/null +++ b/.changeset/typescript-6.md @@ -0,0 +1,10 @@ +--- +"@bradygaster/squad-sdk": patch +"@bradygaster/squad-cli": patch +--- + +chore: upgrade to TypeScript 6 (closes #1325, #1328, #1334) + +- Bumps `typescript` devDependency from `^5.7.0` to `^6.0.3` in packages/squad-sdk, packages/squad-cli, and the root workspace. +- Bumps `@typescript-eslint/parser` in root from `^8.57.1` to `^8.61.1` to align with the already-present `^8.61.1` plugin (v8.61.1 peer-deps cover `typescript >=4.8.4 <6.1.0`). +- Adds `"types": ["node"]` to `compilerOptions` in `packages/squad-cli/tsconfig.json` and `packages/squad-sdk/tsconfig.json`; TypeScript 6 no longer auto-injects Node globals without an explicit `types` declaration. diff --git a/.changeset/vitest-4-upgrade.md b/.changeset/vitest-4-upgrade.md new file mode 100644 index 000000000..0eac58616 --- /dev/null +++ b/.changeset/vitest-4-upgrade.md @@ -0,0 +1,6 @@ +--- +"@bradygaster/squad-sdk": patch +"@bradygaster/squad-cli": patch +--- + +Bump vitest from v3 to v4 alongside @vitest/coverage-v8 v4 to resolve peer-dep mismatch. Both packages now align at ^4.1.9 across root and all sample workspaces. No API adaptations required — vitest 4's spying rewrite is backwards-compatible for the mock/spyOn patterns used in this codebase. diff --git a/.changeset/vscode-jsonrpc-9-sdk.md b/.changeset/vscode-jsonrpc-9-sdk.md new file mode 100644 index 000000000..7ccaa4ae9 --- /dev/null +++ b/.changeset/vscode-jsonrpc-9-sdk.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-sdk": patch +--- + +Sync lockfile for the vscode-jsonrpc 8→9 bump in packages/squad-sdk. v9 adds the missing `exports` field that v8 lacked, making `vscode-jsonrpc/node` resolvable under strict ESM without `patch-esm-imports.mjs` runtime workarounds. \ No newline at end of file diff --git a/.copilot/skills/cli-wiring/SKILL.md b/.copilot/skills/cli-wiring/SKILL.md index 07ab25284..465d13e9b 100644 --- a/.copilot/skills/cli-wiring/SKILL.md +++ b/.copilot/skills/cli-wiring/SKILL.md @@ -38,7 +38,7 @@ source: "extracted" | Type | Example | How to wire | |------|---------|-------------| | Standard command | `export.ts`, `build.ts` | `run*()` function, parse flags from `args` | -| Placeholder command | `loop`, `hire` | Inline in cli-entry.ts, prints pending message | +| Placeholder command | `loop`, `cast` (alias: `hire`) | Inline in cli-entry.ts, prints pending message | | Utility/check module | `rc-tunnel.ts`, `copilot-bridge.ts` | Wire as diagnostic check (e.g., `isDevtunnelAvailable()`) | | Subcommand of another | `init-remote.ts` | Already used inside parent + standalone alias | @@ -52,4 +52,4 @@ Use dynamic `await import()` for command modules to keep startup fast (lazy load ## History -- **#237 / PR #244:** 4 commands wired (rc, copilot-bridge, init-remote, rc-tunnel). aspire, link, loop, hire were already present. +- **#237 / PR #244:** 4 commands wired (rc, copilot-bridge, init-remote, rc-tunnel). aspire, link, loop, cast (née hire) were already present. diff --git a/.copilot/skills/init-mode/SKILL.md b/.copilot/skills/init-mode/SKILL.md index 430c6ae15..4f937bc65 100644 --- a/.copilot/skills/init-mode/SKILL.md +++ b/.copilot/skills/init-mode/SKILL.md @@ -42,7 +42,7 @@ No team exists yet. Propose one — but **DO NOT create any files until the user 5. Use the `ask_user` tool to confirm the roster. Provide choices so the user sees a selectable menu: - **question:** *"Look right?"* - - **choices:** `["Yes, hire this team", "Add someone", "Change a role"]` + - **choices:** `["Yes, cast this team", "Add someone", "Change a role"]` **⚠️ STOP. Your response ENDS here. Do NOT proceed to Phase 2. Do NOT create any files or directories. Wait for the user's reply.** @@ -69,7 +69,7 @@ No team exists yet. Propose one — but **DO NOT create any files until the user ``` The `union` merge driver keeps all lines from both sides, which is correct for append-only files. This makes worktree-local strategy work seamlessly when branches merge — decisions, memories, and logs from all branches combine automatically. -7. Say: *"✅ Team hired. Try: '{FirstCastName}, set up the project structure'"* +7. Say: *"✅ Team cast. Try: '{FirstCastName}, set up the project structure'"* 8. **Post-setup input sources** (optional — ask after team is created, not during casting): - PRD/spec: *"Do you have a PRD or spec document? (file path, paste it, or skip)"* → If provided, follow PRD Mode flow @@ -87,9 +87,9 @@ The `union` merge driver keeps all lines from both sides, which is correct for a 4. User: *"TypeScript CLI tool with GitHub API integration"* 5. Coordinator runs casting algorithm → selects "The Usual Suspects" universe 6. Proposes: Keaton (Lead), Verbal (Prompt), Fenster (Backend), Hockney (Tester), Scribe, Ralph -7. Uses `ask_user` with choices → user selects "Yes, hire this team" +7. Uses `ask_user` with choices → user selects "Yes, cast this team" 8. Coordinator creates `.squad/` structure, initializes casting state, seeds agents -9. Says: *"✅ Team hired. Try: 'Keaton, set up the project structure'"* +9. Says: *"✅ Team cast. Try: 'Keaton, set up the project structure'"* ## Anti-Patterns diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..ab8e36695 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# For more information, see https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +# This repository is maintained by: +* @bradygaster @tamirdresher diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 8a6ce5d79..e3e017f45 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -1,7 +1,6 @@ --- name: Squad description: "Your AI team. Describe what you're building, get a team of specialists that live in your repo." -tools: ['*'] --- @@ -47,73 +46,13 @@ Check: Does `{TEAM_ROOT}/team.md` exist? (fall back to `.ai-team/team.md` for re --- -## Init Mode — Phase 1: Propose the Team +## Init Mode -No team exists yet. Propose one — but **DO NOT create any files until the user confirms.** +**Trigger:** No `.squad/team.md` exists in the resolved team root — i.e., this is a fresh repo or one that has never been squadified. -1. **Identify the user.** Run `git config user.name` to learn who you're working with. Use their name in conversation (e.g., *"Hey {user}, what are you building?"*). Store their name (NOT email) in `team.md` under Project Context. **Never read or store `git config user.email` — email addresses are PII and must not be written to committed files.** -2. Ask: *"What are you building? (language, stack, what it does)"* -3. **Cast the team.** Before proposing names, run the Casting & Persistent Naming algorithm (see that section): - - Determine team size: pick **4–5 cast (user-domain) agents**, then add the **4 always-on built-ins** (Scribe + Ralph + Rai + Fact Checker — see their dedicated sections below). So a typical fresh squad has **8–9 total roster entries**, not 4–5. - - Determine assignment shape from the user's project description. - - Derive resonance signals from the session and repo context. - - Select a universe. Allocate character names from that universe. - - Scribe is always "Scribe" — exempt from casting. - - Ralph is always "Ralph" — exempt from casting. - - Rai is always "Rai" — exempt from casting. - - Fact Checker is always "Fact Checker" — exempt from casting (same pattern as Scribe / Ralph / Rai). -4. Propose the team with their cast names. Example (names will vary per cast): +**Action:** Invoke the `skill` tool on **`coordinator-init-mode`** to load the full two-phase Init Mode protocol (Phase 1 = propose the team and `ask_user` for confirmation, no files written; Phase 2 = create the `.squad/` scaffolding, casting state, `.gitattributes` for merge drivers, and the always-on built-ins Scribe / Ralph / Rai / Fact Checker). Do NOT improvise — read the skill, then execute Phase 1. -``` -🏗️ {CastName1} — Lead Scope, decisions, code review -⚛️ {CastName2} — Frontend Dev React, UI, components -🔧 {CastName3} — Backend Dev APIs, database, services -🧪 {CastName4} — Tester Tests, quality, edge cases -📋 Scribe — (silent) Memory, decisions, session logs -🔄 Ralph — (monitor) Work queue, backlog, keep-alive -🛡️ Rai — (background) RAI awareness, content safety -``` - -5. Use the `ask_user` tool to confirm the roster. Provide choices so the user sees a selectable menu: - - **question:** *"Look right?"* - - **choices:** `["Yes, hire this team", "Add someone", "Change a role"]` - -**⚠️ STOP. Your response ENDS here. Do NOT proceed to Phase 2. Do NOT create any files or directories. Wait for the user's reply.** - ---- - -## Init Mode — Phase 2: Create the Team - -**Trigger:** The user replied to Phase 1 with confirmation ("yes", "looks good", or similar affirmative), OR the user's reply to Phase 1 is a task (treat as implicit "yes"). - -> If the user said "add someone" or "change a role," go back to Phase 1 step 3 and re-propose. Do NOT enter Phase 2 until the user confirms. - -6. Create the `.squad/` directory structure (see `.squad/templates/` for format guides or use the standard structure: team.md, routing.md, ceremonies.md, decisions.md, decisions/inbox/, casting/, agents/, orchestration-log/, skills/, log/, rai/). - -**Casting state initialization:** Copy `.squad/templates/casting-policy.json` to `.squad/casting/policy.json` (or create from defaults). Create `registry.json` (entries: persistent_name, universe, created_at, legacy_named: false, status: "active") and `history.json` (first assignment snapshot with unique assignment_id). - -**Seeding:** Each agent's `history.md` starts with the project description, tech stack, and the user's name so they have day-1 context. Agent folder names are the cast name in lowercase (e.g., `.squad/agents/ripley/`). The Scribe's charter includes maintaining `decisions.md` and cross-agent context sharing. Rai's charter is seeded from the `Rai-charter.md` template, and `.squad/rai/policy.md` is seeded from `rai-policy.md`. - -**Team.md structure:** `team.md` MUST contain a section titled exactly `## Members` (not "## Team Roster" or other variations) containing the roster table. This header is hard-coded in GitHub workflows (`squad-heartbeat.yml`, `squad-issue-assign.yml`, `squad-triage.yml`, `sync-squad-labels.yml`) for label automation. If the header is missing or titled differently, label routing breaks. - -**Merge driver for append-only files:** Create or update `.gitattributes` at the repo root to enable conflict-free merging of `.squad/` state across branches: -``` -.squad/decisions.md merge=union -.squad/agents/*/history.md merge=union -.squad/log/** merge=union -.squad/orchestration-log/** merge=union -.squad/rai/audit-trail.md merge=union -``` -The `union` merge driver keeps all lines from both sides, which is correct for append-only files. This makes worktree-local strategy work seamlessly when branches merge — decisions, memories, and logs from all branches combine automatically. - -7. Say: *"✅ Team hired. Try: '{FirstCastName}, set up the project structure'"* - -8. **Post-setup input sources** (optional — ask after team is created, not during casting): - - PRD/spec: *"Do you have a PRD or spec document? (file path, paste it, or skip)"* → If provided, follow PRD Mode flow - - GitHub issues: *"Is there a GitHub repo with issues I should pull from? (owner/repo, or skip)"* → If provided, follow GitHub Issues Mode flow - - Human members: *"Are any humans joining the team? (names and roles, or just AI for now)"* → If provided, add per Human Team Members section - - Copilot agent: *"Want to include @copilot? It can pick up issues autonomously. (yes/no)"* → If yes, follow Copilot Coding Agent Member section and ask about auto-assignment - - These are additive. Don't block — if the user skips or gives a task instead, proceed immediately. +**⚠️ Eager-execution exception:** Init Mode is the ONE exception to the eager-execution / parallel-fan-out doctrine. Phase 1 MUST end with a user confirmation before any file is created. --- @@ -122,16 +61,45 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **⚠️ CRITICAL RULE: You are a DISPATCHER, not a DOER. Every task that needs domain expertise MUST be dispatched to a specialist agent — never performed inline.** **DISPATCH MECHANISM (detect once per session, then use consistently):** +- **Copilot App:** `create_session` tool → sub-sessions for commit-producing work (preferred when available) - **CLI:** `task` tool → use it with agent_type, mode, model, name, description, prompt - **VS Code:** `runSubagent` tool → use it with the full agent prompt - **Neither available:** work inline (fallback only — LAST RESORT) +**Platform detection probe (run once at session start):** +1. Check: is `create_session` tool available? → **App mode** (sub-sessions) +2. Else: is `runSubagent` available? → **VS Code mode** +3. Else: is `task` tool available? → **CLI mode** +4. Else: none available → **work inline** (last resort fallback) +5. Cache the result — use the same mechanism for all spawns in this session. + +**Sub-session rules (App mode only):** +- Use `create_session` for agents that produce commits (code, config, docs) +- Use `task` tool for pure analysis, coordination, or read-only research +- **Naming:** `"{Name} {verb}ing {noun}"` — 40-char max, sentence case +- **Concurrency:** Maximum 4-5 simultaneous sub-sessions; queue additional spawns +- **Depth:** No sub-sub-sessions — spawned agents use `task` if they need to delegate +- **Fallback:** If `create_session` fails for an agent, retry with `task` tool +- **Params:** `coordinate_with_creator: true`, `notify_on_idle: "once"`, `kickoff.mode: "autopilot"` + **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. Resolve `CURRENT_DATETIME` once from the `` value in your system context. Sanity-check that it is a real ISO-like timestamp, not placeholder text, with a plausible year and timezone (`Z` or an offset). If the system value is missing or implausible, run a local date command and use that result instead (`date +"%Y-%m-%dT%H:%M:%S%z"` on macOS/Linux, or `Get-Date -Format o` in PowerShell). Pass the team root and the resolved literal current datetime into every spawn prompt as `TEAM_ROOT` and `CURRENT_DATETIME` respectively. Never pass placeholder text for `CURRENT_DATETIME`. 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. **Resolve state backend:** Read `.squad/config.json` (at the resolved TEAM_ROOT) and check the `stateBackend` field. Valid values: `"local"` (default), `"orphan"`, `"two-layer"`. Legacy alias: `"worktree"` maps to `"local"`. Deprecated: `"git-notes"` maps to `"two-layer"` with a deprecation warning. Store as `STATE_BACKEND` and pass it into every spawn prompt. This determines how agents read and write mutable state (history, decisions, logs). Static config (charters, team.md, routing.md) always lives on disk regardless of backend. The `"two-layer"` option combines git-notes (commit-scoped annotations) with orphan branch (permanent state) — see the blog post for the full architecture. +**State-backend handshake — MANDATORY on every session before any state mutation (bradygaster/squad#1305):** + +For all backends EXCEPT `"local"` / `"worktree"`, the runtime owns persistence and you MUST NOT touch `.squad/decisions.md`, `.squad/decisions/inbox/`, `.squad/agents/*/history.md`, `.squad/casting/*.json`, `.squad/identity/*.md`, or `.squad/memory/*` paths via `create` / `edit` / `write_file` tools. Those writes either fail at the pre-commit hook or create phantom state the runtime overwrites at next read — a contract violation that produces silent data loss. + +The `squad_state_*` and `memory.*` tools that own persistence are exposed via the `squad_state` MCP server (declared in `.mcp.json`). Copilot CLI may load MCP tools **lazily** — they are not always advertised in your initial function list at session start. You MUST proactively confirm they are reachable: + +1. If `STATE_BACKEND ∈ {"local", "worktree"}`: file ops on `.squad/` are valid; skip the probe. +2. Otherwise (backend is `orphan`, `two-layer`, or `git-notes`): probe for `squad_state_health` (or any `squad_state_*` / `memory.*` tool) using whatever tool-discovery mechanism your runtime exposes (e.g. `tool_search_tool_regex` in Copilot CLI). If you can locate the tool, call `squad_state_health` once to confirm it answers; on success, treat the bridge as available for the rest of the session. +3. **If the probe fails** (tool not found, or `squad_state_health` errors): **HALT** before any state write. Tell the user verbatim: *"Squad's runtime state bridge is missing for backend `{STATE_BACKEND}`. The `squad_state` MCP server in `.mcp.json` is not reachable in this Copilot session. Restart Copilot CLI so `.mcp.json` is loaded, or change `stateBackend` to `local` in `.squad/config.json`."* — and stop until the user acknowledges. Do not silently fall back to raw file ops. + +This handshake runs **once per session**, not per spawn. Cache the result. + **⚡ 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). **Session catch-up (lazy — not on every start):** Do NOT scan logs on every session start. Only provide a catch-up summary when: @@ -274,14 +242,33 @@ The `name` parameter generates the human-readable agent ID shown in the tasks pa ### Memory Governance Tools -When memory tools are available, use them before writing durable memory by hand: +The `memory.*` tools share the same `squad_state` MCP server as `squad_state_*` (they're aliases in the same registry — see `packages/squad-cli/src/cli/commands/state-mcp.ts`). After the state-backend handshake above confirms the bridge is reachable, prefer governed memory tools for durable writes: - Classify candidate memories with `memory.classify`. - Persist approved durable facts, decisions, and policies with `memory.write`. - Search governed memory with `memory.search` before relying only on raw file search. - Promote, delete, and audit governed entries with `memory.promote`, `memory.delete`, and `memory.audit`. -If memory tools are not available, use runtime state tools for durable Squad state when present. In MCP sessions these are exposed as `squad_state_read`, `squad_state_write`, `squad_state_append`, `squad_state_delete`, `squad_state_list`, and `squad_state_health` aliases. Only fall back to local `.squad/` file writes when `STATE_BACKEND` is `worktree`/`local` and no runtime state tool exists. For `git-notes`, `orphan`, or `two-layer`, do not hand-write mutable state; report that the `squad_state` MCP/runtime state bridge is missing. Never claim provider-backed Copilot Memory, semantic indexing, or remote deletion unless a configured tool or CLI bridge performed the operation. External semantic memory is opt-in; forbidden or transient content must not be persisted. +If `memory.*` is not present in the bridge (older Squad versions before the bridge landed) but `squad_state_*` is, use `squad_state_*` directly. Both are governed paths. + +**HARD RULE — Backend contract enforcement:** If `STATE_BACKEND ∈ {"orphan", "two-layer", "git-notes"}` AND the state-backend handshake (above) did NOT confirm reachable tools, you MUST NOT write to ANY of these paths via `create` / `edit` / `write_file`: + +- `.squad/decisions.md` +- `.squad/decisions/inbox/**` +- `.squad/agents/*/history.md` +- `.squad/casting/*.json` +- `.squad/identity/*.md` +- `.squad/memory/**` +- `.squad/orchestration-log/**` +- `.squad/log/**` +- `.squad/rai/audit-trail.md` +- `.squad/fact-checker/audit-trail.md` + +These are runtime-managed paths under non-local backends. Hand-writing creates phantom state. The pre-commit hook will catch it and fail the user; even if it didn't, the runtime overwrites the file at next read. Report the missing bridge and halt instead. + +For `STATE_BACKEND ∈ {"local", "worktree"}`, file writes to `.squad/` are valid because the local backend IS the filesystem. + +**External memory:** Never claim provider-backed Copilot Memory, semantic indexing, or remote deletion unless a configured tool or CLI bridge performed the operation. External semantic memory is opt-in; forbidden or transient content must not be persisted. ### Routing @@ -349,75 +336,16 @@ Confidence bumps when an agent independently validates an existing skill — app ### Response Mode Selection -After routing determines WHO handles work, select the response MODE based on task complexity. Bias toward upgrading — when uncertain, go one tier higher rather than risk under-serving. - -| Mode | When | How | Target | -|------|------|-----|--------| -| **Direct** | Status checks, factual questions the coordinator already knows, simple answers from context | Coordinator answers directly — NO agent spawn | ~2-3s | -| **Lightweight** | Single-file edits, small fixes, follow-ups, simple scoped read-only queries | Spawn ONE agent with minimal prompt (see Lightweight Spawn Template). Use `agent_type: "explore"` for read-only queries | ~8-12s | -| **Standard** | Normal tasks, single-agent work requiring full context | Spawn one agent with full ceremony — charter inline, history read, decisions read. This is the current default | ~25-35s | -| **Full** | Multi-agent work, complex tasks touching 3+ concerns, "Team" requests | Parallel fan-out, full ceremony, Scribe included | ~40-60s | - -**Direct Mode exemplars** (coordinator answers instantly, no spawn): -- "Where are we?" → Summarize current state from context: branch, recent work, what the team's been doing. A user favorite — make it instant. -- "How many tests do we have?" → Run a quick command, answer directly. -- "What branch are we on?" → `git branch --show-current`, answer directly. -- "Who's on the team?" → Answer from team.md already in context. -- "What did we decide about X?" → Answer from decisions.md already in context. - -**Lightweight Mode exemplars** (one agent, minimal prompt): -- "Fix the typo in README" → Spawn one agent, no charter, no history read. -- "Add a comment to line 42" → Small scoped edit, minimal context needed. -- "What does this function do?" → `agent_type: "explore"` (Haiku model, fast). -- Follow-up edits after a Standard/Full response — context is fresh, skip ceremony. - -**Standard Mode exemplars** (one agent, full ceremony): -- "{AgentName}, add error handling to the export function" -- "{AgentName}, review the prompt structure" -- Any task requiring architectural judgment or multi-file awareness. - -**Full Mode exemplars** (multi-agent, parallel fan-out): -- "Team, build the login page" -- "Add OAuth support" -- Any request that touches 3+ agent domains. - -**Mode upgrade rules:** -- If a Lightweight task turns out to need history or decisions context → treat as Standard. -- If uncertain between Direct and Lightweight → choose Lightweight. -- If uncertain between Lightweight and Standard → choose Standard. -- Never downgrade mid-task. If you started Standard, finish Standard. - -**Lightweight Spawn Template** (skip charter, history, and decisions reads — just the task): +After routing determines WHO handles work, select a **response MODE** (Direct / Lightweight / Standard / Full) based on task complexity. Bias toward upgrading — when uncertain, go one tier higher. -``` -agent_type: "general-purpose" -model: "{resolved_model}" -mode: "background" -name: "{name}" -description: "{emoji} {Name}: {brief task summary}" -prompt: | - You are {Name}, the {Role} on this project. - TEAM ROOT: {team_root} - CURRENT_DATETIME: - WORKTREE_PATH: {worktree_path} - WORKTREE_MODE: {true|false} - **Requested by:** {current user name} - - {% if WORKTREE_MODE %} - **WORKTREE:** Working in `{WORKTREE_PATH}`. All operations relative to this path. Do NOT switch branches. - {% endif %} - - TASK: {specific task description} - TARGET FILE(S): {exact file path(s)} - - Do the work. Keep it focused. - If you made a meaningful decision, persist it with `memory.write` (class: `decision`) when available, or fall back to `squad_decide` / `squad_state_write` to `decisions/inbox/{name}-{brief-slug}.md`. Do not run git notes, switch branches, or write mutable `.squad/` state by hand. - - ⚠️ OUTPUT: Report outcomes in human terms. Never expose tool internals or SQL. - ⚠️ RESPONSE ORDER: After ALL tool calls, write a plain text summary as FINAL output. -``` +| Mode | When (one-line) | +|------|------| +| **Direct** | Status checks the coordinator can answer from context — no agent spawn | +| **Lightweight** | Single-file edits, follow-ups, read-only queries (one agent, minimal prompt) | +| **Standard** | Normal tasks needing full context (one agent, full ceremony) — *default* | +| **Full** | Multi-agent "Team" requests touching 3+ concerns (parallel fan-out) | -For read-only queries, use the explore agent: `agent_type: "explore"` with `"You are {Name}, the {Role}. CURRENT_DATETIME: — {question} TEAM ROOT: {team_root}"` +**For the full decision table, exemplar prompts, mode-upgrade rules, the Lightweight Spawn Template, and explore-agent usage:** invoke the `skill` tool on **`coordinator-response-mode`** to load the complete protocol. ### Per-Agent Model Selection @@ -427,9 +355,40 @@ Use silent fallback chains when a chosen model is unavailable, and omit the `mod **On-demand reference:** Read `.squad/templates/model-selection-reference.md` for the full layer hierarchy, role mapping, fallback chains, spawn formatting, and valid models catalog. +### Per-Agent Reasoning Effort + +Reasoning effort controls how much internal thinking a model does before responding. Higher effort = deeper analysis but more tokens/cost. This is SEPARATE from model selection — you can run the same model at different effort levels. + +Valid levels: `low`, `medium`, `high`, `xhigh`. The value `auto` means "let the model decide" (platform default). + +**Resolution — check these layers in order (first match wins):** + +1. **Persistent Config:** `.squad/config.json` → `agentReasoningEffortOverrides.{agentName}`, then `defaultReasoningEffort` +2. **User directive:** User says "use xhigh thinking" or "think harder" → apply to this spawn +3. **Charter preference:** Agent's `## Model` section → `**Reasoning Effort:** xhigh` +4. **Default:** Do not set reasoning effort (platform decides) + +**When user requests different thinking levels:** Use the SAME model with different reasoning effort — do NOT switch to a different model variant. Reasoning effort is a session parameter, not a model choice. + +- **When user says "always use xhigh thinking" / "think harder by default":** Write `defaultReasoningEffort` to `.squad/config.json`. Acknowledge: `✅ Reasoning effort saved: xhigh — all future sessions will use this until changed.` +- **When user says "use xhigh thinking for {agent}":** Write to `agentReasoningEffortOverrides.{agent}` in `.squad/config.json`. Acknowledge: `✅ {Agent} will always use xhigh reasoning — saved to config.` +- **When user says "clear thinking preference":** Remove reasoning effort fields from `.squad/config.json`. Acknowledge: `✅ Reasoning effort preference cleared — returning to automatic.` + +**Passing reasoning effort to spawns:** + +When the resolved reasoning effort is not `auto` or default, include it in the agent's charter-compiled spawn prompt or session config. The SDK threads it through to `SquadSessionConfig.reasoningEffort` automatically via the charter's `## Model` section. + +**Spawn output format — show the model choice and effort:** + +Follow `.squad/templates/model-selection-reference.md` for the base model-selection rules. When an agent uses a non-default reasoning effort, append it in the acknowledgment (for example, `🧠 DeepThink (claude-opus-4.7-1m-internal · xhigh) — deep architecture analysis`). + ### Client Compatibility -Detect the client surface once per session and adapt spawning behavior accordingly: CLI uses `task`/`read_agent`, VS Code uses `runSubagent`, and inline work is last-resort fallback only. +Detect the client surface once per session and adapt spawning behavior accordingly: CLI uses `task`/`read_agent`, VS Code uses `runSubagent`. + +**Inline-dispatch gate:** Doing domain work yourself inline is permitted ONLY in Direct Mode, or when NEITHER `task` NOR `runSubagent` is available in this session. In every other case you MUST dispatch — `task` on CLI, `runSubagent` on VS Code. Inline is never a shortcut to skip spawning; "it's a small task" is not an exemption (that is Lightweight Mode, which still spawns one agent). + +**VS Code (`runSubagent`) micro-playbook:** Call `runSubagent` with the full inline prompt as the task; drop CLI-only params (`agent_type`, `mode`, `model`, `description`). Issue multiple `runSubagent` calls in one turn to run agents concurrently. You cannot set a per-spawn model on VS Code — accept the session default. Read `client-compatibility-reference.md` only for edge cases (feature degradation, SQL caveats). Do not rely on CLI-only capabilities such as per-spawn model control or the `sql` tool in cross-platform paths. @@ -583,6 +542,8 @@ Before issue-based spawns, check whether worktree mode is active. If it is, reso Every domain task MUST be dispatched through the platform tool (`task` on CLI, `runSubagent` on VS Code). Keep `name` and `description` agent-specific, inline the charter, and pass `TEAM_ROOT`, `CURRENT_DATETIME`, `STATE_BACKEND`, requester, and any worktree context into the prompt. +**STOP gate:** If you are about to produce a domain artifact (code, prose, analysis, a design, a decision) and you have NOT called `task` / `runSubagent` this turn, STOP and dispatch instead. The only exceptions are Direct Mode (answering from context, no spawn) and sessions where no spawn tool exists. "I'll just do this one myself" is the regression this gate prevents. + Preserve the runtime state tool contract exactly as written; backend-specific git choreography belongs to the runtime, not agent prompts. **Full Spawn Template** (inline charter/history/decisions as needed): @@ -695,41 +656,20 @@ If the user wants to remove someone: ## Source of Truth Hierarchy -> **State backend note:** Files below marked as "Derived / append-only" are **mutable state** — agents access them with runtime state tools (`squad_state_read`, `squad_state_write`, `squad_state_append`, `squad_state_delete`, `squad_state_list`). The runtime decides whether the configured backend stores them on disk, git-native state, or an external provider. Files marked as "Authoritative" are **static config** and always live on disk regardless of backend. - -| File | Status | Who May Write | Who May Read | -|------|--------|---------------|--------------| -| `.github/agents/squad.agent.md` | **Authoritative governance.** All roles, handoffs, gates, and enforcement rules. | Repo maintainer (human) | Squad (Coordinator) | -| `.squad/decisions.md` | **Authoritative decision ledger.** Single canonical location for scope, architecture, and process decisions. | Squad (Coordinator) — append only | All agents | -| `.squad/team.md` | **Authoritative roster.** Current team composition. | Squad (Coordinator) | All agents | -| `.squad/routing.md` | **Authoritative routing.** Work assignment rules. | Squad (Coordinator) | Squad (Coordinator) | -| `.squad/ceremonies.md` | **Authoritative ceremony config.** Definitions, triggers, and participants for team ceremonies. | Squad (Coordinator) | Squad (Coordinator), Facilitator agent (read-only at ceremony time) | -| `.squad/casting/policy.json` | **Authoritative casting config.** Universe allowlist and capacity. | Squad (Coordinator) | Squad (Coordinator) | -| `.squad/casting/registry.json` | **Authoritative name registry.** Persistent agent-to-name mappings. | Squad (Coordinator) | Squad (Coordinator) | -| `.squad/casting/history.json` | **Derived / append-only.** Universe usage history and assignment snapshots. | Squad (Coordinator) — append only | Squad (Coordinator) | -| `.squad/agents/{name}/charter.md` | **Authoritative agent identity.** Per-agent role and boundaries. | Squad (Coordinator) at creation; agent may not self-modify | Squad (Coordinator) reads to inline at spawn; owning agent receives via prompt | -| `.squad/agents/{name}/history.md` | **Derived / append-only.** Personal learnings. Never authoritative for enforcement. | Owning agent (append only), Scribe (cross-agent updates, summarization) | Owning agent only | -| `.squad/agents/{name}/history-archive.md` | **Derived / append-only.** Archived history entries. Preserved for reference. | Scribe | Owning agent (read-only) | -| `.squad/orchestration-log/` | **Derived / append-only.** Agent routing evidence. Never edited after write. | Scribe | All agents (read-only) | -| `.squad/log/` | **Derived / append-only.** Session logs. Diagnostic archive. Never edited after write. | Scribe | All agents (read-only) | -| `.squad/templates/` | **Reference.** Format guides for runtime files. Not authoritative for enforcement. | Squad (Coordinator) at init | Squad (Coordinator) | -| `.squad/rai/policy.md` | **Authoritative RAI policy.** Check categories, terminology standards, and opt-out rules. | Squad (Coordinator) at init; Rai may propose updates via decisions inbox | Rai, All agents (read-only) | -| `.squad/rai/audit-trail.md` | **Derived / append-only.** RAI review evidence log. Redacted — never contains raw secrets or harmful content. | Rai (append only) | Rai, Squad (Coordinator) | -| `.squad/fact-checker/policy.md` | **Authoritative verification + Devil's Advocate policy.** Confidence rating taxonomy, hard anti-fabrication rules, mode triggers, opt-out model. | Squad (Coordinator) at init; Fact Checker may propose updates via decisions inbox | Fact Checker, All agents (read-only) | -| `.squad/fact-checker/audit-trail.md` | **Derived / append-only.** Verification verdicts + DA brief evidence log. Succinct — verdict + citation, never raw source material. | Fact Checker (append only) | Fact Checker, Squad (Coordinator) | -| `.squad/plugins/marketplaces.json` | **Authoritative plugin config.** Registered marketplace sources. | Squad CLI (`squad plugin marketplace`) | Squad (Coordinator) | - -**Rules:** -1. If this file (`squad.agent.md`) and any other file conflict, this file wins. -2. Append-only files must never be retroactively edited to change meaning. -3. Agents may only write to files listed in their "Who May Write" column above. -4. Non-coordinator agents may propose decisions in their responses, but only Squad records accepted decisions in `.squad/decisions.md`. +Squad files split into **authoritative** (governance, roster, charters — static) and **derived / append-only** (decisions, history, logs — runtime-owned). The four governing rules: + +1. **`squad.agent.md` wins** any conflict with another file. +2. **Append-only files** are never retroactively edited. +3. **Agents may only write to files in their "Who May Write" column** of the hierarchy. +4. **Only Squad (Coordinator)** records accepted decisions in `.squad/decisions.md`. + +**For the full file-by-file table** (who writes / who reads / authoritative vs derived for `team.md`, `decisions.md`, `routing.md`, `casting/*`, `agents/{name}/*`, `rai/*`, `fact-checker/*`, `orchestration-log/`, `log/`, `templates/`, `plugins/marketplaces.json`): invoke the `skill` tool on **`coordinator-source-of-truth`** to load the complete reference. --- ## Casting & Persistent Naming -Agent names are drawn from a single fictional universe per assignment. Names are persistent identifiers — they do NOT change tone, voice, or behavior. No role-play. No catchphrases. No character speech patterns. Names are easter eggs: never explain or document the mapping rationale in output, logs, or docs. +Agent names are drawn from a single fictional universe per assignment. Names are persistent identifiers — they do NOT change tone, voice, or behavior. No role-play. No catchphrases. No character speech patterns. Names are spoiler-free easter eggs: never explain or document the mapping rationale in output, logs, or docs. ### Universe Allowlist @@ -746,14 +686,15 @@ Agent names are drawn from a single fictional universe per assignment. Names are After selecting a universe: 1. Choose character names that imply pressure, function, or consequence — NOT authority or literal role descriptions. -2. Each agent gets a unique name. No reuse within the same repo unless an agent is explicitly retired and archived. -3. **Scribe is always "Scribe"** — exempt from casting. -4. **Ralph is always "Ralph"** — exempt from casting. -5. **Rai is always "Rai"** — exempt from casting. -6. **@copilot is always "@copilot"** — exempt from casting. If the user says "add team member copilot" or "add copilot", this is the GitHub Copilot coding agent. Do NOT cast a name — follow the Copilot Coding Agent Member section instead. -7. Store the mapping in `.squad/casting/registry.json`. -8. Record the assignment snapshot in `.squad/casting/history.json`. -9. Use the allocated name everywhere: charter.md, history.md, team.md, routing.md, spawn prompts. +2. Avoid spoiler-laden names. Do NOT allocate names, titles, or epithets that reveal hidden identity, fate, twists, or later-acquired roles/states. Prefer the name as introduced early; if only spoiler-bearing options fit, choose a different spoiler-free character from the same universe. +3. Each agent gets a unique name. No reuse within the same repo unless an agent is explicitly retired and archived. +4. **Scribe is always "Scribe"** — exempt from casting. +5. **Ralph is always "Ralph"** — exempt from casting. +6. **Rai is always "Rai"** — exempt from casting. +7. **@copilot is always "@copilot"** — exempt from casting. If the user says "add team member copilot" or "add copilot", this is the GitHub Copilot coding agent. Do NOT cast a name — follow the Copilot Coding Agent Member section instead. +8. Store the mapping in `.squad/casting/registry.json`. +9. Record the assignment snapshot in `.squad/casting/history.json`. +10. Use the allocated name everywhere: charter.md, history.md, team.md, routing.md, spawn prompts. ### Overflow Handling diff --git a/.github/dependabot.yml b/.github/dependabot.yml index da2410e7e..dea6876fb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,26 +1,5 @@ version: 2 updates: - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - day: "monday" - time: "08:00" - timezone: "Etc/UTC" - open-pull-requests-limit: 5 - labels: - - "dependencies" - groups: - opentelemetry: - patterns: - - "@opentelemetry/*" - npm-minor-and-patch: - patterns: - - "*" - update-types: - - "minor" - - "patch" - - package-ecosystem: "nuget" directory: "/src/Squad.Agents.AI" schedule: @@ -79,6 +58,85 @@ updates: update-types: - "version-update:semver-major" + # npm — root workspace (vitest, typescript, shared tooling) + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Etc/UTC" + open-pull-requests-limit: 5 + labels: ["dependencies", "squad:belanna"] + commit-message: + prefix: "deps" + groups: + minor-patch: + update-types: + - "minor" + - "patch" + + # npm — Squad CLI package + - package-ecosystem: "npm" + directory: "/packages/squad-cli" + schedule: + interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Etc/UTC" + open-pull-requests-limit: 5 + labels: ["dependencies", "squad:belanna"] + commit-message: + prefix: "deps" + groups: + minor-patch: + update-types: + - "minor" + - "patch" + + # npm — Squad SDK package + - package-ecosystem: "npm" + directory: "/packages/squad-sdk" + schedule: + interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Etc/UTC" + open-pull-requests-limit: 5 + labels: ["dependencies", "squad:belanna"] + commit-message: + prefix: "deps" + groups: + minor-patch: + update-types: + - "minor" + - "patch" + + # npm — Docs site (Astro/Starlight) + - package-ecosystem: "npm" + directory: "/docs" + schedule: + interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Etc/UTC" + open-pull-requests-limit: 5 + labels: ["dependencies", "squad:belanna"] + commit-message: + prefix: "deps" + groups: + astro-minor-patch: + patterns: + - "@astrojs/*" + - "astro" + update-types: + - "minor" + - "patch" + minor-patch: + update-types: + - "minor" + - "patch" + - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.github/workflows/squad-agents-ai-ci.yml b/.github/workflows/squad-agents-ai-ci.yml index cdd8b5081..b2078ee6b 100644 --- a/.github/workflows/squad-agents-ai-ci.yml +++ b/.github/workflows/squad-agents-ai-ci.yml @@ -44,10 +44,10 @@ jobs: DOTNET_NOLOGO: '1' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Setup .NET SDK - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: '10.0.x' @@ -77,14 +77,14 @@ jobs: - name: Upload test results if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: squad-agents-ai-testresults-${{ matrix.os }} path: TestResults if-no-files-found: ignore - name: Upload NuGet packages - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: squad-agents-ai-nupkgs-${{ matrix.os }} path: nupkgs diff --git a/.github/workflows/squad-agents-ai-release.yml b/.github/workflows/squad-agents-ai-release.yml index cf84a2d89..dd7ef861c 100644 --- a/.github/workflows/squad-agents-ai-release.yml +++ b/.github/workflows/squad-agents-ai-release.yml @@ -40,7 +40,6 @@ on: - main paths: - 'src/Squad.Agents.AI/**' - - 'test/Squad.Agents.AI.Tests/**' - 'Directory.*.props' - 'global.json' @@ -63,12 +62,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Setup .NET SDK - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: '10.0.x' @@ -163,7 +162,7 @@ jobs: - name: Upload NuGet package artifacts if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: squad-agents-ai-nupkgs-${{ steps.version.outputs.PACKAGE_VERSION }} path: nupkgs/* diff --git a/.github/workflows/squad-ci.yml b/.github/workflows/squad-ci.yml index 1070d4484..c709dd5ee 100644 --- a/.github/workflows/squad-ci.yml +++ b/.github/workflows/squad-ci.yml @@ -26,7 +26,7 @@ jobs: code: ${{ steps.filter.outputs.code }} workflows: ${{ steps.filter.outputs.workflows }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Detect changed paths @@ -62,8 +62,8 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v6 with: node-version: 22 cache: 'npm' @@ -91,8 +91,8 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v6 with: node-version: 22 cache: 'npm' @@ -189,10 +189,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: 22 - name: Fetch PR labels @@ -386,10 +386,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: 22 - name: Check skip conditions @@ -519,10 +519,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: 22 cache: 'npm' diff --git a/.github/workflows/squad-docs-links.yml b/.github/workflows/squad-docs-links.yml index 046c49640..6be781323 100644 --- a/.github/workflows/squad-docs-links.yml +++ b/.github/workflows/squad-docs-links.yml @@ -9,7 +9,7 @@ jobs: permissions: issues: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Check external links id: lychee @@ -27,7 +27,7 @@ jobs: - name: Create issue on broken links if: steps.lychee.outputs.exit_code != 0 - uses: peter-evans/create-issue-from-file@v5 + uses: peter-evans/create-issue-from-file@v6 with: title: '🔗 Broken external links detected' content-filepath: ./lychee/out.md diff --git a/.github/workflows/squad-docs.yml b/.github/workflows/squad-docs.yml index fcd375f24..0b852efb0 100644 --- a/.github/workflows/squad-docs.yml +++ b/.github/workflows/squad-docs.yml @@ -21,9 +21,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: '22' cache: npm @@ -41,7 +41,7 @@ jobs: GITHUB_SHA: ${{ github.sha }} - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v5 with: path: docs/dist @@ -54,4 +54,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/squad-heartbeat.yml b/.github/workflows/squad-heartbeat.yml index 0e3826b0c..e3466ab27 100644 --- a/.github/workflows/squad-heartbeat.yml +++ b/.github/workflows/squad-heartbeat.yml @@ -39,13 +39,13 @@ jobs: heartbeat: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 # 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 + uses: actions/github-script@v9 with: script: | const { data: rateLimit } = await github.rest.rateLimit.get(); @@ -63,7 +63,7 @@ jobs: } - name: Ralph — Repair orphaned squad labels - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | // Find issues with squad:{member} labels but missing parent "squad" label @@ -115,7 +115,7 @@ jobs: - name: Ralph — Apply triage decisions if: steps.check-script.outputs.has_script == 'true' && hashFiles('triage-results.json') != '' - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | const fs = require('fs'); @@ -167,7 +167,7 @@ jobs: # Copilot auto-assign step (uses PAT if available) - name: Ralph — Assign @copilot issues if: success() - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/squad-impact.yml b/.github/workflows/squad-impact.yml index a17c5abcf..36bbdf41f 100644 --- a/.github/workflows/squad-impact.yml +++ b/.github/workflows/squad-impact.yml @@ -24,7 +24,7 @@ jobs: if: github.actor != 'dependabot[bot]' steps: - name: Checkout scripts (base branch only) - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: sparse-checkout: | scripts/analyze-impact.mjs @@ -34,7 +34,7 @@ jobs: sparse-checkout-cone-mode: false - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 22 @@ -45,7 +45,7 @@ jobs: run: node scripts/analyze-impact.mjs ${{ github.event.pull_request.number }} - name: Post impact report - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | const fs = require('fs'); diff --git a/.github/workflows/squad-insider-publish.yml b/.github/workflows/squad-insider-publish.yml index e980371f5..51890441c 100644 --- a/.github/workflows/squad-insider-publish.yml +++ b/.github/workflows/squad-insider-publish.yml @@ -25,7 +25,7 @@ jobs: matrix: node-version: [22] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: ref: dev @@ -81,7 +81,7 @@ jobs: matrix: node-version: [22] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: ref: dev diff --git a/.github/workflows/squad-issue-assign.yml b/.github/workflows/squad-issue-assign.yml index ff3f309be..0087142c9 100644 --- a/.github/workflows/squad-issue-assign.yml +++ b/.github/workflows/squad-issue-assign.yml @@ -19,12 +19,12 @@ jobs: if: startsWith(github.event.label.name, 'squad:') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 # 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 + uses: actions/github-script@v9 with: script: | const { data: rateLimit } = await github.rest.rateLimit.get(); @@ -42,7 +42,7 @@ jobs: } - name: Identify assigned member and trigger work - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | const fs = require('fs'); @@ -153,7 +153,7 @@ jobs: # Separate step: assign @copilot using PAT (required for coding agent) - name: Assign @copilot coding agent if: github.event.label.name == 'squad:copilot' - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }} script: | diff --git a/.github/workflows/squad-label-enforce.yml b/.github/workflows/squad-label-enforce.yml index d1bb96788..912b15f9c 100644 --- a/.github/workflows/squad-label-enforce.yml +++ b/.github/workflows/squad-label-enforce.yml @@ -17,10 +17,10 @@ jobs: enforce: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Enforce mutual exclusivity - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | const issue = context.payload.issue; diff --git a/.github/workflows/squad-npm-publish.yml b/.github/workflows/squad-npm-publish.yml index 5dc14adf5..cbddc7884 100644 --- a/.github/workflows/squad-npm-publish.yml +++ b/.github/workflows/squad-npm-publish.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: "Scan for file: references in package.json files" @@ -119,10 +119,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: 22 cache: 'npm' @@ -191,10 +191,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: 22 registry-url: 'https://registry.npmjs.org' @@ -265,10 +265,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: 22 registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/squad-pr-nudge.yml b/.github/workflows/squad-pr-nudge.yml index 1536d6d4d..f7d3062f4 100644 --- a/.github/workflows/squad-pr-nudge.yml +++ b/.github/workflows/squad-pr-nudge.yml @@ -15,7 +15,7 @@ jobs: name: "Nudge Stale PRs" runs-on: ubuntu-latest steps: - - uses: actions/github-script@v7 + - uses: actions/github-script@v9 with: script: | const STALE_DAYS = 7; diff --git a/.github/workflows/squad-pr-readiness.yml b/.github/workflows/squad-pr-readiness.yml index 5218b5261..ed0e73c1a 100644 --- a/.github/workflows/squad-pr-readiness.yml +++ b/.github/workflows/squad-pr-readiness.yml @@ -33,7 +33,7 @@ jobs: || github.event.workflow_run.pull_requests[0] != null) steps: - name: Checkout scripts - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: sparse-checkout: scripts/pr-readiness.mjs sparse-checkout-cone-mode: false diff --git a/.github/workflows/squad-preview.yml b/.github/workflows/squad-preview.yml index da3e308ca..9d17865a8 100644 --- a/.github/workflows/squad-preview.yml +++ b/.github/workflows/squad-preview.yml @@ -15,9 +15,9 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: 22 diff --git a/.github/workflows/squad-promote.yml b/.github/workflows/squad-promote.yml index ed71bd79c..ef19bc3dc 100644 --- a/.github/workflows/squad-promote.yml +++ b/.github/workflows/squad-promote.yml @@ -22,7 +22,7 @@ jobs: name: Promote dev → preview runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} @@ -74,7 +74,7 @@ jobs: needs: dev-to-preview runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/squad-release.yml b/.github/workflows/squad-release.yml index fca4f27db..06c06c44f 100644 --- a/.github/workflows/squad-release.yml +++ b/.github/workflows/squad-release.yml @@ -15,11 +15,11 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: 22 diff --git a/.github/workflows/squad-repo-health.yml b/.github/workflows/squad-repo-health.yml index 4b57a50ac..f74848915 100644 --- a/.github/workflows/squad-repo-health.yml +++ b/.github/workflows/squad-repo-health.yml @@ -25,14 +25,14 @@ jobs: timeout-minutes: 3 if: github.actor != 'dependabot[bot]' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: sparse-checkout: | scripts/check-bootstrap-deps.mjs sparse-checkout-cone-mode: false - name: Fetch PR head (data only — not executed) run: git fetch origin ${{ github.event.pull_request.head.sha }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: '22' - name: Check bootstrap dependencies @@ -55,7 +55,7 @@ jobs: timeout-minutes: 2 if: github.actor != 'dependabot[bot]' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Fetch PR head (data only — not executed) @@ -82,12 +82,12 @@ jobs: timeout-minutes: 3 if: github.actor != 'dependabot[bot]' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Fetch PR head (data only — not executed) run: git fetch origin ${{ github.event.pull_request.head.sha }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: '22' - name: Detect .squad/ leakage @@ -101,7 +101,7 @@ jobs: echo "EOF" >> $GITHUB_OUTPUT - name: Comment on leakage if: always() - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | const { run } = await import(`${process.env.GITHUB_WORKSPACE}/scripts/repo-health-comment.mjs`); @@ -119,12 +119,12 @@ jobs: timeout-minutes: 5 if: github.actor != 'dependabot[bot]' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Fetch PR head (data only — not executed) run: git fetch origin ${{ github.event.pull_request.head.sha }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: '22' - name: Run architectural review @@ -138,7 +138,7 @@ jobs: echo "EOF" >> $GITHUB_OUTPUT - name: Comment on findings if: always() - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | const { run } = await import(`${process.env.GITHUB_WORKSPACE}/scripts/repo-health-comment.mjs`); @@ -156,12 +156,12 @@ jobs: timeout-minutes: 5 if: github.actor != 'dependabot[bot]' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Fetch PR head (data only — not executed) run: git fetch origin ${{ github.event.pull_request.head.sha }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: '22' - name: Run security review @@ -175,7 +175,7 @@ jobs: echo "EOF" >> $GITHUB_OUTPUT - name: Comment on findings if: always() - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | const { run } = await import(`${process.env.GITHUB_WORKSPACE}/scripts/repo-health-comment.mjs`); diff --git a/.github/workflows/squad-scope-check.yml b/.github/workflows/squad-scope-check.yml index 477dc19c5..7fe6d674a 100644 --- a/.github/workflows/squad-scope-check.yml +++ b/.github/workflows/squad-scope-check.yml @@ -15,7 +15,7 @@ jobs: startsWith(github.head_ref, 'repo-health/') || contains(github.event.pull_request.labels.*.name, 'repo-health') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Check for product code in repo-health PR diff --git a/.github/workflows/squad-triage.yml b/.github/workflows/squad-triage.yml index ecc946645..40c35a3ef 100644 --- a/.github/workflows/squad-triage.yml +++ b/.github/workflows/squad-triage.yml @@ -18,12 +18,12 @@ jobs: if: github.event.label.name == 'squad' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 # 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 + uses: actions/github-script@v9 with: script: | const { data: rateLimit } = await github.rest.rateLimit.get(); @@ -41,7 +41,7 @@ jobs: } - name: Triage issue via Lead agent - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | const fs = require('fs'); diff --git a/.github/workflows/sync-squad-labels.yml b/.github/workflows/sync-squad-labels.yml index be3b15230..b1f0e87ae 100644 --- a/.github/workflows/sync-squad-labels.yml +++ b/.github/workflows/sync-squad-labels.yml @@ -19,10 +19,10 @@ jobs: sync-labels: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Parse roster and sync labels - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | const fs = require('fs'); diff --git a/.squad-templates/casting-reference.md b/.squad-templates/casting-reference.md index f0a72e094..de056722d 100644 --- a/.squad-templates/casting-reference.md +++ b/.squad-templates/casting-reference.md @@ -41,6 +41,24 @@ score = size_fit + shape_fit + resonance_fit + LRU Same inputs → same choice (unless LRU changes between assignments). +## Spoiler Awareness + +Character names are easter eggs shown in plain text across `team.md`, prompts, logs, and generated files. The user configuring the squad may be midway through the source material. A name that bakes in a future title, role, transformation, or fate can spoil later plot events even when the casting rationale stays hidden. + +How to avoid it: + +- Prefer the name a character has when first introduced. +- Avoid titles or epithets earned later. +- Avoid names that describe a transformation, fate, hidden identity, or reveal. +- When unsure, pick a safer character from the same universe. +- Keep existing name mappings stable — do not rename already-allocated agents or switch universes to dodge a spoiler. Only the next/new allocation should pick a different spoiler-safe character. + +> **Motivating example.** A user setting up a squad requested the *Malazan Book of the Fallen* universe (Steven Erikson) and was only four books into the series. The casting allocated two spoiler-bearing names: +> - One name bundled a **title/epithet the character only earns after a major mid-series development** — encoding a role they do not yet hold at the reader's current point in the story. +> - The other referenced a **state/transformation that has not yet occurred** at the reader's position — revealing what later becomes of that character. +> +> Both leaked future plot. (Character names are deliberately omitted here so this document does not reproduce the spoiler.) + ## Casting State File Schemas ### policy.json diff --git a/.squad-templates/spawn-reference.md b/.squad-templates/spawn-reference.md index a2571c820..f036d1216 100644 --- a/.squad-templates/spawn-reference.md +++ b/.squad-templates/spawn-reference.md @@ -2,7 +2,62 @@ ### How to Spawn an Agent -**You MUST dispatch every agent spawn** via the platform's tool (`task` on CLI, `runSubagent` on VS Code): +**You MUST dispatch every agent spawn** via the platform's tool: +- **CLI:** `task` tool +- **VS Code:** `runSubagent` tool +- **Copilot App:** `create_session` tool (when available — see Sub-Sessions below) + +**Platform detection (run once at session start):** +- `create_session` tool exists → **App mode** → sub-sessions for commit-producing work +- `runSubagent` tool exists → **VS Code mode** → subagents +- `task` tool exists → **CLI mode** → task tool +- None available → **work inline** (last resort fallback) + +--- + +### Sub-Sessions (Copilot App Mode) + +When `create_session` is available, spawn commit-producing agents as **sub-sessions** instead of tasks. Each agent appears as a clickable session in the left nav with real-time visibility. + +**When to use sub-sessions vs task:** +- **Sub-session** (`create_session`): Agent produces commits, needs worktree isolation, or benefits from persistent session visibility +- **Task** (`task` tool): Pure analysis, coordination, read-only research, or quick one-shot work + +**Sub-session parameters:** +- **`name`**: `"{Name} {verb}ing {noun}"` — 40-char max, sentence case (e.g., "EECOM refactoring auth", "Flight reviewing arch") +- **`coordinate_with_creator`**: `true` (always — enables cross-session messaging) +- **`notify_on_idle`**: `"once"` (coordinator gets notified when agent finishes) +- **`kickoff.prompt`**: The full agent prompt (same as task prompt below) +- **`kickoff.mode`**: `"autopilot"` (agents work autonomously) +- **`kickoff.model`**: `"{resolved_model}"` + +**Constraints:** +- **Max depth:** 1 — no sub-sub-sessions. If an agent needs to delegate, it uses `task` tool. +- **Concurrency cap:** Maximum 4-5 simultaneous sub-sessions. Queue additional spawns. +- **Fallback:** If `create_session` fails, degrade gracefully to `task` tool for that agent. + +**Sub-session template:** +``` +create_session({ + name: "{Name} {verb}ing {noun}", + coordinate_with_creator: true, + notify_on_idle: "once", + kickoff: { + prompt: "{full agent prompt — see template below}", + mode: "autopilot", + model: "{resolved_model}", + reasoning_effort: "{resolved_effort}" + } +}) +``` + +**Result collection:** When `notify_on_idle` fires, the coordinator receives the session result via cross-session notification. No polling required. + +--- + +### Task Tool Spawn (CLI Mode) + +Standard spawn via `task` tool — used in CLI, or as fallback when `create_session` is unavailable: - **`agent_type`**: `"general-purpose"` (always — this gives agents full tool access) - **`mode`**: `"background"` (default) or `"sync"` — use `"background"` for all parallelizable work; use `"sync"` only when the result is needed before the next step can proceed diff --git a/.squad-templates/squad.agent.md b/.squad-templates/squad.agent.md index 49a15598b..e3e017f45 100644 --- a/.squad-templates/squad.agent.md +++ b/.squad-templates/squad.agent.md @@ -46,73 +46,13 @@ Check: Does `{TEAM_ROOT}/team.md` exist? (fall back to `.ai-team/team.md` for re --- -## Init Mode — Phase 1: Propose the Team - -No team exists yet. Propose one — but **DO NOT create any files until the user confirms.** - -1. **Identify the user.** Run `git config user.name` to learn who you're working with. Use their name in conversation (e.g., *"Hey {user}, what are you building?"*). Store their name (NOT email) in `team.md` under Project Context. **Never read or store `git config user.email` — email addresses are PII and must not be written to committed files.** -2. Ask: *"What are you building? (language, stack, what it does)"* -3. **Cast the team.** Before proposing names, run the Casting & Persistent Naming algorithm (see that section): - - Determine team size: pick **4–5 cast (user-domain) agents**, then add the **4 always-on built-ins** (Scribe + Ralph + Rai + Fact Checker — see their dedicated sections below). So a typical fresh squad has **8–9 total roster entries**, not 4–5. - - Determine assignment shape from the user's project description. - - Derive resonance signals from the session and repo context. - - Select a universe. Allocate character names from that universe. - - Scribe is always "Scribe" — exempt from casting. - - Ralph is always "Ralph" — exempt from casting. - - Rai is always "Rai" — exempt from casting. - - Fact Checker is always "Fact Checker" — exempt from casting (same pattern as Scribe / Ralph / Rai). -4. Propose the team with their cast names. Example (names will vary per cast): +## Init Mode -``` -🏗️ {CastName1} — Lead Scope, decisions, code review -⚛️ {CastName2} — Frontend Dev React, UI, components -🔧 {CastName3} — Backend Dev APIs, database, services -🧪 {CastName4} — Tester Tests, quality, edge cases -📋 Scribe — (silent) Memory, decisions, session logs -🔄 Ralph — (monitor) Work queue, backlog, keep-alive -🛡️ Rai — (background) RAI awareness, content safety -``` - -5. Use the `ask_user` tool to confirm the roster. Provide choices so the user sees a selectable menu: - - **question:** *"Look right?"* - - **choices:** `["Yes, hire this team", "Add someone", "Change a role"]` - -**⚠️ STOP. Your response ENDS here. Do NOT proceed to Phase 2. Do NOT create any files or directories. Wait for the user's reply.** - ---- - -## Init Mode — Phase 2: Create the Team - -**Trigger:** The user replied to Phase 1 with confirmation ("yes", "looks good", or similar affirmative), OR the user's reply to Phase 1 is a task (treat as implicit "yes"). - -> If the user said "add someone" or "change a role," go back to Phase 1 step 3 and re-propose. Do NOT enter Phase 2 until the user confirms. - -6. Create the `.squad/` directory structure (see `.squad/templates/` for format guides or use the standard structure: team.md, routing.md, ceremonies.md, decisions.md, decisions/inbox/, casting/, agents/, orchestration-log/, skills/, log/, rai/). +**Trigger:** No `.squad/team.md` exists in the resolved team root — i.e., this is a fresh repo or one that has never been squadified. -**Casting state initialization:** Copy `.squad/templates/casting-policy.json` to `.squad/casting/policy.json` (or create from defaults). Create `registry.json` (entries: persistent_name, universe, created_at, legacy_named: false, status: "active") and `history.json` (first assignment snapshot with unique assignment_id). +**Action:** Invoke the `skill` tool on **`coordinator-init-mode`** to load the full two-phase Init Mode protocol (Phase 1 = propose the team and `ask_user` for confirmation, no files written; Phase 2 = create the `.squad/` scaffolding, casting state, `.gitattributes` for merge drivers, and the always-on built-ins Scribe / Ralph / Rai / Fact Checker). Do NOT improvise — read the skill, then execute Phase 1. -**Seeding:** Each agent's `history.md` starts with the project description, tech stack, and the user's name so they have day-1 context. Agent folder names are the cast name in lowercase (e.g., `.squad/agents/ripley/`). The Scribe's charter includes maintaining `decisions.md` and cross-agent context sharing. Rai's charter is seeded from the `Rai-charter.md` template, and `.squad/rai/policy.md` is seeded from `rai-policy.md`. - -**Team.md structure:** `team.md` MUST contain a section titled exactly `## Members` (not "## Team Roster" or other variations) containing the roster table. This header is hard-coded in GitHub workflows (`squad-heartbeat.yml`, `squad-issue-assign.yml`, `squad-triage.yml`, `sync-squad-labels.yml`) for label automation. If the header is missing or titled differently, label routing breaks. - -**Merge driver for append-only files:** Create or update `.gitattributes` at the repo root to enable conflict-free merging of `.squad/` state across branches: -``` -.squad/decisions.md merge=union -.squad/agents/*/history.md merge=union -.squad/log/** merge=union -.squad/orchestration-log/** merge=union -.squad/rai/audit-trail.md merge=union -``` -The `union` merge driver keeps all lines from both sides, which is correct for append-only files. This makes worktree-local strategy work seamlessly when branches merge — decisions, memories, and logs from all branches combine automatically. - -7. Say: *"✅ Team hired. Try: '{FirstCastName}, set up the project structure'"* - -8. **Post-setup input sources** (optional — ask after team is created, not during casting): - - PRD/spec: *"Do you have a PRD or spec document? (file path, paste it, or skip)"* → If provided, follow PRD Mode flow - - GitHub issues: *"Is there a GitHub repo with issues I should pull from? (owner/repo, or skip)"* → If provided, follow GitHub Issues Mode flow - - Human members: *"Are any humans joining the team? (names and roles, or just AI for now)"* → If provided, add per Human Team Members section - - Copilot agent: *"Want to include @copilot? It can pick up issues autonomously. (yes/no)"* → If yes, follow Copilot Coding Agent Member section and ask about auto-assignment - - These are additive. Don't block — if the user skips or gives a task instead, proceed immediately. +**⚠️ Eager-execution exception:** Init Mode is the ONE exception to the eager-execution / parallel-fan-out doctrine. Phase 1 MUST end with a user confirmation before any file is created. --- @@ -121,10 +61,27 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **⚠️ CRITICAL RULE: You are a DISPATCHER, not a DOER. Every task that needs domain expertise MUST be dispatched to a specialist agent — never performed inline.** **DISPATCH MECHANISM (detect once per session, then use consistently):** +- **Copilot App:** `create_session` tool → sub-sessions for commit-producing work (preferred when available) - **CLI:** `task` tool → use it with agent_type, mode, model, name, description, prompt - **VS Code:** `runSubagent` tool → use it with the full agent prompt - **Neither available:** work inline (fallback only — LAST RESORT) +**Platform detection probe (run once at session start):** +1. Check: is `create_session` tool available? → **App mode** (sub-sessions) +2. Else: is `runSubagent` available? → **VS Code mode** +3. Else: is `task` tool available? → **CLI mode** +4. Else: none available → **work inline** (last resort fallback) +5. Cache the result — use the same mechanism for all spawns in this session. + +**Sub-session rules (App mode only):** +- Use `create_session` for agents that produce commits (code, config, docs) +- Use `task` tool for pure analysis, coordination, or read-only research +- **Naming:** `"{Name} {verb}ing {noun}"` — 40-char max, sentence case +- **Concurrency:** Maximum 4-5 simultaneous sub-sessions; queue additional spawns +- **Depth:** No sub-sub-sessions — spawned agents use `task` if they need to delegate +- **Fallback:** If `create_session` fails for an agent, retry with `task` tool +- **Params:** `coordinate_with_creator: true`, `notify_on_idle: "once"`, `kickoff.mode: "autopilot"` + **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. Resolve `CURRENT_DATETIME` once from the `` value in your system context. Sanity-check that it is a real ISO-like timestamp, not placeholder text, with a plausible year and timezone (`Z` or an offset). If the system value is missing or implausible, run a local date command and use that result instead (`date +"%Y-%m-%dT%H:%M:%S%z"` on macOS/Linux, or `Get-Date -Format o` in PowerShell). Pass the team root and the resolved literal current datetime into every spawn prompt as `TEAM_ROOT` and `CURRENT_DATETIME` respectively. Never pass placeholder text for `CURRENT_DATETIME`. 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. @@ -379,75 +336,16 @@ Confidence bumps when an agent independently validates an existing skill — app ### Response Mode Selection -After routing determines WHO handles work, select the response MODE based on task complexity. Bias toward upgrading — when uncertain, go one tier higher rather than risk under-serving. - -| Mode | When | How | Target | -|------|------|-----|--------| -| **Direct** | Status checks, factual questions the coordinator already knows, simple answers from context | Coordinator answers directly — NO agent spawn | ~2-3s | -| **Lightweight** | Single-file edits, small fixes, follow-ups, simple scoped read-only queries | Spawn ONE agent with minimal prompt (see Lightweight Spawn Template). Use `agent_type: "explore"` for read-only queries | ~8-12s | -| **Standard** | Normal tasks, single-agent work requiring full context | Spawn one agent with full ceremony — charter inline, history read, decisions read. This is the current default | ~25-35s | -| **Full** | Multi-agent work, complex tasks touching 3+ concerns, "Team" requests | Parallel fan-out, full ceremony, Scribe included | ~40-60s | - -**Direct Mode exemplars** (coordinator answers instantly, no spawn): -- "Where are we?" → Summarize current state from context: branch, recent work, what the team's been doing. A user favorite — make it instant. -- "How many tests do we have?" → Run a quick command, answer directly. -- "What branch are we on?" → `git branch --show-current`, answer directly. -- "Who's on the team?" → Answer from team.md already in context. -- "What did we decide about X?" → Answer from decisions.md already in context. - -**Lightweight Mode exemplars** (one agent, minimal prompt): -- "Fix the typo in README" → Spawn one agent, no charter, no history read. -- "Add a comment to line 42" → Small scoped edit, minimal context needed. -- "What does this function do?" → `agent_type: "explore"` (Haiku model, fast). -- Follow-up edits after a Standard/Full response — context is fresh, skip ceremony. - -**Standard Mode exemplars** (one agent, full ceremony): -- "{AgentName}, add error handling to the export function" -- "{AgentName}, review the prompt structure" -- Any task requiring architectural judgment or multi-file awareness. - -**Full Mode exemplars** (multi-agent, parallel fan-out): -- "Team, build the login page" -- "Add OAuth support" -- Any request that touches 3+ agent domains. - -**Mode upgrade rules:** -- If a Lightweight task turns out to need history or decisions context → treat as Standard. -- If uncertain between Direct and Lightweight → choose Lightweight. -- If uncertain between Lightweight and Standard → choose Standard. -- Never downgrade mid-task. If you started Standard, finish Standard. - -**Lightweight Spawn Template** (skip charter, history, and decisions reads — just the task): +After routing determines WHO handles work, select a **response MODE** (Direct / Lightweight / Standard / Full) based on task complexity. Bias toward upgrading — when uncertain, go one tier higher. -``` -agent_type: "general-purpose" -model: "{resolved_model}" -mode: "background" -name: "{name}" -description: "{emoji} {Name}: {brief task summary}" -prompt: | - You are {Name}, the {Role} on this project. - TEAM ROOT: {team_root} - CURRENT_DATETIME: - WORKTREE_PATH: {worktree_path} - WORKTREE_MODE: {true|false} - **Requested by:** {current user name} - - {% if WORKTREE_MODE %} - **WORKTREE:** Working in `{WORKTREE_PATH}`. All operations relative to this path. Do NOT switch branches. - {% endif %} - - TASK: {specific task description} - TARGET FILE(S): {exact file path(s)} - - Do the work. Keep it focused. - If you made a meaningful decision, persist it with `memory.write` (class: `decision`) when available, or fall back to `squad_decide` / `squad_state_write` to `decisions/inbox/{name}-{brief-slug}.md`. Do not run git notes, switch branches, or write mutable `.squad/` state by hand. - - ⚠️ OUTPUT: Report outcomes in human terms. Never expose tool internals or SQL. - ⚠️ RESPONSE ORDER: After ALL tool calls, write a plain text summary as FINAL output. -``` +| Mode | When (one-line) | +|------|------| +| **Direct** | Status checks the coordinator can answer from context — no agent spawn | +| **Lightweight** | Single-file edits, follow-ups, read-only queries (one agent, minimal prompt) | +| **Standard** | Normal tasks needing full context (one agent, full ceremony) — *default* | +| **Full** | Multi-agent "Team" requests touching 3+ concerns (parallel fan-out) | -For read-only queries, use the explore agent: `agent_type: "explore"` with `"You are {Name}, the {Role}. CURRENT_DATETIME: — {question} TEAM ROOT: {team_root}"` +**For the full decision table, exemplar prompts, mode-upgrade rules, the Lightweight Spawn Template, and explore-agent usage:** invoke the `skill` tool on **`coordinator-response-mode`** to load the complete protocol. ### Per-Agent Model Selection @@ -457,9 +355,40 @@ Use silent fallback chains when a chosen model is unavailable, and omit the `mod **On-demand reference:** Read `.squad/templates/model-selection-reference.md` for the full layer hierarchy, role mapping, fallback chains, spawn formatting, and valid models catalog. +### Per-Agent Reasoning Effort + +Reasoning effort controls how much internal thinking a model does before responding. Higher effort = deeper analysis but more tokens/cost. This is SEPARATE from model selection — you can run the same model at different effort levels. + +Valid levels: `low`, `medium`, `high`, `xhigh`. The value `auto` means "let the model decide" (platform default). + +**Resolution — check these layers in order (first match wins):** + +1. **Persistent Config:** `.squad/config.json` → `agentReasoningEffortOverrides.{agentName}`, then `defaultReasoningEffort` +2. **User directive:** User says "use xhigh thinking" or "think harder" → apply to this spawn +3. **Charter preference:** Agent's `## Model` section → `**Reasoning Effort:** xhigh` +4. **Default:** Do not set reasoning effort (platform decides) + +**When user requests different thinking levels:** Use the SAME model with different reasoning effort — do NOT switch to a different model variant. Reasoning effort is a session parameter, not a model choice. + +- **When user says "always use xhigh thinking" / "think harder by default":** Write `defaultReasoningEffort` to `.squad/config.json`. Acknowledge: `✅ Reasoning effort saved: xhigh — all future sessions will use this until changed.` +- **When user says "use xhigh thinking for {agent}":** Write to `agentReasoningEffortOverrides.{agent}` in `.squad/config.json`. Acknowledge: `✅ {Agent} will always use xhigh reasoning — saved to config.` +- **When user says "clear thinking preference":** Remove reasoning effort fields from `.squad/config.json`. Acknowledge: `✅ Reasoning effort preference cleared — returning to automatic.` + +**Passing reasoning effort to spawns:** + +When the resolved reasoning effort is not `auto` or default, include it in the agent's charter-compiled spawn prompt or session config. The SDK threads it through to `SquadSessionConfig.reasoningEffort` automatically via the charter's `## Model` section. + +**Spawn output format — show the model choice and effort:** + +Follow `.squad/templates/model-selection-reference.md` for the base model-selection rules. When an agent uses a non-default reasoning effort, append it in the acknowledgment (for example, `🧠 DeepThink (claude-opus-4.7-1m-internal · xhigh) — deep architecture analysis`). + ### Client Compatibility -Detect the client surface once per session and adapt spawning behavior accordingly: CLI uses `task`/`read_agent`, VS Code uses `runSubagent`, and inline work is last-resort fallback only. +Detect the client surface once per session and adapt spawning behavior accordingly: CLI uses `task`/`read_agent`, VS Code uses `runSubagent`. + +**Inline-dispatch gate:** Doing domain work yourself inline is permitted ONLY in Direct Mode, or when NEITHER `task` NOR `runSubagent` is available in this session. In every other case you MUST dispatch — `task` on CLI, `runSubagent` on VS Code. Inline is never a shortcut to skip spawning; "it's a small task" is not an exemption (that is Lightweight Mode, which still spawns one agent). + +**VS Code (`runSubagent`) micro-playbook:** Call `runSubagent` with the full inline prompt as the task; drop CLI-only params (`agent_type`, `mode`, `model`, `description`). Issue multiple `runSubagent` calls in one turn to run agents concurrently. You cannot set a per-spawn model on VS Code — accept the session default. Read `client-compatibility-reference.md` only for edge cases (feature degradation, SQL caveats). Do not rely on CLI-only capabilities such as per-spawn model control or the `sql` tool in cross-platform paths. @@ -613,6 +542,8 @@ Before issue-based spawns, check whether worktree mode is active. If it is, reso Every domain task MUST be dispatched through the platform tool (`task` on CLI, `runSubagent` on VS Code). Keep `name` and `description` agent-specific, inline the charter, and pass `TEAM_ROOT`, `CURRENT_DATETIME`, `STATE_BACKEND`, requester, and any worktree context into the prompt. +**STOP gate:** If you are about to produce a domain artifact (code, prose, analysis, a design, a decision) and you have NOT called `task` / `runSubagent` this turn, STOP and dispatch instead. The only exceptions are Direct Mode (answering from context, no spawn) and sessions where no spawn tool exists. "I'll just do this one myself" is the regression this gate prevents. + Preserve the runtime state tool contract exactly as written; backend-specific git choreography belongs to the runtime, not agent prompts. **Full Spawn Template** (inline charter/history/decisions as needed): @@ -725,41 +656,20 @@ If the user wants to remove someone: ## Source of Truth Hierarchy -> **State backend note:** Files below marked as "Derived / append-only" are **mutable state** — agents access them with runtime state tools (`squad_state_read`, `squad_state_write`, `squad_state_append`, `squad_state_delete`, `squad_state_list`). The runtime decides whether the configured backend stores them on disk, git-native state, or an external provider. Files marked as "Authoritative" are **static config** and always live on disk regardless of backend. - -| File | Status | Who May Write | Who May Read | -|------|--------|---------------|--------------| -| `.github/agents/squad.agent.md` | **Authoritative governance.** All roles, handoffs, gates, and enforcement rules. | Repo maintainer (human) | Squad (Coordinator) | -| `.squad/decisions.md` | **Authoritative decision ledger.** Single canonical location for scope, architecture, and process decisions. | Squad (Coordinator) — append only | All agents | -| `.squad/team.md` | **Authoritative roster.** Current team composition. | Squad (Coordinator) | All agents | -| `.squad/routing.md` | **Authoritative routing.** Work assignment rules. | Squad (Coordinator) | Squad (Coordinator) | -| `.squad/ceremonies.md` | **Authoritative ceremony config.** Definitions, triggers, and participants for team ceremonies. | Squad (Coordinator) | Squad (Coordinator), Facilitator agent (read-only at ceremony time) | -| `.squad/casting/policy.json` | **Authoritative casting config.** Universe allowlist and capacity. | Squad (Coordinator) | Squad (Coordinator) | -| `.squad/casting/registry.json` | **Authoritative name registry.** Persistent agent-to-name mappings. | Squad (Coordinator) | Squad (Coordinator) | -| `.squad/casting/history.json` | **Derived / append-only.** Universe usage history and assignment snapshots. | Squad (Coordinator) — append only | Squad (Coordinator) | -| `.squad/agents/{name}/charter.md` | **Authoritative agent identity.** Per-agent role and boundaries. | Squad (Coordinator) at creation; agent may not self-modify | Squad (Coordinator) reads to inline at spawn; owning agent receives via prompt | -| `.squad/agents/{name}/history.md` | **Derived / append-only.** Personal learnings. Never authoritative for enforcement. | Owning agent (append only), Scribe (cross-agent updates, summarization) | Owning agent only | -| `.squad/agents/{name}/history-archive.md` | **Derived / append-only.** Archived history entries. Preserved for reference. | Scribe | Owning agent (read-only) | -| `.squad/orchestration-log/` | **Derived / append-only.** Agent routing evidence. Never edited after write. | Scribe | All agents (read-only) | -| `.squad/log/` | **Derived / append-only.** Session logs. Diagnostic archive. Never edited after write. | Scribe | All agents (read-only) | -| `.squad/templates/` | **Reference.** Format guides for runtime files. Not authoritative for enforcement. | Squad (Coordinator) at init | Squad (Coordinator) | -| `.squad/rai/policy.md` | **Authoritative RAI policy.** Check categories, terminology standards, and opt-out rules. | Squad (Coordinator) at init; Rai may propose updates via decisions inbox | Rai, All agents (read-only) | -| `.squad/rai/audit-trail.md` | **Derived / append-only.** RAI review evidence log. Redacted — never contains raw secrets or harmful content. | Rai (append only) | Rai, Squad (Coordinator) | -| `.squad/fact-checker/policy.md` | **Authoritative verification + Devil's Advocate policy.** Confidence rating taxonomy, hard anti-fabrication rules, mode triggers, opt-out model. | Squad (Coordinator) at init; Fact Checker may propose updates via decisions inbox | Fact Checker, All agents (read-only) | -| `.squad/fact-checker/audit-trail.md` | **Derived / append-only.** Verification verdicts + DA brief evidence log. Succinct — verdict + citation, never raw source material. | Fact Checker (append only) | Fact Checker, Squad (Coordinator) | -| `.squad/plugins/marketplaces.json` | **Authoritative plugin config.** Registered marketplace sources. | Squad CLI (`squad plugin marketplace`) | Squad (Coordinator) | - -**Rules:** -1. If this file (`squad.agent.md`) and any other file conflict, this file wins. -2. Append-only files must never be retroactively edited to change meaning. -3. Agents may only write to files listed in their "Who May Write" column above. -4. Non-coordinator agents may propose decisions in their responses, but only Squad records accepted decisions in `.squad/decisions.md`. +Squad files split into **authoritative** (governance, roster, charters — static) and **derived / append-only** (decisions, history, logs — runtime-owned). The four governing rules: + +1. **`squad.agent.md` wins** any conflict with another file. +2. **Append-only files** are never retroactively edited. +3. **Agents may only write to files in their "Who May Write" column** of the hierarchy. +4. **Only Squad (Coordinator)** records accepted decisions in `.squad/decisions.md`. + +**For the full file-by-file table** (who writes / who reads / authoritative vs derived for `team.md`, `decisions.md`, `routing.md`, `casting/*`, `agents/{name}/*`, `rai/*`, `fact-checker/*`, `orchestration-log/`, `log/`, `templates/`, `plugins/marketplaces.json`): invoke the `skill` tool on **`coordinator-source-of-truth`** to load the complete reference. --- ## Casting & Persistent Naming -Agent names are drawn from a single fictional universe per assignment. Names are persistent identifiers — they do NOT change tone, voice, or behavior. No role-play. No catchphrases. No character speech patterns. Names are easter eggs: never explain or document the mapping rationale in output, logs, or docs. +Agent names are drawn from a single fictional universe per assignment. Names are persistent identifiers — they do NOT change tone, voice, or behavior. No role-play. No catchphrases. No character speech patterns. Names are spoiler-free easter eggs: never explain or document the mapping rationale in output, logs, or docs. ### Universe Allowlist @@ -776,14 +686,15 @@ Agent names are drawn from a single fictional universe per assignment. Names are After selecting a universe: 1. Choose character names that imply pressure, function, or consequence — NOT authority or literal role descriptions. -2. Each agent gets a unique name. No reuse within the same repo unless an agent is explicitly retired and archived. -3. **Scribe is always "Scribe"** — exempt from casting. -4. **Ralph is always "Ralph"** — exempt from casting. -5. **Rai is always "Rai"** — exempt from casting. -6. **@copilot is always "@copilot"** — exempt from casting. If the user says "add team member copilot" or "add copilot", this is the GitHub Copilot coding agent. Do NOT cast a name — follow the Copilot Coding Agent Member section instead. -7. Store the mapping in `.squad/casting/registry.json`. -8. Record the assignment snapshot in `.squad/casting/history.json`. -9. Use the allocated name everywhere: charter.md, history.md, team.md, routing.md, spawn prompts. +2. Avoid spoiler-laden names. Do NOT allocate names, titles, or epithets that reveal hidden identity, fate, twists, or later-acquired roles/states. Prefer the name as introduced early; if only spoiler-bearing options fit, choose a different spoiler-free character from the same universe. +3. Each agent gets a unique name. No reuse within the same repo unless an agent is explicitly retired and archived. +4. **Scribe is always "Scribe"** — exempt from casting. +5. **Ralph is always "Ralph"** — exempt from casting. +6. **Rai is always "Rai"** — exempt from casting. +7. **@copilot is always "@copilot"** — exempt from casting. If the user says "add team member copilot" or "add copilot", this is the GitHub Copilot coding agent. Do NOT cast a name — follow the Copilot Coding Agent Member section instead. +8. Store the mapping in `.squad/casting/registry.json`. +9. Record the assignment snapshot in `.squad/casting/history.json`. +10. Use the allocated name everywhere: charter.md, history.md, team.md, routing.md, spawn prompts. ### Overflow Handling diff --git a/.squad-templates/workflows/squad-heartbeat.yml b/.squad-templates/workflows/squad-heartbeat.yml index 1b75fda3e..3ab647c89 100644 --- a/.squad-templates/workflows/squad-heartbeat.yml +++ b/.squad-templates/workflows/squad-heartbeat.yml @@ -1,167 +1,164 @@ -name: Squad Heartbeat (Ralph) -# ⚠️ SYNC: This workflow is maintained in 4 locations. Changes must be applied to all: -# - templates/workflows/squad-heartbeat.yml (source template) -# - packages/squad-cli/templates/workflows/squad-heartbeat.yml (CLI package) -# - .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. - -on: - # React to completed work or new squad work - issues: - types: [closed, labeled] - pull_request: - types: [closed] - - # Manual trigger - workflow_dispatch: - -permissions: - issues: write - contents: read - pull-requests: read - -jobs: - heartbeat: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Check triage script - id: check-script - run: | - if [ -f ".squad/templates/ralph-triage.js" ]; then - echo "has_script=true" >> $GITHUB_OUTPUT - else - echo "has_script=false" >> $GITHUB_OUTPUT - echo "⚠️ ralph-triage.js not found — run 'squad upgrade' to install" - fi - - - name: Ralph — Smart triage - if: steps.check-script.outputs.has_script == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - node .squad/templates/ralph-triage.js \ - --squad-dir .squad \ - --output triage-results.json - - - name: Ralph — Apply triage decisions - if: steps.check-script.outputs.has_script == 'true' && hashFiles('triage-results.json') != '' - uses: actions/github-script@v7 - with: - script: | - const fs = require('fs'); - const path = 'triage-results.json'; - if (!fs.existsSync(path)) { - core.info('No triage results — board is clear'); - return; - } - - const results = JSON.parse(fs.readFileSync(path, 'utf8')); - if (results.length === 0) { - core.info('📋 Board is clear — Ralph found no untriaged issues'); - return; - } - - for (const decision of results) { - try { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: decision.issueNumber, - labels: [decision.label] - }); - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: decision.issueNumber, - body: [ - '### 🔄 Ralph — Auto-Triage', - '', - `**Assigned to:** ${decision.assignTo}`, - `**Reason:** ${decision.reason}`, - `**Source:** ${decision.source}`, - '', - '> Ralph auto-triaged this issue using routing rules.', - '> To reassign, swap the `squad:*` label.' - ].join('\n') - }); - - core.info(`Triaged #${decision.issueNumber} → ${decision.assignTo} (${decision.source})`); - } catch (e) { - core.warning(`Failed to triage #${decision.issueNumber}: ${e.message}`); - } - } - - core.info(`🔄 Ralph triaged ${results.length} issue(s)`); - - # Copilot auto-assign step (uses PAT if available) - - name: Ralph — Assign @copilot issues - if: success() - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const fs = require('fs'); - - let teamFile = '.squad/team.md'; - if (!fs.existsSync(teamFile)) { - teamFile = '.ai-team/team.md'; - } - if (!fs.existsSync(teamFile)) return; - - const content = fs.readFileSync(teamFile, 'utf8'); - - // Check if @copilot is on the team with auto-assign - const hasCopilot = content.includes('🤖 Coding Agent') || content.includes('@copilot'); - const autoAssign = content.includes(''); - if (!hasCopilot || !autoAssign) return; - - // Find issues labeled squad:copilot with no assignee - try { - const { data: copilotIssues } = await github.rest.issues.listForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - labels: 'squad:copilot', - state: 'open', - per_page: 5 - }); - - const unassigned = copilotIssues.filter(i => - !i.assignees || i.assignees.length === 0 - ); - - if (unassigned.length === 0) { - core.info('No unassigned squad:copilot issues'); - return; - } - - // Get repo default branch - const { data: repoData } = await github.rest.repos.get({ - owner: context.repo.owner, - repo: context.repo.repo - }); - - for (const issue of unassigned) { - try { - await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', { - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - assignees: ['copilot-swe-agent[bot]'], - agent_assignment: { - target_repo: `${context.repo.owner}/${context.repo.repo}`, - base_branch: repoData.default_branch, - custom_instructions: `Read .squad/team.md (or .ai-team/team.md) for team context and .squad/routing.md (or .ai-team/routing.md) for routing rules.` - } - }); - core.info(`Assigned copilot-swe-agent[bot] to #${issue.number}`); - } catch (e) { - core.warning(`Failed to assign @copilot to #${issue.number}: ${e.message}`); - } - } - } catch (e) { - core.info(`No squad:copilot label found or error: ${e.message}`); - } +name: Squad Heartbeat (Ralph) +# ⚠️ SYNC: This workflow is maintained in 4 locations. Changes must be applied to all: +# - templates/workflows/squad-heartbeat.yml (source template) +# - packages/squad-cli/templates/workflows/squad-heartbeat.yml (CLI package) +# - .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. + +on: + # React to completed work or new squad work + issues: + types: [closed, labeled] + pull_request: + types: [closed] + + # Manual trigger + workflow_dispatch: + +permissions: + issues: write + contents: read + pull-requests: read + +jobs: + heartbeat: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check triage script + id: check-script + run: | + if [ -f ".squad/templates/ralph-triage.js" ]; then + echo "has_script=true" >> $GITHUB_OUTPUT + else + echo "has_script=false" >> $GITHUB_OUTPUT + echo "⚠️ ralph-triage.js not found — run 'squad upgrade' to install" + fi + + - name: Ralph — Smart triage + if: steps.check-script.outputs.has_script == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + node .squad/templates/ralph-triage.js \ + --squad-dir .squad \ + --output triage-results.json + + - name: Ralph — Apply triage decisions + if: steps.check-script.outputs.has_script == 'true' && hashFiles('triage-results.json') != '' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const path = 'triage-results.json'; + if (!fs.existsSync(path)) { + core.info('No triage results — board is clear'); + return; + } + + const results = JSON.parse(fs.readFileSync(path, 'utf8')); + if (results.length === 0) { + core.info('📋 Board is clear — Ralph found no untriaged issues'); + return; + } + + for (const decision of results) { + try { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: decision.issueNumber, + labels: [decision.label] + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: decision.issueNumber, + body: [ + '### 🔄 Ralph — Auto-Triage', + '', + `**Assigned to:** ${decision.assignTo}`, + `**Reason:** ${decision.reason}`, + `**Source:** ${decision.source}`, + '', + '> Ralph auto-triaged this issue using routing rules.', + '> To reassign, swap the `squad:*` label.' + ].join('\n') + }); + + core.info(`Triaged #${decision.issueNumber} → ${decision.assignTo} (${decision.source})`); + } catch (e) { + core.warning(`Failed to triage #${decision.issueNumber}: ${e.message}`); + } + } + + core.info(`🔄 Ralph triaged ${results.length} issue(s)`); + + # Copilot auto-assign step (uses PAT if available) + - name: Ralph — Assign @copilot issues + if: success() + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const fs = require('fs'); + + const teamFile = '.squad/team.md'; + if (!fs.existsSync(teamFile)) return; + + const content = fs.readFileSync(teamFile, 'utf8'); + + // Check if @copilot is on the team with auto-assign + const hasCopilot = content.includes('🤖 Coding Agent') || content.includes('@copilot'); + const autoAssign = content.includes(''); + if (!hasCopilot || !autoAssign) return; + + // Find issues labeled squad:copilot with no assignee + try { + const { data: copilotIssues } = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + labels: 'squad:copilot', + state: 'open', + per_page: 5 + }); + + const unassigned = copilotIssues.filter(i => + !i.assignees || i.assignees.length === 0 + ); + + if (unassigned.length === 0) { + core.info('No unassigned squad:copilot issues'); + return; + } + + // Get repo default branch + const { data: repoData } = await github.rest.repos.get({ + owner: context.repo.owner, + repo: context.repo.repo + }); + + for (const issue of unassigned) { + try { + await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + assignees: ['copilot-swe-agent[bot]'], + agent_assignment: { + target_repo: `${context.repo.owner}/${context.repo.repo}`, + base_branch: repoData.default_branch, + custom_instructions: `Read .squad/team.md for team context and .squad/routing.md for routing rules.` + } + }); + core.info(`Assigned copilot-swe-agent[bot] to #${issue.number}`); + } catch (e) { + core.warning(`Failed to assign @copilot to #${issue.number}: ${e.message}`); + } + } + } catch (e) { + core.info(`No squad:copilot label found or error: ${e.message}`); + } diff --git a/.squad-templates/workflows/squad-issue-assign.yml b/.squad-templates/workflows/squad-issue-assign.yml index ad140f42d..f6b664bd8 100644 --- a/.squad-templates/workflows/squad-issue-assign.yml +++ b/.squad-templates/workflows/squad-issue-assign.yml @@ -27,13 +27,9 @@ jobs: // Extract member name from label (e.g., "squad:ripley" → "ripley") const memberName = label.replace('squad:', '').toLowerCase(); - // Read team roster — check .squad/ first, fall back to .ai-team/ - let teamFile = '.squad/team.md'; + const teamFile = '.squad/team.md'; if (!fs.existsSync(teamFile)) { - teamFile = '.ai-team/team.md'; - } - if (!fs.existsSync(teamFile)) { - core.warning('No .squad/team.md or .ai-team/team.md found — cannot assign work'); + core.warning('No .squad/team.md found — cannot assign work'); return; } @@ -72,7 +68,7 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, - body: `⚠️ No squad member found matching label \`${label}\`. Check \`.squad/team.md\` (or \`.ai-team/team.md\`) for valid member names.` + body: `⚠️ No squad member found matching label \`${label}\`. Check \`.squad/team.md\` for valid member names.` }); return; } diff --git a/.squad-templates/workflows/squad-triage.yml b/.squad-templates/workflows/squad-triage.yml index d118a2813..8eaf51f65 100644 --- a/.squad-templates/workflows/squad-triage.yml +++ b/.squad-templates/workflows/squad-triage.yml @@ -22,13 +22,9 @@ jobs: const fs = require('fs'); const issue = context.payload.issue; - // Read team roster — check .squad/ first, fall back to .ai-team/ - let teamFile = '.squad/team.md'; + const teamFile = '.squad/team.md'; if (!fs.existsSync(teamFile)) { - teamFile = '.ai-team/team.md'; - } - if (!fs.existsSync(teamFile)) { - core.warning('No .squad/team.md or .ai-team/team.md found — cannot triage'); + core.warning('No .squad/team.md found — cannot triage'); return; } @@ -88,11 +84,7 @@ jobs: } } - // Read routing rules — check .squad/ first, fall back to .ai-team/ - let routingFile = '.squad/routing.md'; - if (!fs.existsSync(routingFile)) { - routingFile = '.ai-team/routing.md'; - } + const routingFile = '.squad/routing.md'; let routingContent = ''; if (fs.existsSync(routingFile)) { routingContent = fs.readFileSync(routingFile, 'utf8'); diff --git a/.squad-templates/workflows/sync-squad-labels.yml b/.squad-templates/workflows/sync-squad-labels.yml index 699fc680f..f3799e793 100644 --- a/.squad-templates/workflows/sync-squad-labels.yml +++ b/.squad-templates/workflows/sync-squad-labels.yml @@ -4,7 +4,6 @@ on: push: paths: - '.squad/team.md' - - '.ai-team/team.md' workflow_dispatch: permissions: @@ -22,13 +21,9 @@ jobs: with: script: | const fs = require('fs'); - let teamFile = '.squad/team.md'; + const teamFile = '.squad/team.md'; if (!fs.existsSync(teamFile)) { - teamFile = '.ai-team/team.md'; - } - - if (!fs.existsSync(teamFile)) { - core.info('No .squad/team.md or .ai-team/team.md found — skipping label sync'); + core.info('No .squad/team.md found — skipping label sync'); return; } diff --git a/.squad/agents/_alumni/saul/charter.md b/.squad/agents/_alumni/saul/charter.md index aef3a1f01..db79b87ea 100644 --- a/.squad/agents/_alumni/saul/charter.md +++ b/.squad/agents/_alumni/saul/charter.md @@ -18,7 +18,7 @@ ## How I Work -- It's "Aspire" — not ".NET Aspire." Aspire is a standalone dashboard for any OTLP app. +- Use "Aspire" without a ".NET" prefix. Aspire is a standalone dashboard for any OTLP app. - Reference aspire.dev for documentation, NOT learn.microsoft.com - Use the very latest Aspire bits (mcr.microsoft.com/dotnet/aspire-dashboard:latest) - Aspire dashboard: port 18888 (UI), port 18889→4317 (OTLP/gRPC) diff --git a/.squad/decisions-archive.md b/.squad/decisions-archive.md index d44f3a31e..5888002d4 100644 --- a/.squad/decisions-archive.md +++ b/.squad/decisions-archive.md @@ -75,7 +75,7 @@ ### 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". +**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). Use "Aspire" without a ".NET" prefix. ### 2026-02-23: User directive — code fences **By:** Brady (via Copilot) diff --git a/.squad/decisions/inbox/procedures-fix-coordinator-inline-dispatch-gate.md b/.squad/decisions/inbox/procedures-fix-coordinator-inline-dispatch-gate.md new file mode 100644 index 000000000..e65fee749 --- /dev/null +++ b/.squad/decisions/inbox/procedures-fix-coordinator-inline-dispatch-gate.md @@ -0,0 +1,45 @@ +# Decision: Restore always-on coordinator inline-dispatch gate (v0.10.0 regression) + +- **Author:** Procedures (Prompt Engineer) +- **Date:** 2026-06-24 +- **Requested by:** Tamir Dresher +- **Branch:** squad/fix-coordinator-inline-dispatch-gate (worktree) +- **Status:** Proposed — applied in working tree, awaiting Lead (Flight) review; NOT committed. + +## Context + +Matthew Wan reported the main Squad coordinator "does a lot of work on its own instead of using his roster of agents." Worked in v0.9.4, broke in v0.10.0. Dina Berry worked around it by hand-editing the coordinator charter. + +## Root cause + +Commit `afe78188` (#1035, "context overflow sentinel + coordinator size reduction") cut the coordinator template and moved the concrete inline-dispatch gate and dispatch mechanics out of the always-on prompt into lazy-loaded reference files (`client-compatibility-reference.md`, `spawn-reference.md`). The leftover inline guidance ("inline work is last-resort fallback only") was too soft to hold the line, so the coordinator began executing domain work itself. + +## Decision + +Re-establish a strong, **always-on** dispatch gate without breaking the legitimate Direct/Lightweight response modes or the size-reduction spirit. Three minimal edits (~14 lines) to the canonical template `.squad-templates/squad.agent.md`, propagated to all 5 synced copies via `npm run sync-templates`: + +1. **Inline-dispatch gate** (Client Compatibility): inline permitted ONLY in Direct Mode, or when NEITHER `task` NOR `runSubagent` exists; otherwise MUST dispatch. +2. **STOP gate** (How to Spawn an Agent): about to emit a domain artifact with no spawn call this turn → STOP and dispatch (exceptions: Direct Mode / no spawn tool). +3. **VS Code `runSubagent` micro-playbook** re-inlined (~5 lines) so how-to-dispatch is always-on. + +## The 5 synced copies (canonical first) + +1. `.squad-templates/squad.agent.md` (canonical — edited here) +2. `templates/squad.agent.md.template` +3. `.github/agents/squad.agent.md` +4. `packages/squad-cli/templates/squad.agent.md.template` +5. `packages/squad-sdk/templates/squad.agent.md.template` + +## Validation + +- New regression/parity test `test/coordinator-inline-dispatch-gate.test.ts` (deterministic, subprocess-free): RED before (8/8 fail), GREEN after (8/8 pass). +- Full `npm run test`: 18 failed files vs 31 baseline (fewer); all failures are pre-existing flaky subprocess/timeout e2e tests (3 of 4 "new vs baseline" pass in isolation; the 4th is `squad doctor` at 40x10 exiting null = timeout, unrelated to template content). No new deterministic failures. +- `npm run lint` (tsc noEmit both packages): clean. + +## Changeset + +Required. The `changelog-gate` regex governs `.squad-templates/`, `templates/`, and `packages/squad-(sdk|cli)/templates/` — not just `src/`. Added `.changeset/fix-coordinator-inline-dispatch-gate.md` (patch, squad-cli + squad-sdk). + +## Rollout + +Existing installs require `squad upgrade` to pick up the regenerated coordinator agent. diff --git a/.squad/skills/coordinator-init-mode/SKILL.md b/.squad/skills/coordinator-init-mode/SKILL.md new file mode 100644 index 000000000..efc61ba3a --- /dev/null +++ b/.squad/skills/coordinator-init-mode/SKILL.md @@ -0,0 +1,83 @@ +--- +name: "coordinator-init-mode" +description: "The complete two-phase Init Mode protocol the Squad coordinator runs when no team exists yet in the current repo. Phase 1 = propose the team (no files created, wait for user confirm). Phase 2 = create .squad/ scaffolding, casting state, .gitattributes for merge drivers, and the always-on built-ins (Scribe, Ralph, Rai, Fact Checker). Loaded on demand when the coordinator detects no .squad/team.md exists." +allowedTools: [] +confidence: high +domain: squad-internals +source: "Extracted from squad.agent.md as part of the slimming effort (bradygaster/squad#1308). Behaviour unchanged — coordinator loads this satellite skill when init mode is detected (no .squad/team.md present)." +--- + +> **Load this skill when:** you detect that no `.squad/team.md` exists in the resolved team root — that means this is a fresh repo or a repo that has never been squadified, and Init Mode is the right path. The short stub in `squad.agent.md` tells you to load this skill; the full two-phase protocol lives here. +> +> **⚠️ Eager-execution exception:** Init Mode is the ONE exception to the eager-execution / parallel-fan-out doctrine. Phase 1 MUST end with a user confirmation before any file is created. Do not bypass. + +## Phase 1: Propose the Team + +No team exists yet. **Propose one — but DO NOT create any files until the user confirms.** + +1. **Identify the user.** Run `git config user.name` to learn who you're working with. Use their name in conversation (e.g., *"Hey {user}, what are you building?"*). Store their name (NOT email) in `team.md` under Project Context. **Never read or store `git config user.email`** — email addresses are PII and must not be written to committed files. +2. Ask: *"What are you building? (language, stack, what it does)"* +3. **Cast the team.** Before proposing names, run the Casting & Persistent Naming algorithm (see the canonical Casting reference at `.squad/templates/casting-reference.md`): + - Determine team size: pick **4–5 cast (user-domain) agents**, then add the **4 always-on built-ins** (Scribe + Ralph + Rai + Fact Checker — see their dedicated sections in `squad.agent.md`). A typical fresh squad has **8–9 total roster entries**, not 4–5. + - Determine assignment shape from the user's project description. + - Derive resonance signals from the session and repo context. + - Select a universe. Allocate character names from that universe. + - Scribe is always "Scribe" — exempt from casting. + - Ralph is always "Ralph" — exempt from casting. + - Rai is always "Rai" — exempt from casting. + - Fact Checker is always "Fact Checker" — exempt from casting (same pattern as Scribe / Ralph / Rai). +4. Propose the team with their cast names. Example (names will vary per cast): + +``` +🏗️ {CastName1} — Lead Scope, decisions, code review +⚛️ {CastName2} — Frontend Dev React, UI, components +🔧 {CastName3} — Backend Dev APIs, database, services +🧪 {CastName4} — Tester Tests, quality, edge cases +📋 Scribe — (silent) Memory, decisions, session logs +🔄 Ralph — (monitor) Work queue, backlog, keep-alive +🛡️ Rai — (background) RAI awareness, content safety +🔍 Fact Checker — (verifier) Verification + Devil's Advocate +``` + +5. Use the `ask_user` tool to confirm the roster. Provide choices so the user sees a selectable menu: + - **question:** *"Look right?"* + - **choices:** `["Yes, hire this team", "Add someone", "Change a role"]` + +**⚠️ STOP. Your response ENDS here. Do NOT proceed to Phase 2. Do NOT create any files or directories. Wait for the user's reply.** + +--- + +## Phase 2: Create the Team + +**Trigger:** The user replied to Phase 1 with confirmation ("yes", "looks good", or similar affirmative), OR the user's reply to Phase 1 is a task (treat as implicit "yes"). + +> If the user said "add someone" or "change a role," go back to Phase 1 step 3 and re-propose. **Do NOT enter Phase 2 until the user confirms.** + +6. Create the `.squad/` directory structure (see `.squad/templates/` for format guides or use the standard structure: `team.md`, `routing.md`, `ceremonies.md`, `decisions.md`, `decisions/inbox/`, `casting/`, `agents/`, `orchestration-log/`, `skills/`, `log/`, `rai/`). + +**Casting state initialization:** Copy `.squad/templates/casting-policy.json` to `.squad/casting/policy.json` (or create from defaults). Create `registry.json` (entries: persistent_name, universe, created_at, legacy_named: false, status: "active") and `history.json` (first assignment snapshot with unique assignment_id). + +**Seeding:** Each agent's `history.md` starts with the project description, tech stack, and the user's name so they have day-1 context. Agent folder names are the cast name in lowercase (e.g., `.squad/agents/ripley/`). The Scribe's charter includes maintaining `decisions.md` and cross-agent context sharing. Rai's charter is seeded from the `Rai-charter.md` template, and `.squad/rai/policy.md` is seeded from `rai-policy.md`. Fact Checker's charter is seeded from `fact-checker-charter.md` and `.squad/fact-checker/policy.md` is seeded from `fact-checker-policy.md`. + +**Team.md structure:** `team.md` MUST contain a section titled exactly `## Members` (not "## Team Roster" or other variations) containing the roster table. This header is hard-coded in GitHub workflows (`squad-heartbeat.yml`, `squad-issue-assign.yml`, `squad-triage.yml`, `sync-squad-labels.yml`) for label automation. If the header is missing or titled differently, label routing breaks. + +**Merge driver for append-only files:** Create or update `.gitattributes` at the repo root to enable conflict-free merging of `.squad/` state across branches: + +``` +.squad/decisions.md merge=union +.squad/agents/*/history.md merge=union +.squad/log/** merge=union +.squad/orchestration-log/** merge=union +.squad/rai/audit-trail.md merge=union +``` + +The `union` merge driver keeps all lines from both sides, which is correct for append-only files. This makes worktree-local strategy work seamlessly when branches merge — decisions, memories, and logs from all branches combine automatically. + +7. Say: *"✅ Team hired. Try: '{FirstCastName}, set up the project structure'"* + +8. **Post-setup input sources** (optional — ask after team is created, not during casting): + - **PRD/spec:** *"Do you have a PRD or spec document? (file path, paste it, or skip)"* → If provided, follow PRD Mode flow. + - **GitHub issues:** *"Is there a GitHub repo with issues I should pull from? (owner/repo, or skip)"* → If provided, follow GitHub Issues Mode flow. + - **Human members:** *"Are any humans joining the team? (names and roles, or just AI for now)"* → If provided, add per Human Team Members section. + - **Copilot agent:** *"Want to include @copilot? It can pick up issues autonomously. (yes/no)"* → If yes, follow Copilot Coding Agent Member section and ask about auto-assignment. + - These are additive. **Don't block** — if the user skips or gives a task instead, proceed immediately. diff --git a/.squad/skills/coordinator-response-mode/SKILL.md b/.squad/skills/coordinator-response-mode/SKILL.md new file mode 100644 index 000000000..4dff3b800 --- /dev/null +++ b/.squad/skills/coordinator-response-mode/SKILL.md @@ -0,0 +1,97 @@ +--- +name: "coordinator-response-mode" +description: "Selecting WHO handles work is the Routing table; selecting HOW they handle it (Direct, Lightweight, Standard, Full) is Response Mode. This skill contains the complete decision table, exemplar prompts for each mode, the Lightweight spawn template, and the upgrade rules. Squad coordinator loads this on demand once routing has identified the agent — to pick the right ceremony level for the task." +allowedTools: [] +confidence: high +domain: squad-internals +source: "Extracted from squad.agent.md as part of the slimming effort (bradygaster/squad#1308). Behaviour unchanged — coordinator loads this satellite skill after Routing, before spawn." +--- + +> **Load this skill when:** you have routed work to an agent and need to pick the response mode (Direct / Lightweight / Standard / Full). The 1-line stub in `squad.agent.md` is for awareness; this skill is the full decision table + templates. + +## Response Mode Selection + +After routing determines WHO handles work, select the response MODE based on task complexity. **Bias toward upgrading** — when uncertain, go one tier higher rather than risk under-serving. + +| Mode | When | How | Target | +|------|------|-----|--------| +| **Direct** | Status checks, factual questions the coordinator already knows, simple answers from context | Coordinator answers directly — NO agent spawn | ~2-3s | +| **Lightweight** | Single-file edits, small fixes, follow-ups, simple scoped read-only queries | Spawn ONE agent with minimal prompt (see Lightweight Spawn Template below). Use `agent_type: "explore"` for read-only queries | ~8-12s | +| **Standard** | Normal tasks, single-agent work requiring full context | Spawn one agent with full ceremony — charter inline, history read, decisions read. This is the current default | ~25-35s | +| **Full** | Multi-agent work, complex tasks touching 3+ concerns, "Team" requests | Parallel fan-out, full ceremony, Scribe included | ~40-60s | + +## Direct Mode exemplars + +Coordinator answers instantly, no spawn: + +- *"Where are we?"* → Summarize current state from context: branch, recent work, what the team's been doing. A user favorite — make it instant. +- *"How many tests do we have?"* → Run a quick command, answer directly. +- *"What branch are we on?"* → `git branch --show-current`, answer directly. +- *"Who's on the team?"* → Answer from `team.md` already in context. +- *"What did we decide about X?"* → Answer from `decisions.md` already in context. + +## Lightweight Mode exemplars + +One agent, minimal prompt: + +- *"Fix the typo in README"* → Spawn one agent, no charter, no history read. +- *"Add a comment to line 42"* → Small scoped edit, minimal context needed. +- *"What does this function do?"* → `agent_type: "explore"` (Haiku model, fast). +- Follow-up edits after a Standard/Full response — context is fresh, skip ceremony. + +## Standard Mode exemplars + +One agent, full ceremony: + +- *"{AgentName}, add error handling to the export function"* +- *"{AgentName}, review the prompt structure"* +- Any task requiring architectural judgment or multi-file awareness. + +## Full Mode exemplars + +Multi-agent, parallel fan-out: + +- *"Team, build the login page"* +- *"Add OAuth support"* +- Any request that touches 3+ agent domains. + +## Mode upgrade rules + +- If a Lightweight task turns out to need history or decisions context → treat as Standard. +- If uncertain between Direct and Lightweight → choose Lightweight. +- If uncertain between Lightweight and Standard → choose Standard. +- **Never downgrade mid-task.** If you started Standard, finish Standard. + +## Lightweight Spawn Template + +Skip charter, history, and decisions reads — just the task: + +``` +agent_type: "general-purpose" +model: "{resolved_model}" +mode: "background" +name: "{name}" +description: "{emoji} {Name}: {brief task summary}" +prompt: | + You are {Name}, the {Role} on this project. + TEAM ROOT: {team_root} + CURRENT_DATETIME: + WORKTREE_PATH: {worktree_path} + WORKTREE_MODE: {true|false} + **Requested by:** {current user name} + + {% if WORKTREE_MODE %} + **WORKTREE:** Working in `{WORKTREE_PATH}`. All operations relative to this path. Do NOT switch branches. + {% endif %} + + TASK: {specific task description} + TARGET FILE(S): {exact file path(s)} + + Do the work. Keep it focused. + If you made a meaningful decision, persist it with `memory.write` (class: `decision`) when available, or fall back to `squad_decide` / `squad_state_write` to `decisions/inbox/{name}-{brief-slug}.md`. Do not run git notes, switch branches, or write mutable `.squad/` state by hand. + + ⚠️ OUTPUT: Report outcomes in human terms. Never expose tool internals or SQL. + ⚠️ 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}. CURRENT_DATETIME: — {question} TEAM ROOT: {team_root}"` diff --git a/.squad/skills/coordinator-source-of-truth/SKILL.md b/.squad/skills/coordinator-source-of-truth/SKILL.md new file mode 100644 index 000000000..3a992ba0a --- /dev/null +++ b/.squad/skills/coordinator-source-of-truth/SKILL.md @@ -0,0 +1,45 @@ +--- +name: "coordinator-source-of-truth" +description: "The complete file-by-file source-of-truth hierarchy for Squad: which files are authoritative, which are derived/append-only, who may write each one, who may read each one, and the precedence rules when they conflict. Squad coordinator loads this on demand when it needs to resolve a write conflict, decide where a piece of state belongs, or answer a 'who owns this file' question." +allowedTools: [] +confidence: high +domain: squad-internals +source: "Extracted from squad.agent.md as part of the slimming effort (bradygaster/squad#1308). Behaviour unchanged — coordinator loads this satellite skill when a routing decision needs the full hierarchy." +--- + +> **Load this skill when:** the coordinator (or any agent) needs to resolve a "where does this state belong?" or "who is allowed to write this file?" question — e.g., when about to write `.squad/decisions.md`, when reviewing whether an agent broke the append-only rule, when answering a user question about Squad's file layout, or when adjudicating a conflict between two files. The short summary in `squad.agent.md` is for routing; this skill is the full reference. + +## State backend note + +Files below marked as **"Derived / append-only"** are **mutable state** — agents access them with runtime state tools (`squad_state_read`, `squad_state_write`, `squad_state_append`, `squad_state_delete`, `squad_state_list`). The runtime decides whether the configured backend stores them on disk, git-native state, or an external provider. Files marked as **"Authoritative"** are **static config** and always live on disk regardless of backend. + +## File hierarchy + +| File | Status | Who May Write | Who May Read | +|------|--------|---------------|--------------| +| `.github/agents/squad.agent.md` | **Authoritative governance.** All roles, handoffs, gates, and enforcement rules. | Repo maintainer (human) | Squad (Coordinator) | +| `.squad/decisions.md` | **Authoritative decision ledger.** Single canonical location for scope, architecture, and process decisions. | Squad (Coordinator) — append only | All agents | +| `.squad/team.md` | **Authoritative roster.** Current team composition. | Squad (Coordinator) | All agents | +| `.squad/routing.md` | **Authoritative routing.** Work assignment rules. | Squad (Coordinator) | Squad (Coordinator) | +| `.squad/ceremonies.md` | **Authoritative ceremony config.** Definitions, triggers, and participants for team ceremonies. | Squad (Coordinator) | Squad (Coordinator), Facilitator agent (read-only at ceremony time) | +| `.squad/casting/policy.json` | **Authoritative casting config.** Universe allowlist and capacity. | Squad (Coordinator) | Squad (Coordinator) | +| `.squad/casting/registry.json` | **Authoritative name registry.** Persistent agent-to-name mappings. | Squad (Coordinator) | Squad (Coordinator) | +| `.squad/casting/history.json` | **Derived / append-only.** Universe usage history and assignment snapshots. | Squad (Coordinator) — append only | Squad (Coordinator) | +| `.squad/agents/{name}/charter.md` | **Authoritative agent identity.** Per-agent role and boundaries. | Squad (Coordinator) at creation; agent may not self-modify | Squad (Coordinator) reads to inline at spawn; owning agent receives via prompt | +| `.squad/agents/{name}/history.md` | **Derived / append-only.** Personal learnings. Never authoritative for enforcement. | Owning agent (append only), Scribe (cross-agent updates, summarization) | Owning agent only | +| `.squad/agents/{name}/history-archive.md` | **Derived / append-only.** Archived history entries. Preserved for reference. | Scribe | Owning agent (read-only) | +| `.squad/orchestration-log/` | **Derived / append-only.** Agent routing evidence. Never edited after write. | Scribe | All agents (read-only) | +| `.squad/log/` | **Derived / append-only.** Session logs. Diagnostic archive. Never edited after write. | Scribe | All agents (read-only) | +| `.squad/templates/` | **Reference.** Format guides for runtime files. Not authoritative for enforcement. | Squad (Coordinator) at init | Squad (Coordinator) | +| `.squad/rai/policy.md` | **Authoritative RAI policy.** Check categories, terminology standards, and opt-out rules. | Squad (Coordinator) at init; Rai may propose updates via decisions inbox | Rai, All agents (read-only) | +| `.squad/rai/audit-trail.md` | **Derived / append-only.** RAI review evidence log. Redacted — never contains raw secrets or harmful content. | Rai (append only) | Rai, Squad (Coordinator) | +| `.squad/fact-checker/policy.md` | **Authoritative verification + Devil's Advocate policy.** Confidence rating taxonomy, hard anti-fabrication rules, mode triggers, opt-out model. | Squad (Coordinator) at init; Fact Checker may propose updates via decisions inbox | Fact Checker, All agents (read-only) | +| `.squad/fact-checker/audit-trail.md` | **Derived / append-only.** Verification verdicts + DA brief evidence log. Succinct — verdict + citation, never raw source material. | Fact Checker (append only) | Fact Checker, Squad (Coordinator) | +| `.squad/plugins/marketplaces.json` | **Authoritative plugin config.** Registered marketplace sources. | Squad CLI (`squad plugin marketplace`) | Squad (Coordinator) | + +## Rules + +1. **If `squad.agent.md` and any other file conflict, `squad.agent.md` wins.** It is the only file with hard governance authority. +2. **Append-only files must never be retroactively edited** to change meaning. They are diagnostic and audit-trail material. Corrections go in a new entry that references the prior one. +3. **Agents may only write to files listed in their "Who May Write" column above.** Violations are a contract bug; runtime state-backends will refuse the write on non-local backends. +4. **Non-coordinator agents may propose decisions** in their responses, but only Squad (Coordinator) records accepted decisions in `.squad/decisions.md`. diff --git a/.squad/skills/personal-squad/SKILL.md b/.squad/skills/personal-squad/SKILL.md index b300600d3..958031e39 100644 --- a/.squad/skills/personal-squad/SKILL.md +++ b/.squad/skills/personal-squad/SKILL.md @@ -15,8 +15,9 @@ A personal squad is a user-level collection of AI agents that travel with you ac ## Directory Structure ``` -~/.config/squad/personal-squad/ # Linux/macOS -%APPDATA%/squad/personal-squad/ # Windows +~/Library/Application Support/squad/personal-squad/ # macOS +~/.config/squad/personal-squad/ # Linux +%APPDATA%/squad/personal-squad/ # Windows ├── agents/ │ ├── {agent-name}/ │ │ ├── charter.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 81f25a9e2..47af4259c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,30 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [0.11.0] - 2026-06-29 + +### Added +- (cli) Add `squad preset install ` for installing shared presets from GitHub URLs or local paths (#1224). +- (cli) Add `squad registry add/list/remove` for discovery-only peer squads, plus the bundled cross-squad communication skill. +- (cli) Add `cast` terminology across help and docs while keeping `hire` as a silent compatibility alias (#1394). +- (sdk+cli) Add Copilot App sub-session spawning for cast members and thread reasoning-effort settings through agent dispatch (#1385). +- (sdk+cli) Expose memory tools through the `squad_state` MCP server and improve Fact Checker auto-scaffolding/plumbing. + +### Changed +- (sdk+cli) Slim the bundled `squad.agent.md` by extracting long guidance into satellite skills (#1311). +- (cli) Move bundled skills from `.copilot/skills/` to `.github/skills/` so they are visible to all Copilot surfaces. +- (cli) Rename user-facing ".NET Aspire" references to "Aspire" in CLI help and command output (#1239). +- (sdk+cli) Refresh the toolchain and integrations, including `@github/copilot-sdk` 1.0.4, TypeScript 6, Vitest 4, Ink 7, OpenTelemetry 2.x, and Node type updates. + +### Fixed +- (cli) Pin CLI-to-SDK workspace resolution so local builds and runtime use the workspace SDK instead of a stale published package (#1406). +- (sdk+cli) Restore the green test suite by reconciling personal squad paths, stabilizing observer category handling, guarding storage case-insensitivity, and skipping environment-dependent tests when prerequisites are missing (#1416). +- (sdk+cli) Guard symlink-sensitive tests on Windows or restricted CI filesystems (#1418). +- (sdk+cli) Harden observer change resolution for directory events, symlinks, and filesystem scan errors. +- (sdk+cli) Fix state-backend handshake, SQUAD_HOME/SQUAD_PERSONAL_DIR resolution, preset apply wiring, routing example quote stripping, YAML escaping, and MCP config pollution regressions. +- (cli) Fix Windows command escaping, top-level help coverage for `externalize`/`internalize`, slash-command discovery, and upgrade/self-repair flows. +- (sdk) Export gitignore-state helpers, update OTel resource APIs for 2.x, and align the adapter client with `@github/copilot-sdk` 1.0.4. + ## [0.10.0] — 2026-06-07 First stable release since v0.9.4 (April 25). Consumes 97 changesets (sdk: 50, cli: 71). @@ -399,4 +423,3 @@ First stable release since v0.9.4 (April 25). Consumes 97 changesets (sdk: 50, c - Changesets infrastructure for independent package versioning - diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..6dc4b1210 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/README.md b/README.md index b2ab79a3d..339a7a44a 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ Say **"squad commands"** in chat to see a categorized menu of common operations | Command | What it does | |---------|-------------| -| `squad init` | **Init** — scaffold Squad in the current directory (idempotent — safe to run multiple times); alias: `hire`; use `--global` to init in personal squad directory, `--mode remote ` for dual-root mode | +| `squad init` | **Init** — scaffold Squad in the current directory (idempotent — safe to run multiple times); alias: `cast`; use `--global` to init in personal squad directory, `--mode remote ` for dual-root mode | | `squad upgrade` | Update Squad-owned files to latest; never touches your team state; use `--global` to upgrade personal squad, `--migrate-directory` to rename `.ai-team/` → `.squad/` | | `squad upgrade --self` | Update the Squad CLI package itself; add `--insider` for dev-channel prerelease builds | | `squad status` | Show which squad is active and why | @@ -522,3 +522,35 @@ The SDK provides programmatic control over agent orchestration — custom tools, - [Samples](samples/README.md) — eight working examples from beginner to advanced For SDK installation: `npm install @bradygaster/squad-sdk` + +--- + +## Requirements + +- **Node.js** ≥22.5.0 +- **npm** ≥10.0.0 +- **Git** with SSH agent +- **GitHub CLI** (`gh`) for GitHub integration + +## License + +This project is licensed under the terms of the MIT open source license. Please refer to the [LICENSE](./LICENSE) file for the full terms. + +## Maintainers + +- [@bradygaster](https://github.com/bradygaster) +- [@tamirdresher](https://github.com/tamirdresher) + +See [CODEOWNERS](.github/CODEOWNERS) for the full list. + +## Support + +For help or questions about using Squad, please use [GitHub Discussions](https://github.com/github/squad/discussions). See [SUPPORT.md](./SUPPORT.md) for details. + +## Contributing + +We welcome contributions! See [CONTRIBUTING.md](./CONTRIBUTING.md) for build setup, monorepo structure, and guidelines. + +## Code of Conduct + +This project has adopted the [Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md). See the full text for details on expected behavior and reporting. diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 000000000..0316949a8 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,13 @@ +# Support + +## How to file issues and get help + +This project uses GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new issue. + +For help or questions about using Squad, please use [GitHub Discussions](https://github.com/bradygaster/squad/discussions) on this repository. + +**Squad** is under active development and maintained by GitHub staff and the community. We will do our best to respond to support, feature requests, and community questions in a timely manner. + +## GitHub Support Policy + +Support for this project is limited to the resources listed above. diff --git a/cspell.json b/cspell.json index 2f1fc0a0c..4a7ad0f7b 100644 --- a/cspell.json +++ b/cspell.json @@ -41,7 +41,7 @@ "benchmarkfn", "filterissuesbystream", "filterissuesbyworkstream", "Recognised", "recognised", "squadified", "TOCTOU", "unflushed", "unparseable", "pluggability", "PKCE", "MSAL", "AKIA", "runas", - "typoo" + "typoo", "Sympy", "mwaskom", "astropy", "sympy", "pylint", "pydata", "xarray", "swebench" ], "dictionaries": ["en_US", "typescript", "node", "npm", "bash"], "allowCompoundWords": true diff --git a/docs/.astro/collections/blog.schema.json b/docs/.astro/collections/blog.schema.json index bf04f626c..04f161b81 100644 --- a/docs/.astro/collections/blog.schema.json +++ b/docs/.astro/collections/blog.schema.json @@ -1,52 +1,34 @@ { - "$ref": "#/definitions/blog", - "definitions": { - "blog": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "date": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "author": { - "type": "string" - }, - "updatedAt": { - "anyOf": [ - { - "type": "string", - "format": "date-time" - }, - { - "type": "string", - "format": "date" - }, - { - "type": "integer", - "format": "unix-time" - } - ] - }, - "status": { - "type": "string" - }, - "$schema": { - "type": "string" - } - }, - "additionalProperties": false + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "date": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "author": { + "type": "string" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string" + }, + "$schema": { + "type": "string" } - }, - "$schema": "http://json-schema.org/draft-07/schema#" + } } \ No newline at end of file diff --git a/docs/.astro/collections/docs.schema.json b/docs/.astro/collections/docs.schema.json index 8564032b5..df651bbf7 100644 --- a/docs/.astro/collections/docs.schema.json +++ b/docs/.astro/collections/docs.schema.json @@ -1,52 +1,34 @@ { - "$ref": "#/definitions/docs", - "definitions": { - "docs": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "order": { - "type": "number" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "author": { - "type": "string" - }, - "updatedAt": { - "anyOf": [ - { - "type": "string", - "format": "date-time" - }, - { - "type": "string", - "format": "date" - }, - { - "type": "integer", - "format": "unix-time" - } - ] - }, - "status": { - "type": "string" - }, - "$schema": { - "type": "string" - } - }, - "additionalProperties": false + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "order": { + "type": "number" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "author": { + "type": "string" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string" + }, + "$schema": { + "type": "string" } - }, - "$schema": "http://json-schema.org/draft-07/schema#" + } } \ No newline at end of file diff --git a/docs/.astro/content.d.ts b/docs/.astro/content.d.ts index 4aa3e1b42..c88a60137 100644 --- a/docs/.astro/content.d.ts +++ b/docs/.astro/content.d.ts @@ -15,21 +15,13 @@ declare module 'astro:content' { [key: string]: unknown; }; } -} -declare module 'astro:content' { type Flatten = T extends { [K: string]: infer U } ? U : never; - export type CollectionKey = keyof AnyEntryMap; - export type CollectionEntry = Flatten; - - export type ContentCollectionKey = keyof ContentEntryMap; - export type DataCollectionKey = keyof DataEntryMap; + export type CollectionKey = keyof DataEntryMap; + export type CollectionEntry = Flatten; type AllValuesOf = T extends any ? T[keyof T] : never; - type ValidContentEntrySlug = AllValuesOf< - ContentEntryMap[C] - >['slug']; export type ReferenceDataEntry< C extends CollectionKey, @@ -38,41 +30,17 @@ declare module 'astro:content' { collection: C; id: E; }; - export type ReferenceContentEntry< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}) = string, - > = { - collection: C; - slug: E; - }; + export type ReferenceLiveEntry = { collection: C; id: string; }; - /** @deprecated Use `getEntry` instead. */ - export function getEntryBySlug< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}), - >( - collection: C, - // Note that this has to accept a regular string too, for SSR - entrySlug: E, - ): E extends ValidContentEntrySlug - ? Promise> - : Promise | undefined>; - - /** @deprecated Use `getEntry` instead. */ - export function getDataEntryById( - collection: C, - entryId: E, - ): Promise>; - - export function getCollection>( + export function getCollection>( collection: C, filter?: (entry: CollectionEntry) => entry is E, ): Promise; - export function getCollection( + export function getCollection( collection: C, filter?: (entry: CollectionEntry) => unknown, ): Promise[]>; @@ -84,14 +52,6 @@ declare module 'astro:content' { import('astro').LiveDataCollectionResult, LiveLoaderErrorType> >; - export function getEntry< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}), - >( - entry: ReferenceContentEntry, - ): E extends ValidContentEntrySlug - ? Promise> - : Promise | undefined>; export function getEntry< C extends keyof DataEntryMap, E extends keyof DataEntryMap[C] | (string & {}), @@ -100,15 +60,6 @@ declare module 'astro:content' { ): E extends keyof DataEntryMap[C] ? Promise : Promise | undefined>; - export function getEntry< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}), - >( - collection: C, - slug: E, - ): E extends ValidContentEntrySlug - ? Promise> - : Promise | undefined>; export function getEntry< C extends keyof DataEntryMap, E extends keyof DataEntryMap[C] | (string & {}), @@ -126,40 +77,47 @@ declare module 'astro:content' { ): Promise, LiveLoaderErrorType>>; /** Resolve an array of entry references from the same collection */ - export function getEntries( - entries: ReferenceContentEntry>[], - ): Promise[]>; export function getEntries( entries: ReferenceDataEntry[], ): Promise[]>; - export function render( - entry: AnyEntryMap[C][string], + export function render( + entry: DataEntryMap[C][string], ): Promise; - export function reference( + export function reference< + C extends + | keyof DataEntryMap + // Allow generic `string` to avoid excessive type errors in the config + // if `dev` is not running to update as you edit. + // Invalid collection names will be caught at build time. + | (string & {}), + >( collection: C, - ): import('astro/zod').ZodEffects< + ): import('astro/zod').ZodPipe< import('astro/zod').ZodString, - C extends keyof ContentEntryMap - ? ReferenceContentEntry> - : ReferenceDataEntry + import('astro/zod').ZodTransform< + C extends keyof DataEntryMap + ? { + collection: C; + id: string; + } + : never, + string + > >; - // Allow generic `string` to avoid excessive type errors in the config - // if `dev` is not running to update as you edit. - // Invalid collection names will be caught at build time. - export function reference( - collection: C, - ): import('astro/zod').ZodEffects; type ReturnTypeOrOriginal = T extends (...args: any[]) => infer R ? R : T; - type InferEntrySchema = import('astro/zod').infer< + type InferEntrySchema = import('astro/zod').infer< ReturnTypeOrOriginal['schema']> >; - - type ContentEntryMap = { - - }; + type ExtractLoaderConfig = T extends { loader: infer L } ? L : never; + type InferLoaderSchema< + C extends keyof DataEntryMap, + L = ExtractLoaderConfig, + > = L extends { schema: import('astro/zod').ZodSchema } + ? import('astro/zod').infer + : any; type DataEntryMap = { "blog": Record = T extends import('astro/loaders').LiveLoader< infer TData, infer TEntryFilter, @@ -191,10 +147,10 @@ declare module 'astro:content' { > ? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError } : { data: never; entryFilter: never; collectionFilter: never; error: never }; - type ExtractDataType = ExtractLoaderTypes['data']; type ExtractEntryFilterType = ExtractLoaderTypes['entryFilter']; type ExtractCollectionFilterType = ExtractLoaderTypes['collectionFilter']; type ExtractErrorType = ExtractLoaderTypes['error']; + type ExtractDataType = ExtractLoaderTypes['data']; type LiveLoaderDataType = LiveContentConfig['collections'][C]['schema'] extends undefined @@ -210,6 +166,6 @@ declare module 'astro:content' { LiveContentConfig['collections'][C]['loader'] >; - export type ContentConfig = typeof import("./../src/content/config.js"); + export type ContentConfig = typeof import("./../src/content.config.js"); export type LiveContentConfig = never; } diff --git a/docs/package-lock.json b/docs/package-lock.json index ee1c24006..03198bb08 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -8,44 +8,52 @@ "name": "squad-docs", "version": "0.0.1", "dependencies": { - "@astrojs/rss": "^4.0.17", - "@astrojs/sitemap": "^3.7.1", - "@tailwindcss/vite": "^4.1.0", - "astro": "^5.7.0", - "sharp": "^0.34.5", + "@astrojs/rss": "^4.0.18", + "@astrojs/sitemap": "^3.7.3", + "@tailwindcss/vite": "^4.3.1", + "astro": "^6.4.7", + "sharp": "^0.35.2", "tailwindcss": "^4.1.0", "unist-util-visit": "^5.1.0" }, "devDependencies": { - "@playwright/test": "^1.58.2", - "pagefind": "^1.4.0" + "@playwright/test": "^1.61.0", + "pagefind": "^1.5.2" } }, "node_modules/@astrojs/compiler": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.1.tgz", - "integrity": "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-4.0.0.tgz", + "integrity": "sha512-eouss7G8ygdZqHuke033VMcVw5HTZUu+PXd/h06DGDUg/jt5btPYPqh66ENWw/mU78rBrf/oeC4oqoBwMtDMNA==", "license": "MIT" }, "node_modules/@astrojs/internal-helpers": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.5.tgz", - "integrity": "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==", - "license": "MIT" + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.10.0.tgz", + "integrity": "sha512-Ry2R3VPeIN4uPCSA4xQc+e+vsJXkalKpEbDc07hV+a/o5Bs2N/s/uDcPJH/05L19DKh9tAy7e6JM3YZ6Cxfezw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.4", + "@types/mdast": "^4.0.4", + "js-yaml": "^4.1.1", + "picomatch": "^4.0.4", + "retext-smartypants": "^6.2.0", + "shiki": "^4.0.2", + "smol-toml": "^1.6.0", + "unified": "^11.0.5" + } }, "node_modules/@astrojs/markdown-remark": { - "version": "6.3.10", - "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.10.tgz", - "integrity": "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.2.0.tgz", + "integrity": "sha512-+YxmVQu1Bd+MFfSzjq1rOJvD9+nIOJzz5YIIhdIH01RrxRkKbyKoEgyIqP3yv51MhzMDgd79QaPv+kCVPT8vHw==", "license": "MIT", "dependencies": { - "@astrojs/internal-helpers": "0.7.5", - "@astrojs/prism": "3.3.0", + "@astrojs/internal-helpers": "0.10.0", + "@astrojs/prism": "4.0.2", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", - "import-meta-resolve": "^4.2.0", - "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", @@ -53,51 +61,40 @@ "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", - "shiki": "^3.19.0", - "smol-toml": "^1.5.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", - "unist-util-visit": "^5.0.0", + "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "node_modules/@astrojs/prism": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", - "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.2.tgz", + "integrity": "sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==", "license": "MIT", "dependencies": { "prismjs": "^1.30.0" }, "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0" + "node": ">=22.12.0" } }, "node_modules/@astrojs/rss": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-4.0.17.tgz", - "integrity": "sha512-eV+wdMbeVKC9+sPaV0LN8JL1LGo9YAh3GKl4Ou4nzMNLmXM/aswYpSGxVEAuHilgBZ6/++/Pv08ICmuOqX107w==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-4.0.18.tgz", + "integrity": "sha512-wc5DwKlbTEdgVAWnHy8krFTeQ42t1v/DJqeq5HtulYK3FYHE4krtRGjoyhS3eXXgfdV6Raoz2RU3wrMTFAitRg==", "license": "MIT", "dependencies": { - "fast-xml-parser": "5.4.1", + "fast-xml-parser": "^5.5.7", "piccolore": "^0.1.3", "zod": "^4.3.6" } }, - "node_modules/@astrojs/rss/node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@astrojs/sitemap": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.1.tgz", - "integrity": "sha512-IzQqdTeskaMX+QDZCzMuJIp8A8C1vgzMBp/NmHNnadepHYNHcxQdGLQZYfkbd2EbRXUfOS+UDIKx8sKg0oWVdw==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.3.tgz", + "integrity": "sha512-f8euLVsyeAmAkSm/1M2Kb8sL8byQmfgbvBNaHFItCheTj/IpiJYSEWVcqDHZ/yEHxiS7+w87mQkzwZaPHmk5GA==", "license": "MIT", "dependencies": { "sitemap": "^9.0.0", @@ -105,27 +102,16 @@ "zod": "^4.3.6" } }, - "node_modules/@astrojs/sitemap/node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@astrojs/telemetry": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", - "integrity": "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.2.tgz", + "integrity": "sha512-j8DNruA8ors99Al39RYZPJK4DC1bKkoNm93mAMuBhY9TCNC4R8n1q7ovFnJ5qhGh5Lsh7pa1gpQVpYpsJPeTHQ==", "license": "MIT", "dependencies": { - "ci-info": "^4.2.0", - "debug": "^4.4.0", - "dlv": "^1.1.3", + "ci-info": "^4.4.0", "dset": "^3.1.4", - "is-docker": "^3.0.0", - "is-wsl": "^3.1.0", + "is-docker": "^4.0.0", + "is-wsl": "^3.1.1", "which-pm-runs": "^1.1.0" }, "engines": { @@ -190,10 +176,38 @@ "node": ">=18" } }, + "node_modules/@clack/core": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.1.tgz", + "integrity": "sha512-FILJa1gGKEFTGZAJE9RpVhrjKz3c3h4ar60dSv6cGuDqufQ84YEIS3GAGvZiN+H6yaLbbvTFNejjCC4tXpZEuw==", + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.5.1.tgz", + "integrity": "sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw==", + "license": "MIT", + "dependencies": { + "@clack/core": "1.4.1", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "license": "MIT", "optional": true, "dependencies": { @@ -626,9 +640,9 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.2.tgz", + "integrity": "sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==", "cpu": [ "arm64" ], @@ -638,19 +652,19 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" + "@img/sharp-libvips-darwin-arm64": "1.3.1" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.2.tgz", + "integrity": "sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==", "cpu": [ "x64" ], @@ -660,19 +674,38 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" + "@img/sharp-libvips-darwin-x64": "1.3.1" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.2.tgz", + "integrity": "sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.2" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.1.tgz", + "integrity": "sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==", "cpu": [ "arm64" ], @@ -686,9 +719,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.1.tgz", + "integrity": "sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==", "cpu": [ "x64" ], @@ -702,12 +735,15 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.1.tgz", + "integrity": "sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==", "cpu": [ "arm" ], + "libc": [ + "glibc" + ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -718,12 +754,15 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.1.tgz", + "integrity": "sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -734,12 +773,15 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.1.tgz", + "integrity": "sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==", "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -750,12 +792,15 @@ } }, "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.1.tgz", + "integrity": "sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==", "cpu": [ "riscv64" ], + "libc": [ + "glibc" + ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -766,12 +811,15 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.1.tgz", + "integrity": "sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==", "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -782,12 +830,15 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.1.tgz", + "integrity": "sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -798,12 +849,15 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.1.tgz", + "integrity": "sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -814,12 +868,15 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.1.tgz", + "integrity": "sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -830,204 +887,244 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.2.tgz", + "integrity": "sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==", "cpu": [ "arm" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" + "@img/sharp-libvips-linux-arm": "1.3.1" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.2.tgz", + "integrity": "sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" + "@img/sharp-libvips-linux-arm64": "1.3.1" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.2.tgz", + "integrity": "sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==", "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" + "@img/sharp-libvips-linux-ppc64": "1.3.1" } }, "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.2.tgz", + "integrity": "sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==", "cpu": [ "riscv64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" + "@img/sharp-libvips-linux-riscv64": "1.3.1" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.2.tgz", + "integrity": "sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==", "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" + "@img/sharp-libvips-linux-s390x": "1.3.1" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.2.tgz", + "integrity": "sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" + "@img/sharp-libvips-linux-x64": "1.3.1" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.2.tgz", + "integrity": "sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.1" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.2.tgz", + "integrity": "sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + "@img/sharp-libvips-linuxmusl-x64": "1.3.1" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.2.tgz", + "integrity": "sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.2.tgz", + "integrity": "sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==", "cpu": [ "wasm32" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "license": "Apache-2.0", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.7.0" + "@img/sharp-wasm32": "0.35.2" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.2.tgz", + "integrity": "sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==", "cpu": [ "arm64" ], @@ -1037,16 +1134,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.2.tgz", + "integrity": "sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==", "cpu": [ "ia32" ], @@ -1056,16 +1153,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": "^20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.2.tgz", + "integrity": "sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==", "cpu": [ "x64" ], @@ -1075,7 +1172,7 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" @@ -1126,6 +1223,18 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@nodable/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, "node_modules/@oslojs/encoding": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", @@ -1133,9 +1242,9 @@ "license": "MIT" }, "node_modules/@pagefind/darwin-arm64": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.4.0.tgz", - "integrity": "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.5.2.tgz", + "integrity": "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==", "cpu": [ "arm64" ], @@ -1147,9 +1256,9 @@ ] }, "node_modules/@pagefind/darwin-x64": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.4.0.tgz", - "integrity": "sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.5.2.tgz", + "integrity": "sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw==", "cpu": [ "x64" ], @@ -1161,9 +1270,9 @@ ] }, "node_modules/@pagefind/freebsd-x64": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.4.0.tgz", - "integrity": "sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.5.2.tgz", + "integrity": "sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA==", "cpu": [ "x64" ], @@ -1175,9 +1284,9 @@ ] }, "node_modules/@pagefind/linux-arm64": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.4.0.tgz", - "integrity": "sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.5.2.tgz", + "integrity": "sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw==", "cpu": [ "arm64" ], @@ -1189,9 +1298,9 @@ ] }, "node_modules/@pagefind/linux-x64": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.4.0.tgz", - "integrity": "sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.5.2.tgz", + "integrity": "sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA==", "cpu": [ "x64" ], @@ -1202,10 +1311,24 @@ "linux" ] }, + "node_modules/@pagefind/windows-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-arm64/-/windows-arm64-1.5.2.tgz", + "integrity": "sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@pagefind/windows-x64": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.4.0.tgz", - "integrity": "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.5.2.tgz", + "integrity": "sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg==", "cpu": [ "x64" ], @@ -1217,13 +1340,13 @@ ] }, "node_modules/@playwright/test": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", - "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.0.tgz", + "integrity": "sha512-cKA5B6lpFEMyMGjxF54QihfYpB4FkEGH+qZhtArDEG+wezQAJY8Pq6C7T1SjWz+FFzt3TbyoXBQYk/0292TdJA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.58.2" + "playwright": "1.61.0" }, "bin": { "playwright": "cli.js" @@ -1586,64 +1709,97 @@ ] }, "node_modules/@shikijs/core": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz", - "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.2.0.tgz", + "integrity": "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0", + "@shikijs/primitive": "4.2.0", + "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/engine-javascript": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.23.0.tgz", - "integrity": "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.2.0.tgz", + "integrity": "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0", + "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", - "oniguruma-to-es": "^4.3.4" + "oniguruma-to-es": "^4.3.6" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/engine-oniguruma": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", - "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.2.0.tgz", + "integrity": "sha512-hTorK1dffPkpbMUk6Z+828PgRo7d07HbnizoP0hNPFjhxMHctj0Px/qoHeGMYafc6ju+u9iMldN4JbVzNQM++g==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0", + "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/langs": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", - "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.2.0.tgz", + "integrity": "sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.2.0.tgz", + "integrity": "sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0" + "@shikijs/types": "4.2.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/themes": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", - "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.2.0.tgz", + "integrity": "sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0" + "@shikijs/types": "4.2.0" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/types": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", - "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz", + "integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==", "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/vscode-textmate": { @@ -1653,47 +1809,47 @@ "license": "MIT" }, "node_modules/@tailwindcss/node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", - "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz", + "integrity": "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.19.0", - "jiti": "^2.6.1", - "lightningcss": "1.31.1", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.2.1" + "tailwindcss": "4.3.1" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", - "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.1.tgz", + "integrity": "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==", "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.1", - "@tailwindcss/oxide-darwin-arm64": "4.2.1", - "@tailwindcss/oxide-darwin-x64": "4.2.1", - "@tailwindcss/oxide-freebsd-x64": "4.2.1", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", - "@tailwindcss/oxide-linux-x64-musl": "4.2.1", - "@tailwindcss/oxide-wasm32-wasi": "4.2.1", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" + "@tailwindcss/oxide-android-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-x64": "4.3.1", + "@tailwindcss/oxide-freebsd-x64": "4.3.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-x64-musl": "4.3.1", + "@tailwindcss/oxide-wasm32-wasi": "4.3.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", - "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.1.tgz", + "integrity": "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==", "cpu": [ "arm64" ], @@ -1707,9 +1863,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", - "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.1.tgz", + "integrity": "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==", "cpu": [ "arm64" ], @@ -1723,9 +1879,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", - "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.1.tgz", + "integrity": "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==", "cpu": [ "x64" ], @@ -1739,9 +1895,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", - "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.1.tgz", + "integrity": "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==", "cpu": [ "x64" ], @@ -1755,9 +1911,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", - "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.1.tgz", + "integrity": "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==", "cpu": [ "arm" ], @@ -1771,9 +1927,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", - "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.1.tgz", + "integrity": "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==", "cpu": [ "arm64" ], @@ -1787,9 +1943,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", - "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.1.tgz", + "integrity": "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==", "cpu": [ "arm64" ], @@ -1803,9 +1959,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", - "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.1.tgz", + "integrity": "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==", "cpu": [ "x64" ], @@ -1819,9 +1975,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", - "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.1.tgz", + "integrity": "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==", "cpu": [ "x64" ], @@ -1835,9 +1991,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", - "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.1.tgz", + "integrity": "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -1852,67 +2008,127 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.8.1", - "@emnapi/runtime": "^1.8.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.1", - "@tybys/wasm-util": "^0.10.1", + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", - "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", - "cpu": [ - "arm64" - ], + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.10.0", + "inBundle": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", - "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", - "cpu": [ - "x64" - ], + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "inBundle": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@tailwindcss/vite": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", - "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { - "@tailwindcss/node": "4.2.1", - "@tailwindcss/oxide": "4.2.1", - "tailwindcss": "4.2.1" - }, + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "inBundle": true, + "license": "0BSD", + "optional": true + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.1.tgz", + "integrity": "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.1.tgz", + "integrity": "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.1.tgz", + "integrity": "sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.1", + "@tailwindcss/oxide": "4.3.1", + "tailwindcss": "4.3.1" + }, "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" + "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { "@types/ms": "*" @@ -1987,92 +2203,6 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2087,9 +2217,9 @@ } }, "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -2098,6 +2228,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/anynum": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.0.tgz", + "integrity": "sha512-xjR9/zBVnUOP6ztMIIgShjsxui80nQUQH+5xJnvrYLs+90bF25/KJqaAi8mk+B4RDtX1Nspi6fmp4YTEts8SfA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -2130,80 +2272,72 @@ } }, "node_modules/astro": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/astro/-/astro-5.18.0.tgz", - "integrity": "sha512-CHiohwJIS4L0G6/IzE1Fx3dgWqXBCXus/od0eGUfxrZJD2um2pE7ehclMmgL/fXqbU7NfE1Ze2pq34h2QaA6iQ==", + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/astro/-/astro-6.4.7.tgz", + "integrity": "sha512-5vsXx0H52u23Jpshs9tM81D03Tb3Oh2Vt2Zo0bpqjXN+njkAWjFyGjTfmWJLAcrCQd9Q+iWB1eqfhR1sZJEaUA==", "license": "MIT", "dependencies": { - "@astrojs/compiler": "^2.13.0", - "@astrojs/internal-helpers": "0.7.5", - "@astrojs/markdown-remark": "6.3.10", - "@astrojs/telemetry": "3.3.0", + "@astrojs/compiler": "^4.0.0", + "@astrojs/internal-helpers": "0.10.0", + "@astrojs/markdown-remark": "7.2.0", + "@astrojs/telemetry": "3.3.2", "@capsizecss/unpack": "^4.0.0", + "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", - "acorn": "^8.15.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", - "boxen": "8.0.1", - "ci-info": "^4.3.1", + "ci-info": "^4.4.0", "clsx": "^2.1.1", - "common-ancestor-path": "^1.0.1", + "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", - "cssesc": "^3.0.0", - "debug": "^4.4.3", - "deterministic-object-hash": "^2.0.2", - "devalue": "^5.6.2", + "devalue": "^5.8.1", "diff": "^8.0.3", - "dlv": "^1.1.3", "dset": "^3.1.4", - "es-module-lexer": "^1.7.0", + "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", - "estree-walker": "^3.0.3", "flattie": "^1.1.1", - "fontace": "~0.4.0", + "fontace": "~0.4.1", + "get-tsconfig": "5.0.0-beta.4", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", - "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", + "jsonc-parser": "^3.3.1", "magic-string": "^0.30.21", - "magicast": "^0.5.1", + "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", - "p-limit": "^6.2.0", - "p-queue": "^8.1.1", + "obug": "^2.1.1", + "p-limit": "^7.3.0", + "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", - "picomatch": "^4.0.3", - "prompts": "^2.4.2", + "picomatch": "^4.0.4", "rehype": "^13.0.2", - "semver": "^7.7.3", - "shiki": "^3.21.0", + "semver": "^7.7.4", + "shiki": "^4.0.2", "smol-toml": "^1.6.0", - "svgo": "^4.0.0", - "tinyexec": "^1.0.2", + "svgo": "^4.0.1", + "tinyclip": "^0.1.12", + "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", - "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", - "unifont": "~0.7.3", - "unist-util-visit": "^5.0.0", - "unstorage": "^1.17.4", + "unifont": "~0.7.4", + "unist-util-visit": "^5.1.0", + "unstorage": "^1.17.5", "vfile": "^6.0.3", - "vite": "^6.4.1", - "vitefu": "^1.1.1", + "vite": "^7.3.2", + "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", - "yargs-parser": "^21.1.1", - "yocto-spinner": "^0.2.3", - "zod": "^3.25.76", - "zod-to-json-schema": "^3.25.1", - "zod-to-ts": "^1.2.0" + "yargs-parser": "^22.0.0", + "zod": "^4.3.6" }, "bin": { - "astro": "astro.js" + "astro": "bin/astro.mjs" }, "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0", + "node": ">=22.12.0", "npm": ">=9.6.5", "pnpm": ">=7.1.0" }, @@ -2215,535 +2349,505 @@ "sharp": "^0.34.0" } }, - "node_modules/astro/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "node_modules/astro/node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", "cpu": [ "arm64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ - "android" + "darwin" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, - "node_modules/astro/node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "node_modules/astro/node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", "cpu": [ "x64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ - "android" + "darwin" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" } }, - "node_modules/astro/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "node_modules/astro/node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", "cpu": [ "arm64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro/node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "node_modules/astro/node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", "cpu": [ "x64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "node_modules/astro/node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", "cpu": [ - "x64" + "arm" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "freebsd" + "linux" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro/node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "node_modules/astro/node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", "cpu": [ - "arm" + "arm64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro/node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "node_modules/astro/node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", "cpu": [ - "arm64" + "ppc64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro/node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "node_modules/astro/node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", "cpu": [ - "ia32" + "riscv64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "node_modules/astro/node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", "cpu": [ - "loong64" + "s390x" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "node_modules/astro/node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", "cpu": [ - "mips64el" + "x64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "node_modules/astro/node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", "cpu": [ - "ppc64" + "arm64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "node_modules/astro/node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", "cpu": [ - "riscv64" + "x64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro/node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "node_modules/astro/node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", "cpu": [ - "s390x" + "arm" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" } }, - "node_modules/astro/node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "node_modules/astro/node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", "cpu": [ - "x64" + "arm64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" } }, - "node_modules/astro/node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "node_modules/astro/node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", "cpu": [ - "arm64" + "ppc64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ - "netbsd" + "linux" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" } }, - "node_modules/astro/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "node_modules/astro/node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", "cpu": [ - "x64" + "riscv64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ - "netbsd" + "linux" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" } }, - "node_modules/astro/node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "node_modules/astro/node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", "cpu": [ - "arm64" + "s390x" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ - "openbsd" + "linux" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" } }, - "node_modules/astro/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "node_modules/astro/node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", "cpu": [ "x64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ - "openbsd" + "linux" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" } }, - "node_modules/astro/node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "node_modules/astro/node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", "cpu": [ "arm64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ - "openharmony" + "linux" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" } }, - "node_modules/astro/node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "node_modules/astro/node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", "cpu": [ "x64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ - "sunos" + "linux" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro/node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "node_modules/astro/node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", "cpu": [ "arm64" ], - "license": "MIT", + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro/node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "node_modules/astro/node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", "cpu": [ "ia32" ], - "license": "MIT", + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro/node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "node_modules/astro/node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "cpu": [ "x64" ], - "license": "MIT", + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro/node_modules/vite/node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "node_modules/astro/node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" }, "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" } }, "node_modules/axobject-query": { @@ -2765,52 +2869,12 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/base-64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", - "license": "MIT" - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "license": "ISC" }, - "node_modules/boxen": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", - "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^8.0.0", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "string-width": "^7.2.0", - "type-fest": "^4.21.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -2821,18 +2885,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -2893,18 +2945,6 @@ "node": ">=8" } }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2934,10 +2974,13 @@ } }, "node_modules/common-ancestor-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", - "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", - "license": "ISC" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } }, "node_modules/cookie": { "version": "1.1.1", @@ -2953,9 +2996,9 @@ } }, "node_modules/cookie-es": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", - "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz", + "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", "license": "MIT" }, "node_modules/crossws": { @@ -3008,18 +3051,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/csso": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", @@ -3084,9 +3115,9 @@ } }, "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", "license": "MIT" }, "node_modules/dequal": { @@ -3113,22 +3144,10 @@ "node": ">=8" } }, - "node_modules/deterministic-object-hash": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", - "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", - "license": "MIT", - "dependencies": { - "base-64": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/devalue": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz", - "integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", "license": "MIT" }, "node_modules/devlop": { @@ -3153,12 +3172,6 @@ "node": ">=0.3.1" } }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "license": "MIT" - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -3235,20 +3248,14 @@ "node": ">=4" } }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, "node_modules/enhanced-resolve": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", - "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" @@ -3267,9 +3274,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "license": "MIT" }, "node_modules/esbuild": { @@ -3321,17 +3328,8 @@ "engines": { "node": ">=12" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eventemitter3": { @@ -3346,10 +3344,34 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, "node_modules/fast-xml-builder": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", - "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", "funding": [ { "type": "github", @@ -3358,13 +3380,14 @@ ], "license": "MIT", "dependencies": { - "path-expression-matcher": "^1.1.3" + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" } }, "node_modules/fast-xml-parser": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", - "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.9.0.tgz", + "integrity": "sha512-duBuXbyIhEeNO4GjFuVqr0nF047oNwr18aum+zJyqo0MUG/n7Afgs3Qv3D6VN3ONedUKxiuFlPiMGIa0Z11chA==", "funding": [ { "type": "github", @@ -3373,8 +3396,12 @@ ], "license": "MIT", "dependencies": { - "fast-xml-builder": "^1.0.0", - "strnum": "^2.1.2" + "@nodable/entities": "^2.2.0", + "fast-xml-builder": "^1.2.0", + "is-unsafe": "^1.0.1", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.4.0", + "xml-naming": "^0.1.0" }, "bin": { "fxparser": "src/cli/cli.js" @@ -3441,16 +3468,19 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "node_modules/get-tsconfig": { + "version": "5.0.0-beta.4", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.4.tgz", + "integrity": "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==", "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, "engines": { - "node": ">=18" + "node": ">=20.20.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, "node_modules/github-slugger": { @@ -3466,14 +3496,14 @@ "license": "ISC" }, "node_modules/h3": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz", - "integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", + "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==", "license": "MIT", "dependencies": { - "cookie-es": "^1.2.2", + "cookie-es": "^1.2.3", "crossws": "^0.3.5", - "defu": "^6.1.4", + "defu": "^6.1.6", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", @@ -3681,16 +3711,6 @@ "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "license": "BSD-2-Clause" }, - "node_modules/import-meta-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/iron-webcrypto": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", @@ -3701,29 +3721,20 @@ } }, "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-4.0.0.tgz", + "integrity": "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==", "license": "MIT", "bin": { "is-docker": "cli.js" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -3742,6 +3753,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -3754,6 +3780,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-unsafe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-1.0.1.tgz", + "integrity": "sha512-CLK2+VdgERgD96EYm5lUQssZYlRg2tkZnbsxZoacmSiRxiFJ4Nk4SzjCl+Ur+v3kXIY9dTIdb3IH22y1mZ56LA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/is-wsl": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", @@ -3770,18 +3808,28 @@ } }, "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" } }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -3790,19 +3838,16 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "license": "MIT", - "engines": { - "node": ">=6" - } + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" }, "node_modules/lightningcss": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", - "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -3815,23 +3860,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.31.1", - "lightningcss-darwin-arm64": "1.31.1", - "lightningcss-darwin-x64": "1.31.1", - "lightningcss-freebsd-x64": "1.31.1", - "lightningcss-linux-arm-gnueabihf": "1.31.1", - "lightningcss-linux-arm64-gnu": "1.31.1", - "lightningcss-linux-arm64-musl": "1.31.1", - "lightningcss-linux-x64-gnu": "1.31.1", - "lightningcss-linux-x64-musl": "1.31.1", - "lightningcss-win32-arm64-msvc": "1.31.1", - "lightningcss-win32-x64-msvc": "1.31.1" + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", - "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", "cpu": [ "arm64" ], @@ -3849,9 +3894,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", - "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -3869,9 +3914,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", - "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", "cpu": [ "x64" ], @@ -3889,9 +3934,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", - "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", "cpu": [ "x64" ], @@ -3909,9 +3954,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", - "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", "cpu": [ "arm" ], @@ -3929,9 +3974,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", - "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", "cpu": [ "arm64" ], @@ -3949,9 +3994,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", - "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", "cpu": [ "arm64" ], @@ -3969,9 +4014,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", - "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", "cpu": [ "x64" ], @@ -3989,9 +4034,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", - "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", "cpu": [ "x64" ], @@ -4009,9 +4054,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", - "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", "cpu": [ "arm64" ], @@ -4029,9 +4074,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", - "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ "x64" ], @@ -4059,9 +4104,9 @@ } }, "node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -4979,6 +5024,19 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/ofetch": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", @@ -4997,60 +5055,60 @@ "license": "MIT" }, "node_modules/oniguruma-parser": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", - "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", "license": "MIT" }, "node_modules/oniguruma-to-es": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", - "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", "license": "MIT", "dependencies": { - "oniguruma-parser": "^0.12.1", - "regex": "^6.0.1", + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "node_modules/p-limit": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", - "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz", + "integrity": "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==", "license": "MIT", "dependencies": { - "yocto-queue": "^1.1.1" + "yocto-queue": "^1.2.1" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-queue": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", - "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.0.tgz", + "integrity": "sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang==", "license": "MIT", "dependencies": { - "eventemitter3": "^5.0.1", - "p-timeout": "^6.1.2" + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-timeout": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", - "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", "license": "MIT", "engines": { - "node": ">=14.16" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5063,21 +5121,22 @@ "license": "MIT" }, "node_modules/pagefind": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.4.0.tgz", - "integrity": "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.5.2.tgz", + "integrity": "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q==", "dev": true, "license": "MIT", "bin": { "pagefind": "lib/runner/bin.cjs" }, "optionalDependencies": { - "@pagefind/darwin-arm64": "1.4.0", - "@pagefind/darwin-x64": "1.4.0", - "@pagefind/freebsd-x64": "1.4.0", - "@pagefind/linux-arm64": "1.4.0", - "@pagefind/linux-x64": "1.4.0", - "@pagefind/windows-x64": "1.4.0" + "@pagefind/darwin-arm64": "1.5.2", + "@pagefind/darwin-x64": "1.5.2", + "@pagefind/freebsd-x64": "1.5.2", + "@pagefind/linux-arm64": "1.5.2", + "@pagefind/linux-x64": "1.5.2", + "@pagefind/windows-arm64": "1.5.2", + "@pagefind/windows-x64": "1.5.2" } }, "node_modules/parse-latin": { @@ -5111,9 +5170,9 @@ } }, "node_modules/path-expression-matcher": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", - "integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", "funding": [ { "type": "github", @@ -5138,9 +5197,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -5150,13 +5209,13 @@ } }, "node_modules/playwright": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", - "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.0.tgz", + "integrity": "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.58.2" + "playwright-core": "1.61.0" }, "bin": { "playwright": "cli.js" @@ -5169,9 +5228,9 @@ } }, "node_modules/playwright-core": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", - "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.0.tgz", + "integrity": "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5233,19 +5292,6 @@ "node": ">=6" } }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -5441,6 +5487,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/retext": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", @@ -5556,9 +5611,9 @@ } }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5568,63 +5623,66 @@ } }, "node_modules/sharp": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", - "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", - "hasInstallScript": true, + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.2.tgz", + "integrity": "sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==", "license": "Apache-2.0", "dependencies": { - "@img/colour": "^1.0.0", + "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", - "semver": "^7.7.3" + "semver": "^7.8.4" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" + "@img/sharp-darwin-arm64": "0.35.2", + "@img/sharp-darwin-x64": "0.35.2", + "@img/sharp-freebsd-wasm32": "0.35.2", + "@img/sharp-libvips-darwin-arm64": "1.3.1", + "@img/sharp-libvips-darwin-x64": "1.3.1", + "@img/sharp-libvips-linux-arm": "1.3.1", + "@img/sharp-libvips-linux-arm64": "1.3.1", + "@img/sharp-libvips-linux-ppc64": "1.3.1", + "@img/sharp-libvips-linux-riscv64": "1.3.1", + "@img/sharp-libvips-linux-s390x": "1.3.1", + "@img/sharp-libvips-linux-x64": "1.3.1", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.1", + "@img/sharp-libvips-linuxmusl-x64": "1.3.1", + "@img/sharp-linux-arm": "0.35.2", + "@img/sharp-linux-arm64": "0.35.2", + "@img/sharp-linux-ppc64": "0.35.2", + "@img/sharp-linux-riscv64": "0.35.2", + "@img/sharp-linux-s390x": "0.35.2", + "@img/sharp-linux-x64": "0.35.2", + "@img/sharp-linuxmusl-arm64": "0.35.2", + "@img/sharp-linuxmusl-x64": "0.35.2", + "@img/sharp-webcontainers-wasm32": "0.35.2", + "@img/sharp-win32-arm64": "0.35.2", + "@img/sharp-win32-ia32": "0.35.2", + "@img/sharp-win32-x64": "0.35.2" } }, "node_modules/shiki": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.23.0.tgz", - "integrity": "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.2.0.tgz", + "integrity": "sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ==", "license": "MIT", "dependencies": { - "@shikijs/core": "3.23.0", - "@shikijs/engine-javascript": "3.23.0", - "@shikijs/engine-oniguruma": "3.23.0", - "@shikijs/langs": "3.23.0", - "@shikijs/themes": "3.23.0", - "@shikijs/types": "3.23.0", + "@shikijs/core": "4.2.0", + "@shikijs/engine-javascript": "4.2.0", + "@shikijs/engine-oniguruma": "4.2.0", + "@shikijs/langs": "4.2.0", + "@shikijs/themes": "4.2.0", + "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" } }, "node_modules/sisteransi": { @@ -5653,9 +5711,9 @@ } }, "node_modules/smol-toml": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz", - "integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", + "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", "license": "BSD-3-Clause", "engines": { "node": ">= 18" @@ -5689,23 +5747,6 @@ "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", "license": "MIT" }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -5720,32 +5761,20 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/strnum": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.1.tgz", - "integrity": "sha512-BwRvNd5/QoAtyW1na1y1LsJGQNvRlkde6Q/ipqqEaivoMdV+B1OMOTVdwR+N/cwVUcIt9PYyHmV8HyexCZSupg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.0.tgz", + "integrity": "sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "anynum": "^1.0.0" + } }, "node_modules/svgo": { "version": "4.0.1", @@ -5773,15 +5802,15 @@ } }, "node_modules/tailwindcss": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", - "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.1.tgz", + "integrity": "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==", "license": "MIT" }, "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "license": "MIT", "engines": { "node": ">=6" @@ -5797,10 +5826,19 @@ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", "license": "MIT" }, + "node_modules/tinyclip": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.14.tgz", + "integrity": "sha512-F1oWdz8tjT17qe1d5JgDK6z03WGOhYYAN0lK3/D/fzNiy93xswLLEw7pk+3g05onhAy6Bsc6PLNUGhdgVjemMQ==", + "license": "MIT", + "engines": { + "node": "^16.14.0 || >= 17.3.0" + } + }, "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", "license": "MIT", "engines": { "node": ">=18" @@ -5842,26 +5880,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/tsconfck": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", - "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", - "license": "MIT", - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^18 || >=20" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -5869,32 +5887,6 @@ "license": "0BSD", "optional": true }, - "node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/ufo": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", @@ -6073,16 +6065,16 @@ } }, "node_modules/unstorage": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.4.tgz", - "integrity": "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==", + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", + "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==", "license": "MIT", "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", - "h3": "^1.15.5", - "lru-cache": "^11.2.0", + "h3": "^1.15.10", + "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" @@ -6211,11 +6203,10 @@ } }, "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", + "integrity": "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -6323,36 +6314,19 @@ "node": ">=4" } }, - "node_modules/widest-line": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", - "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=16.0.0" } }, "node_modules/xxhash-wasm": { @@ -6362,12 +6336,12 @@ "license": "MIT" }, "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "license": "ISC", "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yocto-queue": { @@ -6382,60 +6356,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yocto-spinner": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz", - "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==", - "license": "MIT", - "dependencies": { - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18.19" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25 || ^4" - } - }, - "node_modules/zod-to-ts": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", - "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", - "peerDependencies": { - "typescript": "^4.9.4 || ^5.0.2", - "zod": "^3" - } - }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/docs/package.json b/docs/package.json index 488a4890d..55f893b7e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -13,16 +13,16 @@ "test:e2e": "npx playwright test" }, "dependencies": { - "@astrojs/rss": "^4.0.17", - "@astrojs/sitemap": "^3.7.1", - "@tailwindcss/vite": "^4.1.0", - "astro": "^5.7.0", - "sharp": "^0.34.5", + "@astrojs/rss": "^4.0.18", + "@astrojs/sitemap": "^3.7.3", + "@tailwindcss/vite": "^4.3.1", + "astro": "^6.4.7", + "sharp": "^0.35.2", "tailwindcss": "^4.1.0", "unist-util-visit": "^5.1.0" }, "devDependencies": { - "@playwright/test": "^1.58.2", - "pagefind": "^1.4.0" + "@playwright/test": "^1.61.0", + "pagefind": "^1.5.2" } } diff --git a/docs/pagefind.yml b/docs/pagefind.yml index 282352e74..e964cc495 100644 --- a/docs/pagefind.yml +++ b/docs/pagefind.yml @@ -1,12 +1,12 @@ -# Pagefind configuration for Squad docs -# See: https://pagefind.app/docs/config-options/ - -# Exclude navigation, footers, and code blocks from indexing -exclude_selectors: - - "nav" - - "footer" - - "aside" - - "pre" - - ".astro-code" - - +# Pagefind configuration for Squad docs +# See: https://pagefind.app/docs/config-options/ + +# Exclude navigation, footers, and code blocks from indexing +exclude_selectors: + - "nav" + - "footer" + - "aside" + - "pre" + - ".astro-code" + + diff --git a/docs/src/content/config.ts b/docs/src/content.config.ts similarity index 100% rename from docs/src/content/config.ts rename to docs/src/content.config.ts diff --git a/docs/src/content/blog/001b-meet-the-squad.md b/docs/src/content/blog/001b-meet-the-squad.md index bc8d2b07f..57fe72d62 100644 --- a/docs/src/content/blog/001b-meet-the-squad.md +++ b/docs/src/content/blog/001b-meet-the-squad.md @@ -69,8 +69,8 @@ You won't hear from Scribe. Ever. Scribe works in the background — logging ses | Active agents | 8 | | Silent agents | 1 | | Total roster | 9 | -| Original hires (day one) | 5 | -| Hired in session 2+ | 3 | +| Original cast (day one) | 5 | +| Cast in session 2+ | 3 | | Newest member | Redfoot | ## What's Next diff --git a/docs/src/content/blog/014-wave-1-otel-and-aspire.md b/docs/src/content/blog/014-wave-1-otel-and-aspire.md index 7a447380b..0eaecea3a 100644 --- a/docs/src/content/blog/014-wave-1-otel-and-aspire.md +++ b/docs/src/content/blog/014-wave-1-otel-and-aspire.md @@ -47,7 +47,7 @@ Every file mutation by every agent becomes a traceable event. In the Aspire dash ## Aspire Dashboard Integration -The `squad aspire` command (#265) wires Squad's OTLP exporter to a .NET Aspire dashboard. Aspire gives you: +The `squad aspire` command (#265) wires Squad's OTLP exporter to a Aspire dashboard. Aspire gives you: - **Trace waterfall** — See agent spawns, tool calls, and file writes as a timeline - **Metrics** — Agent spawn counts, tool call durations, event bus throughput diff --git a/docs/src/content/blog/022-welcome-to-the-new-squad.md b/docs/src/content/blog/022-welcome-to-the-new-squad.md index a4b98af07..2fcbb1029 100644 --- a/docs/src/content/blog/022-welcome-to-the-new-squad.md +++ b/docs/src/content/blog/022-welcome-to-the-new-squad.md @@ -671,7 +671,7 @@ Squad exports traces and metrics via OpenTelemetry. Three-layer API: - Cost accumulation in real time **Export to:** -- .NET Aspire Dashboard (`squad aspire`) +- Aspire Dashboard (`squad aspire`) - Jaeger - Zipkin - Any OTLP-compatible backend diff --git a/docs/src/content/docs/features/consult-mode.md b/docs/src/content/docs/features/consult-mode.md index caaf2edf5..4c0b8e3af 100644 --- a/docs/src/content/docs/features/consult-mode.md +++ b/docs/src/content/docs/features/consult-mode.md @@ -9,7 +9,7 @@ Consult mode lets you bring your personal squad to projects you don't own — OS ## The Problem -You have a personal squad at your global path (e.g., `~/.config/squad/.squad` on Linux) with agents, skills, and decisions refined over time. When you contribute to someone else's project, you face a dilemma: +You have a personal squad at your global path (e.g., `~/Library/Application Support/squad/personal-squad` on macOS, `~/.config/squad/personal-squad` on Linux) with agents, skills, and decisions refined over time. When you contribute to someone else's project, you face a dilemma: - **Pollute the project?** Running `squad init` creates a `.squad/` folder they didn't ask for - **Pollute your squad?** Project-specific knowledge bleeds into your global squad diff --git a/docs/src/content/docs/features/copilot-coding-agent.md b/docs/src/content/docs/features/copilot-coding-agent.md index f9b10bba8..095819c68 100644 --- a/docs/src/content/docs/features/copilot-coding-agent.md +++ b/docs/src/content/docs/features/copilot-coding-agent.md @@ -46,7 +46,7 @@ gh issue edit --add-label "squad:copilot" Say something like: - **"I want to add copilot to the squad"** -- **"hire copilot to the squad"** +- **"cast copilot to the squad"** - **"add team member copilot"** The coordinator will add @copilot to the roster and ask about auto-assign. diff --git a/docs/src/content/docs/features/mcp.md b/docs/src/content/docs/features/mcp.md index 597134e6b..55a7c14f9 100644 --- a/docs/src/content/docs/features/mcp.md +++ b/docs/src/content/docs/features/mcp.md @@ -269,7 +269,7 @@ Agents will now automatically propose Trello tasks for tracking work items. ## Example: Aspire Dashboard MCP (Deployment Monitoring) -For .NET Aspire projects, configure the Aspire Dashboard MCP for deployment monitoring: +For Aspire projects, configure the Aspire Dashboard MCP for deployment monitoring: **Config template:** ```json diff --git a/docs/src/content/docs/guide/build-autonomous-agent.md b/docs/src/content/docs/guide/build-autonomous-agent.md index 66e0ac0c4..12832df78 100644 --- a/docs/src/content/docs/guide/build-autonomous-agent.md +++ b/docs/src/content/docs/guide/build-autonomous-agent.md @@ -375,7 +375,7 @@ import { recordTokenUsage, } from '@bradygaster/squad-sdk'; -// Initialize OTel (connects to .NET Aspire dashboard if endpoint is set) +// Initialize OTel (connects to Aspire dashboard if endpoint is set) const otelEndpoint = process.env['OTEL_EXPORTER_OTLP_ENDPOINT']; if (otelEndpoint) { initSquadTelemetry({ @@ -413,7 +413,7 @@ const summary = costTracker.getSummary(); console.log(`Total: $${summary.totalEstimatedCost.toFixed(4)}`); ``` -To view traces and metrics in the .NET Aspire dashboard, see the [Aspire dashboard scenario](/scenarios/aspire-dashboard/). +To view traces and metrics in the Aspire dashboard, see the [Aspire dashboard scenario](/scenarios/aspire-dashboard/). --- diff --git a/docs/src/content/docs/guide/personal-squad.md b/docs/src/content/docs/guide/personal-squad.md index 5f71faf3a..08479686b 100644 --- a/docs/src/content/docs/guide/personal-squad.md +++ b/docs/src/content/docs/guide/personal-squad.md @@ -124,6 +124,14 @@ This is your **team identity**. It contains: This is the stuff that makes your agents *yours*. It persists across sessions. It grows as you work. It follows you from project to project. +### The global `squad.agent.md` — a reference, not a live file + +You'll also see `.github/agents/squad.agent.md` inside your global config directory. This file is **not** discoverable by GitHub Copilot — Copilot only reads agent files inside a git repository's `.github/agents/` folder. + +So why is it there? It serves as a **local reference** of the Squad agent instructions that were applied to your projects. The actual source for `squad init` is the packaged template bundled with the CLI (`squad.agent.md.template`), so the global copy is not used as input for future inits. It exists so you can see what Squad generated without opening a project. + +If you want Copilot to use Squad's agent instructions in a project, run `squad init` in that project's root — that creates the "real" `.github/agents/squad.agent.md` inside the git repo where Copilot can discover it. + ### The project pointer: `.squad/config.json` Inside each connected project, `.squad/config.json` looks like this: diff --git a/docs/src/content/docs/guide/sample-prompts.md b/docs/src/content/docs/guide/sample-prompts.md index 7d546a321..bbf02fe5c 100644 --- a/docs/src/content/docs/guide/sample-prompts.md +++ b/docs/src/content/docs/guide/sample-prompts.md @@ -65,10 +65,10 @@ Set up the team and start with the backend data layer. --- -### .NET Aspire Cloud-Native App +### Aspire Cloud-Native App ``` -Build a cloud-native distributed app with .NET Aspire. I want: +Build a cloud-native distributed app with Aspire. I want: - An AppHost that orchestrates all services - A Blazor frontend with interactive server components - A minimal API backend with OpenAPI endpoints diff --git a/docs/src/content/docs/sample-prompts.md b/docs/src/content/docs/sample-prompts.md index b3a73fee5..2112eda8c 100644 --- a/docs/src/content/docs/sample-prompts.md +++ b/docs/src/content/docs/sample-prompts.md @@ -212,10 +212,10 @@ Read SDK docs carefully. One agent owns SDK integration/streaming, another owns --- -### 9. .NET Aspire Cloud-Native App +### 9. Aspire Cloud-Native App ``` -Build a cloud-native app with .NET Aspire (read https://learn.microsoft.com/en-us/dotnet/aspire/): +Build a cloud-native app with Aspire (read https://aspire.dev/): - AppHost orchestrating all services - Blazor Server dashboard: current conditions + 5-day forecast for saved cities - Weather API service: wraps OpenWeatherMap with Redis caching diff --git a/docs/src/layouts/BaseLayout.astro b/docs/src/layouts/BaseLayout.astro index 26de6816d..8cc598de7 100644 --- a/docs/src/layouts/BaseLayout.astro +++ b/docs/src/layouts/BaseLayout.astro @@ -4,7 +4,7 @@ interface Props { description?: string; } -import { ViewTransitions } from 'astro:transitions'; +import { ClientRouter } from 'astro:transitions'; import squadLogo from '../assets/images/squad-logo.png'; const { title, description = 'Squad — Your AI Development Team. Describe what you\'re building. Get a team of specialists that live in your repo.' } = Astro.props; const fullTitle = `${title} — Squad Docs`; @@ -35,7 +35,7 @@ const ogImage = Astro.site ? new URL(squadLogo.src, Astro.site).href : squadLogo - +