Skip to content

v2.20.0 - Honest PRs, skills-only installs stay skills-only

Latest

Choose a tag to compare

@admarble admarble released this 19 Oct 01:38
· 35 commits to main since this release
12aa658

Added

  • sequant merge --check warns about open PRs outside the batch that touch the batch's files (#1454). A new external-pr-overlap check lists each open PR on origin whose changed files intersect the batch's, as one warning naming the PR number, head branch and shared paths. PRs whose head branch is in the batch are excluded. The warnings never change the batch verdict or the exit code. When gh is unavailable or the PR list call fails, the report shows one Open-PR overlap check skipped: <reason> line. The file lists come from a new GitHubProvider.listOpenPRFilesSync, and assess-collision-detect.ts gains formatOpenPrCollisionWarnings, which /assess will call once the plugin freeze lifts. See ADR-0016.
  • Standalone sequant ready records its run to .sequant/metrics.json (#929). One MetricRun per gate pass with source: "ready", the policy in flags, success/failed from the gate result, qaIterations, tokensUsed and the effort/model escalations. Recording is best-effort: a write failure warns on stderr and leaves the exit code and --json stdout alone. sequant stats reports bySource counts, and headline averages cover run records only. Records with no source read as run; no migration.
  • The main ruleset payload uses a merge queue instead of strict up-to-date (#1350). scripts/ruleset-main.sh --print now drops strict_required_status_checks_policy, adds a squash merge_queue rule and lists canary beside test (the live ruleset already required both). ci.yml runs on merge_group so queued PRs get their checks. The live ruleset is unchanged: applying the queue waits on #1368, where /release learns to wait for a queued merge before tagging.
  • /exec and /qa run the tests a branch affects instead of the full suite (#1349). npx vitest run --changed origin/main (jest: --changedSince), plus AC Evidence: test files, changed test files and tests that mention a changed file. Stacks with no affected mode keep the full command (resolveAffectedTestCommand). CI still runs everything.
  • gitleaks scans every PR for the leak classes generic scanners miss (#1352). .github/workflows/gitleaks.yml runs a pinned, checksum-verified gitleaks with .gitleaks.toml on pull_request and merge_group, and fails when an entire/* ref exists on origin. Project rules flag real home paths (/Users/<name>/, /home/<name>/) and Claude session links and ids; placeholder names are allowlisted by name. scripts/gitleaks-precommit.sh is an optional local gate that also loads an untracked .gitleaks.local.toml. GitHub secret scanning, push protection and Dependabot security updates are now enabled on the repo. The committed eval results and two test files no longer carry a real home path or session id. See docs/reference/secret-scanning.md.
  • /qa enforces a follow-up ledger, and the PR body lists QA's follow-ups (#1249). Every item an earlier phase deferred (spec's Open Questions and out-of-scope notes, exec's PR-body follow-ups, QA's own document findings) must end filed #N, fixed in this PR or dropped: <reason>. An unresolved item caps the verdict at AC_MET_BUT_NOT_A_PLUS through a new §7 gate, followup_ledger_status, in both Standard and Simple Fix mode. QA writes each document finding's resolution as a suffix on its description, and sequant run's PR body renders the latest QA pass's document findings as a ## Follow-ups checklist; a finding with no resolution reads unresolved. The sibling-site scan now names the defect class it searched (e.g. "every git call without -C <target>") rather than searching for the changed code, and an AC that promises end-to-end delivery (e.g. "surfaces in the PR body") can no longer be marked MET with that path under "Not tested": QA runs it or marks the AC PENDING. qa/SKILL.md did not grow. See ADR-0007.
  • A PR from a sequant run that QA has not reviewed says so (#1246). The automated PR body carries one line, > **QA has not run** — phases run: spec, exec. …, until QA has reviewed the latest exec: a qa pass after the run's last exec, or, in a run with no exec, a qa an earlier run completed (--resume past qa). The post-QA update replaces it with the verdict note, or drops it for READY_FOR_MERGE, and a later qa-only run on the same PR removes it too. A run that skips qa (--phases exec, MCP phases: "spec,exec") or stops before it keeps the line. No draft PRs; the PR-creation gate is unchanged. The MCP sequant_run phases description no longer offers 'spec,exec' as a way to "skip QA".

Changed

  • update refuses on a skills-only install (#1482). It read no install scope, so it wrote hooks, agents and settings.json over an install that chose skills only. On a partial scope it now writes nothing, exits non-zero and names sequant sync --only skills. Full installs are unchanged. ADR-0015 amended.
  • The @anthropic-ai/claude-agent-sdk floor is ^0.3.296 (#1440). Phase agents run the Claude Code bundled with the SDK (2.1.296 at 0.3.296), not the installed claude, so a fresh install can no longer resolve a bundle older than the 2.1.290–2.1.296 fixes (remote MCP reconnect backoff, .mcp.json servers switched off for a folder, rate_limit_event extra-usage flag, file-read-state, disableClaudeAiConnectors / allowedMcpServers coverage). The README Prerequisites note says which Claude Code a phase agent runs.

Removed

  • Dead src/lib/plugin-version-sync.ts module and its test (#1325). Nothing in production called checkVersionSync; CI's Check version sync step does the check. docs/internal/plugin-updates.md now names that step.

Fixed

  • sequant_run no longer reports another run's issues and verdict when its own run log is missing (#1515). A runId lookup miss fell back to the newest log in the time window, so parallel runs whose log was never written showed a sibling run's result. resolveRunLog now retries the exact runId lookup (3 × 200 ms) and returns null on a miss; the response carries runLogMissing, requestedIssues and a note saying the result is not a parsed log. The no-runId path is unchanged.
  • new-feature.sh reuses the issue's local branch when no worktree has it (#1491). It printed advice (one option was git branch -D) and exited 1, while ensureWorktree checks the branch out as is. It now runs git worktree add <path> <branch> with no fetch or reset, like the TS side, and keeps a recorded sequantBase. The existing-worktree lookup matches the branch exactly, so feature/7-x no longer matches feature/7-xy.
  • doctor Skills Version check warns on content drift (#1490). At a matching version it always passed with Up to date, while the pre-flight banner in the same run reported differing files. It now warns with the file count and the banner's remedy, which both read from one function.
  • doctor no longer sends a skills-only install to init --force (#1489). A missing AGENTS.md raised a warning naming sequant init --force, which installs hooks, agents and settings the install skipped. On a partial scope the check now passes and says AGENTS.md is outside the install. Full installs keep the warning.
  • sequant merge --check works on a repo whose default branch is not main (#1484). The combined-branch test, each branch's filesModified diff, overlap detection and the residual pattern scan all hard-coded origin/main. On a master repo the diffs failed, their callers read the empty output as "no changes", and the overlap checks (including #1454's external-PR overlap) reported clean. runMergeChecks now resolves origin/<default branch> once and passes it to all four. A failed filesModified diff is no longer an empty list: the report carries an error finding for that branch naming the git command, and the batch is BLOCKED. The worktree path of resolveBranches also uses the resolved base instead of main.
  • Plain sync and doctor no longer steer a skills-only install into a full one (#1485). A plain sync on a manifest with groups: ["skills"] keeps that scope: it counts drift and writes only .claude/skills/, and widening is sequant init. doctor's Skills Version check names sync --only skills, and doctor advises the "groups": ["skills"] hand edit for a legacy manifest with no hooks, settings or agents. See ADR-0015.
  • sequant run reuses an issue's existing branch whatever the issue's title (#1487). The branch was looked up by a name rebuilt from the title. A failed gh issue view (title Issue #N) or a retitled issue therefore cut a second branch, feature/N-issue-n, from the base, and QA reviewed the base instead of the PR. ensureWorktree now finds the issue's branch by number: a local feature/N-* (which covers worktrees) or origin/feature/N-*. One match is reused. With several matches, the one the title names is used. Otherwise no worktree is created and every candidate is listed. When the title could not be read, a non-dry run now prints a warning that names the issue and the gh error.
  • new-feature.sh starts from origin/<branch> when the remote already has the issue's branch (#1486). It always cut the worktree from origin/<base>, so a pushed PR branch lost its commits and a push from the new worktree diverged. The script now ports resolveBranchStartRef: it uses origin/<branch> unless that branch is merged into the base (merge commit or squash), and --print-start-ref <branch> <base-ref> exposes the result for the parity test.
  • An issue whose worktree could not be created fails, and no longer runs its phases in the main checkout (#1469). When git worktree add failed, sequant run dropped the issue from the worktree map and ran spec, exec and QA in the main checkout on whatever branch was checked out there. Under worktree isolation the issue now fails before any phase runs, and its abortReason and the run log carry git's stderr. IssueExecutionContext gets an explicit worktreeRequired flag, because a missing worktree also means "isolation off". The provisioning summary counts failures (N created, M reused, K failed). Two CLIs provisioning in one repo race on .git/config.lock. A could not lock config file failure is now retried up to 5 times with 100–800 ms backoff, and a retry adds the branch the failed -b attempt already created. recordWorktreeBaseRef uses the same retry and warns on a final failure instead of swallowing it.
  • Phase agents get SEQUANT_BASE_BRANCH (#1460). sequant run never set it, so skills that read ${SEQUANT_BASE_BRANCH:-main} always used main, and on a repository whose default branch is master every origin/main in a phase's git commands failed. Every phase, spec included, now gets the base branch name from resolveBaseRef. When no base is recorded, resolveBaseRef falls back to origin/<default branch> of the repository it reads, not a literal origin/main.
  • Uncommitted changes to .claude/hooks/ now trigger the same warning as uncommitted skills (#1456). Phase worktrees check out the committed hooks, so a sync that rewrote pre-tool.sh and was never committed left every phase on the old guard logic with no signal. getSkillsCommitState lists tracked-but-modified files under both directories, and the shared warning names each directory. Untracked hooks stay silent. run, doctor and sync print it through their existing call sites.
  • A worktree for a non-default bare base (--base release) is cut from origin/<base> after a fetch (#1406). It was cut from the local branch, which can lag or not exist, while staleness and the recorded sequantBaseRef read origin/<base>. Creation, recreate and the record now share one ref. Stacked predecessors and bases with no origin/<base> stay local.
  • A SEQUANT_MUTATION marker whose text contains } no longer parses to nothing (#1305). The parser matched to the first }, so a mutation such as regex changed to /x{9}/ made the marker vanish from QA §6i and from the exec carry-over. It now matches to the --> terminator, and renderMutationMarker escapes --> and <!-- inside a value so it round-trips. Existing markers, including ones whose JSON spans lines or whose values hold an HTML comment, parse as before.
  • A skills-only install no longer warns about files it never installed, and its remedy no longer overwrites the constitution (#1409). After init --manifest-only + sync --only skills, every command warned N file(s) differ for the hooks, agents, scripts/dev and .claude/settings.json the install left out, and suggested sync --force, which overwrites .claude/memory/constitution.md. .sequant-manifest.json now records the install scope in a groups field (init --manifest-only writes [], sync --only skills adds "skills", a full sync of a manifest without a scope removes it; update refuses on a partial scope). Drift is counted only inside that scope, and the remedy is sync --only skills --force. A manifest without the field is a full install, so existing installs are unchanged. See ADR-0015.
  • A fresh machine's first worktree for a pushed PR branch starts from origin/<branch>, and a worktree already at origin/<branch> is not recreated on every run (#1405). With no local branch, ensureWorktree cut a new branch from the base even when the remote had the PR's commits. It now starts from origin/<branch> with upstream tracking, unless that branch is already merged into the base (merge commit or squash) or was deleted from the remote. A stale, clean worktree whose HEAD equals origin/<branch> is reused instead of being deleted and re-added. The check uses local git only, with no gh call. See ADR-0014.
  • Phase agents no longer receive the user's claude.ai connectors (#1396). The Claude Code driver passes settings: { disableClaudeAiConnectors: true } to query(), so Gmail, Docs and other account connectors the SDK adds outside the #936 allowlist are not reachable from a bypassPermissions phase agent. The sequant server, .mcp.json servers and mcpAllowlist names are unaffected.
  • A stale worktree for a pushed PR branch is recreated from origin/<branch>, not from the base (#1380). When a worktree was more than 5 commits behind and clean, ensureWorktree deleted the local branch and cut a new one from the base, so a qa re-run reviewed the base without the PR's commits. The recreate now starts from origin/<branch> when the remote has it. If the start ref does not contain the worktree's HEAD (an offline fetch, or a local-only branch with commits of its own), it prints a warning and keeps the existing worktree. See ADR-0013.
  • First-run papercuts from a stranger smoke test (#1387). The "Running sequant from local node_modules" warning no longer tells a project that declares sequant in package.json to uninstall it (a stray, undeclared install still warns). init's next steps now say to commit the generated files and run npx sequant run <issue>. doctor's Warnings: count includes the upstream #43869 notice it prints, so a healthy install now reads "All checks passed (1 warning)" unless --quiet. The still running line shows time since the current phase started, not the issue. run --help describes --no-rebase as skipping the rebase onto the base branch.
  • sequant run --base <branch> opens the PR against <branch> (#1386). The worktree was cut from --base and rebased onto it, but createPR set gh pr create --base only under --stacked, so a plain --base (or settings.run.defaultBase) opened the PR against the repo default and its diff carried every difference between the two branches. createPR now takes the run's one resolved base and derives the PR target through resolvePrBase; --stacked still wins for non-last links, and a base equal to the repo default passes no --base, as before.
  • Uncommitted skills no longer pass silently (#1354). sequant run checks phase worktrees out from git, so a sequant sync that was never committed never reached a phase, and the Skills are outdated warning, which reads the on-disk version, named a version the phases weren't using. sequant run (before provisioning worktrees), doctor, sync and update now warn when tracked files under .claude/skills/ have uncommitted changes, naming the committed version phases will run. See troubleshooting.
  • Worktree provisioning's skip message now says no package.json at the worktree root for JS stacks, so a repo whose package lives in a subdirectory sees why nothing installed. The npm ci EUSAGE itself was fixed by #1196 (#1243)
  • The run dashboard shows phases in the order they run (#1356). When label detection adds a phase to --phases (e.g. exec,qa gains test), the dashboard's issue card listed it last, after qa, because the orchestrator never applied the resolved phase plan the plain-text renderer already used. It now does, so exec ▸ test ▸ qa reads correctly from the start, and a configured phase the plan dropped no longer lingers as pending.
  • .sequant-manifest.json ends with a newline (#1355). sequant init, sync and update wrote the manifest with no final newline, so a repo whose CI runs prettier --check failed after every sync.
  • Parallel runs (every MCP sequant_run) no longer drop per-issue warnings (#1342). runIssueWithLogging silenced all per-issue output in parallel mode, including warnings: the spec-recommendation parse failure, "PR after exec failed", the checkpoint-commit failure, relay and ready-gate warnings, and evidence bundles. They now go through a warn channel built on bracketedConsoleLog, which uses the renderer's appendNotice under the TUI and console.log otherwise. Progress lines stay suppressed.
  • The recommended Claude Code version is now 2.1.288 (#1346). Sequant relies on Claude Code's native dangerous-rm analyzer, which before 2.1.288 let a bash -c or sh -c script run a dangerous rm without a prompt under bypassPermissions, the mode phase agents run in. The README gives the new recommendation and the reason, and docs/THREAT-MODEL.md lists the analyzer as a defense with that version dependency. ADR-0009 (#1345) is accepted.
  • /verify ignores Claude Code's pre-commit nudge (#1345). Claude Code ≥ 2.1.286 tells Claude to run any project skill named verify before committing, and Sequant ships one that expects an issue number and posts to GitHub. /verify now returns a one-line no-op, with no command, comment or question, when a Sequant phase other than verify calls it (checked through SEQUANT_PHASE, even with an issue number), when it gets no issue number, or when Claude calls it on its own before a commit. A /verify <N> you ask for, and the verify phase, run as before. sequant update / sync deliver the guard to existing projects. /upstream now files changelog lines that name a skill Sequant ships under a new Name Collision category instead of No Action, and warns if it cannot find the shipped skill list. See ADR-0009.
  • The sequant run PR body carries exec's ## Summary again (#1311). The Claude Code, Codex and opencode drivers joined the agent's text messages with no separator, so a message ending mid-line ("Now the edits.") glued onto exec's final ## Summary and the line-anchored heading was never found; the PR got the placeholder body even though exec wrote a full summary (4 of 4 runs on 2026-10-05). Messages now start on their own line. The placeholder warning also printed nowhere under the MCP server, because per-issue logging is off in parallel mode; it now prints there and names the exec output's length, so "exec wrote nothing" and "exec's summary was missed" read differently.
  • /release bumps the plugin manifest users install (#1301). Step 4.5 edited and staged the root .claude-plugin/plugin.json, which nothing installs; the shipped plugin/.claude-plugin/plugin.json was bumped only by scripts/release.sh. The root manifest is retired: /release (validate, bump, git add, summary), scripts/release.sh, scripts/prepare-marketplace.ts, plugin-version-sync.ts and the CI plugin checks now read plugin/.claude-plugin/plugin.json, the one manifest. plugin-install-test.yml now triggers on templates/memory/**, the source of the shipped plugin/memory/, instead of the repo's own root memory/.
  • /qa, /clean and /setup no longer pre-approve file-deleting or API wildcards (#1292). /qa drops Bash(gh api:*), which also allowed gh api -X DELETE. /clean drops Bash(rm:*) and Bash(find:*), and narrows Bash(mv:*) to moves into docs/archive/ and scripts/archive/. /setup narrows Bash(cp:*), Bash(cat:*) and Bash(sed:*) to the files it writes or reads. When you run these skills by hand, the removed commands now ask for permission; sequant run phases are unaffected. The #1287 gate test now also rejects Bash(<tool>:*) for gh api, rm, mv, find, sed and cp, unless an allowlist entry gives a reason.
  • The docs no longer cite the 44% second-look figure (#1309). docs/reference/ready-command.md, docs/features/run-ready-gate.md, docs/investigations/ready-gate-backtest.md and docs/internal/what-weve-built.md now give the figure rebuilt from the surviving transcripts: 10 of 27 second looks caught a would-ship bug, 7 of them only after the human's "any gaps?" follow-up and 3 in the first verdict. They link docs/investigations/qa-second-look-dataset.md, and no longer state that the in-orchestrator QA under-catches, which the transcripts cannot show.
  • A failed gh call no longer reads as a posted comment or label (#1312). GitHubProvider.postComment, addLabel and removeLabel ran spawnSync and never looked at the result, so a non-zero exit, a timeout or a failed spawn resolved as success and postQaVerdictComment's "Failed to post QA verdict comment" warning could never fire. They now throw with gh's stderr, so a lost QA verdict is logged.
  • postPRComment no longer fails silently, and the ready gate no longer swallows a failed report post (#1315). GitHubProvider.postPRComment had the same unchecked spawnSync as #1312 and now throws with gh's stderr. The ready gate's postReport catch was empty; it now logs Failed to post ready-gate report for #N: <error> (through the new optional log option, default console.warn) and stays non-fatal. PlatformProvider's doc comment now states the contract: every async method rejects on failure, except checkAuth, which answers false. getIssueComments now follows it and rejects instead of resolving [] when gh fails.
  • Skills no longer break when Claude Code substitutes $0/$1 in their text (#1289). Claude Code replaces positional $N in a skill's body when the skill gets arguments, and fenced code blocks are not exempt: /qa 1249 turned substr($0, 2) into substr(1249, 2), silently breaking §6c's added-line extraction and the manual-AC attribution program. Awk fields in the shipped skills are now written $(N), which Claude Code leaves alone and which also works for drivers that read the files untemplated (codex, opencode). The §6c extraction moved to qa/scripts/added-lines.sh. A new CI lint, npm run lint:skill-argument-substitution, rejects any $N in a SKILL.md body, escaped or not.
  • A ladder halt is recorded, not only printed (#1254). When a run halts on SPEC_DIVERGENCE, DIVERGENCE_SUSPECT or TOP_OF_LADDER, the issue result carries the failure category ladder_halt, an abortReason such as ladder halt: SPEC_DIVERGENCE in exec (AC-2) and the evidence bundle; the run log's issue entry records failure with the same reason and bundle. A phase that declared SPEC_DIVERGENCE now reads failed in state (with outcome and divergenceAcs), in its run-log row and in its progress event, even when the agent exited cleanly. The MCP sequant_run result names haltReason, declaredAcs and abortReason per issue. Any other failure no failed phase accounts for (e.g. a rebase-conflict abort) is now also logged failure with its abortReason, instead of success.
  • Under sequant run, phase agents no longer edit the PR body (#1302). A quality-loop exec had rewritten a PR's body itself (gh api PATCH), because QA's missing-marker gap pointed at the PR body. That body then read as hand-edited, and every later sequant update (QA verdict note, follow-ups) was dropped. The exec skill now forbids changing the PR's body or title by any means under the orchestrator, and routes the AC table, mutation markers, settle-against-base blocks and hook-verdict changes into its final ## Summary, which the orchestrator carries into the body. QA's §6i Missing gap names exec's final Summary, not the PR body.
  • The sequant run PR body keeps exec's mutation markers and AC table when exec wrote no ## Summary (#1297). The body now carries every valid SEQUANT_MUTATION marker from exec's final output, including markers exec fenced in a code block (outside the 4,000-character cap, never duplicated) and, with no Summary, the AC table; the placeholder only appears when there is nothing to carry, and the run output warns when it does. The issue state's stored summary carries the markers too, so a qa-only re-run's PR update keeps them.
  • Skills no longer pre-approve broad shell access in allowed-tools (#1287). Bare Bash (/fullsolve, /loop, /test), Bash(*) (/verify), tool wildcards such as Bash(git *) and Bash(gh *) (/assess, /solve, /improve, /upstream, /merger), and runner wildcards such as Bash(npx tsc:*) and Bash(node -e:*) are replaced by the subcommands each skill runs, or removed. /test's unrendered Bash({{PM_RUN}} dev:*) becomes literal npm/pnpm/yarn/bun dev entries. When you run these skills by hand, commands outside their list now ask for permission; sequant run phases are unaffected. A new gate test rejects these forms in all four skill copies.
  • /qa §6i counts changed .github/workflows/*.yml and *.yaml files in its diff-file list, so a SEQUANT_MUTATION marker naming a CI workflow step classifies valid instead of test_not_in_diff (#1276)
  • A quality loop that ends qa AC_NOT_MET → loop → qa NEEDS_VERIFICATION records awaiting_verification instead of ready_for_merge, and the PR-body QA note and the MCP sequant_run result (verdict, acMet/acTotal, gaps, findings) report the latest qa pass, not the first (#1245). The latest-attempt accessor moved to src/lib/workflow/latest-phase.ts; the failed-phase scans in deriveFailureCategory and run-display.ts use it too

Full Changelog: v2.19.0...v2.20.0