diff --git a/.changeset/init-next-steps.md b/.changeset/init-next-steps.md new file mode 100644 index 000000000..c736fe74f --- /dev/null +++ b/.changeset/init-next-steps.md @@ -0,0 +1,5 @@ +--- +"@bradygaster/squad-cli": patch +--- + +`squad init` now tells you to run the host you chose (#2453). `squad init --client claude` ends with `claude --agent squad --mcp-config .mcp.json` and a matching `claude -p` tip, instead of telling Claude Code users to run Copilot. With several hosts, it gives one line per host. diff --git a/.changeset/portable-launch.md b/.changeset/portable-launch.md new file mode 100644 index 000000000..0e5d6fe83 --- /dev/null +++ b/.changeset/portable-launch.md @@ -0,0 +1,6 @@ +--- +"@bradygaster/squad-sdk": patch +"@bradygaster/squad-cli": patch +--- + +Committed files no longer contain paths from your machine (#2462). When the Squad CLI is installed, `squad init` and `squad upgrade` used to write `node /your/path/cli-entry.js state-mcp` into `.mcp.json`, the other hosts' MCP configs and every Claude Code hook in `.claude/settings.json`, so they failed on any other clone. They now write `node .squad/squad.mjs state-mcp`, a launcher Squad commits that finds a Squad CLI on each machine (`SQUAD_CLI`, the project's `node_modules`, then `squad` on PATH; `npx` only with `SQUAD_ALLOW_NPX=1`). Claude hooks reach it through `$CLAUDE_PROJECT_DIR`, and standalone bundle installs write `squad state-mcp`. Run `squad upgrade` once to fix an existing repository. `squad doctor` has a new check that flags machine-specific launch commands and a missing launcher. diff --git a/CHANGELOG.md b/CHANGELOG.md index 80e402e4b..c335fc66c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,7 +64,15 @@ Claude Code is the first host after GitHub Copilot where Squad uses the host's o - The Claude coordinator names Claude Code's `Agent` subagent tool (formerly `Task`). The Claude Code spec, docs and host feature matrix now match the code, and host-projection regression guards keep them that way (#2440, #2445). - Every generated Claude Markdown file states its projection version. Files from earlier projections are still recognised as Squad's and upgraded in place, and rolling back the `CLAUDE.md`, `AGENTS.md` and `GEMINI.md` blocks restores the original bytes exactly (#2450). -- Copilot output, `.github/agents/squad.agent.md` and the projections for other hosts are unchanged. +- Copilot output, `.github/agents/squad.agent.md` and the projections for other hosts are unchanged, apart from the portable `squad_state` launch command below, which every host gets. + +#### Fixed + +- **Committed files no longer contain paths from your machine** (#2462). With the Squad CLI installed, `squad init` and `squad upgrade` used to write `node /your/path/cli-entry.js state-mcp` into `.mcp.json`, the other hosts' MCP configs and every Claude Code hook, so a clone on any other machine failed. They now write `node .squad/squad.mjs state-mcp`. + - `.squad/squad.mjs` is a launcher Squad commits. It finds a Squad CLI on each machine: `SQUAD_CLI`, the project's `node_modules`, then `squad` on PATH. It skips a CLI older than the one that wrote it, and uses `npx` only with `SQUAD_ALLOW_NPX=1`. It shares its code with the plugin's launcher. + - Claude hooks reach the launcher through `$CLAUDE_PROJECT_DIR`. Standalone bundle installs write `squad state-mcp`. A CLI run through `npx` keeps the exact-version `npx` command. + - `squad doctor` flags machine-specific launch commands and a missing launcher. Run `squad upgrade` once to fix an existing repository. +- `squad init --client claude` tells you to run `claude --agent squad --mcp-config .mcp.json`, not Copilot. With several hosts it gives one line per host (#2453). ## [2.0.0-preview.4] - 2026-11-09 diff --git a/docs/src/content/docs/features/mcp.md b/docs/src/content/docs/features/mcp.md index 624850367..aaa3cd2b1 100644 --- a/docs/src/content/docs/features/mcp.md +++ b/docs/src/content/docs/features/mcp.md @@ -31,6 +31,8 @@ Where you add a server depends on your host: Copilot CLI doesn't read a `.copilot/mcp-config.json` inside the project (`squad init` writes a sample one there), so a server added to it never loads. Hosts added with `squad init --client` have their own MCP config (Claude Code shares `.mcp.json`), and Squad adds `squad_state` to it for you. +The `squad_state` entry Squad writes works on every clone of the repository. If you installed the Squad CLI (globally, in the project, or from a source build), the entry runs `node .squad/squad.mjs state-mcp`. That small launcher, which Squad writes and you commit, finds a Squad CLI on whichever machine runs it: `SQUAD_CLI` if set, then the project's `node_modules`, then `squad` on PATH. It skips a CLI older than the one that wrote it. If none is found, it prints how to install one; set `SQUAD_ALLOW_NPX=1` to let it run the exact version with `npx` instead. Claude Code hooks use the same launcher. A standalone bundle install writes `squad state-mcp`, and a CLI run through `npx` writes the exact-version `npx` command. `squad doctor` warns when a committed entry points at a path on one machine. + This guide covers Copilot CLI and VS Code. Pick the one that matches your workflow. --- diff --git a/docs/src/content/docs/scenarios/troubleshooting.md b/docs/src/content/docs/scenarios/troubleshooting.md index e98d521eb..6b625c227 100644 --- a/docs/src/content/docs/scenarios/troubleshooting.md +++ b/docs/src/content/docs/scenarios/troubleshooting.md @@ -154,6 +154,16 @@ declarations, which multiplied the process count. --- +## `squad_state` or Claude hooks fail after cloning + +**Problem:** On a fresh clone or another machine, the `squad_state` MCP server doesn't start, or every Claude Code hook reports an error. `squad doctor` shows **portable squad_state launch** failing. + +**Cause:** The repository was set up with a Squad version that wrote absolute paths from one machine into `.mcp.json` and `.claude/settings.json`. + +**Fix:** run `squad upgrade` once and commit the result. It replaces those paths with `node .squad/squad.mjs`, a launcher that finds the Squad CLI on each machine. If the launcher says no Squad CLI was found, install one (`npm install -g @bradygaster/squad-cli`) or set `SQUAD_ALLOW_NPX=1`. + +--- + ## Upgrade doesn't change anything **Problem:** Running `squad upgrade` completes but nothing changes. diff --git a/docs/src/content/docs/scenarios/upgrading.md b/docs/src/content/docs/scenarios/upgrading.md index 6f44c22db..697fe54f0 100644 --- a/docs/src/content/docs/scenarios/upgrading.md +++ b/docs/src/content/docs/scenarios/upgrading.md @@ -183,12 +183,12 @@ Every row below is a pinned, published artifact of an older Squad release driven ### Runs -All recorded runs executed against **one tree**, commit `9da01a2e41ba15794d31b0b8251ddaa3abd34ba8`, with harness hash `bca27c39dc2dcbff…` (sha256 over the suite, renderer, workflow, fs hook and this fixture minus `runs`; the integrity test refuses a recorded run whose harness differs from the committed one, so folding results may change nothing but the runs data). +All recorded runs executed against **one tree**, commit `9b254ba858abc19ef62e513993d9802312bfe34c`, with harness hash `a9713d320fdb479c…` (sha256 over the suite, renderer, workflow, fs hook and this fixture minus `runs`; the integrity test refuses a recorded run whose harness differs from the committed one, so folding results may change nothing but the runs data). | Platform | Arch | Node | Target | Tree (targetSha) | Trigger | Recorded | Outcome | |----------|------|------|--------|------------------|---------|----------|---------| -| Linux (`linux`) | x64 | v22.23.3 | v1.2.0-preview.1 | `9da01a2e41ba` | [workflow_dispatch](https://github.com/bradygaster/squad/actions/runs/39903943542) | 2026-10-23 | 35 rows passed all non-xfail assertions; 4 of them carry a known expected failure (F3: Squad 0.9.1 squad upgrade --dry-run is not dry); 0 rows failed; 7 rows are structurally inapplicable (listed below). | -| Windows (`win32`) | x64 | v22.23.4 | v1.2.0-preview.1 | `9da01a2e41ba` | [workflow_dispatch](https://github.com/bradygaster/squad/actions/runs/39903943542) | 2026-10-23 | 35 rows passed all non-xfail assertions; 4 of them carry a known expected failure (F3: Squad 0.9.1 squad upgrade --dry-run is not dry); 0 rows failed; 7 rows are structurally inapplicable (listed below). | +| Linux (`linux`) | x64 | v22.23.4 | v2.0.0-preview.4 | `9b254ba858ab` | [pull_request](https://github.com/github/squad/actions/runs/43147851248) | 2026-11-12 | 35 rows passed all non-xfail assertions; 4 of them carry a known expected failure (F3: Squad 0.9.1 squad upgrade --dry-run is not dry); 0 rows failed; 7 rows are structurally inapplicable (listed below). | +| Windows (`win32`) | x64 | v22.23.4 | v2.0.0-preview.4 | `9b254ba858ab` | [pull_request](https://github.com/github/squad/actions/runs/43147851248) | 2026-11-12 | 35 rows passed all non-xfail assertions; 4 of them carry a known expected failure (F3: Squad 0.9.1 squad upgrade --dry-run is not dry); 0 rows failed; 7 rows are structurally inapplicable (listed below). | ### Producers (pinned, immutable provenance) @@ -197,8 +197,8 @@ All recorded runs executed against **one tree**, commit `9da01a2e41ba15794d31b0b | **0.9.1** | npm | `@bradygaster/squad-cli@0.9.1`
sha1 `a5ee3dbfc44b7457eba56476f708ad6fdc2e09ca`
`sha512-TcZqRbJk3XBg4N8Y5/5OWJeA52ARAMuzk4MGtB0bfdr87e7iJRV6lJEw8+OIYz9IQ+1JxN/Q30dZDALMOcdi2w==`
`@bradygaster/squad-sdk@0.9.1`
sha1 `4735aba9f125de415160cd979ea3f1467903f16e`
`sha512-1KqZ2N6Lt/B4uYlU/Su9JjA1s/7ng0OAEb9CZrkRk19YSPi6tg7K5QWdTKl5gY/gp8zPAJqfjVffOwrZwGlzmw==` | Oldest 0.x line that still installs on Node 22 (engines >=22.5); pinned with its own @bradygaster/squad-sdk@0.9.1 (its `>=0.9.0` range would otherwise float to 1.0.1). Scaffolds `.squad/` with an EMPTY `casting/` directory and skills under `.copilot/skills/`; predates --state-backend. | no | | **0.13.1** | npm | `@bradygaster/squad-cli@0.13.1`
sha1 `c8f6427bab4cae989934574315eabda98495fb37`
`sha512-ogfx2nhyx6fqMyqlaI5tDnOzUbRdh+a6Uh/rMQ0aNJwAXnvQp4N37Xp/eOTyB5CxcB7E34NBtAfCxyXbWSiS8w==`
`@bradygaster/squad-sdk@0.13.1`
sha1 `90e98450971e921ca2072673b325fcc8126a2fb6`
`sha512-1Jt6c9kahJq1JCcE77j/50D79YcXVG6Rt1genuLJ3O9J2SKTZKemlbvHgftkc5LgGH22DkWqHh5zLNEQ/p+ukA==` | Last 0.x stable; first line with --state-backend (local\|orphan\|two-layer) and user-profile squads (writes teamRoot when a profile squad exists). | yes | | **1.0.0** | npm | `@bradygaster/squad-cli@1.0.0`
sha1 `e4e776ef938cbe8c65d8d2c729f2249a48bca638`
`sha512-mnZXnA+GAtp4KSLf7a4Zw0BpeOaHFAxYV5oxAy58ancZ9C2LguUGbcTxWIG0djM96XWET8K9fcncif3Vm6Z9RQ==`
`@bradygaster/squad-sdk@1.0.0`
sha1 `4abb8e450e79827b865a7846af6589789d079cab`
`sha512-9f4NthJaQDq6ifXU3f6VfsZz2bbWs2u57fHhXKtDgDMSvZ3Xq8FPjMvddxhRvseWqaSlkYcWsag3M/lzTtKaJA==` | First 1.x stable; last version published to npm (dist-tag latest). | yes | -| **1.0.1** | GitHub Release | [bradygaster/squad v1.0.1](https://github.com/bradygaster/squad/releases/tag/v1.0.1) — assets by immutable id:
`squad-darwin-arm64.tar.gz` id `610330560` sha256 `47a8d44d0a9a338ae2c2b6192cb761187ebd71edbd1171a4800cee11cb001b43`
`squad-darwin-x64.tar.gz` id `610330564` sha256 `b88db7e8949878ba2cdf756758651cd50a0e9b5fc83f4c759bd7438d596b4951`
`squad-linux-arm64.tar.gz` id `610330558` sha256 `9d8105c903eeac4d1762e43432f54827a6c4676f4aa42cfdf34bf645e497e756`
`squad-linux-x64.tar.gz` id `610330551` sha256 `283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7`
`squad-win32-arm64.zip` id `610330674` sha256 `586c4e2d3d708f4d3f08b2c8ffb965d9312d6586d0830a1b8a817b27c3c14bd0`
`squad-win32-x64.zip` id `610330559` sha256 `f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe` | Patch release whose CLI never reached npm (release run 37222633918: SDK publish failed, CLI publish skipped); only the standalone bundles exist. | yes | -| **1.1.0** | GitHub Release | [bradygaster/squad v1.1.0](https://github.com/bradygaster/squad/releases/tag/v1.1.0) — assets by immutable id:
`squad-darwin-arm64.tar.gz` id `659607638` sha256 `057c8d2296e1c95cd77933bae55475409a2dce88aa5b3443cc6ca6cfcd545631`
`squad-darwin-x64.tar.gz` id `659607644` sha256 `4e4e8574f6a339f5537c82eb586d66ecf2ee3005e86b058b834b965be33a43cd`
`squad-linux-arm64.tar.gz` id `659607639` sha256 `992e9dbfe677e3c772446960051329aaed1578d5e4dbcb3350827dbb755e8b00`
`squad-linux-x64.tar.gz` id `659607641` sha256 `2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff`
`squad-win32-arm64.zip` id `659607808` sha256 `3c922c37863b255546403cffb2aa1c7dff2a347be87ca582759c149aec7bb1da`
`squad-win32-x64.zip` id `659607643` sha256 `571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57` | Latest 1.x release; npm CLI publish failed in release run 39442663504 ("Could not verify @bradygaster/squad-cli@1.1.0 on npm dist-tag 'latest' after 20 attempts"), so the standalone bundle is the only published artifact. | yes | +| **1.0.1** | GitHub Release | [github/squad v1.0.1](https://github.com/github/squad/releases/tag/v1.0.1) — assets by immutable id:
`squad-darwin-arm64.tar.gz` id `610330560` sha256 `47a8d44d0a9a338ae2c2b6192cb761187ebd71edbd1171a4800cee11cb001b43`
`squad-darwin-x64.tar.gz` id `610330564` sha256 `b88db7e8949878ba2cdf756758651cd50a0e9b5fc83f4c759bd7438d596b4951`
`squad-linux-arm64.tar.gz` id `610330558` sha256 `9d8105c903eeac4d1762e43432f54827a6c4676f4aa42cfdf34bf645e497e756`
`squad-linux-x64.tar.gz` id `610330551` sha256 `283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7`
`squad-win32-arm64.zip` id `610330674` sha256 `586c4e2d3d708f4d3f08b2c8ffb965d9312d6586d0830a1b8a817b27c3c14bd0`
`squad-win32-x64.zip` id `610330559` sha256 `f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe` | Patch release whose CLI never reached npm (release run 37222633918: SDK publish failed, CLI publish skipped); only the standalone bundles exist. | yes | +| **1.1.0** | GitHub Release | [github/squad v1.1.0](https://github.com/github/squad/releases/tag/v1.1.0) — assets by immutable id:
`squad-darwin-arm64.tar.gz` id `659607638` sha256 `057c8d2296e1c95cd77933bae55475409a2dce88aa5b3443cc6ca6cfcd545631`
`squad-darwin-x64.tar.gz` id `659607644` sha256 `4e4e8574f6a339f5537c82eb586d66ecf2ee3005e86b058b834b965be33a43cd`
`squad-linux-arm64.tar.gz` id `659607639` sha256 `992e9dbfe677e3c772446960051329aaed1578d5e4dbcb3350827dbb755e8b00`
`squad-linux-x64.tar.gz` id `659607641` sha256 `2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff`
`squad-win32-arm64.zip` id `659607808` sha256 `3c922c37863b255546403cffb2aa1c7dff2a347be87ca582759c149aec7bb1da`
`squad-win32-x64.zip` id `659607643` sha256 `571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57` | Latest 1.x release; npm CLI publish failed in release run 39442663504 ("Could not verify @bradygaster/squad-cli@1.1.0 on npm dist-tag 'latest' after 20 attempts"), so the standalone bundle is the only published artifact. | yes | | **HEAD** | local HEAD (pack) | npm pack of packages/squad-sdk and packages/squad-cli from the tree under test (targetSha) | Same-version idempotency: the CLI packed from this tree scaffolds and then upgrades itself. | yes | npm tarballs are fetched by exact version through the registry npm is configured for and verified against the recorded sha1 + sha512 before use; GitHub Release bundles are downloaded by immutable asset id and verified against the recorded sha256. A verification mismatch **fails** the producer's rows (it is never a skip); only an unreachable registry/release yields a skip, and that skip carries the reason. @@ -314,6 +314,7 @@ Paths a plain `squad upgrade` may create or rewrite (the only changes `user-stat - `^\.copilot/skills/` - `^\.github/workflows/(squad-[\w-]+|sync-squad-labels)\.yml(\.local-backup)?$` - `^\.squad/\.first-run$` +- `^\.squad/squad\.mjs$` - `^\.squad/config\.json$` - `^\.squad/casting/` - `^\.squad/templates/` @@ -394,12 +395,12 @@ Recorded by the `downgrade-probe` check (dry-run on a copy; the only assertion i | Platform | Producer | Shapes | Observed | |----------|----------|--------|----------| -| `linux` | 0.9.1 | all 4 shapes | exit 0, last lines "✓ refreshed .squad/templates/ \| Upgrade complete: v1.2.0-preview.1 → v0.9.1 \| Preserves user state: team.md, decisions/, agents/*/history.md"; MODIFIED 45 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …) | +| `linux` | 0.9.1 | all 4 shapes | exit 0, last lines "✓ refreshed .squad/templates/ \| Upgrade complete: v2.0.0-preview.4 → v0.9.1 \| Preserves user state: team.md, decisions/, agents/*/history.md"; MODIFIED 68 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …) | | `linux` | 0.13.1 | all 6 shapes | exit 0, last lines "overwrite ../.github/workflows/squad-triage.yml \| overwrite ../.github/workflows/sync-squad-labels.yml \| Run without --dry-run to apply."; modified nothing | | `linux` | 1.0.0 | all 6 shapes | exit 0, last lines "overwrite ../.github/workflows/squad-triage.yml \| overwrite ../.github/workflows/sync-squad-labels.yml \| Run without --dry-run to apply."; modified nothing | | `linux` | 1.0.1 | all 6 shapes | exit 0, last lines "overwrite ../.github/workflows/squad-triage.yml \| overwrite ../.github/workflows/sync-squad-labels.yml \| Run without --dry-run to apply."; modified nothing | | `linux` | 1.1.0 | all 6 shapes | exit 0, last lines "overwrite ../.github/workflows/squad-triage.yml \| overwrite ../.github/workflows/sync-squad-labels.yml \| Run without --dry-run to apply."; modified nothing | -| `win32` | 0.9.1 | all 4 shapes | exit 0, last lines "✓ refreshed .squad/templates/ \| Upgrade complete: v1.2.0-preview.1 → v0.9.1 \| Preserves user state: team.md, decisions/, agents/*/history.md"; MODIFIED 44 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …) | +| `win32` | 0.9.1 | all 4 shapes | exit 0, last lines "✓ refreshed .squad/templates/ \| Upgrade complete: v2.0.0-preview.4 → v0.9.1 \| Preserves user state: team.md, decisions/, agents/*/history.md"; MODIFIED 67 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …) | | `win32` | 0.13.1 | all 6 shapes | exit 0, last lines "overwrite ../.github/workflows/squad-triage.yml \| overwrite ../.github/workflows/sync-squad-labels.yml \| Run without --dry-run to apply."; modified nothing | | `win32` | 1.0.0 | all 6 shapes | exit 0, last lines "overwrite ../.github/workflows/squad-triage.yml \| overwrite ../.github/workflows/sync-squad-labels.yml \| Run without --dry-run to apply."; modified nothing | | `win32` | 1.0.1 | all 6 shapes | exit 0, last lines "overwrite ../.github/workflows/squad-triage.yml \| overwrite ../.github/workflows/sync-squad-labels.yml \| Run without --dry-run to apply."; modified nothing | diff --git a/packages/squad-cli/src/cli/commands/client.ts b/packages/squad-cli/src/cli/commands/client.ts index 0a4ec240e..d90a9cc3a 100644 --- a/packages/squad-cli/src/cli/commands/client.ts +++ b/packages/squad-cli/src/cli/commands/client.ts @@ -21,7 +21,7 @@ import { } from '@bradygaster/squad-sdk/client'; import { readClientFlag, readConfiguredClients, readUnknownConfiguredClients, syncClients, reportClientResults, parseClients } from '../core/clients.js'; import { detectSquadDir } from '../core/detect-squad-dir.js'; -import { projectSquadStateMcpSpec, resolveSquadStateMcpSpec } from '../core/mcp-spec.js'; +import { projectSquadStateMcpSpec, resolveCommittedSquadStateMcpSpec } from '../core/mcp-spec.js'; import { getPackageVersion } from '../core/version.js'; import { BOLD, DIM, GREEN, RESET, YELLOW } from '../core/output.js'; @@ -55,7 +55,9 @@ export async function listClients(dest: string): Promise { const unknown = readUnknownConfiguredClients(squadDir); const effective: ClientId[] = configured.length > 0 ? configured : ['copilot']; - const spec = projectSquadStateMcpSpec(await resolveSquadStateMcpSpec(getPackageVersion())); + const spec = projectSquadStateMcpSpec( + await resolveCommittedSquadStateMcpSpec(getPackageVersion(), dest, squadDir, { write: false }), + ); const ctx = { root: dest, mcp: { command: spec.command, args: spec.args }, diff --git a/packages/squad-cli/src/cli/commands/doctor.ts b/packages/squad-cli/src/cli/commands/doctor.ts index 6891ad06a..ba05db2de 100644 --- a/packages/squad-cli/src/cli/commands/doctor.ts +++ b/packages/squad-cli/src/cli/commands/doctor.ts @@ -716,6 +716,82 @@ export async function checkSquadStateBridge(cwd: string): Promise { } } +// ── portable launch commands (#2462) ───────────────────────────────── + +/** Squad's launch commands in files the repository commits: `.mcp.json` and Claude hooks. */ +function committedSquadLaunches(cwd: string): { file: string; command: string; args: string[] }[] { + const found: { file: string; command: string; args: string[] }[] = []; + const read = (rel: string): unknown => { + try { + return JSON.parse(readFileSync(path.join(cwd, rel), 'utf8')); + } catch { + return undefined; + } + }; + const mcp = read('.mcp.json') as { mcpServers?: Record } | undefined; + const entry = mcp?.mcpServers?.['squad_state']; + if (entry && typeof entry.command === 'string') { + found.push({ file: '.mcp.json', command: entry.command, args: Array.isArray(entry.args) ? entry.args.map(String) : [] }); + } + const settings = read('.claude/settings.json') as { hooks?: Record } | undefined; + for (const groups of Object.values(settings?.hooks ?? {})) { + if (!Array.isArray(groups)) continue; + for (const group of groups as { hooks?: unknown }[]) { + for (const h of Array.isArray(group?.hooks) ? (group.hooks as Record[]) : []) { + const args = Array.isArray(h?.['args']) ? (h['args'] as unknown[]).map(String) : []; + if (typeof h?.['command'] !== 'string') continue; + const squadHook = args.slice(-3, -1).join(' ') === 'hook claude' || / hook claude [A-Za-z]+$/.test(h['command'] as string); + if (squadHook) found.push({ file: '.claude/settings.json', command: h['command'] as string, args }); + } + } + } + return found; +} + +/** + * Committed launch commands must work on every clone: no absolute paths into one machine's + * install, and the repository launcher they name must exist. Returns undefined when the + * repository commits no Squad launch command. + */ +export function checkPortableSquadLaunch(cwd: string): DoctorCheck | undefined { + const name = 'portable squad_state launch'; + const launches = committedSquadLaunches(cwd); + if (launches.length === 0) return undefined; + const fix = "Run 'squad upgrade' to switch to the repository launcher (.squad/squad.mjs), then commit the result."; + // A committed file may come from any OS: recognise Windows and POSIX absolute paths everywhere. + const isAbs = (p: string) => path.posix.isAbsolute(p) || path.win32.isAbsolute(p); + const absolute = launches.filter(l => isAbs(l.command) || l.args.some(a => /cli-entry\.js$/.test(a) && isAbs(a))); + if (absolute.length > 0) { + const files = [...new Set(absolute.map(l => l.file))].join(', '); + // Only an absolute command is a file to check; a bare `node` is found on PATH. + const missing = absolute.some(l => (isAbs(l.command) && !lstatExists(l.command)) || + l.args.some(a => isAbs(a) && !lstatExists(a))); + return { + name, + status: missing ? 'fail' : 'warn', + message: missing + ? `${files} start Squad from a path that does not exist on this machine (written on another machine?), so squad_state and the Claude hooks fail. ${fix}` + : `${files} start Squad from an absolute path on this machine, which fails on every other clone. ${fix}`, + }; + } + for (const l of launches) { + const script = l.command === 'node' ? l.args[0] : /^node "\$CLAUDE_PROJECT_DIR\/([^"]+)"/.exec(l.command)?.[1]; + if (script && /(?:^|\/)squad\.mjs$/.test(script) && !isAbs(script) && !lstatExists(path.join(cwd, script))) { + return { name, status: 'fail', message: `${l.file} runs ${script}, which is missing. ${fix}` }; + } + } + return { name, status: 'pass', message: `${launches.length} committed launch command(s) are portable` }; +} + +function lstatExists(file: string): boolean { + try { + lstatSync(file); + return true; + } catch { + return false; + } +} + // ── git sync hooks check ───────────────────────────────────────────── const SQUAD_SYNC_HOOK_MARKER = '# --- squad-sync-hook ---'; @@ -1159,6 +1235,8 @@ export async function runDoctor(cwd?: string): Promise { if (upstreamCheck) checks.push(upstreamCheck); checks.push(await checkSquadStateBridge(resolvedCwd)); + const portableLaunch = checkPortableSquadLaunch(resolvedCwd); + if (portableLaunch) checks.push(portableLaunch); // Hook presence check (only for two-layer / orphan backends) const hookCheck = checkGitSyncHooks(resolvedCwd, squadDir); diff --git a/packages/squad-cli/src/cli/core/clients.ts b/packages/squad-cli/src/cli/core/clients.ts index daeba0e3a..6c49c4b06 100644 --- a/packages/squad-cli/src/cli/core/clients.ts +++ b/packages/squad-cli/src/cli/core/clients.ts @@ -27,7 +27,7 @@ import { type ApplyResult, type ClientId, } from '@bradygaster/squad-sdk/client'; -import { projectSquadStateMcpSpec, resolveSquadStateMcpSpec } from './mcp-spec.js'; +import { projectSquadStateMcpSpec, resolveCommittedSquadStateMcpSpec } from './mcp-spec.js'; import { getPackageVersion } from './version.js'; export { CLIENT_IDS, parseClients, type ClientId }; @@ -184,7 +184,7 @@ export async function syncClients( if (options.claudePlugin !== undefined) persistClaudePlugin(squadDir, options.claudePlugin); const version = options.version ?? getPackageVersion(); - const spec = projectSquadStateMcpSpec(await resolveSquadStateMcpSpec(version)); + const spec = projectSquadStateMcpSpec(await resolveCommittedSquadStateMcpSpec(version, dest, squadDir)); const ctx = { root: dest, mcp: { command: spec.command, args: spec.args }, diff --git a/packages/squad-cli/src/cli/core/init.ts b/packages/squad-cli/src/cli/core/init.ts index 209f4e044..282ad8425 100644 --- a/packages/squad-cli/src/cli/core/init.ts +++ b/packages/squad-cli/src/cli/core/init.ts @@ -14,7 +14,7 @@ import { getPackageVersion, stampVersion } from './version.js'; import { initSquad as sdkInitSquad, cleanupOrphanInitPrompt, clearResolveSquadCache, type InitOptions } from '@bradygaster/squad-sdk'; import { installGitHooks } from '../commands/install-hooks.js'; import { liftInitMutableStateOntoOrphan } from '../commands/migrate-backend.js'; -import { projectSquadStateMcpSpec, resolveSquadStateMcpSpec } from './mcp-spec.js'; +import { projectSquadStateMcpSpec, resolveCommittedSquadStateMcpSpec } from './mcp-spec.js'; import { describeMcpSpec, ensureUserOwnedTemplates, recordShippedFilesAfterInit } from './upgrade.js'; import { syncCopilotInstructionsFile } from './managed-file.js'; import { getTemplatesDir } from './templates.js'; @@ -23,7 +23,8 @@ import { reconcileSquadStateUserConfig, tombstoneStaleSquadStateInProjectMcp, } from './mcp-root.js'; -import { applyClientProjectionsForInit, reportClientResults, type ClientId } from './clients.js'; +import { applyClientProjectionsForInit, readConfiguredClients, reportClientResults, type ClientId } from './clients.js'; +import { getHostProjection, readClaudePluginMode } from '@bradygaster/squad-sdk/client'; import { readTeamMd, writeTeamMd, @@ -189,6 +190,41 @@ export interface RunInitOptions { includeVscodeDefault?: boolean; } +/** A next-step line; `on`/`off` wrap the command (colour codes in the terminal, empty in tests). */ +type Styled = (on: string, off: string) => string; + +/** + * What `squad init` tells the user to run next, for the hosts they chose (#2453). + * No `--client` means Copilot, the default host. With one host the line is + * bare; with several, each line is prefixed with the host's name. + */ +export function initNextSteps( + clients: readonly ClientId[] | undefined, + claudePlugin?: boolean, +): { lines: Styled[]; tips: string[] } { + const chosen: ClientId[] = clients && clients.length > 0 ? [...new Set(clients)] : ['copilot']; + if (claudePlugin && !chosen.includes('claude')) chosen.push('claude'); + const steps: { host: string; text: Styled }[] = []; + const tips: string[] = []; + for (const id of chosen) { + const host = getHostProjection(id).label; + if (id === 'copilot') { + steps.push({ host, text: (on, off) => `Run ${on}copilot --agent squad${off} and tell it what you're building.` }); + tips.push('"squad:copilot": "copilot --agent squad --additional-mcp-config @.mcp.json"'); + } else if (id === 'claude' && claudePlugin) { + steps.push({ host, text: (on, off) => `Run ${on}claude${off}; the Squad plugin's coordinator starts by default in this repository.` }); + tips.push('"squad:claude": "claude -p --agent squad:squad"'); + } else if (id === 'claude') { + steps.push({ host, text: (on, off) => `Run ${on}claude --agent squad --mcp-config .mcp.json${off} and tell it what you're building.` }); + tips.push('"squad:claude": "claude -p --agent squad --mcp-config .mcp.json"'); + } else { + steps.push({ host, text: () => `Open ${host} in this repository and ask for the squad agent.` }); + } + } + if (steps.length === 1) return { lines: [steps[0]!.text], tips }; + return { lines: steps.map(s => (on: string, off: string) => `${s.host}: ${s.text(on, off)}`), tips }; +} + /** * Main init command handler */ @@ -452,7 +488,7 @@ export async function runInit(dest: string, options: RunInitOptions = {}): Promi // stale project-level entry left by the SDK init writer in // `.copilot/mcp-config.json`. No HOME modifications. try { - const mcpSpec = projectSquadStateMcpSpec(await resolveSquadStateMcpSpec(getPackageVersion())); + const mcpSpec = projectSquadStateMcpSpec(await resolveCommittedSquadStateMcpSpec(getPackageVersion(), dest, squadDir)); const rootResult = ensureSquadStateMcpInRoot(dest, getPackageVersion(), mcpSpec); if (rootResult.written) { success(`installed squad_state MCP server to .mcp.json (${describeMcpSpec(mcpSpec)}) — Copilot CLI will auto-load on next invocation`); @@ -480,7 +516,7 @@ export async function runInit(dest: string, options: RunInitOptions = {}): Promi // for vanilla `squad init` (no --state-backend flag) so the squad_state // MCP entry is reachable regardless of init path. No HOME modifications. try { - const mcpSpec = projectSquadStateMcpSpec(await resolveSquadStateMcpSpec(version)); + const mcpSpec = projectSquadStateMcpSpec(await resolveCommittedSquadStateMcpSpec(version, dest, squadDir)); const rootResult = ensureSquadStateMcpInRoot(dest, version, mcpSpec); if (rootResult.written) { success(`installed squad_state MCP server to .mcp.json (${describeMcpSpec(mcpSpec)}) — Copilot CLI will auto-load on next invocation`); @@ -599,14 +635,23 @@ export async function runInit(dest: string, options: RunInitOptions = {}): Promi if (!isInitNoColor()) await sleep(80); console.log(); - console.log(`${GREEN}${BOLD}Squad initialized.${RESET} Run ${CYAN}${BOLD}copilot --agent squad${RESET} and tell it what you're building.`); - if (options.claudePlugin) { - console.log(`Claude Code: run ${CYAN}${BOLD}claude${RESET}; the Squad plugin's coordinator starts by default in this repository.`); + // What this repository is now set up for: the flags, else what .squad/config.json already records + // (re-running `squad init` keeps an earlier --client and plugin mode). + const effectiveClients = options.clients && options.clients.length > 0 ? options.clients : readConfiguredClients(squadDir); + const next = initNextSteps(effectiveClients, options.claudePlugin ?? readClaudePluginMode(squadDir)); + const steps = next.lines.map(line => line(CYAN + BOLD, RESET)); + if (steps.length === 1) { + console.log(`${GREEN}${BOLD}Squad initialized.${RESET} ${steps[0]}`); + } else { + console.log(`${GREEN}${BOLD}Squad initialized.${RESET} Next, in the host you want to use:`); + for (const step of steps) console.log(` ${step}`); } 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 --agent squad --additional-mcp-config @.mcp.json"${RESET}`); - console.log(); + if (next.tips.length > 0) { + console.log(`${DIM}Tip: for non-interactive scripts that need squad_state tools, add to package.json:${RESET}`); + for (const tip of next.tips) console.log(`${DIM} ${tip}${RESET}`); + console.log(); + } if (squadInfo.isLegacy) { showDeprecationWarning(); diff --git a/packages/squad-cli/src/cli/core/mcp-root.ts b/packages/squad-cli/src/cli/core/mcp-root.ts index 666e7ac86..652e5fd74 100644 --- a/packages/squad-cli/src/cli/core/mcp-root.ts +++ b/packages/squad-cli/src/cli/core/mcp-root.ts @@ -33,7 +33,7 @@ import path from 'node:path'; import { realpathSync } from 'node:fs'; import { createHash } from 'node:crypto'; import { FSStorageProvider } from '@bradygaster/squad-sdk'; -import { projectSquadStateMcpSpec, type SquadStateMcpSpec } from './mcp-spec.js'; +import { isMachineSpecificMcpSpec, projectSquadStateMcpSpec, type SquadStateMcpSpec } from './mcp-spec.js'; import { writeManagedText, writeManagedTextAtomic } from './managed-file.js'; const storage = new FSStorageProvider(); @@ -121,7 +121,7 @@ export function ensureSquadStateMcpInRoot( 'x-squad': { managed: true, version: _cliVersion, - machineSpecific: projectSpec.source !== 'pinned', + machineSpecific: isMachineSpecificMcpSpec(projectSpec), source: projectSpec.source, ...(projectSpec.fallback ? { fallback: projectSpec.fallback } : {}), }, diff --git a/packages/squad-cli/src/cli/core/mcp-spec.ts b/packages/squad-cli/src/cli/core/mcp-spec.ts index 865b2c5a7..25aa6ca0b 100644 --- a/packages/squad-cli/src/cli/core/mcp-spec.ts +++ b/packages/squad-cli/src/cli/core/mcp-spec.ts @@ -11,16 +11,33 @@ * Dist-tags are intentionally never written. Registry mirrors frequently omit * `insider` and `preview`, while an installed entry point or exact version is * deterministic. + * + * 1 and 2 are paths on this machine. Files a repository commits (`.mcp.json`, + * host MCP configs, `.claude/settings.json` hooks) use + * {@link resolveCommittedSquadStateMcpSpec} instead, which swaps them for a + * portable form (#2462). */ -import { existsSync } from 'node:fs'; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import { + resolveInsideRoot, + SQUAD_REPO_LAUNCHER_MARKER, + SQUAD_REPO_LAUNCHER_PATH, + squadRepoLauncher, +} from '@bradygaster/squad-sdk/client'; export interface SquadStateMcpSpec { command: string; args: string[]; - source: 'standalone' | 'local-entry' | 'pinned'; + /** + * `standalone`, `local-entry`: paths on this machine. `pinned`: exact-version npx. + * `repo-launcher`: `node /squad.mjs`, committed with the repository. + * `path`: a bare `squad` from PATH (standalone bundle installs, which may have no Node). + */ + source: 'standalone' | 'local-entry' | 'pinned' | 'repo-launcher' | 'path'; /** Portable exact-version fallback recorded beside machine-local specs. */ fallback?: { command: 'npx'; @@ -133,3 +150,126 @@ function exactVersionFallback(version: string): { command: 'npx'; args: string[] args: ['-y', `${PACKAGE_NAME}@${version}`, 'state-mcp'], }; } + +/** True for a spec that names a path on this machine, which must not be committed. */ +export function isMachineSpecificMcpSpec(spec: SquadStateMcpSpec): boolean { + return spec.source === 'standalone' || spec.source === 'local-entry'; +} + +/** + * The portable form of `direct` for files a repository commits (#2462). + * + * - `local-entry` becomes `node /squad.mjs state-mcp`, the repository launcher + * ({@link writeSquadRepoLauncher}), which finds a Squad CLI on whatever machine runs it. A CLI + * running from a package runner's temporary cache (`npx`, `pnpm dlx`, `yarn dlx`) is gone once + * that command exits, so it keeps the exact-version `npx` form instead. + * - `standalone` becomes a bare `squad state-mcp`: standalone installs put `squad` on PATH and may + * have no Node to run the launcher. + * - `pinned` (exact-version npx) is already portable and is kept. + * + * `squadDir` is the squad directory relative to the repository root, POSIX separators. A squad + * directory outside the repository has no portable form; `direct` is returned. + */ +export function committedSquadStateMcpSpec(direct: SquadStateMcpSpec, squadDir: string | null): SquadStateMcpSpec { + if (direct.source === 'local-entry' && direct.fallback && isTransientCliEntry(direct.args[0] ?? '')) { + return { command: direct.fallback.command, args: [...direct.fallback.args], source: 'pinned' }; + } + if (direct.source === 'local-entry' && squadDir) { + return { + command: 'node', + args: [`${squadDir}/${SQUAD_REPO_LAUNCHER_PATH}`, 'state-mcp'], + source: 'repo-launcher', + ...(direct.fallback ? { fallback: direct.fallback } : {}), + }; + } + if (direct.source === 'standalone') { + return { command: 'squad', args: ['state-mcp'], source: 'path', ...(direct.fallback ? { fallback: direct.fallback } : {}) }; + } + return direct; +} + +/** + * True for an entry inside a package runner's temporary install: npm's `_npx` cache, pnpm's + * `dlx` cache, or yarn's `dlx-` / `xfs-` temporary directories. + */ +export function isTransientCliEntry(entry: string): boolean { + const parts = entry.split(/[\\/]+/); + return parts.includes('_npx') || parts.includes('dlx') || parts.some(p => /^(?:dlx|xfs)-[\w-]+$/.test(p)); +} + +/** The squad directory relative to `root` (POSIX), or null when it is outside `root`. */ +export function squadDirInRoot(root: string, squadDirAbs: string): string | null { + const rel = path.relative(path.resolve(root), path.resolve(squadDirAbs)); + if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return null; + return rel.split(path.sep).join('/'); +} + +/** + * Write (or refresh) `/squad.mjs`. A file there without Squad's marker is the user's + * and is left alone. The path must stay inside `root` once symlinks are followed (a linked squad + * directory or a dangling link at the launcher path is refused), so this never writes outside the + * workspace. + */ +export function writeSquadRepoLauncher( + root: string, + squadDir: string, + version: string, +): 'created' | 'updated' | 'unchanged' | 'skipped' { + let file: string; + try { + file = resolveInsideRoot(root, `${squadDir}/${SQUAD_REPO_LAUNCHER_PATH}`); + } catch { + return 'skipped'; + } + const content = squadRepoLauncher(version, squadDir); + if (existsSync(file)) { + const existing = readFileSync(file, 'utf8'); + if (existing === content) return 'unchanged'; + if (!existing.includes(SQUAD_REPO_LAUNCHER_MARKER)) return 'skipped'; + writeFileSync(file, content, { mode: 0o755 }); + return 'updated'; + } + mkdirSync(path.dirname(file), { recursive: true }); + writeFileSync(file, content, { mode: 0o755 }); + return 'created'; +} + +/** + * Resolve the spec for committed files under `root`, writing the repository launcher when the + * spec uses it (unless `write` is false, for read-only callers such as `squad client list`). + * A launcher Squad may not overwrite (user-authored) keeps the machine-specific spec. + */ +export async function resolveCommittedSquadStateMcpSpec( + cliVersion: string, + root: string, + squadDirAbs: string, + options: ResolveSquadStateMcpSpecOptions & { write?: boolean } = {}, +): Promise { + const direct = await resolveSquadStateMcpSpec(cliVersion, options); + const squadDir = squadDirInRoot(root, squadDirAbs); + const spec = committedSquadStateMcpSpec(direct, squadDir); + if (spec.source === 'repo-launcher' && squadDir && options.write !== false) { + if (writeSquadRepoLauncher(root, squadDir, cliVersion) === 'skipped') return direct; + const entry = direct.args[0]; + if (entry && !isTransientCliEntry(entry)) recordSquadCli(entry, cliVersion); + } + return spec; +} + +/** + * Record this CLI's entry in `/cli.json`, outside the repository, so the repository + * launcher also works on this machine when the CLI is neither on PATH nor in the project (a + * source build run by path, for example). Best effort: a failure only skips the record. + */ +export function recordSquadCli(entry: string, version: string, env: NodeJS.ProcessEnv = process.env): void { + try { + const home = env['SQUAD_HOME'] ? path.resolve(env['SQUAD_HOME']) : path.join(os.homedir(), '.squad'); + const file = path.join(home, 'cli.json'); + const content = `${JSON.stringify({ entry, version }, null, 2)}\n`; + if (existsSync(file) && readFileSync(file, 'utf8') === content) return; + mkdirSync(home, { recursive: true }); + writeFileSync(file, content); + } catch { + // best effort + } +} diff --git a/packages/squad-cli/src/cli/core/upgrade.ts b/packages/squad-cli/src/cli/core/upgrade.ts index 577f31fd5..f5216c1b0 100644 --- a/packages/squad-cli/src/cli/core/upgrade.ts +++ b/packages/squad-cli/src/cli/core/upgrade.ts @@ -25,7 +25,7 @@ import { TEMPLATE_MANIFEST, getTemplatesDir } from './templates.js'; import { runMigrations } from './migrations.js'; import { scrubEmails } from './email-scrub.js'; import { getPackageVersion, stampVersion, readInstalledVersion } from './version.js'; -import { projectSquadStateMcpSpec, resolveSquadStateMcpSpec, type SquadStateMcpSpec } from './mcp-spec.js'; +import { projectSquadStateMcpSpec, resolveCommittedSquadStateMcpSpec, type SquadStateMcpSpec } from './mcp-spec.js'; import { copyManagedFile, syncCopilotInstructionsFile, writeManagedText } from './managed-file.js'; export { resolveSquadStateMcpSpec } from './mcp-spec.js'; import { @@ -1231,7 +1231,9 @@ async function runEnsureChecks( // 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 = projectSquadStateMcpSpec(await resolveSquadStateMcpSpec(getPackageVersion())); + const pinnedSpec = projectSquadStateMcpSpec( + await resolveCommittedSquadStateMcpSpec(getPackageVersion(), dest, detectSquadDir(dest).path), + ); try { const rootResult = ensureSquadStateMcpInRoot(dest, getPackageVersion(), pinnedSpec); if (rootResult.written) { @@ -1273,6 +1275,12 @@ export function describeMcpSpec(spec: SquadStateMcpSpec): string { if (spec.source === 'local-entry') { return `${spec.command} ${spec.args[0]} (machine-specific installed CLI; exact-version fallback recorded)`; } + if (spec.source === 'repo-launcher') { + return `node ${spec.args[0]} (repository launcher: finds the Squad CLI on each machine)`; + } + if (spec.source === 'path') { + return 'squad from PATH'; + } const pkg = spec.args[1] ?? ''; return pkg; } diff --git a/packages/squad-sdk/src/client/adapters/claude-hooks.ts b/packages/squad-sdk/src/client/adapters/claude-hooks.ts index 586b3de73..f04b9a915 100644 --- a/packages/squad-sdk/src/client/adapters/claude-hooks.ts +++ b/packages/squad-sdk/src/client/adapters/claude-hooks.ts @@ -94,19 +94,42 @@ export function readClaudeHooksMode(squadAbs: string): ClaudeHooksMode { /** * The hook launch command: the MCP launcher with its trailing `state-mcp` replaced. * - * No command allowlist on purpose. `mcp` is resolved by the running CLI from its own install - * (standalone launcher, `node /cli-entry.js`, or `npx` with the exact package version), never - * from repository content, and the same command is already projected into `.mcp.json` as the - * `squad_state` server. Hooks therefore add no new executable. + * No command allowlist on purpose. `mcp` is resolved by the running CLI (a bare `squad`, `npx` with + * the exact package version, or the repository launcher `node /squad.mjs` that Squad + * writes), and the same command is already projected into `.mcp.json` as the `squad_state` server. + * Hooks therefore add no new executable. + * + * The repository launcher becomes a shell command through `$CLAUDE_PROJECT_DIR`, as Claude Code + * documents for project hooks, so it works whatever the session's working directory is (#2462). + * Its `args` is empty and the handler is written without one. */ export function claudeHookLaunch(mcp: McpLaunchSpec, event: ClaudeHookEvent): McpLaunchSpec | null { if (mcp.args[mcp.args.length - 1] !== 'state-mcp') return null; + const launcher = repoLauncherPath(mcp); + if (launcher) return { command: `node "$CLAUDE_PROJECT_DIR/${launcher}" hook claude ${event}`, args: [] }; return { command: mcp.command, args: [...mcp.args.slice(0, -1), 'hook', 'claude', event] }; } -/** A handler Squad wrote: a command hook whose arguments end in `hook claude `. */ +/** `/squad.mjs` when `mcp` is the repository launcher, else null. */ +function repoLauncherPath(mcp: McpLaunchSpec): string | null { + const [script, sub] = mcp.args; + if (mcp.command !== 'node' || mcp.args.length !== 2 || sub !== 'state-mcp' || !script) return null; + return /^(?:[\w.-]+\/)+squad\.mjs$/.test(script) && !script.split('/').includes('..') ? script : null; +} + +const REPO_LAUNCHER_HOOK = /^node "\$CLAUDE_PROJECT_DIR\/(?:[\w.-]+\/)+squad\.mjs" hook claude ([A-Za-z]+)$/; + +/** + * A handler Squad wrote: a command hook whose arguments end in `hook claude `, or the + * repository-launcher shell command `node "$CLAUDE_PROJECT_DIR//squad.mjs" hook claude `. + */ export function isSquadHookHandler(h: unknown): boolean { - if (!isObject(h) || h['type'] !== 'command' || !Array.isArray(h['args'])) return false; + if (!isObject(h) || h['type'] !== 'command') return false; + if (!('args' in h) && typeof h['command'] === 'string') { + const m = REPO_LAUNCHER_HOOK.exec(h['command']); + return !!m && (CLAUDE_HOOK_EVENTS as readonly unknown[]).includes(m[1]); + } + if (!Array.isArray(h['args'])) return false; const a = h['args'] as unknown[]; return ( a.length >= 3 && @@ -164,7 +187,9 @@ export function mergeClaudeHooks(settings: Json, mcp: McpLaunchSpec | null): Jso if (!launch) return stripped; const groups = hooks[event] ?? []; if (!Array.isArray(groups)) throw new Error(`Refusing to overwrite "hooks.${event}" in .claude/settings.json: expected an array`); - const handler = { type: 'command', command: launch.command, args: launch.args, timeout: HOOK_TIMEOUT_SECONDS }; + const handler = launch.args.length === 0 + ? { type: 'command', command: launch.command, timeout: HOOK_TIMEOUT_SECONDS } + : { type: 'command', command: launch.command, args: launch.args, timeout: HOOK_TIMEOUT_SECONDS }; const matcher = MATCHERS[event]; hooks[event] = [...groups, matcher ? { matcher, hooks: [handler] } : { hooks: [handler] }]; } diff --git a/packages/squad-sdk/src/client/adapters/claude-plugin.ts b/packages/squad-sdk/src/client/adapters/claude-plugin.ts index f38141b46..ecd4a8f47 100644 --- a/packages/squad-sdk/src/client/adapters/claude-plugin.ts +++ b/packages/squad-sdk/src/client/adapters/claude-plugin.ts @@ -102,27 +102,37 @@ function walk(dir: string, rel = ''): string[] { const NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; -/** The launcher every plugin entry point goes through: resolves a Squad CLI and passes argv on. */ -export const CLAUDE_PLUGIN_LAUNCHER = `#!/usr/bin/env node -// ${NOTE_TEXT} -// -// Squad plugin launcher. Runs the Squad CLI with this process's arguments, resolved in this order: -// 1. SQUAD_CLI (a command, or a .js entry run with this Node) -// 2. the project's own install: node_modules/${CLI_PACKAGE} -// 3. a \`squad\` on PATH (never this plugin's own bin/) -// 4. npx -y ${CLI_PACKAGE}@, only when SQUAD_ALLOW_NPX=1: it downloads and runs -// code, so it is opt-in. Without it, the launcher fails with install instructions. -// 2 and 3 are skipped when their version is older than this plugin's: an older CLI lacks commands the -// plugin calls (\`dashboard\`, \`hook --plugin\`). The version is read from package.json, BUNDLE-INFO.json -// (standalone bundles), or an embedded node_modules. A CLI whose version cannot be read is used as found. -import { spawn } from 'node:child_process'; +/** + * Where {@link squadRepoLauncher} lives, relative to the squad directory (#2462). Not under a + * `bin/` directory: many repositories ignore `bin/`, which would leave the launcher uncommitted. + */ +export const SQUAD_REPO_LAUNCHER_PATH = 'squad.mjs'; +/** First line after the shebang of a repository launcher Squad wrote: marks the file as Squad's. */ +export const SQUAD_REPO_LAUNCHER_MARKER = '// Generated by Squad. Managed file: rewritten by `squad init` and `squad upgrade`.'; + +interface LauncherVariant { + /** Header comment lines after the shebang. */ + header: string; + /** Statements defining `root` (for `ownBin`) and `projectStart` (where the project is). */ + roots: string; + /** Body of `pluginVersion()`: the minimum CLI version. */ + version: string; + /** Who the messages come from. */ + who: string; + /** Who downloads with npx, in the install message. */ + npxActor: string; +} + +function squadLauncher(v: LauncherVariant): string { + return `#!/usr/bin/env node +${v.header}import { spawn } from 'node:child_process'; import fs from 'node:fs'; +import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const PKG = '${CLI_PACKAGE}'; -const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); -const ownBin = path.join(root, 'bin'); +${v.roots}const ownBin = path.join(root, 'bin'); const win = process.platform === 'win32'; function canonical(file) { @@ -132,7 +142,7 @@ function canonical(file) { // Hooks must be cheap no-ops outside an opted-in team, before CLI or npx resolution. function hooksEnabled() { - let project = path.resolve(process.env.CLAUDE_PROJECT_DIR || process.cwd()); + let project = path.resolve(projectStart()); for (;;) { for (const name of ['.squad', '.ai-team']) { const local = path.join(project, name); @@ -160,12 +170,7 @@ function hooksEnabled() { if (process.argv[2] === 'hook' && process.argv[3] === 'claude' && !hooksEnabled()) process.exit(0); function pluginVersion() { - try { - return JSON.parse(fs.readFileSync(path.join(root, '.claude-plugin', 'plugin.json'), 'utf8')).version; - } catch { - return undefined; - } -} +${v.version}} /** The version of the Squad CLI behind \`entry\` (a script, symlink or npm shim), if it can be found. */ function cliVersion(entry) { @@ -250,20 +255,34 @@ function onPath(name) { return undefined; } +/** The CLI entry \`squad init\` / \`squad upgrade\` last recorded on this machine (\`/cli.json\`). */ +function recordedCli() { + try { + const home = process.env.SQUAD_HOME ? path.resolve(process.env.SQUAD_HOME) : path.join(os.homedir(), '.squad'); + const entry = JSON.parse(fs.readFileSync(path.join(home, 'cli.json'), 'utf8')).entry; + if (typeof entry === 'string' && path.isAbsolute(entry) && /\.[cm]?js$/.test(entry) && fs.existsSync(entry)) return entry; + } catch { + // no record + } + return undefined; +} + function resolveCli() { const override = process.env.SQUAD_CLI; if (override) return /\\.[cm]?js$/.test(override) ? [process.execPath, [override]] : [override, []]; const version = pluginVersion(); - const project = process.env.CLAUDE_PROJECT_DIR || process.cwd(); + const project = projectStart(); const local = path.join(project, 'node_modules', ...PKG.split('/'), 'dist', 'cli-entry.js'); if (fs.existsSync(local) && recentEnough(local, version)) return [process.execPath, [local]]; const global = onPath('squad'); if (global && recentEnough(global, version)) return [global, []]; + const recorded = recordedCli(); + if (recorded && recentEnough(recorded, version)) return [process.execPath, [recorded]]; const want = version ? \`\${PKG}@\${version}\` : PKG; if (/^(1|true|yes)$/i.test(process.env.SQUAD_ALLOW_NPX || '')) return [win ? 'npx.cmd' : 'npx', ['-y', want]]; throw new Error( - \`no Squad CLI\${version ? \` \${version} or newer\` : ''} found (looked at SQUAD_CLI, this project's node_modules and PATH). \` + - \`Install it with: npm install -g \${want}. Or set SQUAD_ALLOW_NPX=1 to let the plugin download and run it with npx.\`, + \`no Squad CLI\${version ? \` \${version} or newer\` : ''} found (looked at SQUAD_CLI, this project's node_modules, PATH and the CLI recorded in ~/.squad/cli.json). \` + + \`Install it with: npm install -g \${want}. Or set SQUAD_ALLOW_NPX=1 to let ${v.npxActor} download and run it with npx.\`, ); } @@ -284,7 +303,7 @@ let command, prefix; try { [command, prefix] = withoutShell(...resolveCli()); } catch (err) { - process.stderr.write(\`squad plugin: \${err.message}\\n\`); + process.stderr.write(\`${v.who}: \${err.message}\\n\`); process.exit(1); } const child = spawn(command, [...prefix, ...process.argv.slice(2)], { @@ -293,11 +312,73 @@ const child = spawn(command, [...prefix, ...process.argv.slice(2)], { }); for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.on(signal, () => child.kill(signal)); child.on('error', (err) => { - process.stderr.write(\`squad plugin: cannot start the Squad CLI (\${err.message}). Install it with: npm install -g \${PKG}\\n\`); + process.stderr.write(\`${v.who}: cannot start the Squad CLI (\${err.message}). Install it with: npm install -g \${PKG}\\n\`); process.exit(1); }); child.on('exit', (code, signal) => process.exit(code ?? (signal ? 1 : 0))); `; +} + +/** The launcher every plugin entry point goes through: resolves a Squad CLI and passes argv on. */ +export const CLAUDE_PLUGIN_LAUNCHER = squadLauncher({ + header: `// ${NOTE_TEXT} +// +// Squad plugin launcher. Runs the Squad CLI with this process's arguments, resolved in this order: +// 1. SQUAD_CLI (a command, or a .js entry run with this Node) +// 2. the project's own install: node_modules/${CLI_PACKAGE} +// 3. a \`squad\` on PATH (never this plugin's own bin/) +// 4. the CLI that last ran \`squad init\` or \`squad upgrade\` on this machine (~/.squad/cli.json) +// 5. npx -y ${CLI_PACKAGE}@, only when SQUAD_ALLOW_NPX=1: it downloads and runs +// code, so it is opt-in. Without it, the launcher fails with install instructions. +// 2, 3 and 4 are skipped when their version is older than this plugin's: an older CLI lacks commands the +// plugin calls (\`dashboard\`, \`hook --plugin\`). The version is read from package.json, BUNDLE-INFO.json +// (standalone bundles), or an embedded node_modules. A CLI whose version cannot be read is used as found. +`, + roots: `const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const projectStart = () => process.env.CLAUDE_PROJECT_DIR || process.cwd(); +`, + version: ` try { + return JSON.parse(fs.readFileSync(path.join(root, '.claude-plugin', 'plugin.json'), 'utf8')).version; + } catch { + return undefined; + } +`, + who: 'squad plugin', + npxActor: 'the plugin', +}); + +/** + * The launcher a repository commits at `/squad.mjs` (#2462). Committed MCP configs and + * Claude hooks run it instead of a machine-specific \`node /cli-entry.js\`, so they work on every + * clone. Same resolution as the plugin launcher, from the repository root this file sits in. + * + * @param version - the CLI version that wrote it: older CLIs are skipped (they lack its commands). + * @param squadDir - the squad directory relative to the repository root, e.g. \`.squad\`. + */ +export function squadRepoLauncher(version: string, squadDir: string): string { + const up = squadDir.split('/').filter(Boolean).map(() => '..').join('/'); + return squadLauncher({ + header: `${SQUAD_REPO_LAUNCHER_MARKER} +// +// Starts the Squad CLI for this repository's MCP configs and Claude Code hooks, so they work on every +// machine that clones it. Resolved in this order: +// 1. SQUAD_CLI (a command, or a .js entry run with this Node) +// 2. this repository's own install: node_modules/${CLI_PACKAGE} +// 3. a \`squad\` on PATH +// 4. the CLI that last ran \`squad init\` or \`squad upgrade\` on this machine (~/.squad/cli.json) +// 5. npx -y ${CLI_PACKAGE}@${version}, only when SQUAD_ALLOW_NPX=1: it downloads and runs code, so it is +// opt-in. Without it, the launcher fails with install instructions. +// 2, 3 and 4 are skipped when their version is older than ${version}, the version that wrote this file. +`, + roots: `const root = path.dirname(fileURLToPath(import.meta.url)); +const projectStart = () => path.resolve(path.dirname(fileURLToPath(import.meta.url)), ${JSON.stringify(up)}); +`, + version: ` return ${JSON.stringify(version)}; +`, + who: 'squad', + npxActor: 'Squad', + }); +} function hooksJson(): string { const hooks: Record = {}; diff --git a/plugins/squad/scripts/squad.mjs b/plugins/squad/scripts/squad.mjs index a2123586d..2a005b99b 100755 --- a/plugins/squad/scripts/squad.mjs +++ b/plugins/squad/scripts/squad.mjs @@ -5,18 +5,21 @@ // 1. SQUAD_CLI (a command, or a .js entry run with this Node) // 2. the project's own install: node_modules/@bradygaster/squad-cli // 3. a `squad` on PATH (never this plugin's own bin/) -// 4. npx -y @bradygaster/squad-cli@, only when SQUAD_ALLOW_NPX=1: it downloads and runs +// 4. the CLI that last ran `squad init` or `squad upgrade` on this machine (~/.squad/cli.json) +// 5. npx -y @bradygaster/squad-cli@, only when SQUAD_ALLOW_NPX=1: it downloads and runs // code, so it is opt-in. Without it, the launcher fails with install instructions. -// 2 and 3 are skipped when their version is older than this plugin's: an older CLI lacks commands the +// 2, 3 and 4 are skipped when their version is older than this plugin's: an older CLI lacks commands the // plugin calls (`dashboard`, `hook --plugin`). The version is read from package.json, BUNDLE-INFO.json // (standalone bundles), or an embedded node_modules. A CLI whose version cannot be read is used as found. import { spawn } from 'node:child_process'; import fs from 'node:fs'; +import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const PKG = '@bradygaster/squad-cli'; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const projectStart = () => process.env.CLAUDE_PROJECT_DIR || process.cwd(); const ownBin = path.join(root, 'bin'); const win = process.platform === 'win32'; @@ -27,7 +30,7 @@ function canonical(file) { // Hooks must be cheap no-ops outside an opted-in team, before CLI or npx resolution. function hooksEnabled() { - let project = path.resolve(process.env.CLAUDE_PROJECT_DIR || process.cwd()); + let project = path.resolve(projectStart()); for (;;) { for (const name of ['.squad', '.ai-team']) { const local = path.join(project, name); @@ -145,19 +148,33 @@ function onPath(name) { return undefined; } +/** The CLI entry `squad init` / `squad upgrade` last recorded on this machine (`/cli.json`). */ +function recordedCli() { + try { + const home = process.env.SQUAD_HOME ? path.resolve(process.env.SQUAD_HOME) : path.join(os.homedir(), '.squad'); + const entry = JSON.parse(fs.readFileSync(path.join(home, 'cli.json'), 'utf8')).entry; + if (typeof entry === 'string' && path.isAbsolute(entry) && /.[cm]?js$/.test(entry) && fs.existsSync(entry)) return entry; + } catch { + // no record + } + return undefined; +} + function resolveCli() { const override = process.env.SQUAD_CLI; if (override) return /\.[cm]?js$/.test(override) ? [process.execPath, [override]] : [override, []]; const version = pluginVersion(); - const project = process.env.CLAUDE_PROJECT_DIR || process.cwd(); + const project = projectStart(); const local = path.join(project, 'node_modules', ...PKG.split('/'), 'dist', 'cli-entry.js'); if (fs.existsSync(local) && recentEnough(local, version)) return [process.execPath, [local]]; const global = onPath('squad'); if (global && recentEnough(global, version)) return [global, []]; + const recorded = recordedCli(); + if (recorded && recentEnough(recorded, version)) return [process.execPath, [recorded]]; const want = version ? `${PKG}@${version}` : PKG; if (/^(1|true|yes)$/i.test(process.env.SQUAD_ALLOW_NPX || '')) return [win ? 'npx.cmd' : 'npx', ['-y', want]]; throw new Error( - `no Squad CLI${version ? ` ${version} or newer` : ''} found (looked at SQUAD_CLI, this project's node_modules and PATH). ` + + `no Squad CLI${version ? ` ${version} or newer` : ''} found (looked at SQUAD_CLI, this project's node_modules, PATH and the CLI recorded in ~/.squad/cli.json). ` + `Install it with: npm install -g ${want}. Or set SQUAD_ALLOW_NPX=1 to let the plugin download and run it with npx.`, ); } diff --git a/scripts/docs-site-location.mjs b/scripts/docs-site-location.mjs index 3da52fdf5..083a288d6 100644 --- a/scripts/docs-site-location.mjs +++ b/scripts/docs-site-location.mjs @@ -127,11 +127,6 @@ export const HISTORICAL_REPO_LINKS = [ reason: 'Working-draft provenance cites the pull requests, issue and commit each draft came from; the Charter text is pinned by SHA-256.', links: PERMALINK, }, - { - path: 'docs/src/content/docs/scenarios/upgrading.md', - reason: 'The generated upgrade matrix records the CI runs and GitHub Releases behind its results (rendered from test-fixtures/spec/suite-v0.2/upgrade-matrix.json).', - section: { from: /^/, to: /^/ }, - }, { path: 'docs/_internal/launch/migration-announcement.md', reason: 'Launch announcement from when Squad first went public.', diff --git a/test-fixtures/spec/suite-v0.2/upgrade-matrix.json b/test-fixtures/spec/suite-v0.2/upgrade-matrix.json index 21fe25b72..b4730598e 100644 --- a/test-fixtures/spec/suite-v0.2/upgrade-matrix.json +++ b/test-fixtures/spec/suite-v0.2/upgrade-matrix.json @@ -86,7 +86,7 @@ }, "artifacts": { "release": { - "repository": "bradygaster/squad", + "repository": "github/squad", "tag": "v1.0.1", "assets": { "squad-darwin-arm64.tar.gz": { @@ -127,7 +127,7 @@ }, "artifacts": { "release": { - "repository": "bradygaster/squad", + "repository": "github/squad", "tag": "v1.1.0", "assets": { "squad-darwin-arm64.tar.gz": { @@ -206,6 +206,7 @@ "^\\.copilot/skills/", "^\\.github/workflows/(squad-[\\w-]+|sync-squad-labels)\\.yml(\\.local-backup)?$", "^\\.squad/\\.first-run$", + "^\\.squad/squad\\.mjs$", "^\\.squad/config\\.json$", "^\\.squad/casting/", "^\\.squad/templates/", @@ -325,13 +326,13 @@ { "platform": "linux", "arch": "x64", - "node": "v22.23.3", - "target": "1.2.0-preview.1", - "targetSha": "9da01a2e41ba15794d31b0b8251ddaa3abd34ba8", - "harnessHash": "bca27c39dc2dcbffc3bf652bf1f45b471c09911a4bb9ba7e5b7247d25705a66e", - "event": "workflow_dispatch", - "runUrl": "https://github.com/bradygaster/squad/actions/runs/39903943542", - "recordedAt": "2026-10-23T05:08:38.415Z", + "node": "v22.23.4", + "target": "2.0.0-preview.4", + "targetSha": "9b254ba858abc19ef62e513993d9802312bfe34c", + "harnessHash": "a9713d320fdb479ced9cd311f36cf33bbd70d5733afb1617cd740831ce6767f8", + "event": "pull_request", + "runUrl": "https://github.com/github/squad/actions/runs/43147851248", + "recordedAt": "2026-11-12T03:25:33.018Z", "results": [ { "producer": "0.9.1", @@ -414,7 +415,7 @@ { "id": "downgrade-probe", "status": "xfail", - "detail": "dry-run MODIFIED 45 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)" + "detail": "dry-run MODIFIED 68 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)" }, { "id": "clients-projected", @@ -439,15 +440,15 @@ } ], "notes": [ - "plain upgrade changed 100 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md, .copilot/skills/reviewer-protocol/SKILL.md … (+96)", - "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v1.2.0-preview.1 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 45 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)", + "plain upgrade changed 229 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md, .copilot/skills/ci-validation-gates/SKILL.md … (+225)", + "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v2.0.0-preview.4 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 68 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)", "downgrade-probe: F3" ] }, { "producer": "0.9.1", "source": "npm", - "shape": "profile", + "shape": "legacy-casting", "status": "pass", "provenance": "npm: @bradygaster/squad-cli@0.9.1 sha512-TcZqRbJk3XBg4N8Y5/5OWJeA52ARAMuzk4MGtB0bfdr87e7iJRV6lJEw8+OIYz9IQ+1JxN/Q30dZDALMOcdi2w==; @bradygaster/squad-sdk@0.9.1 sha512-1KqZ2N6Lt/B4uYlU/Su9JjA1s/7ng0OAEb9CZrkRk19YSPi6tg7K5QWdTKl5gY/gp8zPAJqfjVffOwrZwGlzmw==", "checks": [ @@ -495,7 +496,7 @@ { "id": "downgrade-probe", "status": "xfail", - "detail": "dry-run MODIFIED 45 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)" + "detail": "dry-run MODIFIED 68 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)" }, { "id": "clients-projected", @@ -515,21 +516,22 @@ }, { "id": "profile-untouched", - "status": "pass" + "status": "skipped", + "detail": "no profile squad seeded in this shape" } ], "notes": [ - "plain upgrade changed 100 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md, .copilot/skills/reviewer-protocol/SKILL.md … (+96)", - "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v1.2.0-preview.1 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 45 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)", + "plain upgrade changed 230 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md, .copilot/skills/ci-validation-gates/SKILL.md … (+226)", + "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v2.0.0-preview.4 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 68 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)", "downgrade-probe: F3" ] }, { - "producer": "0.13.1", + "producer": "0.9.1", "source": "npm", - "shape": "default", + "shape": "profile", "status": "pass", - "provenance": "npm: @bradygaster/squad-cli@0.13.1 sha512-ogfx2nhyx6fqMyqlaI5tDnOzUbRdh+a6Uh/rMQ0aNJwAXnvQp4N37Xp/eOTyB5CxcB7E34NBtAfCxyXbWSiS8w==; @bradygaster/squad-sdk@0.13.1 sha512-1Jt6c9kahJq1JCcE77j/50D79YcXVG6Rt1genuLJ3O9J2SKTZKemlbvHgftkc5LgGH22DkWqHh5zLNEQ/p+ukA==", + "provenance": "npm: @bradygaster/squad-cli@0.9.1 sha512-TcZqRbJk3XBg4N8Y5/5OWJeA52ARAMuzk4MGtB0bfdr87e7iJRV6lJEw8+OIYz9IQ+1JxN/Q30dZDALMOcdi2w==; @bradygaster/squad-sdk@0.9.1 sha512-1KqZ2N6Lt/B4uYlU/Su9JjA1s/7ng0OAEb9CZrkRk19YSPi6tg7K5QWdTKl5gY/gp8zPAJqfjVffOwrZwGlzmw==", "checks": [ { "id": "producer-artifact-verified", @@ -574,7 +576,8 @@ }, { "id": "downgrade-probe", - "status": "pass" + "status": "xfail", + "detail": "dry-run MODIFIED 68 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)" }, { "id": "clients-projected", @@ -594,19 +597,19 @@ }, { "id": "profile-untouched", - "status": "skipped", - "detail": "no profile squad seeded in this shape" + "status": "pass" } ], "notes": [ - "plain upgrade changed 58 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md, .github/skills/coordinator-source-of-truth/SKILL.md … (+54)", - "old 0.13.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" + "plain upgrade changed 229 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md, .copilot/skills/ci-validation-gates/SKILL.md … (+225)", + "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v2.0.0-preview.4 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 68 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)", + "downgrade-probe: F3" ] }, { "producer": "0.9.1", "source": "npm", - "shape": "legacy-casting", + "shape": "ai-team", "status": "pass", "provenance": "npm: @bradygaster/squad-cli@0.9.1 sha512-TcZqRbJk3XBg4N8Y5/5OWJeA52ARAMuzk4MGtB0bfdr87e7iJRV6lJEw8+OIYz9IQ+1JxN/Q30dZDALMOcdi2w==; @bradygaster/squad-sdk@0.9.1 sha512-1KqZ2N6Lt/B4uYlU/Su9JjA1s/7ng0OAEb9CZrkRk19YSPi6tg7K5QWdTKl5gY/gp8zPAJqfjVffOwrZwGlzmw==", "checks": [ @@ -640,8 +643,7 @@ }, { "id": "ai-team-migrated", - "status": "skipped", - "detail": "not an .ai-team shape" + "status": "pass" }, { "id": "team-root-absent", @@ -654,7 +656,7 @@ { "id": "downgrade-probe", "status": "xfail", - "detail": "dry-run MODIFIED 45 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)" + "detail": "dry-run MODIFIED 68 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)" }, { "id": "clients-projected", @@ -679,17 +681,17 @@ } ], "notes": [ - "plain upgrade changed 101 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md, .copilot/skills/reviewer-protocol/SKILL.md … (+97)", - "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v1.2.0-preview.1 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 45 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)", + "plain upgrade changed 229 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md, .copilot/skills/ci-validation-gates/SKILL.md … (+225)", + "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v2.0.0-preview.4 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 68 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)", "downgrade-probe: F3" ] }, { - "producer": "0.9.1", + "producer": "0.13.1", "source": "npm", - "shape": "ai-team", + "shape": "default", "status": "pass", - "provenance": "npm: @bradygaster/squad-cli@0.9.1 sha512-TcZqRbJk3XBg4N8Y5/5OWJeA52ARAMuzk4MGtB0bfdr87e7iJRV6lJEw8+OIYz9IQ+1JxN/Q30dZDALMOcdi2w==; @bradygaster/squad-sdk@0.9.1 sha512-1KqZ2N6Lt/B4uYlU/Su9JjA1s/7ng0OAEb9CZrkRk19YSPi6tg7K5QWdTKl5gY/gp8zPAJqfjVffOwrZwGlzmw==", + "provenance": "npm: @bradygaster/squad-cli@0.13.1 sha512-ogfx2nhyx6fqMyqlaI5tDnOzUbRdh+a6Uh/rMQ0aNJwAXnvQp4N37Xp/eOTyB5CxcB7E34NBtAfCxyXbWSiS8w==; @bradygaster/squad-sdk@0.13.1 sha512-1Jt6c9kahJq1JCcE77j/50D79YcXVG6Rt1genuLJ3O9J2SKTZKemlbvHgftkc5LgGH22DkWqHh5zLNEQ/p+ukA==", "checks": [ { "id": "producer-artifact-verified", @@ -721,7 +723,8 @@ }, { "id": "ai-team-migrated", - "status": "pass" + "status": "skipped", + "detail": "not an .ai-team shape" }, { "id": "team-root-absent", @@ -733,8 +736,7 @@ }, { "id": "downgrade-probe", - "status": "xfail", - "detail": "dry-run MODIFIED 45 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)" + "status": "pass" }, { "id": "clients-projected", @@ -759,9 +761,8 @@ } ], "notes": [ - "plain upgrade changed 100 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md, .copilot/skills/reviewer-protocol/SKILL.md … (+96)", - "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v1.2.0-preview.1 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 45 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)", - "downgrade-probe: F3" + "plain upgrade changed 150 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+146)", + "old 0.13.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { @@ -839,7 +840,7 @@ ], "notes": [ "producer wrote teamRoot=/squad at init", - "plain upgrade changed 59 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md, .github/skills/coordinator-source-of-truth/SKILL.md … (+55)", + "plain upgrade changed 151 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+147)", "old 0.13.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -928,7 +929,7 @@ } ], "notes": [ - "plain upgrade changed 58 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md, .github/skills/coordinator-source-of-truth/SKILL.md … (+54)", + "plain upgrade changed 150 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+146)", "old 0.13.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -1006,14 +1007,14 @@ } ], "notes": [ - "plain upgrade changed 58 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md, .github/skills/coordinator-source-of-truth/SKILL.md … (+54)", + "plain upgrade changed 150 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+146)", "old 0.13.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { "producer": "0.13.1", "source": "npm", - "shape": "orphan", + "shape": "two-layer", "status": "pass", "provenance": "npm: @bradygaster/squad-cli@0.13.1 sha512-ogfx2nhyx6fqMyqlaI5tDnOzUbRdh+a6Uh/rMQ0aNJwAXnvQp4N37Xp/eOTyB5CxcB7E34NBtAfCxyXbWSiS8w==; @bradygaster/squad-sdk@0.13.1 sha512-1Jt6c9kahJq1JCcE77j/50D79YcXVG6Rt1genuLJ3O9J2SKTZKemlbvHgftkc5LgGH22DkWqHh5zLNEQ/p+ukA==", "checks": [ @@ -1085,14 +1086,14 @@ } ], "notes": [ - "plain upgrade changed 56 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md, .github/skills/coordinator-source-of-truth/SKILL.md … (+52); squad-state blobs for 3 seeded path(s) identical", + "plain upgrade changed 148 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+144); squad-state blobs for 3 seeded path(s) identical", "old 0.13.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { "producer": "0.13.1", "source": "npm", - "shape": "two-layer", + "shape": "orphan", "status": "pass", "provenance": "npm: @bradygaster/squad-cli@0.13.1 sha512-ogfx2nhyx6fqMyqlaI5tDnOzUbRdh+a6Uh/rMQ0aNJwAXnvQp4N37Xp/eOTyB5CxcB7E34NBtAfCxyXbWSiS8w==; @bradygaster/squad-sdk@0.13.1 sha512-1Jt6c9kahJq1JCcE77j/50D79YcXVG6Rt1genuLJ3O9J2SKTZKemlbvHgftkc5LgGH22DkWqHh5zLNEQ/p+ukA==", "checks": [ @@ -1164,7 +1165,7 @@ } ], "notes": [ - "plain upgrade changed 56 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md, .github/skills/coordinator-source-of-truth/SKILL.md … (+52); squad-state blobs for 3 seeded path(s) identical", + "plain upgrade changed 148 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+144); squad-state blobs for 3 seeded path(s) identical", "old 0.13.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -1243,7 +1244,7 @@ } ], "notes": [ - "plain upgrade changed 37 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+33)", + "plain upgrade changed 84 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+80)", "old 1.0.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -1322,7 +1323,7 @@ ], "notes": [ "producer wrote teamRoot=/squad at init", - "plain upgrade changed 38 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+34)", + "plain upgrade changed 85 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+81)", "old 1.0.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -1411,7 +1412,7 @@ } ], "notes": [ - "plain upgrade changed 42 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+38)", + "plain upgrade changed 89 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+85)", "old 1.0.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -1490,7 +1491,7 @@ } ], "notes": [ - "plain upgrade changed 35 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+31); squad-state blobs for 3 seeded path(s) identical", + "plain upgrade changed 82 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+78); squad-state blobs for 3 seeded path(s) identical", "old 1.0.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -1569,16 +1570,16 @@ } ], "notes": [ - "plain upgrade changed 35 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+31); squad-state blobs for 3 seeded path(s) identical", + "plain upgrade changed 82 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+78); squad-state blobs for 3 seeded path(s) identical", "old 1.0.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { - "producer": "1.0.1", - "source": "github-release", - "shape": "default", + "producer": "1.0.0", + "source": "npm", + "shape": "ai-team", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.0.1 asset squad-linux-x64.tar.gz id 610330551 sha256 283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7 (bundled node 22.20.0)", + "provenance": "npm: @bradygaster/squad-cli@1.0.0 sha512-mnZXnA+GAtp4KSLf7a4Zw0BpeOaHFAxYV5oxAy58ancZ9C2LguUGbcTxWIG0djM96XWET8K9fcncif3Vm6Z9RQ==; @bradygaster/squad-sdk@1.0.0 sha512-9f4NthJaQDq6ifXU3f6VfsZz2bbWs2u57fHhXKtDgDMSvZ3Xq8FPjMvddxhRvseWqaSlkYcWsag3M/lzTtKaJA==", "checks": [ { "id": "producer-artifact-verified", @@ -1610,8 +1611,7 @@ }, { "id": "ai-team-migrated", - "status": "skipped", - "detail": "not an .ai-team shape" + "status": "pass" }, { "id": "team-root-absent", @@ -1648,16 +1648,16 @@ } ], "notes": [ - "plain upgrade changed 37 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+33)", - "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" + "plain upgrade changed 84 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+80)", + "old 1.0.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { - "producer": "1.0.0", - "source": "npm", - "shape": "ai-team", + "producer": "1.0.1", + "source": "github-release", + "shape": "default", "status": "pass", - "provenance": "npm: @bradygaster/squad-cli@1.0.0 sha512-mnZXnA+GAtp4KSLf7a4Zw0BpeOaHFAxYV5oxAy58ancZ9C2LguUGbcTxWIG0djM96XWET8K9fcncif3Vm6Z9RQ==; @bradygaster/squad-sdk@1.0.0 sha512-9f4NthJaQDq6ifXU3f6VfsZz2bbWs2u57fHhXKtDgDMSvZ3Xq8FPjMvddxhRvseWqaSlkYcWsag3M/lzTtKaJA==", + "provenance": "github-release: github/squad@v1.0.1 asset squad-linux-x64.tar.gz id 610330551 sha256 283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7 (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -1689,7 +1689,8 @@ }, { "id": "ai-team-migrated", - "status": "pass" + "status": "skipped", + "detail": "not an .ai-team shape" }, { "id": "team-root-absent", @@ -1726,8 +1727,8 @@ } ], "notes": [ - "plain upgrade changed 37 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+33)", - "old 1.0.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" + "plain upgrade changed 84 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+80)", + "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { @@ -1735,7 +1736,7 @@ "source": "github-release", "shape": "profile", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.0.1 asset squad-linux-x64.tar.gz id 610330551 sha256 283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7 (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.0.1 asset squad-linux-x64.tar.gz id 610330551 sha256 283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7 (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -1805,7 +1806,7 @@ ], "notes": [ "producer wrote teamRoot=/squad at init", - "plain upgrade changed 38 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+34)", + "plain upgrade changed 85 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+81)", "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -1815,7 +1816,7 @@ "shape": "upstream", "status": "skipped", "reason": "Upstream inheritance (`.squad/upstream.json`, `squad upstream add`) is a Squad 2.0 feature; 1.0.1 cannot declare an upstream, so there is nothing for the upgrade to preserve.", - "provenance": "github-release: bradygaster/squad@v1.0.1 asset squad-linux-x64.tar.gz id 610330551 sha256 283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7 (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.0.1 asset squad-linux-x64.tar.gz id 610330551 sha256 283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7 (bundled node 22.20.0)", "checks": [], "notes": [] }, @@ -1824,7 +1825,7 @@ "source": "github-release", "shape": "legacy-casting", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.0.1 asset squad-linux-x64.tar.gz id 610330551 sha256 283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7 (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.0.1 asset squad-linux-x64.tar.gz id 610330551 sha256 283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7 (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -1894,7 +1895,7 @@ } ], "notes": [ - "plain upgrade changed 42 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+38)", + "plain upgrade changed 89 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+85)", "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -1903,7 +1904,7 @@ "source": "github-release", "shape": "orphan", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.0.1 asset squad-linux-x64.tar.gz id 610330551 sha256 283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7 (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.0.1 asset squad-linux-x64.tar.gz id 610330551 sha256 283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7 (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -1973,16 +1974,16 @@ } ], "notes": [ - "plain upgrade changed 35 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+31); squad-state blobs for 3 seeded path(s) identical", + "plain upgrade changed 82 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+78); squad-state blobs for 3 seeded path(s) identical", "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { "producer": "1.0.1", "source": "github-release", - "shape": "two-layer", + "shape": "ai-team", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.0.1 asset squad-linux-x64.tar.gz id 610330551 sha256 283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7 (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.0.1 asset squad-linux-x64.tar.gz id 610330551 sha256 283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7 (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -2014,8 +2015,7 @@ }, { "id": "ai-team-migrated", - "status": "skipped", - "detail": "not an .ai-team shape" + "status": "pass" }, { "id": "team-root-absent", @@ -2052,16 +2052,16 @@ } ], "notes": [ - "plain upgrade changed 35 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+31); squad-state blobs for 3 seeded path(s) identical", + "plain upgrade changed 84 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+80)", "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { - "producer": "1.1.0", + "producer": "1.0.1", "source": "github-release", - "shape": "default", + "shape": "two-layer", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.1.0 asset squad-linux-x64.tar.gz id 659607641 sha256 2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.0.1 asset squad-linux-x64.tar.gz id 610330551 sha256 283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7 (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -2131,16 +2131,16 @@ } ], "notes": [ - "plain upgrade changed 27 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/workflows/squad-ci.yml, .github/workflows/squad-ci.yml.local-backup … (+23)", - "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" + "plain upgrade changed 82 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+78); squad-state blobs for 3 seeded path(s) identical", + "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { - "producer": "1.0.1", + "producer": "1.1.0", "source": "github-release", - "shape": "ai-team", + "shape": "default", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.0.1 asset squad-linux-x64.tar.gz id 610330551 sha256 283a1893be9ddad11056dcc8bd0673eff6b48a789bbf523b3f7ec31994d27ac7 (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.1.0 asset squad-linux-x64.tar.gz id 659607641 sha256 2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -2172,7 +2172,8 @@ }, { "id": "ai-team-migrated", - "status": "pass" + "status": "skipped", + "detail": "not an .ai-team shape" }, { "id": "team-root-absent", @@ -2209,16 +2210,16 @@ } ], "notes": [ - "plain upgrade changed 37 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+33)", - "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" + "plain upgrade changed 54 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+50)", + "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { "producer": "1.1.0", "source": "github-release", - "shape": "profile", + "shape": "legacy-casting", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.1.0 asset squad-linux-x64.tar.gz id 659607641 sha256 2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.1.0 asset squad-linux-x64.tar.gz id 659607641 sha256 2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -2283,12 +2284,12 @@ }, { "id": "profile-untouched", - "status": "pass" + "status": "skipped", + "detail": "no profile squad seeded in this shape" } ], "notes": [ - "producer wrote teamRoot=/squad at init", - "plain upgrade changed 28 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/workflows/squad-ci.yml, .github/workflows/squad-ci.yml.local-backup … (+24)", + "plain upgrade changed 59 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+55)", "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -2298,16 +2299,16 @@ "shape": "upstream", "status": "skipped", "reason": "Upstream inheritance (`.squad/upstream.json`, `squad upstream add`) is a Squad 2.0 feature; 1.1.0 cannot declare an upstream, so there is nothing for the upgrade to preserve.", - "provenance": "github-release: bradygaster/squad@v1.1.0 asset squad-linux-x64.tar.gz id 659607641 sha256 2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.1.0 asset squad-linux-x64.tar.gz id 659607641 sha256 2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff (bundled node 22.20.0)", "checks": [], "notes": [] }, { "producer": "1.1.0", "source": "github-release", - "shape": "legacy-casting", + "shape": "profile", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.1.0 asset squad-linux-x64.tar.gz id 659607641 sha256 2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.1.0 asset squad-linux-x64.tar.gz id 659607641 sha256 2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -2372,21 +2373,21 @@ }, { "id": "profile-untouched", - "status": "skipped", - "detail": "no profile squad seeded in this shape" + "status": "pass" } ], "notes": [ - "plain upgrade changed 32 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/workflows/squad-ci.yml, .github/workflows/squad-ci.yml.local-backup … (+28)", + "producer wrote teamRoot=/squad at init", + "plain upgrade changed 55 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+51)", "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { - "producer": "HEAD", - "source": "pack", - "shape": "default", + "producer": "1.1.0", + "source": "github-release", + "shape": "ai-team", "status": "pass", - "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8 (v1.2.0-preview.1)", + "provenance": "github-release: github/squad@v1.1.0 asset squad-linux-x64.tar.gz id 659607641 sha256 2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -2418,8 +2419,7 @@ }, { "id": "ai-team-migrated", - "status": "skipped", - "detail": "not an .ai-team shape" + "status": "pass" }, { "id": "team-root-absent", @@ -2431,8 +2431,7 @@ }, { "id": "downgrade-probe", - "status": "skipped", - "detail": "producer is the target version" + "status": "pass" }, { "id": "clients-projected", @@ -2457,15 +2456,16 @@ } ], "notes": [ - "plain upgrade changed 13 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-insider-release.yml, .github/workflows/squad-label-enforce.yml … (+9)" + "plain upgrade changed 54 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+50)", + "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { "producer": "1.1.0", "source": "github-release", - "shape": "ai-team", + "shape": "two-layer", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.1.0 asset squad-linux-x64.tar.gz id 659607641 sha256 2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.1.0 asset squad-linux-x64.tar.gz id 659607641 sha256 2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -2497,7 +2497,8 @@ }, { "id": "ai-team-migrated", - "status": "pass" + "status": "skipped", + "detail": "not an .ai-team shape" }, { "id": "team-root-absent", @@ -2534,16 +2535,16 @@ } ], "notes": [ - "plain upgrade changed 27 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/workflows/squad-ci.yml, .github/workflows/squad-ci.yml.local-backup … (+23)", + "plain upgrade changed 52 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+48); squad-state blobs for 3 seeded path(s) identical", "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { "producer": "1.1.0", "source": "github-release", - "shape": "two-layer", + "shape": "orphan", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.1.0 asset squad-linux-x64.tar.gz id 659607641 sha256 2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.1.0 asset squad-linux-x64.tar.gz id 659607641 sha256 2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -2613,16 +2614,16 @@ } ], "notes": [ - "plain upgrade changed 25 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/workflows/squad-ci.yml, .github/workflows/squad-ci.yml.local-backup … (+21); squad-state blobs for 3 seeded path(s) identical", + "plain upgrade changed 52 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+48); squad-state blobs for 3 seeded path(s) identical", "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { - "producer": "1.1.0", - "source": "github-release", - "shape": "orphan", + "producer": "HEAD", + "source": "pack", + "shape": "profile", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.1.0 asset squad-linux-x64.tar.gz id 659607641 sha256 2de647a3feaef334051eb1b3084acba37a511818aad029a16abfe32fadb41aff (bundled node 22.20.0)", + "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9b254ba858abc19ef62e513993d9802312bfe34c (v2.0.0-preview.4)", "checks": [ { "id": "producer-artifact-verified", @@ -2667,7 +2668,8 @@ }, { "id": "downgrade-probe", - "status": "pass" + "status": "skipped", + "detail": "producer is the target version" }, { "id": "clients-projected", @@ -2687,21 +2689,19 @@ }, { "id": "profile-untouched", - "status": "skipped", - "detail": "no profile squad seeded in this shape" + "status": "pass" } ], "notes": [ - "plain upgrade changed 25 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/workflows/squad-ci.yml, .github/workflows/squad-ci.yml.local-backup … (+21); squad-state blobs for 3 seeded path(s) identical", - "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" + "plain upgrade changed 12 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-insider-release.yml, .github/workflows/squad-label-enforce.yml … (+8)" ] }, { "producer": "HEAD", "source": "pack", - "shape": "profile", + "shape": "default", "status": "pass", - "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8 (v1.2.0-preview.1)", + "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9b254ba858abc19ef62e513993d9802312bfe34c (v2.0.0-preview.4)", "checks": [ { "id": "producer-artifact-verified", @@ -2767,11 +2767,12 @@ }, { "id": "profile-untouched", - "status": "pass" + "status": "skipped", + "detail": "no profile squad seeded in this shape" } ], "notes": [ - "plain upgrade changed 13 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-insider-release.yml, .github/workflows/squad-label-enforce.yml … (+9)" + "plain upgrade changed 12 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-insider-release.yml, .github/workflows/squad-label-enforce.yml … (+8)" ] }, { @@ -2779,7 +2780,7 @@ "source": "pack", "shape": "legacy-casting", "status": "pass", - "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8 (v1.2.0-preview.1)", + "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9b254ba858abc19ef62e513993d9802312bfe34c (v2.0.0-preview.4)", "checks": [ { "id": "producer-artifact-verified", @@ -2850,7 +2851,7 @@ } ], "notes": [ - "plain upgrade changed 18 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-insider-release.yml, .github/workflows/squad-label-enforce.yml … (+14)" + "plain upgrade changed 17 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-insider-release.yml, .github/workflows/squad-label-enforce.yml … (+13)" ] }, { @@ -2858,7 +2859,7 @@ "source": "pack", "shape": "ai-team", "status": "pass", - "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8 (v1.2.0-preview.1)", + "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9b254ba858abc19ef62e513993d9802312bfe34c (v2.0.0-preview.4)", "checks": [ { "id": "producer-artifact-verified", @@ -2928,7 +2929,7 @@ } ], "notes": [ - "plain upgrade changed 13 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-insider-release.yml, .github/workflows/squad-label-enforce.yml … (+9)" + "plain upgrade changed 12 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-insider-release.yml, .github/workflows/squad-label-enforce.yml … (+8)" ] }, { @@ -2936,7 +2937,7 @@ "source": "pack", "shape": "orphan", "status": "pass", - "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8 (v1.2.0-preview.1)", + "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9b254ba858abc19ef62e513993d9802312bfe34c (v2.0.0-preview.4)", "checks": [ { "id": "producer-artifact-verified", @@ -3007,7 +3008,7 @@ } ], "notes": [ - "plain upgrade changed 11 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-insider-release.yml, .github/workflows/squad-label-enforce.yml … (+7); squad-state blobs for 3 seeded path(s) identical" + "plain upgrade changed 15 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-insider-release.yml, .github/workflows/squad-label-enforce.yml … (+11); squad-state blobs for 3 seeded path(s) identical" ] }, { @@ -3015,7 +3016,7 @@ "source": "pack", "shape": "two-layer", "status": "pass", - "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8 (v1.2.0-preview.1)", + "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9b254ba858abc19ef62e513993d9802312bfe34c (v2.0.0-preview.4)", "checks": [ { "id": "producer-artifact-verified", @@ -3086,7 +3087,7 @@ } ], "notes": [ - "plain upgrade changed 11 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-insider-release.yml, .github/workflows/squad-label-enforce.yml … (+7); squad-state blobs for 3 seeded path(s) identical" + "plain upgrade changed 15 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-insider-release.yml, .github/workflows/squad-label-enforce.yml … (+11); squad-state blobs for 3 seeded path(s) identical" ] }, { @@ -3094,7 +3095,7 @@ "source": "pack", "shape": "upstream", "status": "pass", - "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8 (v1.2.0-preview.1)", + "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9b254ba858abc19ef62e513993d9802312bfe34c (v2.0.0-preview.4)", "checks": [ { "id": "producer-artifact-verified", @@ -3165,7 +3166,7 @@ } ], "notes": [ - "plain upgrade changed 13 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-insider-release.yml, .github/workflows/squad-label-enforce.yml … (+9)" + "plain upgrade changed 12 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-insider-release.yml, .github/workflows/squad-label-enforce.yml … (+8)" ] } ] @@ -3174,12 +3175,12 @@ "platform": "win32", "arch": "x64", "node": "v22.23.4", - "target": "1.2.0-preview.1", - "targetSha": "9da01a2e41ba15794d31b0b8251ddaa3abd34ba8", - "harnessHash": "bca27c39dc2dcbffc3bf652bf1f45b471c09911a4bb9ba7e5b7247d25705a66e", - "event": "workflow_dispatch", - "runUrl": "https://github.com/bradygaster/squad/actions/runs/39903943542", - "recordedAt": "2026-10-23T05:10:56.011Z", + "target": "2.0.0-preview.4", + "targetSha": "9b254ba858abc19ef62e513993d9802312bfe34c", + "harnessHash": "a9713d320fdb479ced9cd311f36cf33bbd70d5733afb1617cd740831ce6767f8", + "event": "pull_request", + "runUrl": "https://github.com/github/squad/actions/runs/43147851248", + "recordedAt": "2026-11-12T03:28:29.483Z", "results": [ { "producer": "0.9.1", @@ -3214,7 +3215,7 @@ { "producer": "0.9.1", "source": "npm", - "shape": "profile", + "shape": "legacy-casting", "status": "pass", "provenance": "npm: @bradygaster/squad-cli@0.9.1 sha512-TcZqRbJk3XBg4N8Y5/5OWJeA52ARAMuzk4MGtB0bfdr87e7iJRV6lJEw8+OIYz9IQ+1JxN/Q30dZDALMOcdi2w==; @bradygaster/squad-sdk@0.9.1 sha512-1KqZ2N6Lt/B4uYlU/Su9JjA1s/7ng0OAEb9CZrkRk19YSPi6tg7K5QWdTKl5gY/gp8zPAJqfjVffOwrZwGlzmw==", "checks": [ @@ -3262,7 +3263,7 @@ { "id": "downgrade-probe", "status": "xfail", - "detail": "dry-run MODIFIED 44 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)" + "detail": "dry-run MODIFIED 67 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)" }, { "id": "clients-projected", @@ -3282,21 +3283,22 @@ }, { "id": "profile-untouched", - "status": "pass" + "status": "skipped", + "detail": "no profile squad seeded in this shape" } ], "notes": [ - "plain upgrade changed 100 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md, .copilot/skills/reviewer-protocol/SKILL.md … (+96)", - "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v1.2.0-preview.1 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 44 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)", + "plain upgrade changed 230 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md, .copilot/skills/ci-validation-gates/SKILL.md … (+226)", + "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v2.0.0-preview.4 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 67 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)", "downgrade-probe: F3" ] }, { - "producer": "0.9.1", + "producer": "0.13.1", "source": "npm", "shape": "default", "status": "pass", - "provenance": "npm: @bradygaster/squad-cli@0.9.1 sha512-TcZqRbJk3XBg4N8Y5/5OWJeA52ARAMuzk4MGtB0bfdr87e7iJRV6lJEw8+OIYz9IQ+1JxN/Q30dZDALMOcdi2w==; @bradygaster/squad-sdk@0.9.1 sha512-1KqZ2N6Lt/B4uYlU/Su9JjA1s/7ng0OAEb9CZrkRk19YSPi6tg7K5QWdTKl5gY/gp8zPAJqfjVffOwrZwGlzmw==", + "provenance": "npm: @bradygaster/squad-cli@0.13.1 sha512-ogfx2nhyx6fqMyqlaI5tDnOzUbRdh+a6Uh/rMQ0aNJwAXnvQp4N37Xp/eOTyB5CxcB7E34NBtAfCxyXbWSiS8w==; @bradygaster/squad-sdk@0.13.1 sha512-1Jt6c9kahJq1JCcE77j/50D79YcXVG6Rt1genuLJ3O9J2SKTZKemlbvHgftkc5LgGH22DkWqHh5zLNEQ/p+ukA==", "checks": [ { "id": "producer-artifact-verified", @@ -3341,8 +3343,7 @@ }, { "id": "downgrade-probe", - "status": "xfail", - "detail": "dry-run MODIFIED 44 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)" + "status": "pass" }, { "id": "clients-projected", @@ -3367,17 +3368,16 @@ } ], "notes": [ - "plain upgrade changed 100 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md, .copilot/skills/reviewer-protocol/SKILL.md … (+96)", - "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v1.2.0-preview.1 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 44 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)", - "downgrade-probe: F3" + "plain upgrade changed 150 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+146)", + "old 0.13.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { - "producer": "0.13.1", + "producer": "0.9.1", "source": "npm", "shape": "default", "status": "pass", - "provenance": "npm: @bradygaster/squad-cli@0.13.1 sha512-ogfx2nhyx6fqMyqlaI5tDnOzUbRdh+a6Uh/rMQ0aNJwAXnvQp4N37Xp/eOTyB5CxcB7E34NBtAfCxyXbWSiS8w==; @bradygaster/squad-sdk@0.13.1 sha512-1Jt6c9kahJq1JCcE77j/50D79YcXVG6Rt1genuLJ3O9J2SKTZKemlbvHgftkc5LgGH22DkWqHh5zLNEQ/p+ukA==", + "provenance": "npm: @bradygaster/squad-cli@0.9.1 sha512-TcZqRbJk3XBg4N8Y5/5OWJeA52ARAMuzk4MGtB0bfdr87e7iJRV6lJEw8+OIYz9IQ+1JxN/Q30dZDALMOcdi2w==; @bradygaster/squad-sdk@0.9.1 sha512-1KqZ2N6Lt/B4uYlU/Su9JjA1s/7ng0OAEb9CZrkRk19YSPi6tg7K5QWdTKl5gY/gp8zPAJqfjVffOwrZwGlzmw==", "checks": [ { "id": "producer-artifact-verified", @@ -3422,7 +3422,8 @@ }, { "id": "downgrade-probe", - "status": "pass" + "status": "xfail", + "detail": "dry-run MODIFIED 67 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)" }, { "id": "clients-projected", @@ -3447,14 +3448,15 @@ } ], "notes": [ - "plain upgrade changed 58 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md, .github/skills/coordinator-source-of-truth/SKILL.md … (+54)", - "old 0.13.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" + "plain upgrade changed 229 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md, .copilot/skills/ci-validation-gates/SKILL.md … (+225)", + "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v2.0.0-preview.4 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 67 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)", + "downgrade-probe: F3" ] }, { "producer": "0.9.1", "source": "npm", - "shape": "ai-team", + "shape": "profile", "status": "pass", "provenance": "npm: @bradygaster/squad-cli@0.9.1 sha512-TcZqRbJk3XBg4N8Y5/5OWJeA52ARAMuzk4MGtB0bfdr87e7iJRV6lJEw8+OIYz9IQ+1JxN/Q30dZDALMOcdi2w==; @bradygaster/squad-sdk@0.9.1 sha512-1KqZ2N6Lt/B4uYlU/Su9JjA1s/7ng0OAEb9CZrkRk19YSPi6tg7K5QWdTKl5gY/gp8zPAJqfjVffOwrZwGlzmw==", "checks": [ @@ -3488,7 +3490,8 @@ }, { "id": "ai-team-migrated", - "status": "pass" + "status": "skipped", + "detail": "not an .ai-team shape" }, { "id": "team-root-absent", @@ -3501,7 +3504,7 @@ { "id": "downgrade-probe", "status": "xfail", - "detail": "dry-run MODIFIED 44 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)" + "detail": "dry-run MODIFIED 67 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)" }, { "id": "clients-projected", @@ -3521,20 +3524,19 @@ }, { "id": "profile-untouched", - "status": "skipped", - "detail": "no profile squad seeded in this shape" + "status": "pass" } ], "notes": [ - "plain upgrade changed 100 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md, .copilot/skills/reviewer-protocol/SKILL.md … (+96)", - "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v1.2.0-preview.1 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 44 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)", + "plain upgrade changed 229 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md, .copilot/skills/ci-validation-gates/SKILL.md … (+225)", + "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v2.0.0-preview.4 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 67 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)", "downgrade-probe: F3" ] }, { "producer": "0.9.1", "source": "npm", - "shape": "legacy-casting", + "shape": "ai-team", "status": "pass", "provenance": "npm: @bradygaster/squad-cli@0.9.1 sha512-TcZqRbJk3XBg4N8Y5/5OWJeA52ARAMuzk4MGtB0bfdr87e7iJRV6lJEw8+OIYz9IQ+1JxN/Q30dZDALMOcdi2w==; @bradygaster/squad-sdk@0.9.1 sha512-1KqZ2N6Lt/B4uYlU/Su9JjA1s/7ng0OAEb9CZrkRk19YSPi6tg7K5QWdTKl5gY/gp8zPAJqfjVffOwrZwGlzmw==", "checks": [ @@ -3568,8 +3570,7 @@ }, { "id": "ai-team-migrated", - "status": "skipped", - "detail": "not an .ai-team shape" + "status": "pass" }, { "id": "team-root-absent", @@ -3582,7 +3583,7 @@ { "id": "downgrade-probe", "status": "xfail", - "detail": "dry-run MODIFIED 44 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)" + "detail": "dry-run MODIFIED 67 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)" }, { "id": "clients-projected", @@ -3607,8 +3608,8 @@ } ], "notes": [ - "plain upgrade changed 101 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md, .copilot/skills/reviewer-protocol/SKILL.md … (+97)", - "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v1.2.0-preview.1 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 44 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/cross-squad/SKILL.md, .copilot/skills/git-workflow/SKILL.md …)", + "plain upgrade changed 229 working-tree path(s), all Squad-owned: .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md, .copilot/skills/ci-validation-gates/SKILL.md … (+225)", + "old 0.9.1 upgrade --dry-run on upgraded project: exit 0, last lines \"\u001b[32m✓\u001b[0m refreshed .squad/templates/ | Upgrade complete: v2.0.0-preview.4 → v0.9.1 | \u001b[2mPreserves user state: team.md, decisions/, agents/*/history.md\u001b[0m\"; MODIFIED 67 files (20 coordinator/templates/workflows, e.g. .copilot/skills/agent-collaboration/SKILL.md, .copilot/skills/agent-conduct/SKILL.md, .copilot/skills/architectural-proposals/SKILL.md …)", "downgrade-probe: F3" ] }, @@ -3687,7 +3688,7 @@ ], "notes": [ "producer wrote teamRoot=\\squad at init", - "plain upgrade changed 59 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md, .github/skills/coordinator-source-of-truth/SKILL.md … (+55)", + "plain upgrade changed 151 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+147)", "old 0.13.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -3776,7 +3777,7 @@ } ], "notes": [ - "plain upgrade changed 58 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md, .github/skills/coordinator-source-of-truth/SKILL.md … (+54)", + "plain upgrade changed 150 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+146)", "old 0.13.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -3854,7 +3855,7 @@ } ], "notes": [ - "plain upgrade changed 58 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md, .github/skills/coordinator-source-of-truth/SKILL.md … (+54)", + "plain upgrade changed 150 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+146)", "old 0.13.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -3933,7 +3934,7 @@ } ], "notes": [ - "plain upgrade changed 56 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md, .github/skills/coordinator-source-of-truth/SKILL.md … (+52); squad-state blobs for 3 seeded path(s) identical", + "plain upgrade changed 148 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+144); squad-state blobs for 3 seeded path(s) identical", "old 0.13.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -4012,7 +4013,7 @@ } ], "notes": [ - "plain upgrade changed 56 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md, .github/skills/coordinator-source-of-truth/SKILL.md … (+52); squad-state blobs for 3 seeded path(s) identical", + "plain upgrade changed 148 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+144); squad-state blobs for 3 seeded path(s) identical", "old 0.13.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -4091,7 +4092,7 @@ } ], "notes": [ - "plain upgrade changed 38 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+34)", + "plain upgrade changed 85 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+81)", "old 1.0.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -4170,7 +4171,7 @@ ], "notes": [ "producer wrote teamRoot=\\squad at init", - "plain upgrade changed 39 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+35)", + "plain upgrade changed 86 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+82)", "old 1.0.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -4259,14 +4260,14 @@ } ], "notes": [ - "plain upgrade changed 43 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+39)", + "plain upgrade changed 90 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+86)", "old 1.0.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { "producer": "1.0.0", "source": "npm", - "shape": "ai-team", + "shape": "orphan", "status": "pass", "provenance": "npm: @bradygaster/squad-cli@1.0.0 sha512-mnZXnA+GAtp4KSLf7a4Zw0BpeOaHFAxYV5oxAy58ancZ9C2LguUGbcTxWIG0djM96XWET8K9fcncif3Vm6Z9RQ==; @bradygaster/squad-sdk@1.0.0 sha512-9f4NthJaQDq6ifXU3f6VfsZz2bbWs2u57fHhXKtDgDMSvZ3Xq8FPjMvddxhRvseWqaSlkYcWsag3M/lzTtKaJA==", "checks": [ @@ -4300,7 +4301,8 @@ }, { "id": "ai-team-migrated", - "status": "pass" + "status": "skipped", + "detail": "not an .ai-team shape" }, { "id": "team-root-absent", @@ -4337,16 +4339,16 @@ } ], "notes": [ - "plain upgrade changed 38 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+34)", + "plain upgrade changed 83 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+79); squad-state blobs for 3 seeded path(s) identical", "old 1.0.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { - "producer": "1.0.1", - "source": "github-release", - "shape": "default", + "producer": "1.0.0", + "source": "npm", + "shape": "ai-team", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.0.1 asset squad-win32-x64.zip id 610330559 sha256 f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe (bundled node 22.20.0)", + "provenance": "npm: @bradygaster/squad-cli@1.0.0 sha512-mnZXnA+GAtp4KSLf7a4Zw0BpeOaHFAxYV5oxAy58ancZ9C2LguUGbcTxWIG0djM96XWET8K9fcncif3Vm6Z9RQ==; @bradygaster/squad-sdk@1.0.0 sha512-9f4NthJaQDq6ifXU3f6VfsZz2bbWs2u57fHhXKtDgDMSvZ3Xq8FPjMvddxhRvseWqaSlkYcWsag3M/lzTtKaJA==", "checks": [ { "id": "producer-artifact-verified", @@ -4378,8 +4380,7 @@ }, { "id": "ai-team-migrated", - "status": "skipped", - "detail": "not an .ai-team shape" + "status": "pass" }, { "id": "team-root-absent", @@ -4416,16 +4417,16 @@ } ], "notes": [ - "plain upgrade changed 38 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+34)", - "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" + "plain upgrade changed 85 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+81)", + "old 1.0.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { - "producer": "1.0.0", - "source": "npm", - "shape": "orphan", + "producer": "1.0.1", + "source": "github-release", + "shape": "default", "status": "pass", - "provenance": "npm: @bradygaster/squad-cli@1.0.0 sha512-mnZXnA+GAtp4KSLf7a4Zw0BpeOaHFAxYV5oxAy58ancZ9C2LguUGbcTxWIG0djM96XWET8K9fcncif3Vm6Z9RQ==; @bradygaster/squad-sdk@1.0.0 sha512-9f4NthJaQDq6ifXU3f6VfsZz2bbWs2u57fHhXKtDgDMSvZ3Xq8FPjMvddxhRvseWqaSlkYcWsag3M/lzTtKaJA==", + "provenance": "github-release: github/squad@v1.0.1 asset squad-win32-x64.zip id 610330559 sha256 f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -4495,8 +4496,8 @@ } ], "notes": [ - "plain upgrade changed 36 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+32); squad-state blobs for 3 seeded path(s) identical", - "old 1.0.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" + "plain upgrade changed 85 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+81)", + "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { @@ -4574,7 +4575,7 @@ } ], "notes": [ - "plain upgrade changed 36 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+32); squad-state blobs for 3 seeded path(s) identical", + "plain upgrade changed 83 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+79); squad-state blobs for 3 seeded path(s) identical", "old 1.0.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -4583,7 +4584,7 @@ "source": "github-release", "shape": "profile", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.0.1 asset squad-win32-x64.zip id 610330559 sha256 f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.0.1 asset squad-win32-x64.zip id 610330559 sha256 f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -4653,7 +4654,7 @@ ], "notes": [ "producer wrote teamRoot=\\squad at init", - "plain upgrade changed 39 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+35)", + "plain upgrade changed 86 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+82)", "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -4663,7 +4664,7 @@ "shape": "upstream", "status": "skipped", "reason": "Upstream inheritance (`.squad/upstream.json`, `squad upstream add`) is a Squad 2.0 feature; 1.0.1 cannot declare an upstream, so there is nothing for the upgrade to preserve.", - "provenance": "github-release: bradygaster/squad@v1.0.1 asset squad-win32-x64.zip id 610330559 sha256 f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.0.1 asset squad-win32-x64.zip id 610330559 sha256 f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe (bundled node 22.20.0)", "checks": [], "notes": [] }, @@ -4672,7 +4673,7 @@ "source": "github-release", "shape": "legacy-casting", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.0.1 asset squad-win32-x64.zip id 610330559 sha256 f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.0.1 asset squad-win32-x64.zip id 610330559 sha256 f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -4742,7 +4743,7 @@ } ], "notes": [ - "plain upgrade changed 43 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+39)", + "plain upgrade changed 90 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+86)", "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -4751,7 +4752,7 @@ "source": "github-release", "shape": "ai-team", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.0.1 asset squad-win32-x64.zip id 610330559 sha256 f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.0.1 asset squad-win32-x64.zip id 610330559 sha256 f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -4820,7 +4821,7 @@ } ], "notes": [ - "plain upgrade changed 38 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+34)", + "plain upgrade changed 85 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+81)", "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -4829,7 +4830,7 @@ "source": "github-release", "shape": "default", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.1.0 asset squad-win32-x64.zip id 659607643 sha256 571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57 (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.1.0 asset squad-win32-x64.zip id 659607643 sha256 571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57 (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -4899,7 +4900,7 @@ } ], "notes": [ - "plain upgrade changed 31 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/workflows/squad-ci.yml, .github/workflows/squad-ci.yml.local-backup … (+27)", + "plain upgrade changed 58 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+54)", "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -4908,7 +4909,7 @@ "source": "github-release", "shape": "orphan", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.0.1 asset squad-win32-x64.zip id 610330559 sha256 f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.0.1 asset squad-win32-x64.zip id 610330559 sha256 f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -4978,7 +4979,7 @@ } ], "notes": [ - "plain upgrade changed 36 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+32); squad-state blobs for 3 seeded path(s) identical", + "plain upgrade changed 83 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+79); squad-state blobs for 3 seeded path(s) identical", "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -4987,7 +4988,7 @@ "source": "github-release", "shape": "two-layer", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.0.1 asset squad-win32-x64.zip id 610330559 sha256 f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.0.1 asset squad-win32-x64.zip id 610330559 sha256 f38a8f85e60982dba340187b761488f01de1aec68ea50c1b85725783d32df2fe (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -5057,7 +5058,7 @@ } ], "notes": [ - "plain upgrade changed 36 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-source-of-truth/SKILL.md, .github/workflows/squad-ci.yml … (+32); squad-state blobs for 3 seeded path(s) identical", + "plain upgrade changed 83 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+79); squad-state blobs for 3 seeded path(s) identical", "old 1.0.1 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -5066,7 +5067,7 @@ "source": "github-release", "shape": "profile", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.1.0 asset squad-win32-x64.zip id 659607643 sha256 571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57 (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.1.0 asset squad-win32-x64.zip id 659607643 sha256 571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57 (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -5136,7 +5137,7 @@ ], "notes": [ "producer wrote teamRoot=\\squad at init", - "plain upgrade changed 32 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/workflows/squad-ci.yml, .github/workflows/squad-ci.yml.local-backup … (+28)", + "plain upgrade changed 59 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+55)", "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -5146,7 +5147,7 @@ "shape": "upstream", "status": "skipped", "reason": "Upstream inheritance (`.squad/upstream.json`, `squad upstream add`) is a Squad 2.0 feature; 1.1.0 cannot declare an upstream, so there is nothing for the upgrade to preserve.", - "provenance": "github-release: bradygaster/squad@v1.1.0 asset squad-win32-x64.zip id 659607643 sha256 571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57 (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.1.0 asset squad-win32-x64.zip id 659607643 sha256 571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57 (bundled node 22.20.0)", "checks": [], "notes": [] }, @@ -5155,7 +5156,7 @@ "source": "github-release", "shape": "legacy-casting", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.1.0 asset squad-win32-x64.zip id 659607643 sha256 571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57 (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.1.0 asset squad-win32-x64.zip id 659607643 sha256 571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57 (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -5225,7 +5226,7 @@ } ], "notes": [ - "plain upgrade changed 36 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/workflows/squad-ci.yml, .github/workflows/squad-ci.yml.local-backup … (+32)", + "plain upgrade changed 63 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+59)", "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -5234,7 +5235,7 @@ "source": "github-release", "shape": "ai-team", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.1.0 asset squad-win32-x64.zip id 659607643 sha256 571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57 (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.1.0 asset squad-win32-x64.zip id 659607643 sha256 571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57 (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -5303,7 +5304,7 @@ } ], "notes": [ - "plain upgrade changed 31 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/workflows/squad-ci.yml, .github/workflows/squad-ci.yml.local-backup … (+27)", + "plain upgrade changed 58 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+54)", "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, @@ -5312,7 +5313,7 @@ "source": "github-release", "shape": "orphan", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.1.0 asset squad-win32-x64.zip id 659607643 sha256 571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57 (bundled node 22.20.0)", + "provenance": "github-release: github/squad@v1.1.0 asset squad-win32-x64.zip id 659607643 sha256 571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57 (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -5382,16 +5383,16 @@ } ], "notes": [ - "plain upgrade changed 29 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/workflows/squad-ci.yml, .github/workflows/squad-ci.yml.local-backup … (+25); squad-state blobs for 3 seeded path(s) identical", + "plain upgrade changed 56 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+52); squad-state blobs for 3 seeded path(s) identical", "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { - "producer": "HEAD", - "source": "pack", - "shape": "default", + "producer": "1.1.0", + "source": "github-release", + "shape": "two-layer", "status": "pass", - "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8 (v1.2.0-preview.1)", + "provenance": "github-release: github/squad@v1.1.0 asset squad-win32-x64.zip id 659607643 sha256 571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57 (bundled node 22.20.0)", "checks": [ { "id": "producer-artifact-verified", @@ -5436,8 +5437,7 @@ }, { "id": "downgrade-probe", - "status": "skipped", - "detail": "producer is the target version" + "status": "pass" }, { "id": "clients-projected", @@ -5462,15 +5462,16 @@ } ], "notes": [ - "plain upgrade changed 17 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-heartbeat.yml, .github/workflows/squad-insider-release.yml … (+13)" + "plain upgrade changed 56 working-tree path(s), all Squad-owned: .gitattributes, .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/skills/coordinator-response-mode/SKILL.md … (+52); squad-state blobs for 3 seeded path(s) identical", + "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" ] }, { - "producer": "1.1.0", - "source": "github-release", - "shape": "two-layer", + "producer": "HEAD", + "source": "pack", + "shape": "default", "status": "pass", - "provenance": "github-release: bradygaster/squad@v1.1.0 asset squad-win32-x64.zip id 659607643 sha256 571d734f7ae718fefefdfa9864f9f82a3d1fb1546f01ec8bf8866fa7e1829c57 (bundled node 22.20.0)", + "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9b254ba858abc19ef62e513993d9802312bfe34c (v2.0.0-preview.4)", "checks": [ { "id": "producer-artifact-verified", @@ -5515,7 +5516,8 @@ }, { "id": "downgrade-probe", - "status": "pass" + "status": "skipped", + "detail": "producer is the target version" }, { "id": "clients-projected", @@ -5540,8 +5542,7 @@ } ], "notes": [ - "plain upgrade changed 29 working-tree path(s), all Squad-owned: .github/agents/squad.agent.md, .github/agents/squad.agent.md.local-backup, .github/workflows/squad-ci.yml, .github/workflows/squad-ci.yml.local-backup … (+25); squad-state blobs for 3 seeded path(s) identical", - "old 1.1.0 upgrade --dry-run on upgraded project: exit 0, last lines \"overwrite ../.github/workflows/squad-triage.yml | overwrite ../.github/workflows/sync-squad-labels.yml | Run without --dry-run to apply.\"; modified nothing" + "plain upgrade changed 17 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-heartbeat.yml, .github/workflows/squad-insider-release.yml … (+13)" ] }, { @@ -5549,7 +5550,7 @@ "source": "pack", "shape": "profile", "status": "pass", - "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8 (v1.2.0-preview.1)", + "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9b254ba858abc19ef62e513993d9802312bfe34c (v2.0.0-preview.4)", "checks": [ { "id": "producer-artifact-verified", @@ -5627,7 +5628,7 @@ "source": "pack", "shape": "legacy-casting", "status": "pass", - "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8 (v1.2.0-preview.1)", + "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9b254ba858abc19ef62e513993d9802312bfe34c (v2.0.0-preview.4)", "checks": [ { "id": "producer-artifact-verified", @@ -5698,7 +5699,7 @@ } ], "notes": [ - "plain upgrade changed 22 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-heartbeat.yml, .github/workflows/squad-insider-release.yml … (+18)" + "plain upgrade changed 21 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-heartbeat.yml, .github/workflows/squad-insider-release.yml … (+17)" ] }, { @@ -5706,7 +5707,7 @@ "source": "pack", "shape": "ai-team", "status": "pass", - "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8 (v1.2.0-preview.1)", + "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9b254ba858abc19ef62e513993d9802312bfe34c (v2.0.0-preview.4)", "checks": [ { "id": "producer-artifact-verified", @@ -5784,7 +5785,7 @@ "source": "pack", "shape": "orphan", "status": "pass", - "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8 (v1.2.0-preview.1)", + "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9b254ba858abc19ef62e513993d9802312bfe34c (v2.0.0-preview.4)", "checks": [ { "id": "producer-artifact-verified", @@ -5855,7 +5856,7 @@ } ], "notes": [ - "plain upgrade changed 15 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-heartbeat.yml, .github/workflows/squad-insider-release.yml … (+11); squad-state blobs for 3 seeded path(s) identical" + "plain upgrade changed 20 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-heartbeat.yml, .github/workflows/squad-insider-release.yml … (+16); squad-state blobs for 3 seeded path(s) identical" ] }, { @@ -5863,7 +5864,7 @@ "source": "pack", "shape": "two-layer", "status": "pass", - "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8 (v1.2.0-preview.1)", + "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9b254ba858abc19ef62e513993d9802312bfe34c (v2.0.0-preview.4)", "checks": [ { "id": "producer-artifact-verified", @@ -5934,7 +5935,7 @@ } ], "notes": [ - "plain upgrade changed 15 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-heartbeat.yml, .github/workflows/squad-insider-release.yml … (+11); squad-state blobs for 3 seeded path(s) identical" + "plain upgrade changed 20 working-tree path(s), all Squad-owned: .github/workflows/squad-ci.yml, .github/workflows/squad-docs.yml, .github/workflows/squad-heartbeat.yml, .github/workflows/squad-insider-release.yml … (+16); squad-state blobs for 3 seeded path(s) identical" ] }, { @@ -5942,7 +5943,7 @@ "source": "pack", "shape": "upstream", "status": "pass", - "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8 (v1.2.0-preview.1)", + "provenance": "pack: npm pack of packages/squad-sdk + packages/squad-cli at 9b254ba858abc19ef62e513993d9802312bfe34c (v2.0.0-preview.4)", "checks": [ { "id": "producer-artifact-verified", diff --git a/test/__snapshots__/host-projection-regression-guards.test.ts.snap b/test/__snapshots__/host-projection-regression-guards.test.ts.snap index 61bab6d33..596988f71 100644 --- a/test/__snapshots__/host-projection-regression-guards.test.ts.snap +++ b/test/__snapshots__/host-projection-regression-guards.test.ts.snap @@ -36,6 +36,6 @@ exports[`host projection regression guards (#2438) > guard 1: the Copilot projec ".github/workflows/squad-triage.yml": "54f8a35713f9cd88bef9ee54b03da8f7d9cbd0bbdcbb03c73cb0dd37fd9bd1f2", ".github/workflows/sync-squad-labels.yml": "105f9f0b1e004833154c6511c84b7f70c465e69e1e8f3da8676ec1255396c34d", ".gitignore": "37aa6c4e49c873d2603b79e173ba46e216b18c596057964aac1cd6e62ff4e89f", - ".mcp.json": "bfdf4acbf0b686e8cf8450c5416b3983ceda7016718e6b1e5a551d2576d1eb80", + ".mcp.json": "fc7033ca67bd82d6c4aab068c6afd7b2cad9533517417dea213dbd5641b986a7", } `; diff --git a/test/cli/upgrade-portability.test.ts b/test/cli/upgrade-portability.test.ts index 6c5a008b6..d7c561524 100644 --- a/test/cli/upgrade-portability.test.ts +++ b/test/cli/upgrade-portability.test.ts @@ -61,17 +61,21 @@ describe('upgrade portability and destination newlines', () => { }; expect(config.mcpServers.custom).toEqual(custom); expect(config.mcpServers.squad_state.env).toEqual({ EXTRA: 'keep' }); - expect(config.mcpServers.squad_state.command).toBe(mode === 'packaged' - ? path.join(root, 'example-bundle', portableCommand) : process.execPath); + // Committed files carry no path into this machine's install (#2462): a standalone bundle + // becomes `squad` from PATH, an installed CLI becomes the repository launcher. + expect(config.mcpServers.squad_state.command).toBe(mode === 'packaged' ? 'squad' : 'node'); expect(config.mcpServers.squad_state.args).toEqual(mode === 'packaged' - ? ['state-mcp'] : [path.resolve('packages/squad-cli/dist/cli-entry.js'), 'state-mcp']); + ? ['state-mcp'] : ['.squad/squad.mjs', 'state-mcp']); expect(config.mcpServers.squad_state['x-squad']).toEqual({ managed: true, version, - machineSpecific: true, - source: mode === 'packaged' ? 'standalone' : 'local-entry', + machineSpecific: false, + source: mode === 'packaged' ? 'path' : 'repo-launcher', fallback, }); + expect(raw).not.toContain(root); + expect(raw).not.toContain(process.execPath); + expect(existsSync(path.join(root, '.squad', 'squad.mjs'))).toBe(mode === 'npm'); expect(raw).not.toMatch(/(? { { path: 'docs/src/content/docs/guide/contributors.md', text: `- [#298](${old}/pull/298)\nSee ${old}/blob/main/CONTRIBUTING.md` }, { path: 'docs/specification/core-v0.1.md', text: `Provenance: [PR #2074](${old}/pull/2074)\nReference implementation: ${old}` }, { path: 'docs/src/content/docs/whatsnew.md', text: `## v2.0.0\nFixed (${old}/pull/2279)\n## v1.1.0 — Stable Release\nPorted ([PR #131](${old}/pull/131))` }, - { - path: 'docs/src/content/docs/scenarios/upgrading.md', - text: `Report at ${old}/issues\n\n| [run](${old}/actions/runs/1) |\n\nAfter: ${old}/releases`, - }, ]; const { offenders, kept } = scanOldRepoLinks(files); @@ -240,8 +236,6 @@ describe('old-repository pattern', () => { 'docs/src/content/docs/guide/contributors.md:2: github.com/bradygaster/squad/blob/main/CONTRIBUTING.md', 'docs/specification/core-v0.1.md:2: github.com/bradygaster/squad', 'docs/src/content/docs/whatsnew.md:2: github.com/bradygaster/squad/pull/2279', - 'docs/src/content/docs/scenarios/upgrading.md:1: github.com/bradygaster/squad/issues', - 'docs/src/content/docs/scenarios/upgrading.md:5: github.com/bradygaster/squad/releases', ]); const keptByEntry = Object.fromEntries([...kept].filter(([, links]) => links.length > 0).map(([entry, links]) => [entry.path, links])); expect(keptByEntry, `kept:\n${JSON.stringify(keptByEntry, null, 2)}`).toEqual({ @@ -249,7 +243,6 @@ describe('old-repository pattern', () => { 'docs/specification/': ['docs/specification/core-v0.1.md:1: github.com/bradygaster/squad/pull/2074'], 'docs/src/content/docs/guide/contributors.md': ['docs/src/content/docs/guide/contributors.md:1: github.com/bradygaster/squad/pull/298'], 'docs/src/content/docs/whatsnew.md': ['docs/src/content/docs/whatsnew.md:4: github.com/bradygaster/squad/pull/131'], - 'docs/src/content/docs/scenarios/upgrading.md': ['docs/src/content/docs/scenarios/upgrading.md:3: github.com/bradygaster/squad/actions/runs/1'], }); }); @@ -257,12 +250,10 @@ describe('old-repository pattern', () => { const old = 'https://github.com/bradygaster/squad'; const files = [ { path: 'docs/src/content/docs/whatsnew.md', text: `## v2.0.0\nPorted ([PR #131](${old}/pull/131))` }, - { path: 'docs/src/content/docs/scenarios/upgrading.md', text: `\n| [run](${old}/actions/runs/1) |` }, ]; expectLines(scanOldRepoLinks(files).offenders, [ 'docs/src/content/docs/whatsnew.md:2: github.com/bradygaster/squad/pull/131', - 'docs/src/content/docs/scenarios/upgrading.md:2: github.com/bradygaster/squad/actions/runs/1', ]); }); }); diff --git a/test/init-next-steps.test.ts b/test/init-next-steps.test.ts new file mode 100644 index 000000000..79ddac398 --- /dev/null +++ b/test/init-next-steps.test.ts @@ -0,0 +1,80 @@ +/** + * `squad init` tells the user to run the host they chose, not always Copilot (#2453). + */ +import { describe, it, expect, vi } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { initNextSteps, runInit } from '../packages/squad-cli/src/cli/core/init.js'; + +const plain = (clients: Parameters[0], plugin?: boolean) => { + const next = initNextSteps(clients, plugin); + return { lines: next.lines.map(line => line('', '')), tips: next.tips }; +}; + +describe('initNextSteps (#2453)', () => { + it('defaults to Copilot when no client is chosen', () => { + for (const clients of [undefined, []]) { + const { lines, tips } = plain(clients); + expect(lines).toEqual(["Run copilot --agent squad and tell it what you're building."]); + expect(tips).toEqual(['"squad:copilot": "copilot --agent squad --additional-mcp-config @.mcp.json"']); + } + }); + + it('tells a Claude-only user to run Claude Code, never Copilot', () => { + const { lines, tips } = plain(['claude']); + expect(lines).toEqual(["Run claude --agent squad --mcp-config .mcp.json and tell it what you're building."]); + expect(tips).toEqual(['"squad:claude": "claude -p --agent squad --mcp-config .mcp.json"']); + expect([...lines, ...tips].join('\n')).not.toMatch(/copilot/i); + }); + + it('uses the plugin coordinator in plugin mode', () => { + const { lines, tips } = plain(['claude'], true); + expect(lines).toEqual(["Run claude; the Squad plugin's coordinator starts by default in this repository."]); + expect(tips).toEqual(['"squad:claude": "claude -p --agent squad:squad"']); + // --claude-plugin without --client keeps Copilot, the default host, beside Claude Code. + expect(plain(undefined, true).lines).toEqual([ + "GitHub Copilot: Run copilot --agent squad and tell it what you're building.", + "Claude Code: Run claude; the Squad plugin's coordinator starts by default in this repository.", + ]); + }); + + it('lists every chosen host, each with its name', () => { + const { lines, tips } = plain(['copilot', 'claude', 'kiro', 'claude']); + expect(lines).toEqual([ + "GitHub Copilot: Run copilot --agent squad and tell it what you're building.", + "Claude Code: Run claude --agent squad --mcp-config .mcp.json and tell it what you're building.", + 'Kiro: Open Kiro in this repository and ask for the squad agent.', + ]); + expect(tips).toHaveLength(2); + }); + + it('wraps the command, and only the command, in the styling', () => { + expect(initNextSteps(['claude']).lines[0]!('<', '>')).toBe( + "Run and tell it what you're building.", + ); + }); + + it('re-running init without flags follows what .squad/config.json records (Copilot review)', async () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-init-next-')); + const prev = { ni: process.env['SQUAD_NONINTERACTIVE'], nc: process.env['NO_COLOR'] }; + process.env['SQUAD_NONINTERACTIVE'] = '1'; + process.env['NO_COLOR'] = '1'; + const log = vi.spyOn(console, 'log').mockImplementation(() => undefined); + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + try { + await runInit(root, { clients: ['claude'], claudePlugin: true, includeWorkflows: false, includeVscodeDefault: false }); + log.mockClear(); + await runInit(root, { includeWorkflows: false, includeVscodeDefault: false }); + const out = log.mock.calls.map(c => String(c[0])).join('\n'); + expect(out).toContain("the Squad plugin's coordinator starts by default"); + expect(out).not.toContain('--mcp-config .mcp.json and tell it'); + } finally { + log.mockRestore(); + warn.mockRestore(); + if (prev.ni === undefined) delete process.env['SQUAD_NONINTERACTIVE']; else process.env['SQUAD_NONINTERACTIVE'] = prev.ni; + if (prev.nc === undefined) delete process.env['NO_COLOR']; else process.env['NO_COLOR'] = prev.nc; + fs.rmSync(root, { recursive: true, force: true }); + } + }, 60_000); +}); diff --git a/test/mcp-spec-init.test.ts b/test/mcp-spec-init.test.ts index 7ba52d4cd..252195506 100644 --- a/test/mcp-spec-init.test.ts +++ b/test/mcp-spec-init.test.ts @@ -233,7 +233,8 @@ describe('init.ts uses resolveSquadStateMcpSpec (asymmetry fix)', () => { 'init.ts', ); const src = readFileSync(initPath, 'utf-8'); - expect(src).toMatch(/resolveSquadStateMcpSpec/); + // The committed-file resolver wraps resolveSquadStateMcpSpec (#2462). + expect(src).toMatch(/resolve(Committed)?SquadStateMcpSpec/); expect(src).toMatch(/from ['"]\.\/mcp-spec\.js['"]/); }); diff --git a/test/portable-launch.test.ts b/test/portable-launch.test.ts new file mode 100644 index 000000000..311107d34 --- /dev/null +++ b/test/portable-launch.test.ts @@ -0,0 +1,277 @@ +/** + * Committed launch commands work on every clone (#2462): `squad init` and `squad upgrade` write + * a repository launcher instead of `node /cli-entry.js`, Claude hooks reach it through + * `$CLAUDE_PROJECT_DIR`, and `squad doctor` flags machine-specific commands. + */ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; +import { + claudeHookLaunch, + isSquadHookHandler, + mergeClaudeHooks, + stripClaudeHooks, + squadRepoLauncher, + SQUAD_REPO_LAUNCHER_MARKER, +} from '@bradygaster/squad-sdk/client'; +import { + committedSquadStateMcpSpec, + isTransientCliEntry, + resolveCommittedSquadStateMcpSpec, + squadDirInRoot, + writeSquadRepoLauncher, + type SquadStateMcpSpec, +} from '../packages/squad-cli/src/cli/core/mcp-spec.js'; +import { checkPortableSquadLaunch } from '../packages/squad-cli/src/cli/commands/doctor.js'; + +const fallback = { command: 'npx' as const, args: ['-y', '@bradygaster/squad-cli@9.9.9', 'state-mcp'] }; +const localEntry: SquadStateMcpSpec = { + command: '/opt/node/bin/node', + args: ['/home/someone/squad/dist/cli-entry.js', 'state-mcp'], + source: 'local-entry', + fallback, +}; +const repoLauncher = { command: 'node', args: ['.squad/squad.mjs', 'state-mcp'] }; + +let root: string; +beforeEach(() => { + root = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-portable-launch-')); +}); +afterEach(() => { + fs.rmSync(root, { recursive: true, force: true }); +}); + +function write(rel: string, body: string): string { + const file = path.join(root, rel); + fs.mkdirSync(path.dirname(file), { recursive: true }); + fs.writeFileSync(file, body); + return file; +} + +describe('committed spec (#2462)', () => { + it('turns an installed CLI into the repository launcher and a bundle into squad from PATH', () => { + expect(committedSquadStateMcpSpec(localEntry, '.squad')).toEqual({ ...repoLauncher, source: 'repo-launcher', fallback }); + expect(committedSquadStateMcpSpec({ ...localEntry, command: '/b/squad', args: ['state-mcp'], source: 'standalone' }, '.squad')) + .toEqual({ command: 'squad', args: ['state-mcp'], source: 'path', fallback }); + const pinned: SquadStateMcpSpec = { ...fallback, args: [...fallback.args], source: 'pinned' }; + expect(committedSquadStateMcpSpec(pinned, '.squad')).toBe(pinned); + // No squad directory inside the repository: no portable form. + expect(committedSquadStateMcpSpec(localEntry, null)).toBe(localEntry); + }); + + it('finds the squad directory only inside the repository', () => { + expect(squadDirInRoot(root, path.join(root, '.squad'))).toBe('.squad'); + expect(squadDirInRoot(root, path.join(root, 'team', '.squad'))).toBe('team/.squad'); + expect(squadDirInRoot(root, path.dirname(root))).toBeNull(); + expect(squadDirInRoot(root, root)).toBeNull(); + }); + + it('writes the launcher, refreshes its own and leaves a user file alone', () => { + expect(writeSquadRepoLauncher(root, '.squad', '9.9.9')).toBe('created'); + const file = path.join(root, '.squad', 'squad.mjs'); + expect(fs.readFileSync(file, 'utf8')).toContain(SQUAD_REPO_LAUNCHER_MARKER); + // Outside any bin/ directory, which repositories often ignore (Copilot review). + expect(path.relative(root, file).split(path.sep)).not.toContain('bin'); + expect(writeSquadRepoLauncher(root, '.squad', '9.9.9')).toBe('unchanged'); + expect(writeSquadRepoLauncher(root, '.squad', '9.9.10')).toBe('updated'); + fs.writeFileSync(file, '// mine\n'); + expect(writeSquadRepoLauncher(root, '.squad', '9.9.10')).toBe('skipped'); + expect(fs.readFileSync(file, 'utf8')).toBe('// mine\n'); + }); + + it('never writes through a symlink that leaves the workspace (Copilot review)', () => { + const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-outside-')); + try { + // The squad directory itself links outside the workspace. + fs.symlinkSync(outside, path.join(root, '.squad'), 'dir'); + expect(writeSquadRepoLauncher(root, '.squad', '9.9.9')).toBe('skipped'); + expect(fs.readdirSync(outside)).toEqual([]); + fs.rmSync(path.join(root, '.squad')); + // A dangling link at the launcher path, pointing outside. + fs.mkdirSync(path.join(root, '.squad')); + fs.symlinkSync(path.join(outside, 'planted.mjs'), path.join(root, '.squad', 'squad.mjs')); + expect(writeSquadRepoLauncher(root, '.squad', '9.9.9')).toBe('skipped'); + expect(fs.existsSync(path.join(outside, 'planted.mjs'))).toBe(false); + } finally { + fs.rmSync(outside, { recursive: true, force: true }); + } + }); + + it('keeps exact-version npx for a CLI running from a package runner cache (Copilot review)', () => { + for (const entry of [ + '/home/u/.npm/_npx/0a1b2c/node_modules/@bradygaster/squad-cli/dist/cli-entry.js', + 'C:\\Users\\u\\AppData\\Local\\npm-cache\\_npx\\0a1b\\node_modules\\@bradygaster\\squad-cli\\dist\\cli-entry.js', + '/home/u/.cache/pnpm/dlx/abc/node_modules/@bradygaster/squad-cli/dist/cli-entry.js', + '/tmp/xfs-1a2b3c/dlx-5555/node_modules/@bradygaster/squad-cli/dist/cli-entry.js', + ]) { + expect(isTransientCliEntry(entry), entry).toBe(true); + expect(committedSquadStateMcpSpec({ ...localEntry, args: [entry, 'state-mcp'] }, '.squad')) + .toEqual({ command: 'npx', args: fallback.args, source: 'pinned' }); + } + expect(isTransientCliEntry('/usr/local/lib/node_modules/@bradygaster/squad-cli/dist/cli-entry.js')).toBe(false); + expect(isTransientCliEntry('/home/u/src/dlxtools/squad/dist/cli-entry.js')).toBe(false); + }); + + it('keeps the machine-specific spec when the launcher path is user-owned', async () => { + write('.squad/squad.mjs', '// mine\n'); + const spec = await resolveCommittedSquadStateMcpSpec('9.9.9', root, path.join(root, '.squad'), { + standaloneCheck: () => null, + localEntryCheck: () => '/home/someone/squad/dist/cli-entry.js', + }); + expect(spec.source).toBe('local-entry'); + }); + + it('read-only callers get the launcher spec without a file being written', async () => { + const spec = await resolveCommittedSquadStateMcpSpec('9.9.9', root, path.join(root, '.squad'), { + standaloneCheck: () => null, + localEntryCheck: () => '/x/cli-entry.js', + write: false, + }); + expect(spec.source).toBe('repo-launcher'); + expect(fs.existsSync(path.join(root, '.squad'))).toBe(false); + }); +}); + +describe('Claude hooks through the repository launcher (#2462)', () => { + it('runs the launcher from $CLAUDE_PROJECT_DIR as a shell command', () => { + expect(claudeHookLaunch(repoLauncher, 'Stop')).toEqual({ + command: 'node "$CLAUDE_PROJECT_DIR/.squad/squad.mjs" hook claude Stop', + args: [], + }); + // Anything else keeps the argv form. + expect(claudeHookLaunch({ command: 'node', args: ['/abs/.squad/squad.mjs', 'state-mcp'] }, 'Stop')!.args) + .toEqual(['/abs/.squad/squad.mjs', 'hook', 'claude', 'Stop']); + expect(claudeHookLaunch({ command: 'node', args: ['../x/bin/squad.mjs', 'state-mcp'] }, 'Stop')!.args.length).toBe(4); + }); + + it('replaces old machine-specific handlers, keeps user hooks, and strips cleanly', () => { + const user = { type: 'command', command: 'echo user' }; + const old = { + type: 'command', + command: '/opt/node/bin/node', + args: ['/home/someone/squad/dist/cli-entry.js', 'hook', 'claude', 'Stop'], + timeout: 15, + }; + const merged = mergeClaudeHooks({ hooks: { Stop: [{ hooks: [user] }, { hooks: [old] }] } }, repoLauncher); + const text = JSON.stringify(merged); + expect(text).not.toContain('/home/someone'); + expect(text).toContain('echo user'); + const handlers = (merged['hooks'] as Record[] }[]>)['Stop']!.flatMap(g => g.hooks); + const squad = handlers.filter(isSquadHookHandler); + expect(squad).toEqual([{ type: 'command', command: 'node "$CLAUDE_PROJECT_DIR/.squad/squad.mjs" hook claude Stop', timeout: 15 }]); + expect(stripClaudeHooks(merged)).toEqual({ hooks: { Stop: [{ hooks: [user] }] } }); + }); + + it('does not take a look-alike user command for Squad\'s', () => { + expect(isSquadHookHandler({ type: 'command', command: 'node "$CLAUDE_PROJECT_DIR/.squad/squad.mjs" hook claude Stop; rm -rf x' })).toBe(false); + expect(isSquadHookHandler({ type: 'command', command: 'node "$CLAUDE_PROJECT_DIR/.squad/squad.mjs" hook claude Nope' })).toBe(false); + expect(isSquadHookHandler({ type: 'command', command: 'squad hook claude Stop' })).toBe(false); + }); +}); + +describe('the repository launcher script (#2462)', () => { + /** A fake CLI that prints its argv, at `/node_modules/@bradygaster/squad-cli`. */ + function fakeCli(dir: string, version: string): void { + const pkg = path.join(dir, 'node_modules', '@bradygaster', 'squad-cli'); + fs.mkdirSync(path.join(pkg, 'dist'), { recursive: true }); + fs.writeFileSync(path.join(pkg, 'package.json'), JSON.stringify({ name: '@bradygaster/squad-cli', version })); + fs.writeFileSync(path.join(pkg, 'dist', 'cli-entry.js'), `console.log('fake ${version}', JSON.stringify(process.argv.slice(2)));\n`); + } + + function run(args: string[], env: Record = {}, cwd = os.tmpdir()) { + return spawnSync(process.execPath, [path.join(root, '.squad', 'squad.mjs'), ...args], { + cwd, + encoding: 'utf8', + // PATH without a real squad, so the result depends only on the repository. + env: { PATH: path.dirname(process.execPath), ...env }, + }); + } + + beforeEach(() => { + write('.squad/squad.mjs', squadRepoLauncher('2.0.0', '.squad')); + }); + + it('runs the repository\'s own install from any working directory', () => { + fakeCli(root, '2.0.1'); + const out = run(['state-mcp']); + expect(out.status).toBe(0); + expect(out.stdout.trim()).toBe('fake 2.0.1 ["state-mcp"]'); + }); + + it('skips a CLI older than the version that wrote it, and says how to install', () => { + fakeCli(root, '1.9.0'); + const out = run(['state-mcp']); + expect(out.status).toBe(1); + expect(out.stderr).toContain('no Squad CLI 2.0.0 or newer found'); + expect(out.stderr).toContain('npm install -g @bradygaster/squad-cli@2.0.0'); + }); + + it('falls back to the CLI recorded on this machine, and skips an older one', async () => { + const { recordSquadCli } = await import('../packages/squad-cli/src/cli/core/mcp-spec.js'); + const other = path.join(root, 'other-machine-dir'); + fakeCli(other, '2.0.2'); + const home = path.join(root, 'squad-home'); + const entry = path.join(other, 'node_modules', '@bradygaster', 'squad-cli', 'dist', 'cli-entry.js'); + recordSquadCli(entry, '2.0.2', { SQUAD_HOME: home }); + expect(JSON.parse(fs.readFileSync(path.join(home, 'cli.json'), 'utf8'))).toEqual({ entry, version: '2.0.2' }); + expect(run(['state-mcp'], { SQUAD_HOME: home }).stdout.trim()).toBe('fake 2.0.2 ["state-mcp"]'); + fs.rmSync(other, { recursive: true }); + fakeCli(other, '1.0.0'); + expect(run(['state-mcp'], { SQUAD_HOME: home }).status).toBe(1); + }); + + it('honours SQUAD_CLI', () => { + const entry = write('elsewhere/cli.js', "console.log('override', process.argv.slice(2).join(' '));\n"); + expect(run(['--version'], { SQUAD_CLI: entry }).stdout.trim()).toBe('override --version'); + }); + + it('is a no-op for hooks unless the team opted in', () => { + write('.squad/config.json', JSON.stringify({ version: 1 })); + const off = run(['hook', 'claude', 'Stop']); + expect(off.status).toBe(0); + expect(off.stdout).toBe(''); + write('.squad/config.json', JSON.stringify({ version: 1, claudeHooks: 'advisory' })); + fakeCli(root, '2.0.0'); + expect(run(['hook', 'claude', 'Stop']).stdout.trim()).toBe('fake 2.0.0 ["hook","claude","Stop"]'); + }); +}); + +describe('squad doctor: portable launch commands (#2462)', () => { + const mcp = (command: string, args: string[]) => JSON.stringify({ mcpServers: { squad_state: { command, args } } }); + + it('is silent when nothing is committed', () => { + expect(checkPortableSquadLaunch(root)).toBeUndefined(); + }); + + it('fails on a path from another machine and warns on this machine\'s own path', () => { + write('.mcp.json', mcp('/nowhere/bin/node', ['/nowhere/cli-entry.js', 'state-mcp'])); + expect(checkPortableSquadLaunch(root)).toMatchObject({ status: 'fail' }); + const entry = write('install/dist/cli-entry.js', ''); + write('.mcp.json', mcp(process.execPath, [entry, 'state-mcp'])); + expect(checkPortableSquadLaunch(root)).toMatchObject({ status: 'warn' }); + }); + + it('recognises Windows paths on any OS, and finds a bare node on PATH (Copilot review)', () => { + write('.mcp.json', mcp('C:\\Program Files\\nodejs\\node.exe', ['C:\\Users\\u\\squad\\dist\\cli-entry.js', 'state-mcp'])); + expect(checkPortableSquadLaunch(root)).toMatchObject({ status: process.platform === 'win32' ? expect.any(String) : 'fail' }); + const entry = write('install/dist/cli-entry.js', ''); + write('.mcp.json', mcp('node', [entry, 'state-mcp'])); + expect(checkPortableSquadLaunch(root)).toMatchObject({ status: 'warn' }); + }); + + it('checks Claude hooks too, and that the launcher exists', () => { + write('.mcp.json', mcp('node', ['.squad/squad.mjs', 'state-mcp'])); + write('.claude/settings.json', JSON.stringify({ + hooks: { Stop: [{ hooks: [{ type: 'command', command: 'node "$CLAUDE_PROJECT_DIR/.squad/squad.mjs" hook claude Stop' }] }] }, + })); + expect(checkPortableSquadLaunch(root)).toMatchObject({ status: 'fail', message: expect.stringContaining('missing') }); + write('.squad/squad.mjs', squadRepoLauncher('2.0.0', '.squad')); + expect(checkPortableSquadLaunch(root)).toMatchObject({ status: 'pass', message: '2 committed launch command(s) are portable' }); + write('.claude/settings.json', JSON.stringify({ + hooks: { Stop: [{ hooks: [{ type: 'command', command: '/nowhere/node', args: ['/nowhere/cli-entry.js', 'hook', 'claude', 'Stop'] }] }] }, + })); + expect(checkPortableSquadLaunch(root)).toMatchObject({ status: 'fail', message: expect.stringContaining('.claude/settings.json') }); + }); +}); diff --git a/test/squad-20-upgrade-matrix.test.ts b/test/squad-20-upgrade-matrix.test.ts index 7a452b8fa..30e5caa28 100644 --- a/test/squad-20-upgrade-matrix.test.ts +++ b/test/squad-20-upgrade-matrix.test.ts @@ -206,7 +206,8 @@ describe('upgrade-matrix.json integrity', () => { } else if (p.source === 'github-release') { expect('release' in p.artifacts, `${p.version}: release producer needs artifacts.release`).toBe(true); const rel = (p.artifacts as { release: { repository: string; tag: string; assets: Record } }).release; - expect(rel.repository).toBe('bradygaster/squad'); + // The repository moved from bradygaster/squad to github/squad; release asset ids are unchanged. + expect(rel.repository).toBe('github/squad'); expect(rel.tag).toBe(`v${p.version}`); for (const name of ['squad-linux-x64.tar.gz', 'squad-linux-arm64.tar.gz', 'squad-darwin-x64.tar.gz', 'squad-darwin-arm64.tar.gz', 'squad-win32-x64.zip', 'squad-win32-arm64.zip']) { const a = rel.assets[name];