sequant 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +81 -5
- package/dist/bin/cli.js +140 -13
- package/dist/src/commands/abort.d.ts +36 -0
- package/dist/src/commands/abort.js +138 -0
- package/dist/src/commands/doctor.d.ts +25 -0
- package/dist/src/commands/doctor.js +36 -1
- package/dist/src/commands/locks.d.ts +67 -0
- package/dist/src/commands/locks.js +290 -0
- package/dist/src/commands/merge.js +11 -0
- package/dist/src/commands/prompt.d.ts +46 -0
- package/dist/src/commands/prompt.js +273 -0
- package/dist/src/commands/run-display.d.ts +11 -2
- package/dist/src/commands/run-display.js +62 -28
- package/dist/src/commands/run-progress.d.ts +42 -0
- package/dist/src/commands/run-progress.js +93 -0
- package/dist/src/commands/run.js +90 -18
- package/dist/src/commands/stats.d.ts +2 -0
- package/dist/src/commands/stats.js +94 -8
- package/dist/src/commands/status.js +12 -0
- package/dist/src/commands/watch.d.ts +18 -0
- package/dist/src/commands/watch.js +211 -0
- package/dist/src/lib/ac-linter.d.ts +1 -1
- package/dist/src/lib/ac-linter.js +81 -0
- package/dist/src/lib/assess-collision-detect.d.ts +91 -0
- package/dist/src/lib/assess-collision-detect.js +217 -0
- package/dist/src/lib/assess-comment-parser.d.ts +59 -1
- package/dist/src/lib/assess-comment-parser.js +124 -2
- package/dist/src/lib/cli-ui/format.d.ts +19 -0
- package/dist/src/lib/cli-ui/format.js +34 -0
- package/dist/src/lib/cli-ui/run-renderer-types.d.ts +220 -0
- package/dist/src/lib/cli-ui/run-renderer-types.js +7 -0
- package/dist/src/lib/cli-ui/run-renderer.d.ts +265 -0
- package/dist/src/lib/cli-ui/run-renderer.js +1390 -0
- package/dist/src/lib/cli-ui/scrollback-harness.d.ts +112 -0
- package/dist/src/lib/cli-ui/scrollback-harness.js +294 -0
- package/dist/src/lib/heuristics/behavior-rule-detector.d.ts +94 -0
- package/dist/src/lib/heuristics/behavior-rule-detector.js +467 -0
- package/dist/src/lib/locks/index.d.ts +7 -0
- package/dist/src/lib/locks/index.js +5 -0
- package/dist/src/lib/locks/lock-manager.d.ts +168 -0
- package/dist/src/lib/locks/lock-manager.js +433 -0
- package/dist/src/lib/locks/types.d.ts +59 -0
- package/dist/src/lib/locks/types.js +31 -0
- package/dist/src/lib/merge-check/types.js +1 -1
- package/dist/src/lib/qa/markdown-only-ci.d.ts +46 -0
- package/dist/src/lib/qa/markdown-only-ci.js +74 -0
- package/dist/src/lib/relay/activation.d.ts +60 -0
- package/dist/src/lib/relay/activation.js +122 -0
- package/dist/src/lib/relay/archive.d.ts +34 -0
- package/dist/src/lib/relay/archive.js +112 -0
- package/dist/src/lib/relay/frame.d.ts +20 -0
- package/dist/src/lib/relay/frame.js +76 -0
- package/dist/src/lib/relay/index.d.ts +13 -0
- package/dist/src/lib/relay/index.js +13 -0
- package/dist/src/lib/relay/paths.d.ts +43 -0
- package/dist/src/lib/relay/paths.js +59 -0
- package/dist/src/lib/relay/pid.d.ts +34 -0
- package/dist/src/lib/relay/pid.js +72 -0
- package/dist/src/lib/relay/reader.d.ts +35 -0
- package/dist/src/lib/relay/reader.js +115 -0
- package/dist/src/lib/relay/types.d.ts +70 -0
- package/dist/src/lib/relay/types.js +85 -0
- package/dist/src/lib/relay/writer.d.ts +48 -0
- package/dist/src/lib/relay/writer.js +113 -0
- package/dist/src/lib/settings.d.ts +31 -1
- package/dist/src/lib/settings.js +18 -3
- package/dist/src/lib/version-check.d.ts +60 -5
- package/dist/src/lib/version-check.js +97 -9
- package/dist/src/lib/workflow/batch-executor.d.ts +20 -1
- package/dist/src/lib/workflow/batch-executor.js +274 -185
- package/dist/src/lib/workflow/config-resolver.js +4 -0
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +48 -1
- package/dist/src/lib/workflow/drivers/aider.d.ts +7 -1
- package/dist/src/lib/workflow/drivers/aider.js +9 -0
- package/dist/src/lib/workflow/drivers/claude-code.d.ts +17 -1
- package/dist/src/lib/workflow/drivers/claude-code.js +51 -2
- package/dist/src/lib/workflow/drivers/index.d.ts +1 -1
- package/dist/src/lib/workflow/event-emitter.d.ts +157 -0
- package/dist/src/lib/workflow/event-emitter.js +102 -0
- package/dist/src/lib/workflow/heartbeat.d.ts +71 -0
- package/dist/src/lib/workflow/heartbeat.js +194 -0
- package/dist/src/lib/workflow/notice.d.ts +32 -0
- package/dist/src/lib/workflow/notice.js +38 -0
- package/dist/src/lib/workflow/phase-executor.d.ts +58 -16
- package/dist/src/lib/workflow/phase-executor.js +244 -130
- package/dist/src/lib/workflow/phase-mapper.d.ts +27 -13
- package/dist/src/lib/workflow/phase-mapper.js +70 -51
- package/dist/src/lib/workflow/phase-registry.d.ts +127 -0
- package/dist/src/lib/workflow/phase-registry.js +233 -0
- package/dist/src/lib/workflow/platforms/github.d.ts +1 -1
- package/dist/src/lib/workflow/platforms/github.js +20 -3
- package/dist/src/lib/workflow/pr-status.d.ts +18 -2
- package/dist/src/lib/workflow/pr-status.js +41 -9
- package/dist/src/lib/workflow/qa-stagnation.d.ts +117 -0
- package/dist/src/lib/workflow/qa-stagnation.js +179 -0
- package/dist/src/lib/workflow/run-log-schema.d.ts +5 -55
- package/dist/src/lib/workflow/run-orchestrator.d.ts +70 -1
- package/dist/src/lib/workflow/run-orchestrator.js +464 -25
- package/dist/src/lib/workflow/run-reflect.js +1 -1
- package/dist/src/lib/workflow/run-state.d.ts +71 -0
- package/dist/src/lib/workflow/run-state.js +14 -0
- package/dist/src/lib/workflow/state-cleanup.d.ts +13 -5
- package/dist/src/lib/workflow/state-cleanup.js +17 -5
- package/dist/src/lib/workflow/state-manager.d.ts +31 -2
- package/dist/src/lib/workflow/state-manager.js +64 -1
- package/dist/src/lib/workflow/state-schema.d.ts +82 -35
- package/dist/src/lib/workflow/state-schema.js +63 -4
- package/dist/src/lib/workflow/types.d.ts +139 -16
- package/dist/src/lib/workflow/types.js +18 -13
- package/dist/src/lib/workflow/worktree-manager.d.ts +8 -1
- package/dist/src/lib/workflow/worktree-manager.js +15 -6
- package/dist/src/mcp/tools/run.d.ts +44 -0
- package/dist/src/mcp/tools/run.js +104 -13
- package/dist/src/ui/tui/App.d.ts +14 -0
- package/dist/src/ui/tui/App.js +41 -0
- package/dist/src/ui/tui/ElapsedTimer.d.ts +10 -0
- package/dist/src/ui/tui/ElapsedTimer.js +31 -0
- package/dist/src/ui/tui/Header.d.ts +6 -0
- package/dist/src/ui/tui/Header.js +15 -0
- package/dist/src/ui/tui/IssueBox.d.ts +16 -0
- package/dist/src/ui/tui/IssueBox.js +68 -0
- package/dist/src/ui/tui/Spinner.d.ts +9 -0
- package/dist/src/ui/tui/Spinner.js +18 -0
- package/dist/src/ui/tui/index.d.ts +15 -0
- package/dist/src/ui/tui/index.js +29 -0
- package/dist/src/ui/tui/theme.d.ts +29 -0
- package/dist/src/ui/tui/theme.js +52 -0
- package/dist/src/ui/tui/truncate.d.ts +11 -0
- package/dist/src/ui/tui/truncate.js +31 -0
- package/package.json +14 -6
- package/templates/agents/sequant-explorer.md +1 -0
- package/templates/agents/sequant-qa-checker.md +2 -1
- package/templates/agents/sequant-testgen.md +1 -0
- package/templates/hooks/post-tool.sh +92 -0
- package/templates/hooks/pre-tool.sh +18 -9
- package/templates/hooks/relay-check.sh +107 -0
- package/templates/relay/frame.txt +11 -0
- package/templates/scripts/cleanup-worktree.sh +25 -3
- package/templates/scripts/new-feature.sh +6 -0
- package/templates/skills/_shared/references/behavior-rule-detection.md +205 -0
- package/templates/skills/_shared/references/subagent-types.md +21 -8
- package/templates/skills/assess/SKILL.md +122 -68
- package/templates/skills/assess/references/predicted-collision-detection.md +109 -0
- package/templates/skills/docs/SKILL.md +141 -22
- package/templates/skills/exec/SKILL.md +10 -8
- package/templates/skills/fullsolve/SKILL.md +79 -5
- package/templates/skills/loop/SKILL.md +28 -0
- package/templates/skills/merger/SKILL.md +621 -0
- package/templates/skills/qa/SKILL.md +727 -8
- package/templates/skills/setup/SKILL.md +12 -6
- package/templates/skills/spec/SKILL.md +52 -0
- package/templates/skills/spec/references/parallel-groups.md +7 -0
- package/templates/skills/spec/references/recommended-workflow.md +4 -2
- package/templates/skills/testgen/SKILL.md +24 -17
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
{
|
|
9
9
|
"name": "sequant",
|
|
10
10
|
"description": "Structured workflow system for Claude Code - GitHub issue resolution with spec, exec, test, and QA phases. Includes 17 skills, MCP server with workflow tools, and pre/post-tool hooks.",
|
|
11
|
-
"version": "2.
|
|
11
|
+
"version": "2.4.0",
|
|
12
12
|
"author": {
|
|
13
13
|
"name": "sequant-io",
|
|
14
14
|
"email": "hello@sequant.io"
|
package/README.md
CHANGED
|
@@ -9,13 +9,16 @@ Solve GitHub issues with structured phases and quality gates — from issue to m
|
|
|
9
9
|
[](https://www.npmjs.com/package/sequant)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
|
|
12
|
-
### What's new in 2.
|
|
12
|
+
### What's new in 2.x
|
|
13
13
|
|
|
14
14
|
- **MCP server** — `sequant serve` exposes workflow orchestration as MCP tools (`sequant_run`, `sequant_status`, `sequant_logs`). Any MCP client can drive Sequant headlessly.
|
|
15
15
|
- **`/assess` unification** — `/solve` is merged into `/assess` with a 6-action vocabulary (PROCEED, CLOSE, MERGE, REWRITE, CLARIFY, PARK). `/solve` still works as an alias.
|
|
16
|
-
- **Parallel execution** — multi-issue runs are concurrent by default
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
16
|
+
- **Parallel execution with per-issue locks** — multi-issue runs are concurrent by default (`--concurrency`); `.sequant/locks/` prevents two sessions from clobbering the same issue.
|
|
17
|
+
- **Stacked PRs** — `sequant run --stacked` chains issue PRs onto their predecessor branch so reviewers see the incremental diff per issue instead of the cumulative chain diff.
|
|
18
|
+
- **Live run dashboard + cohort analytics** — unified renderer with a two-zone TTY grid (active issues + append-only events log); `sequant stats --label <name> --since YYYY-MM-DD` filters runs for measuring feature- or class-specific success rates.
|
|
19
|
+
- **Interactive relay** — `sequant prompt <issue> "<message>"` sends queries or directives into a running headless `sequant run`, and `sequant watch <issue>` tails the replies. Bidirectional communication with detached and CI-driven sessions.
|
|
20
|
+
- **Worktree isolation for parallel agents** — `sequant run --isolate-parallel` gives each parallel `/exec` agent group its own sub-worktree with merge-back via `git merge --no-ff`, eliminating file conflicts between concurrent agents structurally rather than by convention.
|
|
21
|
+
- **GitHub Actions & multi-agent backends** — label-triggered and comment-triggered CI workflows out of the box; `--agent aider` for non-Claude-Code execution.
|
|
19
22
|
|
|
20
23
|
Upgrading from v1.x? See the [migration guide](CHANGELOG.md#migration-from-v1x).
|
|
21
24
|
|
|
@@ -71,7 +74,7 @@ Or step-by-step:
|
|
|
71
74
|
- Git (for worktree-based isolation)
|
|
72
75
|
|
|
73
76
|
**For npm installation:**
|
|
74
|
-
- Node.js
|
|
77
|
+
- Node.js 22.12+
|
|
75
78
|
|
|
76
79
|
**Optional MCP servers (enhanced features):**
|
|
77
80
|
- `chrome-devtools` — enables `/test` for browser-based UI testing
|
|
@@ -207,14 +210,87 @@ npx sequant status # Show version and config
|
|
|
207
210
|
npx sequant run <issues...> # Execute workflow
|
|
208
211
|
npx sequant merge <issues...> # Batch integration QA before merging
|
|
209
212
|
npx sequant state <cmd> # Manage workflow state (init/rebuild/clean)
|
|
213
|
+
npx sequant locks <cmd> # Inspect/clear per-issue concurrency locks
|
|
210
214
|
npx sequant stats # View local workflow analytics
|
|
211
215
|
npx sequant dashboard # Launch real-time workflow dashboard
|
|
212
216
|
```
|
|
213
217
|
|
|
218
|
+
Cohort filtering: `npx sequant stats --label docs --since 2026-01-01`. See [Analytics — Filtering by Label or Date](docs/reference/analytics.md#filtering-by-label-or-date) for details.
|
|
219
|
+
|
|
214
220
|
See [Run Command Options](docs/reference/run-command.md), [Merge Command](docs/reference/merge-command.md), [State Command](docs/reference/state-command.md), and [Analytics](docs/reference/analytics.md) for details.
|
|
215
221
|
|
|
216
222
|
---
|
|
217
223
|
|
|
224
|
+
## Concurrency
|
|
225
|
+
|
|
226
|
+
Sequant prevents two sessions from working on the same GitHub issue at the
|
|
227
|
+
same time. When `sequant run` starts, each issue claims a per-issue lock at
|
|
228
|
+
`.sequant/locks/<issue>.lock` containing the holder's PID, hostname, start
|
|
229
|
+
time, and command. A second session attempting the same issue is skipped
|
|
230
|
+
with a clear error and the rest of the batch continues.
|
|
231
|
+
|
|
232
|
+
**Stale recovery.** Locks are auto-cleared in three situations:
|
|
233
|
+
|
|
234
|
+
1. Same host, PID no longer alive → cleared immediately (covers SIGKILL and
|
|
235
|
+
crashes).
|
|
236
|
+
2. Cross-host, lock older than 2 hours → cleared by age.
|
|
237
|
+
3. Manual: `sequant locks clear <issue>` (with safety check by default).
|
|
238
|
+
|
|
239
|
+
**Taking over an active session.** `sequant run --force <issue>` writes a
|
|
240
|
+
new lock claiming the issue. Add `--signal-other` to also SIGTERM the prior
|
|
241
|
+
PID (same host, alive only). Plain `--force` does not signal — use it when
|
|
242
|
+
you already know the other session is dead.
|
|
243
|
+
|
|
244
|
+
**Inspecting locks.**
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
npx sequant locks list # Show every active lock
|
|
248
|
+
npx sequant locks clear 123 # Clear lock for #123 (refuses fresh)
|
|
249
|
+
npx sequant locks clear 123 --force # Clear unconditionally
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Skill wiring** (`/fullsolve`, `/assess`). The `/fullsolve` skill claims the
|
|
253
|
+
lock at Phase 0.3, releases it at Phase 5.5, AND releases on every halt
|
|
254
|
+
branch (spec failure, exec exhausted, etc.) so an aborted run frees the
|
|
255
|
+
lock immediately. `/assess` probes it read-only and surfaces a dashboard
|
|
256
|
+
warning when any issue is in use. Both use these subcommands directly from
|
|
257
|
+
bash:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
npx sequant locks acquire 123 --command="/fullsolve 123" --skip-pid-check
|
|
261
|
+
npx sequant locks release 123 # idempotent; safe on every error path
|
|
262
|
+
npx sequant locks check 123 --json # exit 1 when held, prints holder JSON
|
|
263
|
+
npx sequant locks check-batch 100 101 102 # /assess: emits ⚠ lines for held issues only
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`--skip-pid-check` is required for skill shells: the Node process that runs
|
|
267
|
+
`locks acquire` exits immediately, so its PID is dead before the lock is
|
|
268
|
+
released. With the flag set, stale detection falls back to age-only on the
|
|
269
|
+
holder's own host. The default skill-lock TTL is **6h** (separate from the
|
|
270
|
+
2h cross-host TTL) — long enough to cover virtually every `/fullsolve` run
|
|
271
|
+
including multi-iteration QA loops. Override per-process via
|
|
272
|
+
`SEQUANT_SKILL_LOCK_TTL_MS=<milliseconds>`.
|
|
273
|
+
|
|
274
|
+
A skill that crashes mid-run leaves at most a 6h orphan; clear it manually
|
|
275
|
+
with `sequant locks clear <issue>` to recover sooner. The skill's explicit
|
|
276
|
+
release calls on every halt branch (see `.claude/skills/fullsolve/SKILL.md`
|
|
277
|
+
Phase 0.3 release contract) mean this corner case should be rare in practice.
|
|
278
|
+
|
|
279
|
+
**Read-only commands** (`status`, `merge`, `/assess`) warn when an issue is
|
|
280
|
+
locked but do not block.
|
|
281
|
+
|
|
282
|
+
**MCP / orchestrator mode.** When the `SEQUANT_ORCHESTRATOR` env var is set
|
|
283
|
+
(in-process or remote MCP-driven runs), all lock operations are no-ops —
|
|
284
|
+
the orchestrator caller is responsible for any coordination.
|
|
285
|
+
|
|
286
|
+
**Caveats.** The lock relies on `open(O_CREAT | O_EXCL)` and is reliable on
|
|
287
|
+
local filesystems. NFS and other network filesystems may not honor those
|
|
288
|
+
semantics; users on networked repos may see false positives. The
|
|
289
|
+
`SEQUANT_LOCKS_DIR` env var overrides the lock directory (used in tests
|
|
290
|
+
and unusual layouts).
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
218
294
|
## Configuration
|
|
219
295
|
|
|
220
296
|
```json
|
package/dist/bin/cli.js
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Sequential AI phases with quality gates for any codebase.
|
|
6
6
|
*/
|
|
7
|
-
import { Command } from "commander";
|
|
7
|
+
import { Command, InvalidArgumentError } from "commander";
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import { fileURLToPath } from "url";
|
|
10
10
|
import { dirname, resolve } from "path";
|
|
11
11
|
import { readFileSync } from "fs";
|
|
12
12
|
import { initCommand } from "../src/commands/init.js";
|
|
13
|
-
import { isLocalNodeModulesInstall } from "../src/lib/version-check.js";
|
|
13
|
+
import { buildHomeStrayWarning, getInstallRoot, isHomeStrayInstall, isLocalNodeModulesInstall, } from "../src/lib/version-check.js";
|
|
14
14
|
import { configureUI, banner } from "../src/lib/cli-ui.js";
|
|
15
15
|
import { isCI, isStdoutTTY } from "../src/lib/tty.js";
|
|
16
16
|
import { detectPackageManagerSync, getPackageManagerCommands, } from "../src/lib/stacks.js";
|
|
@@ -47,7 +47,32 @@ import { stateInitCommand, stateRebuildCommand, stateCleanCommand, } from "../sr
|
|
|
47
47
|
import { syncCommand, areSkillsOutdated } from "../src/commands/sync.js";
|
|
48
48
|
import { mergeCommand } from "../src/commands/merge.js";
|
|
49
49
|
import { conventionsCommand } from "../src/commands/conventions.js";
|
|
50
|
+
import { locksListCommand, locksClearCommand, locksAcquireCommand, locksReleaseCommand, locksCheckCommand, locksCheckBatchCommand, } from "../src/commands/locks.js";
|
|
51
|
+
import { promptCommand } from "../src/commands/prompt.js";
|
|
52
|
+
import { watchCommand } from "../src/commands/watch.js";
|
|
53
|
+
import { abortCommand } from "../src/commands/abort.js";
|
|
50
54
|
import { getManifest } from "../src/lib/manifest.js";
|
|
55
|
+
import { phaseRegistry } from "../src/lib/workflow/phase-registry.js";
|
|
56
|
+
/**
|
|
57
|
+
* Validate `--phases` argument against the phase registry.
|
|
58
|
+
*
|
|
59
|
+
* Splits a comma-separated phase list, checks each name against
|
|
60
|
+
* `phaseRegistry`, and exits with a clear error message if any phase
|
|
61
|
+
* is unknown. Returns the original string unchanged so downstream
|
|
62
|
+
* `RunOptions.phases` parsing in `config-resolver.ts` is undisturbed.
|
|
63
|
+
*/
|
|
64
|
+
function validatePhasesFlag(value) {
|
|
65
|
+
const names = value
|
|
66
|
+
.split(",")
|
|
67
|
+
.map((p) => p.trim())
|
|
68
|
+
.filter((p) => p.length > 0);
|
|
69
|
+
const unknown = names.filter((n) => !phaseRegistry.has(n));
|
|
70
|
+
if (unknown.length > 0) {
|
|
71
|
+
const available = phaseRegistry.names().join(", ");
|
|
72
|
+
throw new InvalidArgumentError(`Unknown phase '${unknown[0]}'. Available: ${available}`);
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
51
76
|
const program = new Command();
|
|
52
77
|
// Handle --no-color before parsing
|
|
53
78
|
if (process.argv.includes("--no-color")) {
|
|
@@ -62,13 +87,22 @@ configureUI({
|
|
|
62
87
|
isCI: isCI(),
|
|
63
88
|
minimal: process.env.SEQUANT_MINIMAL === "1",
|
|
64
89
|
});
|
|
65
|
-
// Warn if running from
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
// Warn if running from a problematic install location.
|
|
91
|
+
// The home-stray case ($HOME/node_modules/sequant) gets a distinct warning
|
|
92
|
+
// because it pollutes resolution for every subdirectory of $HOME, which the
|
|
93
|
+
// generic "local node_modules" message doesn't communicate. Resolve the
|
|
94
|
+
// install root once and pass it to both predicates to avoid a second walk.
|
|
95
|
+
if (!process.argv.includes("--quiet")) {
|
|
96
|
+
const installRoot = getInstallRoot();
|
|
97
|
+
if (installRoot && isHomeStrayInstall(installRoot)) {
|
|
98
|
+
console.warn(chalk.yellow(buildHomeStrayWarning(installRoot)));
|
|
99
|
+
}
|
|
100
|
+
else if (isLocalNodeModulesInstall()) {
|
|
101
|
+
const pmCommands = getPackageManagerCommands(detectPackageManagerSync());
|
|
102
|
+
console.warn(chalk.yellow("! Running sequant from local node_modules\n" +
|
|
103
|
+
" For latest version: npx sequant@latest\n" +
|
|
104
|
+
` To remove local: ${pmCommands.removePkg} sequant\n`));
|
|
105
|
+
}
|
|
72
106
|
}
|
|
73
107
|
program
|
|
74
108
|
.name("sequant")
|
|
@@ -104,6 +138,7 @@ program
|
|
|
104
138
|
.command("doctor")
|
|
105
139
|
.description("Check your Sequant installation for issues")
|
|
106
140
|
.option("--skip-issue-check", "Skip closed-issue verification check")
|
|
141
|
+
.option("-q, --quiet", "Suppress informational warnings")
|
|
107
142
|
.action(doctorCommand);
|
|
108
143
|
program
|
|
109
144
|
.command("status")
|
|
@@ -128,7 +163,7 @@ program
|
|
|
128
163
|
.command("run")
|
|
129
164
|
.description("Execute workflow for GitHub issues using Claude Agent SDK")
|
|
130
165
|
.argument("[issues...]", "Issue numbers to process")
|
|
131
|
-
.option("--phases <list>", "Phases to run (default: spec,exec,qa)")
|
|
166
|
+
.option("--phases <list>", "Phases to run (default: spec,exec,qa)", validatePhasesFlag)
|
|
132
167
|
.option("--sequential", "Stop on first issue failure (default: continue)")
|
|
133
168
|
.option("-d, --dry-run", "Preview without execution")
|
|
134
169
|
.option("-v, --verbose", "Verbose output with streaming")
|
|
@@ -136,14 +171,16 @@ program
|
|
|
136
171
|
.option("--log-json", "Enable structured JSON logging (default: true)")
|
|
137
172
|
.option("--no-log", "Disable JSON logging for this run")
|
|
138
173
|
.option("--log-path <path>", "Custom log directory path")
|
|
139
|
-
.option("-
|
|
174
|
+
.option("-Q, --quality-loop", "Enable quality loop with auto-retry")
|
|
140
175
|
.option("--max-iterations <n>", "Max iterations for quality loop (default: 3)", parseInt)
|
|
141
176
|
.option("--batch <issues>", 'Group of issues to run together (e.g., --batch "1 2" --batch "3")', (value, prev) => prev.concat([value]), [])
|
|
142
177
|
.option("--smart-tests", "Enable smart test detection (default)")
|
|
143
178
|
.option("--no-smart-tests", "Disable smart test detection")
|
|
144
179
|
.option("--testgen", "Run testgen phase after spec")
|
|
145
|
-
.option("--
|
|
180
|
+
.option("--security-review", "Run security-review phase after spec")
|
|
181
|
+
.option("-q, --quiet", "Suppress version warnings and non-essential output")
|
|
146
182
|
.option("--chain", "Chain issues: each branches from previous (implies --sequential)")
|
|
183
|
+
.option("--stacked", "Stack PRs: middle PRs target predecessor branch instead of main; first/last target main (implies --chain)")
|
|
147
184
|
.option("--qa-gate", "Wait for QA pass before starting next issue in chain (requires --chain)")
|
|
148
185
|
.option("--base <branch>", "Base branch for worktree creation (default: main or settings.run.defaultBase)")
|
|
149
186
|
.option("--no-mcp", "Disable MCP server injection in headless mode")
|
|
@@ -151,12 +188,61 @@ program
|
|
|
151
188
|
.option("--resume", "Resume from last completed phase (reads phase markers from GitHub)")
|
|
152
189
|
.option("--no-rebase", "Skip pre-PR rebase onto origin/main (use when you want to handle rebasing manually)")
|
|
153
190
|
.option("--no-pr", "Skip PR creation after successful QA (manual PR workflow)")
|
|
154
|
-
.option("-f, --force", "Force re-execution of completed issues (bypass pre-flight state guard)")
|
|
191
|
+
.option("-f, --force", "Force re-execution of completed issues (bypass pre-flight state guard) and take over per-issue locks")
|
|
192
|
+
.option("--signal-other", "With --force, SIGTERM the prior PID holding the lock (same-host alive only)")
|
|
155
193
|
.option("--concurrency <n>", "Max concurrent issues in parallel mode (default: 3)", parseInt)
|
|
156
194
|
.option("--isolate-parallel", "Isolate parallel agent groups in separate worktrees (prevents file conflicts)")
|
|
157
195
|
.option("--reflect", "Analyze run results and suggest improvements")
|
|
158
196
|
.option("--agent <name>", 'Agent driver for phase execution (default: "claude-code")')
|
|
197
|
+
.option("--experimental-tui", "Render live multi-issue dashboard (requires TTY; falls back to linear output when piped)")
|
|
198
|
+
.option("--no-relay", "Disable interactive relay (#383); `sequant prompt` cannot reach this run")
|
|
159
199
|
.action(runCommand);
|
|
200
|
+
program
|
|
201
|
+
.command("prompt")
|
|
202
|
+
.description("Send a message into a running headless sequant session (#383)")
|
|
203
|
+
.argument("[args...]", '[<issue>] "<message>"')
|
|
204
|
+
.option("--type <type>", "Message type: query (default), directive, abort", "query")
|
|
205
|
+
.option("--wait <seconds>", "Block until a reply arrives or the timeout elapses (#645, Gap 4)", parseInt)
|
|
206
|
+
.option("--json", "Output as JSON")
|
|
207
|
+
.action((args, options) => {
|
|
208
|
+
return promptCommand({
|
|
209
|
+
args,
|
|
210
|
+
options: {
|
|
211
|
+
type: options.type,
|
|
212
|
+
waitSeconds: typeof options.wait === "number" ? options.wait : undefined,
|
|
213
|
+
json: Boolean(options.json),
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
program
|
|
218
|
+
.command("watch")
|
|
219
|
+
.description("Tail the relay outbox for replies from a running sequant session (#383)")
|
|
220
|
+
.argument("<issue>", "Issue number to watch")
|
|
221
|
+
.option("--json", "Output as JSON lines")
|
|
222
|
+
.action((issueArg, options) => {
|
|
223
|
+
return watchCommand({
|
|
224
|
+
args: [issueArg],
|
|
225
|
+
options: { json: Boolean(options.json) },
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
program
|
|
229
|
+
.command("abort")
|
|
230
|
+
.description("Out-of-band abort: signal a running sequant session directly (#645)")
|
|
231
|
+
.argument("[issue]", "Issue number (auto-resolved when a single run is active)")
|
|
232
|
+
.option("--force", "Skip the SIGINT grace period; SIGTERM immediately")
|
|
233
|
+
.option("--grace <seconds>", "Seconds to wait after SIGINT before escalating (default: 10)", parseInt)
|
|
234
|
+
.option("--json", "Output as JSON")
|
|
235
|
+
.action((issueArg, options) => {
|
|
236
|
+
const args = issueArg === undefined ? [] : [issueArg];
|
|
237
|
+
return abortCommand({
|
|
238
|
+
args,
|
|
239
|
+
options: {
|
|
240
|
+
force: Boolean(options.force),
|
|
241
|
+
graceSeconds: typeof options.grace === "number" ? options.grace : undefined,
|
|
242
|
+
json: Boolean(options.json),
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
});
|
|
160
246
|
program
|
|
161
247
|
.command("merge")
|
|
162
248
|
.description("Batch-level integration QA — verify feature branches before merging")
|
|
@@ -195,6 +281,8 @@ program
|
|
|
195
281
|
.option("--csv", "Output as CSV")
|
|
196
282
|
.option("--json", "Output as JSON")
|
|
197
283
|
.option("--detailed", "Show detailed analytics (QA verdicts, trends, label segmentation)")
|
|
284
|
+
.option("--label <name>", "Filter to runs whose issues carry the given label")
|
|
285
|
+
.option("--since <date>", "Filter to runs with startTime on/after YYYY-MM-DD (UTC)")
|
|
198
286
|
.action(statsCommand);
|
|
199
287
|
program
|
|
200
288
|
.command("dashboard")
|
|
@@ -244,6 +332,45 @@ stateCmd
|
|
|
244
332
|
.option("--max-age <days>", "Remove entries older than N days", parseInt)
|
|
245
333
|
.option("--all", "Remove all orphaned entries (merged and abandoned)")
|
|
246
334
|
.action(stateCleanCommand);
|
|
335
|
+
// Per-issue concurrency locks (#625)
|
|
336
|
+
const locksCmd = program
|
|
337
|
+
.command("locks")
|
|
338
|
+
.description("Inspect and clear per-issue concurrency locks");
|
|
339
|
+
locksCmd
|
|
340
|
+
.command("list")
|
|
341
|
+
.description("List active locks with staleness metadata")
|
|
342
|
+
.option("--json", "Output as JSON")
|
|
343
|
+
.action(locksListCommand);
|
|
344
|
+
locksCmd
|
|
345
|
+
.command("clear <issue>")
|
|
346
|
+
.description("Manually clear the lock for an issue (safety check unless --force)")
|
|
347
|
+
.option("-f, --force", "Skip safety check; clear even a fresh same-host alive lock")
|
|
348
|
+
.option("--json", "Output as JSON")
|
|
349
|
+
.action(locksClearCommand);
|
|
350
|
+
locksCmd
|
|
351
|
+
.command("acquire <issue>")
|
|
352
|
+
.description("Claim the lock for an issue (used by /fullsolve, /assess; exits 1 if held)")
|
|
353
|
+
.option("--command <command>", "Human-readable command label", "unknown")
|
|
354
|
+
.option("--skip-pid-check", "Mark the lock so stale recovery skips same-host PID checks (use from skill shells)")
|
|
355
|
+
.option("-f, --force", "Take over even if another holder is alive")
|
|
356
|
+
.option("--signal-other", "When forcing, SIGTERM the prior same-host holder if alive")
|
|
357
|
+
.option("--json", "Output as JSON")
|
|
358
|
+
.action(locksAcquireCommand);
|
|
359
|
+
locksCmd
|
|
360
|
+
.command("release <issue>")
|
|
361
|
+
.description("Release a lock previously acquired on this host (skill or current process)")
|
|
362
|
+
.option("--json", "Output as JSON")
|
|
363
|
+
.action(locksReleaseCommand);
|
|
364
|
+
locksCmd
|
|
365
|
+
.command("check <issue>")
|
|
366
|
+
.description("Read-only probe: print lock holder if any; exit 1 when held (for /assess)")
|
|
367
|
+
.option("--json", "Output as JSON")
|
|
368
|
+
.action(locksCheckCommand);
|
|
369
|
+
locksCmd
|
|
370
|
+
.command("check-batch <issues...>")
|
|
371
|
+
.description("Batch read-only probe: emit canonical ⚠ warning lines for held issues (for /assess dashboard)")
|
|
372
|
+
.option("--json", "Output as JSON instead of canonical text lines")
|
|
373
|
+
.action(locksCheckBatchCommand);
|
|
247
374
|
// Auto-sync skills after npm upgrade (version mismatch detection)
|
|
248
375
|
// Only triggers when skills were previously synced (has .sequant-version marker).
|
|
249
376
|
// Projects that manage skills manually (no marker) are not affected.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sequant abort <issue>` — out-of-band escape hatch for a running headless
|
|
3
|
+
* session (#645, Gap 7).
|
|
4
|
+
*
|
|
5
|
+
* `sequant prompt --type abort` queues an abort message into the inbox, which
|
|
6
|
+
* the agent must read via the PostToolUse hook chain. When that chain is
|
|
7
|
+
* broken (the bug originally reported in #645), no in-band abort can land.
|
|
8
|
+
*
|
|
9
|
+
* This command bypasses the inbox entirely: it locates the orchestrator PID
|
|
10
|
+
* via `state.json.relay.pid` (with the per-issue pidfile as fallback) and
|
|
11
|
+
* sends signals directly. The receiving end is the existing ShutdownManager
|
|
12
|
+
* in `sequant run`, which already performs a clean teardown on SIGINT/SIGTERM.
|
|
13
|
+
*/
|
|
14
|
+
export interface AbortCommandOptions {
|
|
15
|
+
/** Skip the SIGINT grace period; SIGTERM immediately. */
|
|
16
|
+
force?: boolean;
|
|
17
|
+
/** Seconds to wait after SIGINT before escalating. Default 10. */
|
|
18
|
+
graceSeconds?: number;
|
|
19
|
+
json?: boolean;
|
|
20
|
+
/** Test seam: override the system kill function. */
|
|
21
|
+
killFn?: (pid: number, signal: NodeJS.Signals) => void;
|
|
22
|
+
/** Test seam: override the liveness check. */
|
|
23
|
+
isAlive?: (pid: number) => boolean;
|
|
24
|
+
/** Test seam: override the poll interval (ms). Default 250. */
|
|
25
|
+
pollIntervalMs?: number;
|
|
26
|
+
/** Test seam: override SIGTERM grace before SIGKILL (ms). Default 3000. */
|
|
27
|
+
sigtermTimeoutMs?: number;
|
|
28
|
+
/** Test seam: override SIGKILL final wait (ms). Default 2000. */
|
|
29
|
+
sigkillTimeoutMs?: number;
|
|
30
|
+
/** Test seam: override cwd for pidfile resolution. */
|
|
31
|
+
cwd?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function abortCommand(argsAndOptions: {
|
|
34
|
+
args: string[];
|
|
35
|
+
options: AbortCommandOptions;
|
|
36
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sequant abort <issue>` — out-of-band escape hatch for a running headless
|
|
3
|
+
* session (#645, Gap 7).
|
|
4
|
+
*
|
|
5
|
+
* `sequant prompt --type abort` queues an abort message into the inbox, which
|
|
6
|
+
* the agent must read via the PostToolUse hook chain. When that chain is
|
|
7
|
+
* broken (the bug originally reported in #645), no in-band abort can land.
|
|
8
|
+
*
|
|
9
|
+
* This command bypasses the inbox entirely: it locates the orchestrator PID
|
|
10
|
+
* via `state.json.relay.pid` (with the per-issue pidfile as fallback) and
|
|
11
|
+
* sends signals directly. The receiving end is the existing ShutdownManager
|
|
12
|
+
* in `sequant run`, which already performs a clean teardown on SIGINT/SIGTERM.
|
|
13
|
+
*/
|
|
14
|
+
import chalk from "chalk";
|
|
15
|
+
import { isPidAlive, readPidFile } from "../lib/relay/pid.js";
|
|
16
|
+
import { StateManager } from "../lib/workflow/state-manager.js";
|
|
17
|
+
import { findActiveIssues, resolveTargetIssue } from "./prompt.js";
|
|
18
|
+
function delay(ms) {
|
|
19
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Send `signal` and wait up to `timeoutMs` for the PID to exit. Returns true
|
|
23
|
+
* if the process died, false if still alive at the deadline.
|
|
24
|
+
*/
|
|
25
|
+
async function signalAndWait(pid, signal, timeoutMs, isAlive, killFn, pollIntervalMs) {
|
|
26
|
+
try {
|
|
27
|
+
killFn(pid, signal);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// ESRCH (process already dead) → success.
|
|
31
|
+
if (!isAlive(pid))
|
|
32
|
+
return true;
|
|
33
|
+
throw new Error(`Failed to send ${signal} to PID ${pid} (signal not delivered)`);
|
|
34
|
+
}
|
|
35
|
+
const deadline = Date.now() + timeoutMs;
|
|
36
|
+
while (Date.now() < deadline) {
|
|
37
|
+
if (!isAlive(pid))
|
|
38
|
+
return true;
|
|
39
|
+
await delay(pollIntervalMs);
|
|
40
|
+
}
|
|
41
|
+
return !isAlive(pid);
|
|
42
|
+
}
|
|
43
|
+
export async function abortCommand(argsAndOptions) {
|
|
44
|
+
const { args, options } = argsAndOptions;
|
|
45
|
+
const json = Boolean(options.json);
|
|
46
|
+
const killFn = options.killFn ??
|
|
47
|
+
((pid, signal) => {
|
|
48
|
+
process.kill(pid, signal);
|
|
49
|
+
});
|
|
50
|
+
const isAlive = options.isAlive ?? isPidAlive;
|
|
51
|
+
const pollIntervalMs = options.pollIntervalMs ?? 250;
|
|
52
|
+
const cwd = options.cwd ?? process.cwd();
|
|
53
|
+
// Resolve target issue: explicit arg or single-active auto-resolve.
|
|
54
|
+
const stateManager = new StateManager();
|
|
55
|
+
let issueNumber;
|
|
56
|
+
const issueArg = args[0];
|
|
57
|
+
if (issueArg !== undefined) {
|
|
58
|
+
const n = Number.parseInt(issueArg, 10);
|
|
59
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
60
|
+
throw new Error(`Invalid issue number: '${issueArg}'`);
|
|
61
|
+
}
|
|
62
|
+
issueNumber = n;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const all = stateManager.stateExists()
|
|
66
|
+
? Object.values(await stateManager.getAllIssueStates())
|
|
67
|
+
: [];
|
|
68
|
+
const active = findActiveIssues(all, isAlive, cwd);
|
|
69
|
+
const target = resolveTargetIssue({ explicit: null, activeIssues: active });
|
|
70
|
+
issueNumber = target.issue;
|
|
71
|
+
}
|
|
72
|
+
const issueState = await stateManager.getIssueState(issueNumber);
|
|
73
|
+
const pid = issueState?.relay?.pid ?? readPidFile(issueNumber, cwd) ?? null;
|
|
74
|
+
if (pid === null) {
|
|
75
|
+
const msg = `No relay PID found for #${issueNumber}. Is the run active?`;
|
|
76
|
+
if (json) {
|
|
77
|
+
console.log(JSON.stringify({ ok: false, issue: issueNumber, error: msg }));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.error(chalk.yellow(msg));
|
|
81
|
+
}
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!isAlive(pid)) {
|
|
86
|
+
const msg = `PID ${pid} for #${issueNumber} is already dead.`;
|
|
87
|
+
if (json) {
|
|
88
|
+
console.log(JSON.stringify({ ok: true, issue: issueNumber, pid, signal: null }));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.log(chalk.gray(msg));
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const graceMs = Math.max(0, (options.graceSeconds ?? 10) * 1000);
|
|
96
|
+
const force = Boolean(options.force);
|
|
97
|
+
let delivered = "SIGINT";
|
|
98
|
+
let died = false;
|
|
99
|
+
if (!force) {
|
|
100
|
+
if (!json) {
|
|
101
|
+
console.log(chalk.gray(`Sending SIGINT to PID ${pid} (#${issueNumber}); waiting up to ${graceMs / 1000}s for graceful exit…`));
|
|
102
|
+
}
|
|
103
|
+
died = await signalAndWait(pid, "SIGINT", graceMs, isAlive, killFn, pollIntervalMs);
|
|
104
|
+
}
|
|
105
|
+
if (!died) {
|
|
106
|
+
delivered = "SIGTERM";
|
|
107
|
+
if (!json) {
|
|
108
|
+
console.log(chalk.yellow(force
|
|
109
|
+
? `Sending SIGTERM to PID ${pid} (#${issueNumber}) (--force)…`
|
|
110
|
+
: `Grace expired; escalating to SIGTERM…`));
|
|
111
|
+
}
|
|
112
|
+
died = await signalAndWait(pid, "SIGTERM", options.sigtermTimeoutMs ?? 3000, isAlive, killFn, pollIntervalMs);
|
|
113
|
+
}
|
|
114
|
+
if (!died) {
|
|
115
|
+
delivered = "SIGKILL";
|
|
116
|
+
if (!json) {
|
|
117
|
+
console.log(chalk.red(`SIGTERM ignored; escalating to SIGKILL on PID ${pid}…`));
|
|
118
|
+
}
|
|
119
|
+
died = await signalAndWait(pid, "SIGKILL", options.sigkillTimeoutMs ?? 2000, isAlive, killFn, pollIntervalMs);
|
|
120
|
+
}
|
|
121
|
+
if (!died) {
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
}
|
|
124
|
+
if (json) {
|
|
125
|
+
console.log(JSON.stringify({
|
|
126
|
+
ok: died,
|
|
127
|
+
issue: issueNumber,
|
|
128
|
+
pid,
|
|
129
|
+
signal: delivered,
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
else if (died) {
|
|
133
|
+
console.log(chalk.green(`Aborted #${issueNumber} (PID ${pid}, ${delivered}).`));
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
console.error(chalk.red(`Failed to abort #${issueNumber}: PID ${pid} still alive after ${delivered}.`));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -3,7 +3,32 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export interface DoctorOptions {
|
|
5
5
|
skipIssueCheck?: boolean;
|
|
6
|
+
/** Suppress informational warnings (e.g., upstream subagent routing notice) */
|
|
7
|
+
quiet?: boolean;
|
|
6
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Upstream subagent routing bug notice (anthropics/claude-code#43869).
|
|
11
|
+
* Subagent `model:` declarations are silently ignored; agents run on the
|
|
12
|
+
* parent session's model. Exported as a constant so tests can match it.
|
|
13
|
+
*/
|
|
14
|
+
export declare const UPSTREAM_SUBAGENT_WARNING: {
|
|
15
|
+
readonly primary: "WARN: Claude Code #43869 — subagent model: declarations are ignored. Agents currently run on parent model.";
|
|
16
|
+
readonly url: "https://github.com/anthropics/claude-code/issues/43869";
|
|
17
|
+
readonly inertFieldNote: "Note: agents.model is currently inert (see #43869).";
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Emit the upstream subagent-routing bug notice and (when applicable) the
|
|
21
|
+
* `agents.model` inert-field note. Honors `--quiet`.
|
|
22
|
+
*
|
|
23
|
+
* AC-2: primary line + link, suppressible via `quiet`.
|
|
24
|
+
* AC-4: when the user's `agents.model` differs from the shipped default,
|
|
25
|
+
* append a second-line note flagging the field as inert.
|
|
26
|
+
*/
|
|
27
|
+
export declare function emitUpstreamWarning(opts: {
|
|
28
|
+
quiet?: boolean;
|
|
29
|
+
agentsModel: string;
|
|
30
|
+
defaultAgentsModel: string;
|
|
31
|
+
}): void;
|
|
7
32
|
interface ClosedIssue {
|
|
8
33
|
number: number;
|
|
9
34
|
title: string;
|
|
@@ -8,11 +8,40 @@ import { GitHubProvider } from "../lib/workflow/platforms/github.js";
|
|
|
8
8
|
import { fileExists, isExecutable } from "../lib/fs.js";
|
|
9
9
|
import { getManifest } from "../lib/manifest.js";
|
|
10
10
|
import { commandExists, isGhAuthenticated, isNativeWindows, isWSL, checkOptionalMcpServers, getMcpServersConfig, OPTIONAL_MCP_SERVERS, } from "../lib/system.js";
|
|
11
|
-
import { getSettings } from "../lib/settings.js";
|
|
11
|
+
import { getSettings, DEFAULT_AGENT_SETTINGS } from "../lib/settings.js";
|
|
12
12
|
import { checkVersionThorough, getVersionWarning, } from "../lib/version-check.js";
|
|
13
13
|
import { areSkillsOutdated } from "./sync.js";
|
|
14
14
|
import { readAgentsMd, checkAgentsMdConsistency, AGENTS_MD_PATH, } from "../lib/agents-md.js";
|
|
15
15
|
import { readFile } from "../lib/fs.js";
|
|
16
|
+
// TODO: remove when anthropics/claude-code#43869 closes
|
|
17
|
+
/**
|
|
18
|
+
* Upstream subagent routing bug notice (anthropics/claude-code#43869).
|
|
19
|
+
* Subagent `model:` declarations are silently ignored; agents run on the
|
|
20
|
+
* parent session's model. Exported as a constant so tests can match it.
|
|
21
|
+
*/
|
|
22
|
+
export const UPSTREAM_SUBAGENT_WARNING = {
|
|
23
|
+
primary: "WARN: Claude Code #43869 — subagent model: declarations are ignored. Agents currently run on parent model.",
|
|
24
|
+
url: "https://github.com/anthropics/claude-code/issues/43869",
|
|
25
|
+
inertFieldNote: "Note: agents.model is currently inert (see #43869).",
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Emit the upstream subagent-routing bug notice and (when applicable) the
|
|
29
|
+
* `agents.model` inert-field note. Honors `--quiet`.
|
|
30
|
+
*
|
|
31
|
+
* AC-2: primary line + link, suppressible via `quiet`.
|
|
32
|
+
* AC-4: when the user's `agents.model` differs from the shipped default,
|
|
33
|
+
* append a second-line note flagging the field as inert.
|
|
34
|
+
*/
|
|
35
|
+
export function emitUpstreamWarning(opts) {
|
|
36
|
+
if (opts.quiet)
|
|
37
|
+
return;
|
|
38
|
+
console.log("");
|
|
39
|
+
console.log(chalk.yellow(` ! ${UPSTREAM_SUBAGENT_WARNING.primary}`));
|
|
40
|
+
console.log(chalk.yellow(` ${UPSTREAM_SUBAGENT_WARNING.url}`));
|
|
41
|
+
if (opts.agentsModel !== opts.defaultAgentsModel) {
|
|
42
|
+
console.log(chalk.yellow(` ${UPSTREAM_SUBAGENT_WARNING.inertFieldNote}`));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
16
45
|
/**
|
|
17
46
|
* Labels that indicate an issue should be skipped from closed-issue verification
|
|
18
47
|
* (case-insensitive matching)
|
|
@@ -492,6 +521,12 @@ export async function doctorCommand(options = {}) {
|
|
|
492
521
|
else
|
|
493
522
|
failCount++;
|
|
494
523
|
}
|
|
524
|
+
// Upstream subagent routing notice (AC-2/AC-4)
|
|
525
|
+
emitUpstreamWarning({
|
|
526
|
+
quiet: options.quiet,
|
|
527
|
+
agentsModel: settings.agents.model,
|
|
528
|
+
defaultAgentsModel: DEFAULT_AGENT_SETTINGS.model,
|
|
529
|
+
});
|
|
495
530
|
// Summary with boxed output
|
|
496
531
|
const totalChecks = passCount + warnCount + failCount;
|
|
497
532
|
let summaryTitle;
|