sequant 2.2.0 → 2.3.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.
Files changed (137) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +73 -0
  4. package/dist/bin/cli.js +94 -9
  5. package/dist/src/commands/doctor.d.ts +25 -0
  6. package/dist/src/commands/doctor.js +36 -1
  7. package/dist/src/commands/locks.d.ts +67 -0
  8. package/dist/src/commands/locks.js +290 -0
  9. package/dist/src/commands/merge.js +11 -0
  10. package/dist/src/commands/prompt.d.ts +39 -0
  11. package/dist/src/commands/prompt.js +179 -0
  12. package/dist/src/commands/run-display.d.ts +11 -2
  13. package/dist/src/commands/run-display.js +62 -28
  14. package/dist/src/commands/run-progress.d.ts +32 -0
  15. package/dist/src/commands/run-progress.js +76 -0
  16. package/dist/src/commands/run.js +80 -18
  17. package/dist/src/commands/stats.d.ts +2 -0
  18. package/dist/src/commands/stats.js +94 -8
  19. package/dist/src/commands/status.js +12 -0
  20. package/dist/src/commands/watch.d.ts +16 -0
  21. package/dist/src/commands/watch.js +147 -0
  22. package/dist/src/lib/ac-linter.d.ts +1 -1
  23. package/dist/src/lib/ac-linter.js +81 -0
  24. package/dist/src/lib/assess-collision-detect.d.ts +91 -0
  25. package/dist/src/lib/assess-collision-detect.js +217 -0
  26. package/dist/src/lib/assess-comment-parser.d.ts +59 -1
  27. package/dist/src/lib/assess-comment-parser.js +124 -2
  28. package/dist/src/lib/cli-ui/format.d.ts +19 -0
  29. package/dist/src/lib/cli-ui/format.js +34 -0
  30. package/dist/src/lib/cli-ui/run-renderer-types.d.ts +181 -0
  31. package/dist/src/lib/cli-ui/run-renderer-types.js +7 -0
  32. package/dist/src/lib/cli-ui/run-renderer.d.ts +239 -0
  33. package/dist/src/lib/cli-ui/run-renderer.js +1173 -0
  34. package/dist/src/lib/heuristics/behavior-rule-detector.d.ts +94 -0
  35. package/dist/src/lib/heuristics/behavior-rule-detector.js +467 -0
  36. package/dist/src/lib/locks/index.d.ts +7 -0
  37. package/dist/src/lib/locks/index.js +5 -0
  38. package/dist/src/lib/locks/lock-manager.d.ts +168 -0
  39. package/dist/src/lib/locks/lock-manager.js +433 -0
  40. package/dist/src/lib/locks/types.d.ts +59 -0
  41. package/dist/src/lib/locks/types.js +31 -0
  42. package/dist/src/lib/qa/markdown-only-ci.d.ts +46 -0
  43. package/dist/src/lib/qa/markdown-only-ci.js +74 -0
  44. package/dist/src/lib/relay/activation.d.ts +60 -0
  45. package/dist/src/lib/relay/activation.js +122 -0
  46. package/dist/src/lib/relay/archive.d.ts +34 -0
  47. package/dist/src/lib/relay/archive.js +106 -0
  48. package/dist/src/lib/relay/frame.d.ts +20 -0
  49. package/dist/src/lib/relay/frame.js +76 -0
  50. package/dist/src/lib/relay/index.d.ts +13 -0
  51. package/dist/src/lib/relay/index.js +13 -0
  52. package/dist/src/lib/relay/paths.d.ts +43 -0
  53. package/dist/src/lib/relay/paths.js +59 -0
  54. package/dist/src/lib/relay/pid.d.ts +34 -0
  55. package/dist/src/lib/relay/pid.js +72 -0
  56. package/dist/src/lib/relay/reader.d.ts +35 -0
  57. package/dist/src/lib/relay/reader.js +115 -0
  58. package/dist/src/lib/relay/types.d.ts +68 -0
  59. package/dist/src/lib/relay/types.js +76 -0
  60. package/dist/src/lib/relay/writer.d.ts +48 -0
  61. package/dist/src/lib/relay/writer.js +113 -0
  62. package/dist/src/lib/settings.d.ts +31 -1
  63. package/dist/src/lib/settings.js +18 -3
  64. package/dist/src/lib/version-check.d.ts +60 -5
  65. package/dist/src/lib/version-check.js +97 -9
  66. package/dist/src/lib/workflow/batch-executor.d.ts +20 -1
  67. package/dist/src/lib/workflow/batch-executor.js +248 -175
  68. package/dist/src/lib/workflow/config-resolver.js +4 -0
  69. package/dist/src/lib/workflow/heartbeat.d.ts +71 -0
  70. package/dist/src/lib/workflow/heartbeat.js +194 -0
  71. package/dist/src/lib/workflow/phase-executor.d.ts +62 -8
  72. package/dist/src/lib/workflow/phase-executor.js +157 -16
  73. package/dist/src/lib/workflow/phase-mapper.d.ts +3 -2
  74. package/dist/src/lib/workflow/phase-mapper.js +17 -20
  75. package/dist/src/lib/workflow/platforms/github.d.ts +1 -1
  76. package/dist/src/lib/workflow/platforms/github.js +20 -3
  77. package/dist/src/lib/workflow/pr-status.d.ts +18 -2
  78. package/dist/src/lib/workflow/pr-status.js +41 -9
  79. package/dist/src/lib/workflow/qa-stagnation.d.ts +117 -0
  80. package/dist/src/lib/workflow/qa-stagnation.js +179 -0
  81. package/dist/src/lib/workflow/run-orchestrator.d.ts +39 -0
  82. package/dist/src/lib/workflow/run-orchestrator.js +340 -15
  83. package/dist/src/lib/workflow/run-reflect.js +1 -1
  84. package/dist/src/lib/workflow/run-state.d.ts +71 -0
  85. package/dist/src/lib/workflow/run-state.js +14 -0
  86. package/dist/src/lib/workflow/state-cleanup.d.ts +13 -5
  87. package/dist/src/lib/workflow/state-cleanup.js +17 -5
  88. package/dist/src/lib/workflow/state-manager.d.ts +12 -1
  89. package/dist/src/lib/workflow/state-manager.js +37 -0
  90. package/dist/src/lib/workflow/state-schema.d.ts +62 -0
  91. package/dist/src/lib/workflow/state-schema.js +35 -1
  92. package/dist/src/lib/workflow/types.d.ts +74 -1
  93. package/dist/src/lib/workflow/worktree-manager.d.ts +8 -1
  94. package/dist/src/lib/workflow/worktree-manager.js +15 -6
  95. package/dist/src/mcp/tools/run.d.ts +44 -0
  96. package/dist/src/mcp/tools/run.js +104 -13
  97. package/dist/src/ui/tui/App.d.ts +14 -0
  98. package/dist/src/ui/tui/App.js +41 -0
  99. package/dist/src/ui/tui/ElapsedTimer.d.ts +10 -0
  100. package/dist/src/ui/tui/ElapsedTimer.js +31 -0
  101. package/dist/src/ui/tui/Header.d.ts +6 -0
  102. package/dist/src/ui/tui/Header.js +15 -0
  103. package/dist/src/ui/tui/IssueBox.d.ts +16 -0
  104. package/dist/src/ui/tui/IssueBox.js +68 -0
  105. package/dist/src/ui/tui/Spinner.d.ts +9 -0
  106. package/dist/src/ui/tui/Spinner.js +18 -0
  107. package/dist/src/ui/tui/index.d.ts +15 -0
  108. package/dist/src/ui/tui/index.js +29 -0
  109. package/dist/src/ui/tui/theme.d.ts +29 -0
  110. package/dist/src/ui/tui/theme.js +52 -0
  111. package/dist/src/ui/tui/truncate.d.ts +11 -0
  112. package/dist/src/ui/tui/truncate.js +31 -0
  113. package/package.json +10 -3
  114. package/templates/agents/sequant-explorer.md +1 -0
  115. package/templates/agents/sequant-qa-checker.md +2 -1
  116. package/templates/agents/sequant-testgen.md +1 -0
  117. package/templates/hooks/post-tool.sh +11 -0
  118. package/templates/hooks/pre-tool.sh +18 -9
  119. package/templates/hooks/relay-check.sh +107 -0
  120. package/templates/relay/frame.txt +11 -0
  121. package/templates/scripts/cleanup-worktree.sh +25 -3
  122. package/templates/scripts/new-feature.sh +6 -0
  123. package/templates/skills/_shared/references/behavior-rule-detection.md +205 -0
  124. package/templates/skills/_shared/references/subagent-types.md +21 -8
  125. package/templates/skills/assess/SKILL.md +103 -49
  126. package/templates/skills/assess/references/predicted-collision-detection.md +109 -0
  127. package/templates/skills/docs/SKILL.md +141 -22
  128. package/templates/skills/exec/SKILL.md +10 -8
  129. package/templates/skills/fullsolve/SKILL.md +79 -5
  130. package/templates/skills/loop/SKILL.md +28 -0
  131. package/templates/skills/merger/SKILL.md +621 -0
  132. package/templates/skills/qa/SKILL.md +727 -8
  133. package/templates/skills/setup/SKILL.md +6 -0
  134. package/templates/skills/spec/SKILL.md +52 -0
  135. package/templates/skills/spec/references/parallel-groups.md +7 -0
  136. package/templates/skills/spec/references/recommended-workflow.md +4 -2
  137. 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.2.0",
11
+ "version": "2.3.0",
12
12
  "author": {
13
13
  "name": "sequant-io",
14
14
  "email": "hello@sequant.io"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sequant",
3
3
  "description": "Structured workflow system for Claude Code - GitHub issue resolution with spec, exec, test, and QA phases",
4
- "version": "2.2.0",
4
+ "version": "2.3.0",
5
5
  "author": {
6
6
  "name": "sequant-io",
7
7
  "email": "hello@sequant.io"
package/README.md CHANGED
@@ -207,14 +207,87 @@ npx sequant status # Show version and config
207
207
  npx sequant run <issues...> # Execute workflow
208
208
  npx sequant merge <issues...> # Batch integration QA before merging
209
209
  npx sequant state <cmd> # Manage workflow state (init/rebuild/clean)
210
+ npx sequant locks <cmd> # Inspect/clear per-issue concurrency locks
210
211
  npx sequant stats # View local workflow analytics
211
212
  npx sequant dashboard # Launch real-time workflow dashboard
212
213
  ```
213
214
 
215
+ 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.
216
+
214
217
  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
218
 
216
219
  ---
217
220
 
221
+ ## Concurrency
222
+
223
+ Sequant prevents two sessions from working on the same GitHub issue at the
224
+ same time. When `sequant run` starts, each issue claims a per-issue lock at
225
+ `.sequant/locks/<issue>.lock` containing the holder's PID, hostname, start
226
+ time, and command. A second session attempting the same issue is skipped
227
+ with a clear error and the rest of the batch continues.
228
+
229
+ **Stale recovery.** Locks are auto-cleared in three situations:
230
+
231
+ 1. Same host, PID no longer alive → cleared immediately (covers SIGKILL and
232
+ crashes).
233
+ 2. Cross-host, lock older than 2 hours → cleared by age.
234
+ 3. Manual: `sequant locks clear <issue>` (with safety check by default).
235
+
236
+ **Taking over an active session.** `sequant run --force <issue>` writes a
237
+ new lock claiming the issue. Add `--signal-other` to also SIGTERM the prior
238
+ PID (same host, alive only). Plain `--force` does not signal — use it when
239
+ you already know the other session is dead.
240
+
241
+ **Inspecting locks.**
242
+
243
+ ```bash
244
+ npx sequant locks list # Show every active lock
245
+ npx sequant locks clear 123 # Clear lock for #123 (refuses fresh)
246
+ npx sequant locks clear 123 --force # Clear unconditionally
247
+ ```
248
+
249
+ **Skill wiring** (`/fullsolve`, `/assess`). The `/fullsolve` skill claims the
250
+ lock at Phase 0.3, releases it at Phase 5.5, AND releases on every halt
251
+ branch (spec failure, exec exhausted, etc.) so an aborted run frees the
252
+ lock immediately. `/assess` probes it read-only and surfaces a dashboard
253
+ warning when any issue is in use. Both use these subcommands directly from
254
+ bash:
255
+
256
+ ```bash
257
+ npx sequant locks acquire 123 --command="/fullsolve 123" --skip-pid-check
258
+ npx sequant locks release 123 # idempotent; safe on every error path
259
+ npx sequant locks check 123 --json # exit 1 when held, prints holder JSON
260
+ npx sequant locks check-batch 100 101 102 # /assess: emits ⚠ lines for held issues only
261
+ ```
262
+
263
+ `--skip-pid-check` is required for skill shells: the Node process that runs
264
+ `locks acquire` exits immediately, so its PID is dead before the lock is
265
+ released. With the flag set, stale detection falls back to age-only on the
266
+ holder's own host. The default skill-lock TTL is **6h** (separate from the
267
+ 2h cross-host TTL) — long enough to cover virtually every `/fullsolve` run
268
+ including multi-iteration QA loops. Override per-process via
269
+ `SEQUANT_SKILL_LOCK_TTL_MS=<milliseconds>`.
270
+
271
+ A skill that crashes mid-run leaves at most a 6h orphan; clear it manually
272
+ with `sequant locks clear <issue>` to recover sooner. The skill's explicit
273
+ release calls on every halt branch (see `.claude/skills/fullsolve/SKILL.md`
274
+ Phase 0.3 release contract) mean this corner case should be rare in practice.
275
+
276
+ **Read-only commands** (`status`, `merge`, `/assess`) warn when an issue is
277
+ locked but do not block.
278
+
279
+ **MCP / orchestrator mode.** When the `SEQUANT_ORCHESTRATOR` env var is set
280
+ (in-process or remote MCP-driven runs), all lock operations are no-ops —
281
+ the orchestrator caller is responsible for any coordination.
282
+
283
+ **Caveats.** The lock relies on `open(O_CREAT | O_EXCL)` and is reliable on
284
+ local filesystems. NFS and other network filesystems may not honor those
285
+ semantics; users on networked repos may see false positives. The
286
+ `SEQUANT_LOCKS_DIR` env var overrides the lock directory (used in tests
287
+ and unusual layouts).
288
+
289
+ ---
290
+
218
291
  ## Configuration
219
292
 
220
293
  ```json
package/dist/bin/cli.js CHANGED
@@ -10,7 +10,7 @@ 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,6 +47,9 @@ 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";
50
53
  import { getManifest } from "../src/lib/manifest.js";
51
54
  const program = new Command();
52
55
  // Handle --no-color before parsing
@@ -62,13 +65,22 @@ configureUI({
62
65
  isCI: isCI(),
63
66
  minimal: process.env.SEQUANT_MINIMAL === "1",
64
67
  });
65
- // Warn if running from local node_modules (not npx cache or global)
66
- // This helps users who accidentally have a stale local install
67
- if (!process.argv.includes("--quiet") && isLocalNodeModulesInstall()) {
68
- const pmCommands = getPackageManagerCommands(detectPackageManagerSync());
69
- console.warn(chalk.yellow("! Running sequant from local node_modules\n" +
70
- " For latest version: npx sequant@latest\n" +
71
- ` To remove local: ${pmCommands.removePkg} sequant\n`));
68
+ // Warn if running from a problematic install location.
69
+ // The home-stray case ($HOME/node_modules/sequant) gets a distinct warning
70
+ // because it pollutes resolution for every subdirectory of $HOME, which the
71
+ // generic "local node_modules" message doesn't communicate. Resolve the
72
+ // install root once and pass it to both predicates to avoid a second walk.
73
+ if (!process.argv.includes("--quiet")) {
74
+ const installRoot = getInstallRoot();
75
+ if (installRoot && isHomeStrayInstall(installRoot)) {
76
+ console.warn(chalk.yellow(buildHomeStrayWarning(installRoot)));
77
+ }
78
+ else if (isLocalNodeModulesInstall()) {
79
+ const pmCommands = getPackageManagerCommands(detectPackageManagerSync());
80
+ console.warn(chalk.yellow("! Running sequant from local node_modules\n" +
81
+ " For latest version: npx sequant@latest\n" +
82
+ ` To remove local: ${pmCommands.removePkg} sequant\n`));
83
+ }
72
84
  }
73
85
  program
74
86
  .name("sequant")
@@ -104,6 +116,7 @@ program
104
116
  .command("doctor")
105
117
  .description("Check your Sequant installation for issues")
106
118
  .option("--skip-issue-check", "Skip closed-issue verification check")
119
+ .option("-q, --quiet", "Suppress informational warnings")
107
120
  .action(doctorCommand);
108
121
  program
109
122
  .command("status")
@@ -142,8 +155,10 @@ program
142
155
  .option("--smart-tests", "Enable smart test detection (default)")
143
156
  .option("--no-smart-tests", "Disable smart test detection")
144
157
  .option("--testgen", "Run testgen phase after spec")
158
+ .option("--security-review", "Run security-review phase after spec")
145
159
  .option("--quiet", "Suppress version warnings and non-essential output")
146
160
  .option("--chain", "Chain issues: each branches from previous (implies --sequential)")
161
+ .option("--stacked", "Stack PRs: middle PRs target predecessor branch instead of main; first/last target main (implies --chain)")
147
162
  .option("--qa-gate", "Wait for QA pass before starting next issue in chain (requires --chain)")
148
163
  .option("--base <branch>", "Base branch for worktree creation (default: main or settings.run.defaultBase)")
149
164
  .option("--no-mcp", "Disable MCP server injection in headless mode")
@@ -151,12 +166,41 @@ program
151
166
  .option("--resume", "Resume from last completed phase (reads phase markers from GitHub)")
152
167
  .option("--no-rebase", "Skip pre-PR rebase onto origin/main (use when you want to handle rebasing manually)")
153
168
  .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)")
169
+ .option("-f, --force", "Force re-execution of completed issues (bypass pre-flight state guard) and take over per-issue locks")
170
+ .option("--signal-other", "With --force, SIGTERM the prior PID holding the lock (same-host alive only)")
155
171
  .option("--concurrency <n>", "Max concurrent issues in parallel mode (default: 3)", parseInt)
156
172
  .option("--isolate-parallel", "Isolate parallel agent groups in separate worktrees (prevents file conflicts)")
157
173
  .option("--reflect", "Analyze run results and suggest improvements")
158
174
  .option("--agent <name>", 'Agent driver for phase execution (default: "claude-code")')
175
+ .option("--experimental-tui", "Render live multi-issue dashboard (requires TTY; falls back to linear output when piped)")
176
+ .option("--no-relay", "Disable interactive relay (#383); `sequant prompt` cannot reach this run")
159
177
  .action(runCommand);
178
+ program
179
+ .command("prompt")
180
+ .description("Send a message into a running headless sequant session (#383)")
181
+ .argument("[args...]", '[<issue>] "<message>"')
182
+ .option("--type <type>", "Message type: query (default), directive, abort", "query")
183
+ .option("--json", "Output as JSON")
184
+ .action((args, options) => {
185
+ return promptCommand({
186
+ args,
187
+ options: {
188
+ type: options.type,
189
+ json: Boolean(options.json),
190
+ },
191
+ });
192
+ });
193
+ program
194
+ .command("watch")
195
+ .description("Tail the relay outbox for replies from a running sequant session (#383)")
196
+ .argument("<issue>", "Issue number to watch")
197
+ .option("--json", "Output as JSON lines")
198
+ .action((issueArg, options) => {
199
+ return watchCommand({
200
+ args: [issueArg],
201
+ options: { json: Boolean(options.json) },
202
+ });
203
+ });
160
204
  program
161
205
  .command("merge")
162
206
  .description("Batch-level integration QA — verify feature branches before merging")
@@ -195,6 +239,8 @@ program
195
239
  .option("--csv", "Output as CSV")
196
240
  .option("--json", "Output as JSON")
197
241
  .option("--detailed", "Show detailed analytics (QA verdicts, trends, label segmentation)")
242
+ .option("--label <name>", "Filter to runs whose issues carry the given label")
243
+ .option("--since <date>", "Filter to runs with startTime on/after YYYY-MM-DD (UTC)")
198
244
  .action(statsCommand);
199
245
  program
200
246
  .command("dashboard")
@@ -244,6 +290,45 @@ stateCmd
244
290
  .option("--max-age <days>", "Remove entries older than N days", parseInt)
245
291
  .option("--all", "Remove all orphaned entries (merged and abandoned)")
246
292
  .action(stateCleanCommand);
293
+ // Per-issue concurrency locks (#625)
294
+ const locksCmd = program
295
+ .command("locks")
296
+ .description("Inspect and clear per-issue concurrency locks");
297
+ locksCmd
298
+ .command("list")
299
+ .description("List active locks with staleness metadata")
300
+ .option("--json", "Output as JSON")
301
+ .action(locksListCommand);
302
+ locksCmd
303
+ .command("clear <issue>")
304
+ .description("Manually clear the lock for an issue (safety check unless --force)")
305
+ .option("-f, --force", "Skip safety check; clear even a fresh same-host alive lock")
306
+ .option("--json", "Output as JSON")
307
+ .action(locksClearCommand);
308
+ locksCmd
309
+ .command("acquire <issue>")
310
+ .description("Claim the lock for an issue (used by /fullsolve, /assess; exits 1 if held)")
311
+ .option("--command <command>", "Human-readable command label", "unknown")
312
+ .option("--skip-pid-check", "Mark the lock so stale recovery skips same-host PID checks (use from skill shells)")
313
+ .option("-f, --force", "Take over even if another holder is alive")
314
+ .option("--signal-other", "When forcing, SIGTERM the prior same-host holder if alive")
315
+ .option("--json", "Output as JSON")
316
+ .action(locksAcquireCommand);
317
+ locksCmd
318
+ .command("release <issue>")
319
+ .description("Release a lock previously acquired on this host (skill or current process)")
320
+ .option("--json", "Output as JSON")
321
+ .action(locksReleaseCommand);
322
+ locksCmd
323
+ .command("check <issue>")
324
+ .description("Read-only probe: print lock holder if any; exit 1 when held (for /assess)")
325
+ .option("--json", "Output as JSON")
326
+ .action(locksCheckCommand);
327
+ locksCmd
328
+ .command("check-batch <issues...>")
329
+ .description("Batch read-only probe: emit canonical ⚠ warning lines for held issues (for /assess dashboard)")
330
+ .option("--json", "Output as JSON instead of canonical text lines")
331
+ .action(locksCheckBatchCommand);
247
332
  // Auto-sync skills after npm upgrade (version mismatch detection)
248
333
  // Only triggers when skills were previously synced (has .sequant-version marker).
249
334
  // Projects that manage skills manually (no marker) are not affected.
@@ -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;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * `sequant locks` — inspect and clear per-issue concurrency locks (#625).
3
+ */
4
+ export interface LocksListOptions {
5
+ json?: boolean;
6
+ }
7
+ export interface LocksClearOptions {
8
+ force?: boolean;
9
+ json?: boolean;
10
+ }
11
+ export interface LocksAcquireOptions {
12
+ command?: string;
13
+ skipPidCheck?: boolean;
14
+ force?: boolean;
15
+ signalOther?: boolean;
16
+ json?: boolean;
17
+ }
18
+ export interface LocksReleaseOptions {
19
+ json?: boolean;
20
+ }
21
+ export interface LocksCheckOptions {
22
+ json?: boolean;
23
+ }
24
+ export interface LocksCheckBatchOptions {
25
+ json?: boolean;
26
+ }
27
+ /** `sequant locks list` — print every active lock with staleness metadata. */
28
+ export declare function locksListCommand(options?: LocksListOptions): Promise<void>;
29
+ /**
30
+ * `sequant locks clear <issue>` — remove a lock manually.
31
+ * By default refuses to clear a fresh same-host lock whose PID is alive;
32
+ * pass `--force` to override.
33
+ */
34
+ export declare function locksClearCommand(issueArg: string, options?: LocksClearOptions): Promise<void>;
35
+ /**
36
+ * `sequant locks acquire <issue>` — claim the lock from a shell context
37
+ * (e.g. a skill SKILL.md). Use `--skip-pid-check` for skill shells whose
38
+ * Node PID dies between acquire and release; stale recovery then falls back
39
+ * to age-only (2h).
40
+ *
41
+ * Exit codes:
42
+ * 0 — acquired
43
+ * 1 — locked by another holder (printed to stderr unless --json)
44
+ * 2 — invalid arguments
45
+ */
46
+ export declare function locksAcquireCommand(issueArg: string, options?: LocksAcquireOptions): Promise<void>;
47
+ /**
48
+ * `sequant locks release <issue>` — release a lock previously acquired by
49
+ * a skill shell on this host. Refuses to release locks held by a foreign
50
+ * host or by a different process (use `locks clear --force` for that).
51
+ */
52
+ export declare function locksReleaseCommand(issueArg: string, options?: LocksReleaseOptions): Promise<void>;
53
+ /**
54
+ * `sequant locks check <issue>` — read-only lock probe for `/assess`-style
55
+ * skills. Prints holder info if any, exit code 0 when free, 1 when held.
56
+ */
57
+ export declare function locksCheckCommand(issueArg: string, options?: LocksCheckOptions): Promise<void>;
58
+ /**
59
+ * `sequant locks check-batch <issue1> <issue2> ...` — read-only batch probe
60
+ * used by `/assess`. Text mode emits one canonical warning line per held
61
+ * issue (nothing for unheld), so the skill can paste the output directly
62
+ * above its dashboard. JSON mode emits a structured `{ warnings: [...] }`.
63
+ *
64
+ * Exit code is always 0 — `/assess` is read-only and should never abort
65
+ * on locked issues; the warning is informational.
66
+ */
67
+ export declare function locksCheckBatchCommand(issueArgs: string[], options?: LocksCheckBatchOptions): Promise<void>;