Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .changeset/1493-upgrade-workflow-backup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bradygaster/squad-cli": patch
---

Back up locally modified workflow files before `squad upgrade` refreshes them.
5 changes: 5 additions & 0 deletions .changeset/1555-fs-storage-rootdir.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bradygaster/squad-sdk": patch
---

`resolveSquadState()` now constructs the local-backend `FSStorageProvider` with `rootDir` set to `paths.teamDir`, so its path-traversal guard actually validates state writes instead of no-op'ing on an unset rootDir. Also fixed `resolveSquadPaths()` treating a `config.teamRoot` of `"."` (the sentinel `squad externalize` writes) as remote mode, which pointed `teamDir` one level above `.squad/` — it now correctly falls through to local mode. Together these were breaking `squad_decide`/`squad_state_*` MCP tools on externalized projects with a "Path traversal blocked" error.
6 changes: 6 additions & 0 deletions .changeset/1605-squad-health.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@bradygaster/squad-cli": minor
"@bradygaster/squad-sdk": patch
---

Add `squad health` readiness diagnostics and fail-fast shared workflow integration.
10 changes: 10 additions & 0 deletions .changeset/1608-team-capability-advertisement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@bradygaster/squad-cli": patch
"@bradygaster/squad-sdk": patch
---

Advertise the squad's real capabilities in `.github/agents/squad.agent.md` so an outer Agentic Workflows coordinator can route to it.

A new generated `Team Capabilities` block lists the actual cast — available specialists with their roles and charter-grounded focus, supported task types, domain-to-agent routing hints, and honest capability boundaries (what the squad *cannot* do is stated explicitly, derived from the absence of evidence rather than from a wish list). The block is rewritten whenever cast composition changes: on `squad init`, on `squad upgrade`, and on casting, so recast and retired members never leave stale names behind.

Generation is deterministic and reuses the existing roster/routing parsers and charter metadata reader rather than introducing a second routing model. All metadata is treated as untrusted data: values are sanitized against markdown table breakage, HTML/comment injection, canary and marker forgery, invisible and bidi characters, and common prompt-injection phrasings before they are embedded.
5 changes: 5 additions & 0 deletions .changeset/1641-pid-tracker-identity-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bradygaster/squad-cli": patch
---

Watch's PID tracker now cross-checks a live process's actual OS start time against the tracked spawnedAt before killing it, instead of trusting a bare PID match. Prevents killing an unrelated process that happens to have been assigned a previously-tracked PID (e.g. after a crash + reboot).
5 changes: 5 additions & 0 deletions .changeset/agent-command-prompt-placeholder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@bradygaster/squad-cli': patch
---

Allow custom watch and loop agent commands to place the generated prompt with one standalone `{prompt}` token while preserving the existing `-p <prompt>` fallback.
5 changes: 5 additions & 0 deletions .changeset/doctor-worktree-eol-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@bradygaster/squad-cli': patch
---

`squad doctor` now checks that every file pinned to `eol=lf` by `.gitattributes` is actually LF **on disk**, not just in the index. `.gitattributes` governs checkout, so adding an `eol=lf` rule never repairs a working tree that already exists — the affected files stay CRLF indefinitely, and a CRLF shebang makes a vitest suite load zero tests while still looking green. The check names the stale files and points at `npm run fix:crlf`, a new repair script that rewrites them from the index and refuses to overwrite any file with uncommitted changes. Closes #1793.
7 changes: 7 additions & 0 deletions .changeset/fix-1490-watch-external-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@bradygaster/squad-cli": patch
---

Fix #1490: `squad watch` and `squad loop` resolved externalized state for their startup reads only — every capability was then handed `teamRoot` and built `.squad/` paths from it directly, so after `squad externalize` the local `.squad/` is a marker-only stub and decision-hygiene, cleanup, retro, subsquad discovery, the `capabilities/` loader, and the ralph-instructions.md check all silently saw an empty directory.

Added `stateRoot` to `WatchContext`, populated from `effectiveSquadDir().stateDir` in both `runWatch` and `runLoop`, and routed every capability's state reads/writes through it instead of joining `.squad/` onto `teamRoot`. `loadExternalCapabilities()` and `buildAgentPrompt()` gained an optional override parameter rather than changing their existing (well-tested) `{teamRoot}/.squad/...` default, so every prior caller and test keeps working unchanged. `notes-promote.ts` needed no change — it only reads the always-local `config.json` for backend-type detection; the two-layer backend's actual state is git-notes/orphan-branch, not filesystem, so externalization doesn't touch it.
5 changes: 5 additions & 0 deletions .changeset/fix-1526-ado-legacy-url-decode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bradygaster/squad-sdk": patch
---

Fix #1526: `parseAzureDevOpsRemote()` didn't decode percent-encoded characters in the org/project/repo segments it pulled out of a git remote URL. A legacy `visualstudio.com` remote with a space in the project name (`.../Pref%20Proj/_git/...`) came out as `Pref%20Proj`, and every `az` CLI call built from that (`az repos pr list --project Pref%20Proj ...`) failed since `az` doesn't decode its own arguments. Added a `decodeSegment()` helper and applied it to all three URL formats (`dev.azure.com`, SSH, and legacy `visualstudio.com`), not just the one from the repro, since the same bug affects any of them.
6 changes: 6 additions & 0 deletions .changeset/lucky-model-routing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@bradygaster/squad-sdk': patch
'@bradygaster/squad-cli': patch
---

Update model catalog tiers and fallback routing for GPT-5.6 Sol, GPT-5.6 Terra, GPT-5.6 Luna, and Gemini 3.1 Pro. Terra is now the default standard model for code and prompt tasks, with Luna as its economy-mode fallback.
19 changes: 19 additions & 0 deletions .changeset/lucky-moons-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@bradygaster/squad-sdk': patch
---

Refuse consult mode when git's `info/exclude` resolves outside the project

`setupConsultMode` hides `.squad/` by appending to git's `info/exclude`, but resolved that
path with `git rev-parse --git-path info/exclude`, which answers for whichever repository
*encloses* the directory. From a linked worktree, or from a directory that is not itself a
repository root, the write landed on another checkout's exclude file — hiding `.squad/`
across the main checkout and every sibling worktree. Because `info/exclude` is untracked
and per-clone, nothing in the repo could undo it.

`setupConsultMode` now verifies the exclude belongs to a repository rooted at
`projectRoot` and refuses otherwise, pointing the caller at the main checkout. A new
`isExcludeOwnedBy()` export performs that containment check.

Writing to a worktree-local exclude is not an alternative: git keeps no per-worktree
`info/exclude`, and a file placed at `.git/worktrees/<id>/info/exclude` is never read.
32 changes: 32 additions & 0 deletions .changeset/scheduler-quoted-task-ref.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
'@bradygaster/squad-sdk': minor
---

Fix scheduler script tasks failing on the default Windows Node install path

`LocalPollingProvider` split `task.ref` on whitespace with no quote handling,
so a command path containing a space was truncated at the first space. On
Windows that breaks the *default* install location — `C:\Program Files\nodejs\node.exe`
became `C:\Program`, and the task failed with `ENOENT`. This affected any user
who did not install Node somewhere unusual, not an exotic edge case.

Script task refs are now parsed with quote awareness, and an unquoted command
path is resolved by widening across spaces (longest match first) the same way
Windows `CreateProcess` does. A ref whose first token already resolves is used
unchanged, so existing configurations — including bare PATH commands like
`node` — keep their exact previous behaviour. Quote characters appearing
mid-token, as in `node -e console.log('hi')`, are still passed through to the
child verbatim rather than being stripped.

`TaskConfig.argv?: string[]` is added as the unambiguous form: when present,
`ref` is used verbatim as the executable and is never parsed. Prefer it for any
command path containing spaces.

Spawn failures are also no longer silent. `execFile` reports them with a
*string* code (`ENOENT`, `EACCES`) and no stdout or stderr, which previously
fell through every branch and produced `code: undefined, stderr: ''`. `TaskResult`
gains `spawnError?: string` for that code, and the error message now names the
ref that could not be spawned.

`shell: false` is retained, so the injection-safety property of the existing
implementation is unchanged.
51 changes: 51 additions & 0 deletions .changeset/scribe-archival-integrity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
'@bradygaster/squad-sdk': minor
'@bradygaster/squad-cli': minor
---

Make archival incapable of destroying state (#1774, #1783, #1760)

Archival is a two-half operation — append to a destination, trim from a source.
Three production defects came from those halves coming apart, each one silently
deleting team history while reporting success.

New `state/io/archival` module in the SDK enforces the five rules in code rather
than only in prompt text:

- `resolveTrackedDestination()` / `isTrackedInGit()` — refuse to archive into a
destination that is not git-tracked, or redirect to a tracked fallback. Under a
git-excluded `.squad/`, already-tracked files still commit while brand-new files
silently never do, which turns archival into deletion (#1783).
- `archiveEntries()` — append, verify by literal heading containment **and** entry
count, and only then trim. A failed append leaves the source completely intact
(#1774).
- `formatArchivalReport()` — reports entry counts and refuses to render an
unbalanced result. File size is not a valid integrity signal, since a merge and
an archive in the same pass move size in opposite directions.
- `prepareInboxBodyForMerge()` / `demoteHeadings()` — fence-aware heading demotion
so inbox `##` sections land at `####` beneath an `###` entry instead of breaking
document hierarchy (#1760). `#` lines inside fenced code blocks are never
rewritten.

Scribe's charter, spawn template, and the `decision-hygiene` watch capability
prompt now carry the same rules.

**`squad nap` no longer destroys decision history.** `archiveDecisions()` in the
CLI is a second, *shipped and user-invocable* archival path (`squad nap`, REPL
`/nap`) that had all three defects independently of the agent path, and it is now
wired to the SDK module:

- It appended to `.squad/decisions-archive.md` with no tracked-destination check.
With `.squad/` git-excluded and no archive file yet, every archived record was
written somewhere that could never be committed while the trim of the tracked
`decisions.md` committed normally — a total loss of the archived records.
- It trimmed the source unconditionally, even on the branch where the append was
skipped, and never verified that the append landed.
- It split records on `/^###\s/` with no fence tracking, so a `###` line inside a
fenced code sample was treated as a record boundary and severed the record.

Archival now refuses to run when the destination is untracked *and* git-ignored,
verifies the appended entry count before removing anything from the source, and
reuses the SDK's fence-aware scanner for record boundaries. Because this changes
the behavior of a user-invocable command rather than only prompt text, the CLI
takes a minor bump.
5 changes: 5 additions & 0 deletions .changeset/watch-stop-signal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@bradygaster/squad-cli': patch
---

Implement the two documented `squad watch` stop signals, which were previously parsed but never read. `--sentinel-file <path>` now stops the run gracefully once that file is deleted, and creating `.squad/ralph-stop` stops the run gracefully as documented in the README. Both are checked at the top of each round and reuse the existing graceful shutdown path, so a watch run can now be stopped from outside the process without sending a signal. Closes #1711.
Loading
Loading