Repository navigation
[4/4] test(upgrade): old-version × OS upgrade matrix - #2282
Conversation
🛫 PR Readiness Check
PR Scope: 🔧 Infrastructure
|
| Status | Check | Details |
|---|---|---|
| ❌ | Single commit | 12 commits — consider squashing before review |
| ✅ | Not in draft | Ready for review |
| ✅ | Branch up to date | Up to date with squad2-c-personal-removal-and-fixes |
| ❌ | Copilot review | No Copilot review yet — it may still be processing |
| ✅ | Changeset present | No source files changed — changeset not required |
| ✅ | Scope clean | No .squad/ or docs/proposals/ files |
| ✅ | No merge conflicts | No merge conflicts |
| ✅ | Copilot threads resolved | 1 active Copilot thread(s) resolved (4 outdated skipped) |
| ❌ | CI passing | 2 check(s) failing: test, gh-aw strict compile |
Files Changed (8 files, +8070 −0)
| File | +/− |
|---|---|
.github/workflows/squad-upgrade-matrix.yml |
+92 −0 |
cspell.json |
+2 −0 |
docs/src/content/docs/get-started/migration.md |
+1 −0 |
docs/src/content/docs/scenarios/upgrading.md |
+258 −0 |
scripts/render-upgrade-matrix.mjs |
+338 −0 |
test-fixtures/spec/suite-v0.2/upgrade-matrix.json |
+6022 −0 |
test/fixtures/upgrade-matrix/fs-access-log.cjs |
+52 −0 |
test/squad-20-upgrade-matrix.test.ts |
+1305 −0 |
Total: +8070 −0
This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.
🟠 Impact Analysis — PR #2282Risk tier: 🟠 HIGH 📊 Summary
🎯 Risk Factors
📦 Modules Affectedci-workflows (1 file)
docs (2 files)
root (2 files)
scripts (1 file)
tests (2 files)
This report is generated automatically for every PR. See #733 for details. |
🏗️ Architectural Review
Automated architectural review — informational only. |
…zation check [3/3] Data's upgrade matrix (#2282) findings UM-F1 and UM-F2: - UM-F1: projects initialized by 0.13-1.1 while a profile squad existed carry `teamRoot: "<platform-config>/squad"`. `isRetiredProfileTeamRoot()` (SDK) recognises the platform config dir, `~/.squad`, `SQUAD_HOME` and the home dir; `resolveSquadPaths` never follows such a teamRoot (local mode), and `squad upgrade` removes the key with one neutral line ("Removed stale teamRoot pointing to <path>; project state is now local"; --dry-run only announces). A genuine dual-root teamRoot (another repo) is preserved. - UM-F2: `stripAgentStamps()` normalizes every stamp form the upgrader writes (`<!-- version: X -->`, `- **Version:** X`, `Squad vX`, `@bradygaster/squad-cli@X`, legacy `<!-- squad-cli vX -->`) before comparing squad.agent.md with the template, and an existing `.local-backup` holding the same bytes is not rewritten. - Docs: upgrading guide notes both and adds "Downgrading and old CLIs" guidance. - Tests: test/upgrade-profile-teamroot.test.ts (6) — both findings verified red against the pre-fix SDK/CLI (remote mode; "local customizations" warning on pass 1). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b822a455-fa94-49a2-9127-eee479ab954e
There was a problem hiding this comment.
🟡 Changes recommended
Historical references conflict with the existing CI gate, scenario failures can escape assertions, and preservation checks leave important state unchecked.
5 open issues
Add retired-symbol exceptions for historical references · New Assert upstream setup scenarios pass before validating upgrades · New Verify preserved state blobs on the backend branch · New Check profile integrity after every lifecycle phase · New Verify byte-exact restoration of all host integrations · New
What changed in this PR
Adds the test and evidence layer for the Squad 2.0 upgrade stack, exercising older published releases against the current packed CLI without changing product code.
Changes:
- Adds an opt-in version × state-shape matrix with preservation checks, expected-failure tracking, and always-on fixture integrity checks.
- Adds Windows/Linux CI execution and result artifacts.
- Generates upgrade-path documentation from recorded results and links it from the migration guide.
| File | Description |
|---|---|
test/squad-20-upgrade-matrix.test.ts |
Adds upgrade scenarios and fixture/documentation integrity checks. |
scripts/render-upgrade-matrix.mjs |
Merges results, summarizes runs, and renders upgrade documentation. |
docs/src/content/docs/scenarios/upgrading.md |
Adds recorded upgrade paths, known defects, and downgrade guidance. |
docs/src/content/docs/get-started/migration.md |
Links to tested upgrade paths. |
cspell.json |
Allows expected-failure status terminology. |
.github/workflows/squad-upgrade-matrix.yml |
Runs the matrix on Linux and Windows and checks documentation synchronization. |
Review effort: Balanced
…zation check [3/3] Data's upgrade matrix (#2282) findings UM-F1 and UM-F2: - UM-F1: projects initialized by 0.13-1.1 while a profile squad existed carry `teamRoot: "<platform-config>/squad"`. `isRetiredProfileTeamRoot()` (SDK) recognises the platform config dir, `~/.squad`, `SQUAD_HOME` and the home dir; `resolveSquadPaths` never follows such a teamRoot (local mode), and `squad upgrade` removes the key with one neutral line ("Removed stale teamRoot pointing to <path>; project state is now local"; --dry-run only announces). A genuine dual-root teamRoot (another repo) is preserved. - UM-F2: `stripAgentStamps()` normalizes every stamp form the upgrader writes (`<!-- version: X -->`, `- **Version:** X`, `Squad vX`, `@bradygaster/squad-cli@X`, legacy `<!-- squad-cli vX -->`) before comparing squad.agent.md with the template, and an existing `.local-backup` holding the same bytes is not rewritten. - Docs: upgrading guide notes both and adds "Downgrading and old CLIs" guidance. - Tests: test/upgrade-profile-teamroot.test.ts (6) — both findings verified red against the pre-fix SDK/CLI (remote mode; "local customizations" warning on pass 1). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b822a455-fa94-49a2-9127-eee479ab954e
9b74e85 to
d8dba30
Compare
…zation check [3/3] Data's upgrade matrix (#2282) findings UM-F1 and UM-F2: - UM-F1: projects initialized by 0.13-1.1 while a profile squad existed carry `teamRoot: "<platform-config>/squad"`. `isRetiredProfileTeamRoot()` (SDK) recognises the platform config dir, `~/.squad`, `SQUAD_HOME` and the home dir; `resolveSquadPaths` never follows such a teamRoot (local mode), and `squad upgrade` removes the key with one neutral line ("Removed stale teamRoot pointing to <path>; project state is now local"; --dry-run only announces). A genuine dual-root teamRoot (another repo) is preserved. - UM-F2: `stripAgentStamps()` normalizes every stamp form the upgrader writes (`<!-- version: X -->`, `- **Version:** X`, `Squad vX`, `@bradygaster/squad-cli@X`, legacy `<!-- squad-cli vX -->`) before comparing squad.agent.md with the template, and an existing `.local-backup` holding the same bytes is not rewritten. - Docs: upgrading guide notes both and adds "Downgrading and old CLIs" guidance. - Tests: test/upgrade-profile-teamroot.test.ts (6) — both findings verified red against the pre-fix SDK/CLI (remote mode; "local customizations" warning on pass 1). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b822a455-fa94-49a2-9127-eee479ab954e
56556ed to
e35383e
Compare
…zation check [3/3] Data's upgrade matrix (#2282) findings UM-F1 and UM-F2: - UM-F1: projects initialized by 0.13-1.1 while a profile squad existed carry `teamRoot: "<platform-config>/squad"`. `isRetiredProfileTeamRoot()` (SDK) recognises the platform config dir, `~/.squad`, `SQUAD_HOME` and the home dir; `resolveSquadPaths` never follows such a teamRoot (local mode), and `squad upgrade` removes the key with one neutral line ("Removed stale teamRoot pointing to <path>; project state is now local"; --dry-run only announces). A genuine dual-root teamRoot (another repo) is preserved. - UM-F2: `stripAgentStamps()` normalizes every stamp form the upgrader writes (`<!-- version: X -->`, `- **Version:** X`, `Squad vX`, `@bradygaster/squad-cli@X`, legacy `<!-- squad-cli vX -->`) before comparing squad.agent.md with the template, and an existing `.local-backup` holding the same bytes is not rewritten. - Docs: upgrading guide notes both and adds "Downgrading and old CLIs" guidance. - Tests: test/upgrade-profile-teamroot.test.ts (6) — both findings verified red against the pre-fix SDK/CLI (remote mode; "local customizations" warning on pass 1). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b822a455-fa94-49a2-9127-eee479ab954e
a832837 to
4a20625
Compare
…zation check [3/3] Data's upgrade matrix (#2282) findings UM-F1 and UM-F2: - UM-F1: projects initialized by 0.13-1.1 while a profile squad existed carry `teamRoot: "<platform-config>/squad"`. `isRetiredProfileTeamRoot()` (SDK) recognises the platform config dir, `~/.squad`, `SQUAD_HOME` and the home dir; `resolveSquadPaths` never follows such a teamRoot (local mode), and `squad upgrade` removes the key with one neutral line ("Removed stale teamRoot pointing to <path>; project state is now local"; --dry-run only announces). A genuine dual-root teamRoot (another repo) is preserved. - UM-F2: `stripAgentStamps()` normalizes every stamp form the upgrader writes (`<!-- version: X -->`, `- **Version:** X`, `Squad vX`, `@bradygaster/squad-cli@X`, legacy `<!-- squad-cli vX -->`) before comparing squad.agent.md with the template, and an existing `.local-backup` holding the same bytes is not rewritten. - Docs: upgrading guide notes both and adds "Downgrading and old CLIs" guidance. - Tests: test/upgrade-profile-teamroot.test.ts (6) — both findings verified red against the pre-fix SDK/CLI (remote mode; "local customizations" warning on pass 1). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b822a455-fa94-49a2-9127-eee479ab954e
Add test/squad-20-upgrade-matrix.test.ts, which drives real published Squad releases (npm tarballs 0.9.1 / 0.13.1 / 1.0.0 via the npm-configured registry, GitHub Release standalone bundles for 1.0.1 / 1.1.0 whose npm publish never completed, plus HEAD->HEAD) through `squad init` under seven project state shapes (default, profile, legacy-casting, orphan, two-layer, ai-team, upstream) and upgrades each with the CLI packed from this tree. Per scenario it asserts: user state byte-exact, casting pair valid, state backend preserved, .ai-team migrated, teamRoot absent, profile tree sha256-identical, user .mcp.json/AGENTS.md/CLAUDE.md/.gitignore content kept, all 7 host projections, `squad doctor` green, uninstallClients rollback byte-exact, second upgrade a no-op, and records the old CLI's behaviour against the upgraded project. Producers that cannot be obtained are skipped loudly with the reason in the results JSON. Opt-in via SQUAD_UPGRADE_MATRIX=1; the fixture/docs integrity layer always runs. test-fixtures/spec/suite-v0.2/upgrade-matrix.json is the source of truth (producers, shapes, checks, findings, expected-failure pins, recorded runs); scripts/render-upgrade-matrix.mjs renders it into the "Tested upgrade paths" section of docs/.../scenarios/upgrading.md (--check / --merge / --summary). Pinned checks are mutation-proven: an xpass fails the suite. Findings recorded (product fixes belong to other layers): - F1 stale teamRoot from 0.13.x-1.x init survives `squad upgrade` and the project resolves in remote mode against the removed profile squad; - F2 writeAgentTemplate never recognises its own version stamp, so every upgrade reports "local customizations" and overwrites .local-backup; - F3 0.9.1 `upgrade --dry-run` is not dry (downgrades in place). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10c92f7c-6aa8-4352-8ff2-eb1568d80993
…ndows New workflow .github/workflows/squad-upgrade-matrix.yml: on pull_request to dev/main/squad2-* and on workflow_dispatch (optional producer filter), a strategy.matrix.os [ubuntu-latest, windows-latest] job installs, builds with SKIP_BUILD_BUMP=1, runs test/squad-20-upgrade-matrix.test.ts with SQUAD_UPGRADE_MATRIX=1 and uploads results-<os>.json as an artifact. Old npm producers resolve through whatever registry npm is configured for on the runner (NPM_CONFIG_REGISTRY wins); standalone bundles come from GitHub Releases with SHA256SUMS verification. Unobtainable producers are skipped loudly, never silently passed. A second job checks the rendered docs section is in sync with upgrade-matrix.json. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10c92f7c-6aa8-4352-8ff2-eb1568d80993
…e5f935 Merge the suite results into test-fixtures/spec/suite-v0.2/upgrade-matrix.json and re-render the "Tested upgrade paths" section of upgrading.md: win32/x64 (node 24.14): 35 pass, 0 fail, 7 skipped (28 cells carry pinned findings) linux/x64 (node 22.23, Docker node:22-bookworm): 35 pass, 0 fail, 7 skipped (28 pinned) Every producer was obtained (0.9.1 / 0.13.1 / 1.0.0 from npm, 1.0.1 / 1.1.0 standalone bundles from GitHub Releases, HEAD packed). Skips are honest: 0.9.1 has no --state-backend (orphan/two-layer) and no old producer can declare upstream.json. Findings F1 (stale teamRoot -> remote mode against the removed profile squad, 0.13.1+ x profile), F2 (.local-backup overwritten on every upgrade from an old version) and F3 (0.9.1 --dry-run is not dry) reproduce identically on both platforms. migration.md points at the table. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10c92f7c-6aa8-4352-8ff2-eb1568d80993
… runners On windows-latest the step runs under bash, so `tar` on PATH is Git's GNU tar, which parses `D:\...` as a `host:path` remote spec and fails to unpack the packed sdk/cli tarballs (beforeAll error → whole matrix red, not a loud skip). Prefer %SystemRoot%\System32\tar.exe (bsdtar) on win32 and pass archive/destination relative to cwd so either implementation accepts them. Reproduced locally by putting Git\usr\bin first on PATH: before the change unpack fails identically; after it HEAD and the 1.1.0 release bundle both extract and 13/13 scenarios pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10c92f7c-6aa8-4352-8ff2-eb1568d80993
…e matrix Replace the local Windows/Docker runs with the results-<os>.json artifacts from run 39855424579 (ubuntu-latest + windows-latest, node 22.23.3, PR merge ref f67551a): 35 pass / 0 fail / 7 skipped on both, same 28 cells pinned to F1/F2/F3 — identical to the local runs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10c92f7c-6aa8-4352-8ff2-eb1568d80993
…e-record both OS runs Layer C now removes a retired-profile teamRoot during `squad upgrade` (isRetiredProfileTeamRoot) and normalizes every version stamp before the coordinator customization compare (stripAgentStamps), so the pinned checks `team-root-absent` / `doctor` (0.13.1+ × profile) and `idempotent-second-upgrade` (all old producers) now pass. Running the matrix with the old pins produced `xpass` for exactly those cells (6 of 6 1.0.0 scenarios), proving the pins; they are removed here and F1/F2 carry a `resolved` note instead. The integrity test now requires every finding to be either pinned or marked resolved, never both. F3 (0.9.1 --dry-run is not dry) stays pinned — it is a defect of the published 0.9.1 CLI. Re-recorded against 21ecca7: win32 (node 24) and linux (Docker node 22) both 35 pass / 0 fail / 7 skipped; only the 4 F3 cells per OS remain xfail. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10c92f7c-6aa8-4352-8ff2-eb1568d80993
Windows (node 24) and Linux (Docker node 22): 35 pass / 0 fail / 7 skipped each; F1/F2 stay resolved, only the 4 F3 cells per OS remain xfail. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10c92f7c-6aa8-4352-8ff2-eb1568d80993
…g its allowlist C's test/retired-symbols.test.ts now runs on squad2-* PRs and flagged D's fixture, rendered docs and test for naming the retired 1.x profile concepts. Reword the fixture/docs to describe the seeded 1.x user-profile layout generically, and assemble the legacy roster directory name from parts in the seed helper (it is test data: old producers only write teamRoot when they find that exact directory, which is what exercises the F1 path). The profile shape still behaves identically (1.0.0 × profile: producer writes teamRoot, 2.0 upgrade removes it, pass). The gate's allowlist is left untouched. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10c92f7c-6aa8-4352-8ff2-eb1568d80993
…venance, full manifests, git-state idempotency, profile read-guard, parsed projections, honest vocabulary Attribution (Bubu 1 / Q 6): every recorded run carries targetSha (the tree under test, passed by CI as the PR head / dispatched commit) and harnessHash (sha256 over the suite, renderer, workflow, fs hook and the fixture minus runs). The integrity test refuses runs whose harness differs from the committed one and requires all runs to share one targetSha; --merge enforces the same. Runs are therefore cleared here and re-recorded by CI at the final head; the fold commit may change nothing but the runs data. Immutable provenance (Bubu 2 / Q 6): npm producers are fetched by exact version (`npm pack`), verified against the recorded sha1 shasum + sha512 integrity, and installed with their own SDK version pinned (0.9.1's `>=0.9.0` range used to float to sdk 1.0.1); GitHub Release bundles are downloaded by immutable asset id and verified against the recorded sha256. A verification mismatch FAILS the producer's rows; only unreachability skips, with the reason. Provenance is recorded per row and labelled on the page (npm / GitHub Release / local HEAD). Q 1: `rollback-byte-exact` → `projection-uninstall-byte-exact`; a `concepts` glossary separates upgrade / projection uninstall / unsupported downgrade on the page and in upgrading.md. Q 2: idempotency now also compares every ref OID, `git status --porcelain` and the squad-state commit count. Q 3: seeds active + retired agents, nested unknown files, spaces/Unicode filenames, a user skill, appended routing/team/decisions and a custom config.json key; asserts a full before/after manifest whose delta must be a subset of the declared `squadOwned` patterns and disjoint from every seeded file. Q 4: `profile-untouched` = sha256 fingerprint AND an fs access logger loaded into every Squad 2.0 process via NODE_OPTIONS=--require (test/fixtures/upgrade-matrix/fs-access-log.cjs) recording zero reads, probes or writes under the profile root. Q 5: `clients-projected` parses every projection (JSON / TOML via smol-toml / YAML frontmatter) and checks the squad_state MCP entry per host. Q 7 / Bubu 3-4: vocabulary is "N rows passed all non-xfail assertions; M of them carry known expected failures (F…)"; F3 rows render as "pass WITH known destructive downgrade-probe failure (F3)"; the 7 structurally inapplicable cells are each explained (fixture `inapplicable`, asserted equal to the derived set and to the recorded skipped rows); upgrading.md gains "There is no supported downgrade" and states uninstallClients is not a downgrade path. New finding F4 (pinned): a faithful 0.9.1 project has an EMPTY .squad/casting/; the first 2.0 `squad upgrade` writes the coordinator before scaffolding the casting pair, so the team-capabilities block only appears on the second upgrade (0.9.1 × default/profile/ai-team). Windows and Linux (Docker) local runs: 35 rows pass all non-xfail assertions, 7 of them carry F3/F4, 0 fail, 7 structurally inapplicable. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10c92f7c-6aa8-4352-8ff2-eb1568d80993
…atus (LD-1/LD-2); verify squad-state blobs, all-host uninstall baseline, per-phase profile checks
LD-1 (Worf): `verifyArtifact()` is the single, pure verification step for
npm (sha1 shasum + sha512 integrity of both tarballs) and GitHub Release
(sha256 of the asset fetched by id). A mismatch sets `failReason`; the
scenario records `producer-artifact-verified=fail` and the row is `fail`.
Only genuine unavailability (network / 404) sets `skipReason`. Always-on
guard tests corrupt each recorded hash kind and drive the real release
resolver against a local HTTP server: wrong bytes → failReason (never a
skip), 404 → skipReason.
LD-2 (Worf / Copilot thread): `Scenario.result('fail', …)` can no longer
produce a fail row without a failing check (a `scenario-setup` check is
recorded), `result('pass')` throws when a check failed, and the outer test
judges every row through `rowVerdict()`, which rejects any non-pass status.
Guard tests cover a failed upstream setup and a failed producer.
Copilot threads: orphan/two-layer now seed decisions.md + agent histories as
git blobs on `squad-state` (plumbing only) and assert every seeded blob OID
is unchanged and no branch path deleted after upgrade and after uninstall;
`profile-untouched` re-fingerprints after every phase and names the
offending one; `projection-uninstall-byte-exact` is judged against the
post-upgrade/pre-projection baseline across all hosts (GEMINI.md, Kiro
steering, Cursor rules, Kiro agent JSON, host MCP JSON/TOML free of
squad_state, .mcp.json equal to baseline minus Squad's own squad_state).
Local verification: Linux (Docker) full matrix 35 rows pass all non-xfail
assertions (4 carry F3/F4), 0 fail, 7 inapplicable; Windows 1.0.0 + HEAD
13/13 with squad-state blobs verified.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 10c92f7c-6aa8-4352-8ff2-eb1568d80993
…are for CI-recorded runs With the F4 pin still in place, the matrix on the new C produced `xpass` for `idempotent-second-upgrade` on exactly 0.9.1 × default/profile/ai-team — the mutation proof that `preflightCastingPair()` now runs before the coordinator write. The pin is removed and F4 is marked resolved. Only F3 (0.9.1 `--dry-run` is not dry) remains pinned. `runs` is intentionally empty in this commit: the recorded runs come from the CI workflow dispatched at this head, folded in a follow-up commit that the integrity test only accepts if it changes nothing but the runs data (harnessHash) and both OS runs target this same SHA. Local verification on this tree (Windows node 24, Linux Docker node 22): 35 rows passed all non-xfail assertions; 4 of them carry the known F3 expected failure; 0 rows failed; 7 rows structurally inapplicable. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10c92f7c-6aa8-4352-8ff2-eb1568d80993
c77f60d to
9da01a2
Compare
…ata only) Both results-<os>.json artifacts from workflow_dispatch run 39903943542 at targetSha 9da01a2 (ubuntu-latest and windows-latest): 35 rows passed all non-xfail assertions; 4 of them carry the known F3 expected failure (0.9.1 --dry-run mutates); 0 rows failed; 7 rows structurally inapplicable — identical on both OSes. Only the `runs` data and the rendered page change; the harness hash recorded by both runs equals the committed harness (enforced by the integrity test). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10c92f7c-6aa8-4352-8ff2-eb1568d80993
…d; project-only resolution; F6/F7/F8 (#2279) * feat!: remove personal squads, consult mode and the global squad; project-only resolution; state-bridge fixes [3/3] BREAKING: Personal squads, consult mode and the global squad (`--global`) were removed; existing `personal-squad/` and global `.squad/` directories under the user profile are no longer read or written and may be deleted manually. Squad 2.0 keeps exactly one place for a team: the project `.squad/`. This removes every trace of the 1.x profile-level features from the product and the docs, with one line of history in the changeset. Removed - CLI: `squad personal|consult|extract`, `--global` on init/upgrade, the user-profile fallback in `squad status`/`squad cost`/the shell, `--legacy-personal` import, the `SQUAD_NO_PERSONAL`/`SQUAD_PERSONAL_DIR` switches, init `teamRoot` auto-link, the `multi-squad` registry. - SDK: `agents/personal`, `sharing/consult`, `resolvePersonalSquadDir`, `ensurePersonalSquadDir`, `ensureSquadPathTriple`, `isConsultMode`, `ResolvedSquadPaths.personalDir`, `SquadDirConfig.consult`/`extractionDisabled`, `InitOptions.extractionDisabled`/`isGlobal`, `multi-squad.ts` and its types. `resolveGlobalSquadPath()` stays only as the base of externalized project state. - Coordinator: Personal Squad / Consult Mode / Ghost Protocol sections and routing rows in the canonical agent and all host digests; personal-squad skill, ghost-protocol and personal-charter templates; docs pages, nav, README rows, whatsnew/upgrading mentions, the retired matrix row. Dead tests for the removed surface. Behaviour - Squad resolves `.squad/` only from the current directory and its parents. Outside a project `squad status`, `squad doctor`, `squad cast` and `squad cost` fail with "No squad in this directory … run squad init" instead of resolving a profile dir. - Nothing ever reads, writes, moves or deletes the 1.x directories. State-bridge fixes (Data F6/F7/F8) - `squad_state_health` reports `State backend: local|orphan|two-layer`, storage class and team squad dir (`ToolRegistry` accepts `backendName`); `squad_state_list` without `dir` lists the root on git-backed backends; Claude Code declares `background-runs` in `capability-matrix.json` and `CAPABILITY_TOKENS_BY_HOST`; matrix cells/notes updated. Tests - test/retired-symbols.test.ts: CI gate grepping every tracked file and the built dist for the retired names/phrases, with an explicit allowlist (the changeset BREAKING line, CHANGELOGs, pre-2.0 whatsnew history, a contributor credit, the two tombstone tests). - test/profile-squad-tombstone.test.ts: a seeded 1.x profile tree (personal-squad/ + global .squad/) stays sha256-identical and is never touched by any fs/storage call through init -> cast -> upgrade x2 -> client install/rollback; roster lands in the project; teamRoot never set; two side-by-side projects independent; no fallback. - test/cli-outside-project.test.ts: built CLI in an empty repo with a seeded profile squad: status/doctor/cast/cost exit non-zero with the remediation message, never mention or touch the profile squad; status inside a project still works. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b822a455-fa94-49a2-9127-eee479ab954e * fix!: never resolve a squad from the user profile; reject --global; prune retired exports; guard tests from the real profile [3/3] Worf Layer C review (LC-1..6) and the test-profile incident: - LC-2 (data integrity): both squad-dir walkers (resolveSquad, resolveSquadPaths/ findSquadDir) stop at the home directory and never accept ~/.squad or SQUAD_HOME as a project squad, so `squad cast` from a directory without a repo marker can no longer write into the profile. Tests: tombstone unit test (fake HOME/USERPROFILE/SQUAD_HOME, no .git, zero fs hits) and a built-CLI test (cast/status/doctor/cost fail with the remediation message, fake profile sha256-identical) — verified red pre-fix (`Added agent Probe` under fake-home/.squad). - LC-3: `--global` on any command fails with `Unknown option: --global` (exit 1) instead of silently running the project form; test for init/upgrade. - LC-1: `./commands/extract` and `./commands/personal` exports removed from the CLI package.json; the retired-symbol gate now scans both manifests' `exports` (retired names + every target must exist in dist; the pre-existing dangling `./shell/sdk-bridge` is listed as known) and the `npm pack --dry-run` file lists. - Incident: new vitest setupFile `test/setup/profile-guard.ts` redirects SQUAD_HOME, APPDATA, LOCALAPPDATA and XDG_CONFIG_HOME into a per-worker temp dir before any test loads, so no test can read or write the real profile (HOME/USERPROFILE untouched so git/gh keep working; the walker fix covers ~/.squad). - LC-4: squad-home docs no longer speak of "personal agents". LC-5: the single BREAKING sentence is repeated verbatim once in whatsnew v2.0.0 and the upgrading guide (gate enforces exactly once, nothing else). LC-6: `health` help line shortened. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b822a455-fa94-49a2-9127-eee479ab954e * fix(upgrade): strip stale profile teamRoot, stamp-insensitive customization check [3/3] Data's upgrade matrix (#2282) findings UM-F1 and UM-F2: - UM-F1: projects initialized by 0.13-1.1 while a profile squad existed carry `teamRoot: "<platform-config>/squad"`. `isRetiredProfileTeamRoot()` (SDK) recognises the platform config dir, `~/.squad`, `SQUAD_HOME` and the home dir; `resolveSquadPaths` never follows such a teamRoot (local mode), and `squad upgrade` removes the key with one neutral line ("Removed stale teamRoot pointing to <path>; project state is now local"; --dry-run only announces). A genuine dual-root teamRoot (another repo) is preserved. - UM-F2: `stripAgentStamps()` normalizes every stamp form the upgrader writes (`<!-- version: X -->`, `- **Version:** X`, `Squad vX`, `@bradygaster/squad-cli@X`, legacy `<!-- squad-cli vX -->`) before comparing squad.agent.md with the template, and an existing `.local-backup` holding the same bytes is not rewritten. - Docs: upgrading guide notes both and adds "Downgrading and old CLIs" guidance. - Tests: test/upgrade-profile-teamroot.test.ts (6) — both findings verified red against the pre-fix SDK/CLI (remote mode; "local customizations" warning on pass 1). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b822a455-fa94-49a2-9127-eee479ab954e * fix: resolver-only doctor admission, XDG on macOS, token-only stamp normalization, tolerant teamRoot preflight [3/3] Copilot review threads on #2279: - doctor: the resolver is the single admission check (a bare ./.squad existence check let `squad doctor` run from the home directory against ~/.squad); test now also runs every outside-project command directly from the seeded home dir (red pre-fix for doctor). - upgrade: the stale-teamRoot preflight tolerates null/array/scalar config.json like readSquadConfig (test); stripAgentStamps normalizes only the version token on the `- **Version:**` line so an edited instruction is still a customization (test). - resolveGlobalSquadPath honours XDG_CONFIG_HOME on macOS as well, so the test profile guard (and the outside-project test, which no longer overrides HOME) isolates macOS too. - retired-symbols gate: validates the changeset fragment only while it exists (changeset version consumes it); post-release it requires the sentence in the CLI CHANGELOG; the whatsnew/upgrading exactly-once checks stay durable. - cast: outside-project message aligned with status/doctor ("No squad in this directory (searched from ...). Run "squad init" here, or use --team-root ..."). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b822a455-fa94-49a2-9127-eee479ab954e * fix: canonical profile boundary (8.3 names), SQUAD_HOME guard shape, acceptance/portability expectations for 2.0 [3/3] CI on 21ecca7 (ubuntu) — all branch-caused, all from the last two commits: - Profile boundary compared `os.homedir()` with the walk's current path textually; on Windows `os.tmpdir()` yields the 8.3 form (`C:\Users\SOMEUS~1\...`) so the boundary was missed and `squad status` from a temp dir resolved `~/.squad` (acceptance saw `Path: C:\Users\TAMIRD~1\.squad`). Boundary comparisons now use `realpathSync.native` on the longest existing prefix (`canonicalDir`); callers still get paths in their own spelling (resolve-cache tests unchanged). - test/setup/profile-guard.ts: SQUAD_HOME is redirected to `<guard>/home/.squad` so the path keeps its real shape (comms-teams tests assert the `.squad` segment). - acceptance features: outside a project `squad status`/`squad doctor` now fail with "No squad in this directory … squad init" (exit 1) — the 2.0 contract, replacing the old fallback expectations. - cli/upgrade-portability: the fixture asserted the UM-F2 bug (a stamp/CRLF-only delta produced a .local-backup); it now applies a genuine local edit, which is still backed up. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b822a455-fa94-49a2-9127-eee479ab954e * fix!: canonical outside-project error, platform-config boundary, fail-closed canonicalization, fs guard, one-pass 0.9.x upgrade [3/3] Q/Bubu/Worf conditions on Layer C: - LC-9: one message for every project-scoped command, from `NO_SQUAD_MESSAGE` / `fatalNoSquad()`: `No squad in this directory. Run "squad init" first.` (status, doctor, cast, cost, upgrade, nap, export, notes, upstream, economy, config, loop, watch, migrate, copilot). economy/config used private walkers that reached ~/.squad — replaced by the SDK resolver. Test runs 11 commands from an empty repo, asserts the identical line, exit 1, and that nothing (no .squad mkdir) is created. - LC-11: the platform config dir (%APPDATA%\squad, ~/.config/squad, ~/Library/…/squad) is part of the profile boundary: a `.squad` at or under it is never admitted (any spelling), and the boundary is established by canonicalizing only the PARENT so the retired directory itself is never stat'ed. - LC-12: canonicalization is fail-closed — `canonicalDir` returns null when the existing prefix cannot be realpath'ed and callers refuse (resolver returns null, teamRoot treated as retired) instead of comparing lexically; an unknown home dir also refuses. A link named `.squad` whose target is profile-owned is refused too. - Fail-closed test guard: `test/setup/profile-fs-guard.cjs` wraps fs/fs.promises (sync, callback, promise) and THROWS on any access under the real profile roots; loaded by the vitest setup file from the original environment and inherited by child processes via NODE_OPTIONS=--require + SQUAD_TEST_FORBIDDEN_ROOTS. test/profile-fs-guard.test.ts proves a deliberate access throws in-process and in a child with faked HOME/APPDATA. - Projects under the profile keep working: test/profile-boundary.test.ts covers <home>/src/proj, long/8.3 paths (skips loudly when 8.3 is disabled), case/separator variants, a junction to the profile root, SQUAD_HOME/APPDATA/HOME/USERPROFILE variants. - F4 (Data, upgrade matrix): the casting pair is ensured BEFORE squad.agent.md is rendered, so a 0.9.x project (empty casting/) gets the capabilities block on the first upgrade and the second upgrade is a byte-identical no-op. - Eradication boundary: the teamRoot cleanup prints `Removed obsolete "teamRoot" setting from .squad/config.json.` (no path). - Single historical record: the BREAKING sentence lives only in whatsnew v2.0.0; the upgrading guide links to that section; the retired-symbol gate enforces both. LC-9 equality, LC-11, LC-12 and F4 tests verified red against the pre-fix code. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b822a455-fa94-49a2-9127-eee479ab954e * test: keep the profile-boundary test clear of retired terms (gate) [3/3] Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b822a455-fa94-49a2-9127-eee479ab954e * test(gate): drop the sdk-bridge dangling-export exemption (fixed on dev by #2285) [3/3] Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b822a455-fa94-49a2-9127-eee479ab954e --------- Co-authored-by: Copilot <Copilot@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b822a455-fa94-49a2-9127-eee479ab954e


WhatLayer D of the Squad 2.0 stack (test/CI/docs only — no product code): an old-version × OS × state-shape upgrade matrix.⚠️ pass WITH known destructive downgrade-probe failure (F3)"; upgrading.md "There is no supported downgrade" + "projection uninstall only … not a way back"; ⚠️ Quick Check- [x] If SDK/CLI source files changed: N/A — no
test/squad-20-upgrade-matrix.test.tsobtains pinned, published artifacts of older Squad releases, scaffolds projects with the OLD CLI under seven state shapes, upgrades them with the CLI packed from the tree under test, and asserts preservation, idempotency (files and git state), profile isolation (no writes and no reads), parsed host projections,squad doctor, byte-exact projection uninstall, and records what each old CLI does to an upgraded repository (the unsupported downgrade path). Results live intest-fixtures/spec/suite-v0.2/upgrade-matrix.jsonand are rendered (with--check) into "Tested upgrade paths" indocs/src/content/docs/scenarios/upgrading.md..github/workflows/squad-upgrade-matrix.ymlruns it onubuntu-latest×windows-latest.Producers (pinned):0.9.1,0.13.1,1.0.0— npm tarballs by exact version, verified against recorded sha1 + sha512, each installed with its own SDK version (0.9.1's>=0.9.0range used to float to sdk 1.0.1);1.0.1,1.1.0— GitHub Release bundles by immutable asset id, verified against recorded sha256 (their npm CLI publish never completed: runs 37222633918 / 39442663504);HEAD—npm packof this tree. A verification mismatch fails the producer's rows; only unreachability skips, with the reason.Shapes:default,profile(seeded 1.x user-profile tree),legacy-casting,orphan,two-layer,ai-team(--migrate-directory),upstream(HEAD only).### WhyReviewer asks (Q / Bubu) for Squad 2.0: prove upgrades from every published release on Windows and Linux with real artifacts, immutable provenance, single-SHA attribution, byte-exact projection uninstall, idempotency including git state, honest vocabulary and every skip explained. Part of the registered follow-up stack #2281 (#2277 → #2278 → #2279 → this).### HowAttestation (single-SHA attribution) — the chosen design. Every recorded run carriestargetSha(the tree under test: CI passesgithub.event.pull_request.head.sha || github.sha, so PR merge refs are never recorded) andharnessHash= sha256 over the suite, renderer, workflow, fs hook and the fixture minusruns. The always-on integrity test (a) recomputes the harness hash and refuses any recorded run whose harness differs from the committed one, (b) requires all recorded runs to share onetargetSha, and (c) requires the recorded skipped rows to equal the declared inapplicable set.--mergeenforces (b) too. Consequence: the harness commit ships withruns: []; CI is dispatched at that head; bothresults-<os>.jsonartifacts are folded in a follow-up commit that can change only the runs data — if anything else changed, the harness hash would differ and the integrity test would fail. The page shows thetargetShaand harness hash for both OS rows. (Picked over "fail unlesstargetSha == HEAD" because a PR checkout is a merge ref and the fold commit necessarily follows the recorded commit; the harness hash makes the "data-only fold" claim machine-checked rather than a reviewer's diff-read.)Reviewer item → check / artifact mapping| Item | Where it is enforced ||---|---|| Bubu 1 / Q 6 single-SHA attribution |runs[].targetSha,runs[].harnessHash; integrity test "recorded runs are attributable to one target SHA, ran with this exact harness…";--mergeguard; page "Runs" table || Bubu 2 / Q 6 immutable provenance |producers[].artifacts(npm: version + shasum + integrity; release: tag + asset id + sha256);resolveNpmProducer/resolveReleaseProducerfail closed; per-rowprovenance; integrity test "every producer carries immutable provenance…" || Lead: 7 skips explained, per-row provenance | fixtureinapplicable[](asserted == set derived from producer capabilities == recorded skipped rows); page "Not-applicable cells, each explained"; results table "Provenance" column || Q 1 terminology | check renamedprojection-uninstall-byte-exact(integrity test rejects the old name); fixtureconceptsglossary rendered as "Three different things"; upgrading.md sections || Q 2 idempotency incl. git state |idempotent-second-upgrade: sha256 of every file +for-each-refOIDs +status --porcelain+rev-list --count squad-state|| Q 3 complete manifest |user-state-preserved: seeds retired agent, nested unknown files, spaces/Unicode names, user skill, customconfig.jsonkey; full before/after manifest; delta ⊆squadOwnedpatterns (listed on the page) and disjoint from every seeded file || Q 4 no reads under profile |profile-untouched: sha256 fingerprint andtest/fixtures/upgrade-matrix/fs-access-log.cjsloaded viaNODE_OPTIONS=--requireinto every Squad 2.0 process (upgrade ×2, upgrade --client, doctor, uninstallClients); zero logged accesses || Q 5 projections parse |clients-projected: JSON (.mcp.json,.kiro/agents/squad.json,.kiro/settings/mcp.json,opencode.json,.cursor/mcp.json), TOML viasmol-toml(.codex/agents/squad.toml,.codex/config.toml), YAML frontmatter for the 7 agent/rule files,squad_stateMCP entry per host — mirrorstest/clients.test.ts|| Q 7 / Bubu 3–4 wording & warnings | suite summary and page: "N rows passed all non-xfail assertions; M of them carry known expected failures (F…)"; cells "✅migration.mdpointer |Recorded results (CIworkflow_dispatchrun 39903943542 attargetSha 9da01a2e41ba15794d31b0b8251ddaa3abd34ba8, ubuntu-latest + windows-latest, node 22.23.3): 35 rows passed all non-xfail assertions; 4 of them carry the known F3 expected failure (0.9.1--dry-runmutates — "pass WITH known destructive downgrade-probe failure (F3)"); 0 rows failed; 7 rows structurally inapplicable (each explained). Identical on both OSes. The fold commitd4c15779changes only therunsdata + rendered page; harness hashbca27c39dc2d…recorded by both runs equals the committed harness (enforced by the integrity test).### 🔴 Findings (product fixes belong to other layers — not fixed here)- F1 (resolved on C @ 21ecca7+): staleteamRootfrom 0.13.1–1.1.0initsurvivedsquad upgrade. Unpinned here after the matrix producedxpasson exactly those cells.- F2 (resolved on C @ 21ecca7+):writeAgentTemplatenever recognised its own stamps →.local-backupoverwritten every run. Unpinned the same way.- F3 (open, historical, pinned): 0.9.1squad upgrade --dry-runis not dry — it performs the downgrade (~44 files with the pinned 0.9.1 SDK). Later CLIs honour--dry-runbut have no newer-version guard. Documented as a user warning.- F4 (resolved on C @ 50afa91+, found by this matrix): a faithful 0.9.1 project has an empty.squad/casting/; the first 2.0squad upgradewrote the coordinator before scaffolding the casting pair, so the<!-- squad:capabilities -->block only appeared on the second upgrade. C now runspreflightCastingPair()first; with the pin still in place the matrix producedxpasson exactly those three cells, then the pin was removed.- Release gap (for the release owner):@bradygaster/squad-cli1.0.1 / 1.1.0 are not installable from npm.---###packages/*/srcchange (test/CI/docs/scripts/fixtures only; no changeset required)### PR Readiness Checklist#### Branch & Commit- [x] Branch created from the stack basesquad2-c-personal-removal-and-fixes— this is PR [4/4] of the stack, targeting C- [x] Branch is up to date with its base- [x] Verified diff contains only intended changes- [x] PR is not in draft mode- [x] Commit history is clean (logical commits; the final "fold" commit changes onlyrunsdata + rendered page)#### Build & Test- [x]SKIP_BUILD_BUMP=1 npm run build— passed (Windows; Linux container)- [x]npx vitest run test/squad-20-upgrade-matrix.test.ts— passed (integrity layer; matrix skipped without opt-in)- [x]SQUAD_UPGRADE_MATRIX=1 npx vitest run test/squad-20-upgrade-matrix.test.ts— Windows and Linux (Dockernode:22-bookworm): 35 rows passed all non-xfail assertions, 7 carry F3/F4, 0 failed, 7 structurally inapplicable; CI runs at the final head are the recorded ones- [x]npx vitest run test/retired-symbols.test.ts— passed- [x]node scripts/render-upgrade-matrix.mjs --check— up to date- [x]npx eslint test/squad-20-upgrade-matrix.test.ts scripts/render-upgrade-matrix.mjs test/fixtures/upgrade-matrix/fs-access-log.cjs— clean- [x]npx tsc --noEmit -p tsconfig.json— no errors in the suite- [x]npx markdownlint-cli2+npx cspellonupgrading.md/migration.md— 0 issues- [x]npm test— Squad CItestjob green on this PR- [x]npm run lint— Squad CItestjob runs it; green on this PR#### Changeset- [x] N/A — nopackages/squad-sdk/src/orpackages/squad-cli/src/change#### Docs- [x]docs/src/content/docs/scenarios/upgrading.md— generated "Tested upgrade paths" + hand-written "There is no supported downgrade" / projection-uninstall note- [x]docs/src/content/docs/get-started/migration.md— pointer#### Exports- [x] N/A---### Breaking ChangesNone.### WaiversNone requested.