sequant 2.1.2 → 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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +73 -0
- package/dist/bin/cli.js +95 -9
- package/dist/src/commands/doctor.d.ts +25 -0
- package/dist/src/commands/doctor.js +36 -1
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.js +118 -0
- 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 +39 -0
- package/dist/src/commands/prompt.js +179 -0
- package/dist/src/commands/run-display.d.ts +26 -0
- package/dist/src/commands/run-display.js +150 -0
- package/dist/src/commands/run-progress.d.ts +32 -0
- package/dist/src/commands/run-progress.js +76 -0
- package/dist/src/commands/run.js +83 -73
- package/dist/src/commands/stats.d.ts +2 -0
- package/dist/src/commands/stats.js +94 -8
- package/dist/src/commands/status.js +27 -1
- package/dist/src/commands/watch.d.ts +16 -0
- package/dist/src/commands/watch.js +147 -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 +181 -0
- package/dist/src/lib/cli-ui/run-renderer-types.js +7 -0
- package/dist/src/lib/cli-ui/run-renderer.d.ts +239 -0
- package/dist/src/lib/cli-ui/run-renderer.js +1173 -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/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 +106 -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 +68 -0
- package/dist/src/lib/relay/types.js +76 -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/skill-version.d.ts +19 -0
- package/dist/src/lib/skill-version.js +68 -0
- package/dist/src/lib/templates.d.ts +1 -0
- package/dist/src/lib/templates.js +1 -1
- 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 +249 -176
- package/dist/src/lib/workflow/config-resolver.js +4 -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/phase-executor.d.ts +88 -3
- package/dist/src/lib/workflow/phase-executor.js +276 -52
- package/dist/src/lib/workflow/phase-mapper.d.ts +3 -2
- package/dist/src/lib/workflow/phase-mapper.js +17 -20
- 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-orchestrator.d.ts +76 -0
- package/dist/src/lib/workflow/run-orchestrator.js +382 -29
- 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 +12 -1
- package/dist/src/lib/workflow/state-manager.js +37 -0
- package/dist/src/lib/workflow/state-schema.d.ts +62 -0
- package/dist/src/lib/workflow/state-schema.js +35 -1
- package/dist/src/lib/workflow/types.d.ts +74 -1
- package/dist/src/lib/workflow/worktree-manager.d.ts +12 -4
- package/dist/src/lib/workflow/worktree-manager.js +76 -17
- 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 +10 -3
- 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 +11 -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 +261 -94
- 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 -49
- package/templates/skills/fullsolve/SKILL.md +80 -32
- package/templates/skills/loop/SKILL.md +28 -0
- package/templates/skills/merger/SKILL.md +621 -0
- package/templates/skills/qa/SKILL.md +746 -8
- package/templates/skills/qa/scripts/quality-checks.sh +47 -1
- package/templates/skills/setup/SKILL.md +6 -0
- package/templates/skills/spec/SKILL.md +217 -964
- package/templates/skills/spec/references/parallel-groups.md +7 -0
- package/templates/skills/spec/references/quality-checklist.md +75 -0
- package/templates/skills/spec/references/recommended-workflow.md +4 -2
- package/templates/skills/test/SKILL.md +0 -27
- package/templates/skills/testgen/SKILL.md +24 -44
|
@@ -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.3.0",
|
|
12
12
|
"author": {
|
|
13
13
|
"name": "sequant-io",
|
|
14
14
|
"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
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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")
|
|
@@ -86,6 +98,7 @@ program
|
|
|
86
98
|
.option("--no-symlinks", "Use copies instead of symlinks for scripts/dev/ files")
|
|
87
99
|
.option("--no-agents-md", "Skip AGENTS.md generation")
|
|
88
100
|
.option("--mcp", "Add Sequant MCP server to detected clients (use with --yes)")
|
|
101
|
+
.option("--upgrade-skills", "Upgrade skill files from installed package templates (with diff preview)")
|
|
89
102
|
.action(initCommand);
|
|
90
103
|
program
|
|
91
104
|
.command("update")
|
|
@@ -103,6 +116,7 @@ program
|
|
|
103
116
|
.command("doctor")
|
|
104
117
|
.description("Check your Sequant installation for issues")
|
|
105
118
|
.option("--skip-issue-check", "Skip closed-issue verification check")
|
|
119
|
+
.option("-q, --quiet", "Suppress informational warnings")
|
|
106
120
|
.action(doctorCommand);
|
|
107
121
|
program
|
|
108
122
|
.command("status")
|
|
@@ -141,8 +155,10 @@ program
|
|
|
141
155
|
.option("--smart-tests", "Enable smart test detection (default)")
|
|
142
156
|
.option("--no-smart-tests", "Disable smart test detection")
|
|
143
157
|
.option("--testgen", "Run testgen phase after spec")
|
|
158
|
+
.option("--security-review", "Run security-review phase after spec")
|
|
144
159
|
.option("--quiet", "Suppress version warnings and non-essential output")
|
|
145
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)")
|
|
146
162
|
.option("--qa-gate", "Wait for QA pass before starting next issue in chain (requires --chain)")
|
|
147
163
|
.option("--base <branch>", "Base branch for worktree creation (default: main or settings.run.defaultBase)")
|
|
148
164
|
.option("--no-mcp", "Disable MCP server injection in headless mode")
|
|
@@ -150,12 +166,41 @@ program
|
|
|
150
166
|
.option("--resume", "Resume from last completed phase (reads phase markers from GitHub)")
|
|
151
167
|
.option("--no-rebase", "Skip pre-PR rebase onto origin/main (use when you want to handle rebasing manually)")
|
|
152
168
|
.option("--no-pr", "Skip PR creation after successful QA (manual PR workflow)")
|
|
153
|
-
.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)")
|
|
154
171
|
.option("--concurrency <n>", "Max concurrent issues in parallel mode (default: 3)", parseInt)
|
|
155
172
|
.option("--isolate-parallel", "Isolate parallel agent groups in separate worktrees (prevents file conflicts)")
|
|
156
173
|
.option("--reflect", "Analyze run results and suggest improvements")
|
|
157
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")
|
|
158
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
|
+
});
|
|
159
204
|
program
|
|
160
205
|
.command("merge")
|
|
161
206
|
.description("Batch-level integration QA — verify feature branches before merging")
|
|
@@ -194,6 +239,8 @@ program
|
|
|
194
239
|
.option("--csv", "Output as CSV")
|
|
195
240
|
.option("--json", "Output as JSON")
|
|
196
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)")
|
|
197
244
|
.action(statsCommand);
|
|
198
245
|
program
|
|
199
246
|
.command("dashboard")
|
|
@@ -243,6 +290,45 @@ stateCmd
|
|
|
243
290
|
.option("--max-age <days>", "Remove entries older than N days", parseInt)
|
|
244
291
|
.option("--all", "Remove all orphaned entries (merged and abandoned)")
|
|
245
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);
|
|
246
332
|
// Auto-sync skills after npm upgrade (version mismatch detection)
|
|
247
333
|
// Only triggers when skills were previously synced (has .sequant-version marker).
|
|
248
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;
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
* sequant init - Initialize Sequant in a project
|
|
3
3
|
*/
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
+
import { diffLines } from "diff";
|
|
5
6
|
import inquirer from "inquirer";
|
|
7
|
+
import { join, dirname } from "path";
|
|
8
|
+
import { readdir } from "fs/promises";
|
|
6
9
|
import { ui, colors } from "../lib/cli-ui.js";
|
|
7
10
|
import { detectStack, detectAllStacks, getStackConfig, detectPackageManager, getPackageManagerCommands, STACKS, } from "../lib/stacks.js";
|
|
8
11
|
import { copyTemplates } from "../lib/templates.js";
|
|
@@ -70,6 +73,11 @@ function logDefault(label, value) {
|
|
|
70
73
|
console.log(chalk.blue(`${label}: ${value} (default)`));
|
|
71
74
|
}
|
|
72
75
|
export async function initCommand(options) {
|
|
76
|
+
// Handle --upgrade-skills: update skill files from installed package templates
|
|
77
|
+
if (options.upgradeSkills) {
|
|
78
|
+
await upgradeSkills();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
73
81
|
// Show banner
|
|
74
82
|
console.log(ui.banner());
|
|
75
83
|
console.log(colors.success("\nInitializing Sequant...\n"));
|
|
@@ -503,3 +511,113 @@ export async function initCommand(options) {
|
|
|
503
511
|
}
|
|
504
512
|
console.log(chalk.gray("\nDocumentation: https://github.com/sequant-io/sequant#readme\n"));
|
|
505
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Upgrade installed skill files from the sequant package's templates.
|
|
516
|
+
* Shows a diff preview for each changed file and asks for confirmation.
|
|
517
|
+
*/
|
|
518
|
+
async function upgradeSkills() {
|
|
519
|
+
console.log(chalk.bold("\nUpgrading skills from package templates...\n"));
|
|
520
|
+
const installedDir = ".claude/skills";
|
|
521
|
+
if (!(await fileExists(installedDir))) {
|
|
522
|
+
console.log(chalk.red("No skills directory found. Run `sequant init` first."));
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
// Resolve the package's templates/skills directory
|
|
526
|
+
const { getTemplatesDir } = await import("../lib/templates.js");
|
|
527
|
+
const templateSkillsDir = join(getTemplatesDir(), "skills");
|
|
528
|
+
// Collect all files from both directories
|
|
529
|
+
const changes = [];
|
|
530
|
+
const newFiles = [];
|
|
531
|
+
async function compareDir(templateDir, installedBaseDir, relativePrefix) {
|
|
532
|
+
let entries;
|
|
533
|
+
try {
|
|
534
|
+
entries = await readdir(templateDir, { withFileTypes: true });
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
for (const entry of entries) {
|
|
540
|
+
const relPath = join(relativePrefix, entry.name);
|
|
541
|
+
const templatePath = join(templateDir, entry.name);
|
|
542
|
+
const installedPath = join(installedBaseDir, relPath);
|
|
543
|
+
if (entry.isDirectory()) {
|
|
544
|
+
await compareDir(templatePath, installedBaseDir, relPath);
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
const templateContent = await readFile(templatePath);
|
|
548
|
+
const exists = await fileExists(installedPath);
|
|
549
|
+
if (!exists) {
|
|
550
|
+
newFiles.push({ path: relPath, content: templateContent });
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
const installedContent = await readFile(installedPath);
|
|
554
|
+
if (installedContent !== templateContent) {
|
|
555
|
+
changes.push({
|
|
556
|
+
path: relPath,
|
|
557
|
+
installed: installedContent,
|
|
558
|
+
template: templateContent,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
await compareDir(templateSkillsDir, installedDir, "");
|
|
566
|
+
if (changes.length === 0 && newFiles.length === 0) {
|
|
567
|
+
console.log(chalk.green("All skills are up to date."));
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
// Show summary
|
|
571
|
+
console.log(chalk.bold("Changes found:"));
|
|
572
|
+
if (changes.length > 0) {
|
|
573
|
+
console.log(chalk.yellow(` Modified: ${changes.length} file(s)`));
|
|
574
|
+
}
|
|
575
|
+
if (newFiles.length > 0) {
|
|
576
|
+
console.log(chalk.green(` New: ${newFiles.length} file(s)`));
|
|
577
|
+
}
|
|
578
|
+
console.log();
|
|
579
|
+
// Show diffs for modified files
|
|
580
|
+
for (const change of changes) {
|
|
581
|
+
console.log(chalk.yellow(`--- ${change.path} ---`));
|
|
582
|
+
const diff = diffLines(change.installed, change.template);
|
|
583
|
+
for (const part of diff) {
|
|
584
|
+
if (part.added) {
|
|
585
|
+
process.stdout.write(chalk.green(part.value));
|
|
586
|
+
}
|
|
587
|
+
else if (part.removed) {
|
|
588
|
+
process.stdout.write(chalk.red(part.value));
|
|
589
|
+
}
|
|
590
|
+
// Skip unchanged lines in diff output
|
|
591
|
+
}
|
|
592
|
+
console.log();
|
|
593
|
+
}
|
|
594
|
+
// Show new files
|
|
595
|
+
for (const file of newFiles) {
|
|
596
|
+
console.log(chalk.green(`+++ ${file.path} (new)`));
|
|
597
|
+
}
|
|
598
|
+
// Ask for confirmation
|
|
599
|
+
const isInteractive = shouldUseInteractiveMode();
|
|
600
|
+
if (isInteractive) {
|
|
601
|
+
const { proceed } = await inquirer.prompt([
|
|
602
|
+
{
|
|
603
|
+
type: "confirm",
|
|
604
|
+
name: "proceed",
|
|
605
|
+
message: `Apply ${changes.length + newFiles.length} skill update(s)?`,
|
|
606
|
+
default: true,
|
|
607
|
+
},
|
|
608
|
+
]);
|
|
609
|
+
if (!proceed) {
|
|
610
|
+
console.log(chalk.gray("Aborted."));
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
// Apply changes
|
|
615
|
+
for (const change of changes) {
|
|
616
|
+
await writeFile(join(installedDir, change.path), change.template);
|
|
617
|
+
}
|
|
618
|
+
for (const file of newFiles) {
|
|
619
|
+
await ensureDir(dirname(join(installedDir, file.path)));
|
|
620
|
+
await writeFile(join(installedDir, file.path), file.content);
|
|
621
|
+
}
|
|
622
|
+
console.log(chalk.green(`\nUpgraded ${changes.length + newFiles.length} skill file(s).`));
|
|
623
|
+
}
|
|
@@ -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>;
|