sequant 2.8.0 → 2.10.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 +23 -4
- package/dist/bin/cli.js +99 -18
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
- package/dist/marketplace/external_plugins/sequant/.mcp.json +1 -1
- package/dist/marketplace/external_plugins/sequant/README.md +58 -2
- package/dist/marketplace/external_plugins/sequant/hooks/parallel-marker.sh +50 -0
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +38 -10
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +405 -67
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/trust-model.md +18 -0
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +250 -344
- package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +10 -7
- package/dist/marketplace/external_plugins/sequant/skills/docs/SKILL.md +4 -4
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +26 -6
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +4 -2
- package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +38 -2
- package/dist/marketplace/external_plugins/sequant/skills/merger/SKILL.md +2 -2
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +337 -57
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/anti-pattern-detection.md +6 -6
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/call-site-review.md +2 -2
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/code-review-checklist.md +2 -2
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/fixtures/injection-issue-body.md +36 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/quality-gates.md +21 -7
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/testing-requirements.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +11 -11
- package/dist/marketplace/external_plugins/sequant/skills/reflect/SKILL.md +27 -13
- package/dist/marketplace/external_plugins/sequant/skills/reflect/references/documentation-tiers.md +80 -68
- package/dist/marketplace/external_plugins/sequant/skills/reflect/references/phase-reflection.md +31 -15
- package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +11 -3
- package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +5 -5
- package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +2 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/verification-criteria.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +1 -1
- package/dist/src/commands/assess-render.d.ts +23 -0
- package/dist/src/commands/assess-render.js +60 -0
- package/dist/src/commands/doctor.js +8 -13
- package/dist/src/commands/init.js +13 -1
- package/dist/src/commands/locks.js +2 -0
- package/dist/src/commands/logs.js +20 -6
- package/dist/src/commands/merge.js +154 -2
- package/dist/src/commands/ready-tui-adapter.js +6 -1
- package/dist/src/commands/ready.d.ts +25 -3
- package/dist/src/commands/ready.js +39 -13
- package/dist/src/commands/resume.d.ts +113 -0
- package/dist/src/commands/resume.js +193 -0
- package/dist/src/commands/run-display.d.ts +20 -0
- package/dist/src/commands/run-display.js +117 -5
- package/dist/src/commands/run-flags.d.ts +42 -1
- package/dist/src/commands/run-flags.js +53 -1
- package/dist/src/commands/run-progress.js +26 -1
- package/dist/src/commands/run.js +11 -10
- package/dist/src/commands/state.js +7 -0
- package/dist/src/commands/stats.js +47 -0
- package/dist/src/commands/status.d.ts +9 -0
- package/dist/src/commands/status.js +24 -1
- package/dist/src/commands/sync.js +90 -19
- package/dist/src/commands/update.js +28 -5
- package/dist/src/lib/ac-parser.d.ts +14 -0
- package/dist/src/lib/ac-parser.js +99 -5
- package/dist/src/lib/assess/index.d.ts +10 -0
- package/dist/src/lib/assess/index.js +9 -0
- package/dist/src/lib/assess/renderer.d.ts +64 -0
- package/dist/src/lib/assess/renderer.js +481 -0
- package/dist/src/lib/assess/types.d.ts +224 -0
- package/dist/src/lib/assess/types.js +241 -0
- package/dist/src/lib/assess-collision-detect.d.ts +19 -2
- package/dist/src/lib/assess-collision-detect.js +68 -4
- package/dist/src/lib/ci/outputs.d.ts +15 -2
- package/dist/src/lib/ci/outputs.js +17 -5
- package/dist/src/lib/cli-flags.d.ts +33 -0
- package/dist/src/lib/cli-flags.js +46 -0
- package/dist/src/lib/cli-ui/run-renderer-types.d.ts +21 -7
- package/dist/src/lib/cli-ui/run-renderer.d.ts +10 -0
- package/dist/src/lib/cli-ui/run-renderer.js +79 -9
- package/dist/src/lib/errors.d.ts +84 -6
- package/dist/src/lib/errors.js +142 -17
- package/dist/src/lib/locks/index.d.ts +3 -3
- package/dist/src/lib/locks/index.js +2 -2
- package/dist/src/lib/locks/lock-manager.d.ts +20 -2
- package/dist/src/lib/locks/lock-manager.js +47 -3
- package/dist/src/lib/locks/types.d.ts +34 -3
- package/dist/src/lib/locks/types.js +16 -0
- package/dist/src/lib/manifest.js +1 -17
- package/dist/src/lib/mcp-config.d.ts +45 -0
- package/dist/src/lib/mcp-config.js +77 -1
- package/dist/src/lib/merge-check/combined-branch-test.d.ts +21 -2
- package/dist/src/lib/merge-check/combined-branch-test.js +233 -110
- package/dist/src/lib/merge-check/command-result.d.ts +36 -0
- package/dist/src/lib/merge-check/command-result.js +56 -0
- package/dist/src/lib/merge-check/index.d.ts +4 -0
- package/dist/src/lib/merge-check/index.js +1 -1
- package/dist/src/lib/merge-check/report.js +5 -1
- package/dist/src/lib/merge-check/types.d.ts +9 -0
- package/dist/src/lib/merge-check/watch.d.ts +121 -0
- package/dist/src/lib/merge-check/watch.js +215 -0
- package/dist/src/lib/qa/infra-blocked-ci.d.ts +90 -0
- package/dist/src/lib/qa/infra-blocked-ci.js +80 -0
- package/dist/src/lib/settings.d.ts +14 -0
- package/dist/src/lib/settings.js +61 -8
- package/dist/src/lib/shutdown.d.ts +42 -2
- package/dist/src/lib/shutdown.js +75 -5
- package/dist/src/lib/skills-check.d.ts +26 -0
- package/dist/src/lib/skills-check.js +31 -0
- package/dist/src/lib/stacks.d.ts +110 -1
- package/dist/src/lib/stacks.js +244 -5
- package/dist/src/lib/templates.d.ts +74 -0
- package/dist/src/lib/templates.js +171 -16
- package/dist/src/lib/test-tautology-detector.d.ts +9 -2
- package/dist/src/lib/test-tautology-detector.js +153 -12
- package/dist/src/lib/version-check.d.ts +31 -0
- package/dist/src/lib/version-check.js +46 -7
- package/dist/src/lib/workflow/batch-executor.d.ts +138 -6
- package/dist/src/lib/workflow/batch-executor.js +550 -54
- package/dist/src/lib/workflow/chain-preflight.d.ts +89 -0
- package/dist/src/lib/workflow/chain-preflight.js +199 -0
- package/dist/src/lib/workflow/chain-resume.d.ts +118 -0
- package/dist/src/lib/workflow/chain-resume.js +170 -0
- package/dist/src/lib/workflow/completed-status.d.ts +52 -0
- package/dist/src/lib/workflow/completed-status.js +57 -0
- package/dist/src/lib/workflow/config-resolver.d.ts +26 -0
- package/dist/src/lib/workflow/config-resolver.js +54 -2
- package/dist/src/lib/workflow/dependency-markers.d.ts +29 -0
- package/dist/src/lib/workflow/dependency-markers.js +79 -0
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +7 -0
- package/dist/src/lib/workflow/drivers/aider.d.ts +2 -0
- package/dist/src/lib/workflow/drivers/aider.js +2 -0
- package/dist/src/lib/workflow/drivers/claude-code.d.ts +21 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +50 -8
- package/dist/src/lib/workflow/error-classifier.d.ts +9 -2
- package/dist/src/lib/workflow/error-classifier.js +14 -1
- package/dist/src/lib/workflow/git-diff-utils.d.ts +35 -3
- package/dist/src/lib/workflow/git-diff-utils.js +77 -4
- package/dist/src/lib/workflow/heartbeat.d.ts +38 -0
- package/dist/src/lib/workflow/heartbeat.js +90 -0
- package/dist/src/lib/workflow/log-writer.d.ts +44 -1
- package/dist/src/lib/workflow/log-writer.js +81 -14
- package/dist/src/lib/workflow/metrics-schema.d.ts +42 -3
- package/dist/src/lib/workflow/metrics-schema.js +16 -0
- package/dist/src/lib/workflow/metrics-writer.d.ts +2 -1
- package/dist/src/lib/workflow/phase-executor.d.ts +234 -13
- package/dist/src/lib/workflow/phase-executor.js +533 -56
- package/dist/src/lib/workflow/phase-registry.js +2 -1
- package/dist/src/lib/workflow/platforms/github.d.ts +64 -0
- package/dist/src/lib/workflow/platforms/github.js +152 -0
- package/dist/src/lib/workflow/qa-cache.d.ts +6 -1
- package/dist/src/lib/workflow/qa-cache.js +22 -9
- package/dist/src/lib/workflow/ready-gate.d.ts +17 -4
- package/dist/src/lib/workflow/ready-gate.js +25 -9
- package/dist/src/lib/workflow/run-log-schema.d.ts +37 -0
- package/dist/src/lib/workflow/run-log-schema.js +92 -2
- package/dist/src/lib/workflow/run-orchestrator.d.ts +33 -0
- package/dist/src/lib/workflow/run-orchestrator.js +429 -47
- package/dist/src/lib/workflow/skills-preflight.d.ts +89 -0
- package/dist/src/lib/workflow/skills-preflight.js +112 -0
- package/dist/src/lib/workflow/state-cleanup.js +9 -2
- package/dist/src/lib/workflow/state-manager.d.ts +33 -0
- package/dist/src/lib/workflow/state-manager.js +83 -0
- package/dist/src/lib/workflow/state-schema.d.ts +26 -0
- package/dist/src/lib/workflow/state-schema.js +47 -0
- package/dist/src/lib/workflow/status-derivation.d.ts +30 -0
- package/dist/src/lib/workflow/status-derivation.js +27 -0
- package/dist/src/lib/workflow/types.d.ts +149 -4
- package/dist/src/lib/workflow/types.js +1 -0
- package/dist/src/lib/workflow/worktree-manager.d.ts +70 -2
- package/dist/src/lib/workflow/worktree-manager.js +201 -52
- package/dist/src/mcp/tools/run.d.ts +11 -1
- package/dist/src/mcp/tools/run.js +22 -1
- package/dist/src/mcp/tools/status.js +6 -0
- package/dist/src/ui/tui/ElapsedTimer.d.ts +12 -3
- package/dist/src/ui/tui/ElapsedTimer.js +12 -10
- package/dist/src/ui/tui/IssueBox.js +10 -2
- package/dist/src/ui/tui/row-cap.js +2 -1
- package/dist/src/ui/tui/theme.d.ts +7 -0
- package/dist/src/ui/tui/theme.js +9 -0
- package/package.json +9 -10
- package/templates/hooks/parallel-marker.sh +50 -0
- package/templates/hooks/post-tool.sh +38 -10
- package/templates/hooks/pre-tool.sh +405 -67
- package/templates/scripts/cleanup-worktree.sh +282 -30
- package/templates/scripts/new-feature.sh +264 -16
- package/templates/skills/_shared/references/trust-model.md +18 -0
- package/templates/skills/assess/SKILL.md +250 -344
- package/templates/skills/assess/references/predicted-collision-detection.md +10 -7
- package/templates/skills/docs/SKILL.md +4 -4
- package/templates/skills/exec/SKILL.md +26 -6
- package/templates/skills/fullsolve/SKILL.md +4 -2
- package/templates/skills/loop/SKILL.md +38 -2
- package/templates/skills/merger/SKILL.md +2 -2
- package/templates/skills/qa/SKILL.md +337 -57
- package/templates/skills/qa/references/anti-pattern-detection.md +6 -6
- package/templates/skills/qa/references/call-site-review.md +2 -2
- package/templates/skills/qa/references/code-review-checklist.md +2 -2
- package/templates/skills/qa/references/fixtures/injection-issue-body.md +36 -0
- package/templates/skills/qa/references/quality-gates.md +21 -7
- package/templates/skills/qa/references/testing-requirements.md +1 -1
- package/templates/skills/qa/scripts/quality-checks.sh +11 -11
- package/templates/skills/reflect/SKILL.md +27 -13
- package/templates/skills/reflect/references/documentation-tiers.md +80 -68
- package/templates/skills/reflect/references/phase-reflection.md +31 -15
- package/templates/skills/release/SKILL.md +11 -3
- package/templates/skills/setup/SKILL.md +5 -5
- package/templates/skills/spec/SKILL.md +2 -0
- package/templates/skills/spec/references/verification-criteria.md +1 -1
- package/templates/skills/test/SKILL.md +1 -1
- package/dist/src/lib/phase-spinner.d.ts +0 -146
- package/dist/src/lib/phase-spinner.js +0 -255
- package/dist/src/lib/workflow/pr-operations.d.ts +0 -86
- package/dist/src/lib/workflow/pr-operations.js +0 -326
- package/dist/src/lib/workflow/run-summary.d.ts +0 -36
- package/dist/src/lib/workflow/run-summary.js +0 -142
|
@@ -58,6 +58,18 @@ export function buildProgressWiring(args) {
|
|
|
58
58
|
// #543: activity events only feed the TUI's nowLine — skip the line renderer.
|
|
59
59
|
if (event === "activity")
|
|
60
60
|
return;
|
|
61
|
+
// #804 AC-7: unlike activity, a waiting event MUST reach the renderer —
|
|
62
|
+
// it is the only signal during a pause that can last hours.
|
|
63
|
+
if (event === "waiting") {
|
|
64
|
+
renderer.onEvent({
|
|
65
|
+
issue,
|
|
66
|
+
phase,
|
|
67
|
+
event,
|
|
68
|
+
text: extra?.text,
|
|
69
|
+
wakeAtMs: extra?.wakeAtMs,
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
61
73
|
// #624 Item 3: pass the outer-loop iteration through so the renderer can
|
|
62
74
|
// render `(attempt N/M)` / `loop N/M`.
|
|
63
75
|
renderer.onEvent({
|
|
@@ -71,9 +83,22 @@ export function buildProgressWiring(args) {
|
|
|
71
83
|
};
|
|
72
84
|
}
|
|
73
85
|
else if (heartbeat) {
|
|
74
|
-
onProgress = (issue, phase, event) => {
|
|
86
|
+
onProgress = (issue, phase, event, extra) => {
|
|
75
87
|
if (event === "activity")
|
|
76
88
|
return;
|
|
89
|
+
// #804 AC-7: mark/clear the auto-wait so the heartbeat reports the wait
|
|
90
|
+
// and, critically, does not report it as a stall. Must be handled before
|
|
91
|
+
// the `else` below, which would otherwise `stop()` the phase entirely and
|
|
92
|
+
// leave a multi-hour wait with no liveness signal at all.
|
|
93
|
+
if (event === "waiting") {
|
|
94
|
+
if (extra?.wakeAtMs !== undefined) {
|
|
95
|
+
heartbeat.pauseForWait({ issueNumber: issue, phase }, extra.wakeAtMs);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
heartbeat.resumeFromWait({ issueNumber: issue, phase });
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
77
102
|
if (event === "start")
|
|
78
103
|
heartbeat.start({
|
|
79
104
|
issueNumber: issue,
|
package/dist/src/commands/run.js
CHANGED
|
@@ -8,15 +8,21 @@ import { parseBatches } from "../lib/workflow/batch-executor.js";
|
|
|
8
8
|
import { RunOrchestrator } from "../lib/workflow/run-orchestrator.js";
|
|
9
9
|
import { displayConfig, displaySummary } from "./run-display.js";
|
|
10
10
|
import { buildProgressWiring } from "./run-progress.js";
|
|
11
|
-
import { normalizeQualityLoop, resolveTuiEnabled } from "./run-flags.js";
|
|
11
|
+
import { normalizeQualityLoop, resolveTuiEnabled, warnDeprecatedFlags, } from "./run-flags.js";
|
|
12
12
|
// Re-export public API for backwards compatibility
|
|
13
13
|
export * from "./run-compat.js";
|
|
14
|
+
// #848: red pre-flight rejection + non-zero exit (process.exitCode, not exit()).
|
|
15
|
+
function rejectPreflight(message) {
|
|
16
|
+
console.log(chalk.red(message));
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
}
|
|
14
19
|
/** Parse CLI args → validate → delegate to RunOrchestrator.run() → display summary. */
|
|
15
20
|
export async function runCommand(issues, options) {
|
|
16
21
|
// #705: fold the hidden `-q` alias into qualityLoop before any consumer reads
|
|
17
22
|
// it (`-q` no longer maps to --quiet, which moved to `-s`). See run-flags.ts.
|
|
18
23
|
options.qualityLoop = normalizeQualityLoop(options);
|
|
19
24
|
console.log(ui.headerBox("SEQUANT WORKFLOW"));
|
|
25
|
+
warnDeprecatedFlags(options);
|
|
20
26
|
if (!options.quiet) {
|
|
21
27
|
try {
|
|
22
28
|
const v = await checkVersionCached();
|
|
@@ -31,31 +37,26 @@ export async function runCommand(issues, options) {
|
|
|
31
37
|
}
|
|
32
38
|
const manifest = await getManifest();
|
|
33
39
|
if (!manifest) {
|
|
34
|
-
|
|
40
|
+
rejectPreflight("❌ Sequant is not initialized. Run `sequant init` first.");
|
|
35
41
|
return;
|
|
36
42
|
}
|
|
37
43
|
const settings = await getSettings();
|
|
38
44
|
// #605: --stacked implies --chain; reject explicit --no-chain combo before
|
|
39
45
|
// we evaluate any --chain-dependent constraint below.
|
|
40
46
|
if (options.stacked && options.chain === false) {
|
|
41
|
-
|
|
47
|
+
rejectPreflight("❌ --stacked cannot be combined with --no-chain");
|
|
42
48
|
return;
|
|
43
49
|
}
|
|
44
50
|
if (options.stacked) {
|
|
45
51
|
options.chain = true;
|
|
46
52
|
}
|
|
47
|
-
// Validate constraints
|
|
48
53
|
if (options.chain && options.batch?.length) {
|
|
49
|
-
|
|
54
|
+
rejectPreflight("❌ --chain cannot be used with --batch");
|
|
50
55
|
return;
|
|
51
56
|
}
|
|
52
57
|
if (options.concurrency !== undefined &&
|
|
53
58
|
(options.concurrency < 1 || !Number.isInteger(options.concurrency))) {
|
|
54
|
-
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
if (options.qaGate && !options.chain) {
|
|
58
|
-
console.log(chalk.red("❌ --qa-gate requires --chain flag"));
|
|
59
|
+
rejectPreflight(`❌ Invalid --concurrency value: ${options.concurrency}. Must be a positive integer.`);
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
61
62
|
let batches = null;
|
|
@@ -27,10 +27,13 @@ export async function stateInitCommand(options = {}) {
|
|
|
27
27
|
const result = await discoverUntrackedWorktrees(discoverOptions);
|
|
28
28
|
if (options.json) {
|
|
29
29
|
console.log(JSON.stringify(result, null, 2));
|
|
30
|
+
if (!result.success)
|
|
31
|
+
process.exitCode = 1;
|
|
30
32
|
return;
|
|
31
33
|
}
|
|
32
34
|
if (!result.success) {
|
|
33
35
|
console.log(chalk.red(`✗ Discovery failed: ${result.error}`));
|
|
36
|
+
process.exitCode = 1;
|
|
34
37
|
return;
|
|
35
38
|
}
|
|
36
39
|
if (result.discovered.length === 0) {
|
|
@@ -106,6 +109,7 @@ export async function stateRebuildCommand(options = {}) {
|
|
|
106
109
|
else {
|
|
107
110
|
console.log(chalk.red(`✗ Log rebuild failed: ${logResult.error}`));
|
|
108
111
|
}
|
|
112
|
+
process.exitCode = 1;
|
|
109
113
|
return;
|
|
110
114
|
}
|
|
111
115
|
// Step 2: Discover and add untracked worktrees
|
|
@@ -204,10 +208,13 @@ export async function stateCleanCommand(options = {}) {
|
|
|
204
208
|
});
|
|
205
209
|
if (options.json) {
|
|
206
210
|
console.log(JSON.stringify(result, null, 2));
|
|
211
|
+
if (!result.success)
|
|
212
|
+
process.exitCode = 1;
|
|
207
213
|
return;
|
|
208
214
|
}
|
|
209
215
|
if (!result.success) {
|
|
210
216
|
console.log(chalk.red(`✗ Cleanup failed: ${result.error}`));
|
|
217
|
+
process.exitCode = 1;
|
|
211
218
|
return;
|
|
212
219
|
}
|
|
213
220
|
const orphanedCount = result.orphaned.length;
|
|
@@ -263,6 +263,35 @@ function loadMetrics() {
|
|
|
263
263
|
return null;
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Compute the failure-category breakdown over runs that recorded a failure —
|
|
268
|
+
* outcome "failed" (every issue failed) or "partial" (at least one issue
|
|
269
|
+
* failed) (#783).
|
|
270
|
+
*
|
|
271
|
+
* NOTE — deviation from AC-1's literal "over failed runs" wording: partial runs
|
|
272
|
+
* also carry a failureCategory (recorded whenever >=1 issue fails) and are
|
|
273
|
+
* genuine signal for "what's killing my runs", so they are counted here by
|
|
274
|
+
* explicit user decision. Success runs never carry the field and are excluded.
|
|
275
|
+
*
|
|
276
|
+
* Runs without the field (pre-#761 records, or a partial whose failure was
|
|
277
|
+
* never categorized) are bucketed as "unclassified" — never dropped and never
|
|
278
|
+
* conflated with the "unknown" enum value.
|
|
279
|
+
*
|
|
280
|
+
* Pure function of the passed runs array, so any upstream cohort filtering is
|
|
281
|
+
* automatically respected (computed after filtering, not before).
|
|
282
|
+
*/
|
|
283
|
+
function calculateFailureCategoryBreakdown(runs) {
|
|
284
|
+
const counts = new Map();
|
|
285
|
+
for (const run of runs) {
|
|
286
|
+
if (run.outcome === "success")
|
|
287
|
+
continue;
|
|
288
|
+
const category = run.failureCategory ?? "unclassified";
|
|
289
|
+
counts.set(category, (counts.get(category) ?? 0) + 1);
|
|
290
|
+
}
|
|
291
|
+
return [...counts.entries()]
|
|
292
|
+
.map(([category, count]) => ({ category, count }))
|
|
293
|
+
.sort((a, b) => b.count - a.count || a.category.localeCompare(b.category));
|
|
294
|
+
}
|
|
266
295
|
/**
|
|
267
296
|
* Calculate analytics from metrics
|
|
268
297
|
*/
|
|
@@ -274,6 +303,7 @@ function calculateMetricsAnalytics(metrics) {
|
|
|
274
303
|
successCount: 0,
|
|
275
304
|
partialCount: 0,
|
|
276
305
|
failedCount: 0,
|
|
306
|
+
failureCategories: [],
|
|
277
307
|
successRate: 0,
|
|
278
308
|
avgTokensPerRun: 0,
|
|
279
309
|
avgFilesChanged: 0,
|
|
@@ -294,6 +324,7 @@ function calculateMetricsAnalytics(metrics) {
|
|
|
294
324
|
const successCount = runs.filter((r) => r.outcome === "success").length;
|
|
295
325
|
const partialCount = runs.filter((r) => r.outcome === "partial").length;
|
|
296
326
|
const failedCount = runs.filter((r) => r.outcome === "failed").length;
|
|
327
|
+
const failureCategories = calculateFailureCategoryBreakdown(runs);
|
|
297
328
|
const successRate = (successCount / runs.length) * 100;
|
|
298
329
|
const avgTokensPerRun = runs.reduce((sum, r) => sum + r.metrics.tokensUsed, 0) / runs.length;
|
|
299
330
|
const avgFilesChanged = runs.reduce((sum, r) => sum + r.metrics.filesChanged, 0) / runs.length;
|
|
@@ -323,6 +354,7 @@ function calculateMetricsAnalytics(metrics) {
|
|
|
323
354
|
successCount,
|
|
324
355
|
partialCount,
|
|
325
356
|
failedCount,
|
|
357
|
+
failureCategories,
|
|
326
358
|
successRate,
|
|
327
359
|
avgTokensPerRun,
|
|
328
360
|
avgFilesChanged,
|
|
@@ -415,6 +447,19 @@ function displayMetricsAnalytics(analytics) {
|
|
|
415
447
|
const failedRate = (analytics.failedCount / total) * 100;
|
|
416
448
|
console.log(` ${colors.error("\u2717 Failed")} ${analytics.failedCount} (${failedRate.toFixed(0)}%) ${failedBar}`);
|
|
417
449
|
}
|
|
450
|
+
// Failure-category breakdown (#783) \u2014 adjacent to the outcome bars above.
|
|
451
|
+
// Counts runs that recorded a failure (outcome "failed" or "partial"); hidden
|
|
452
|
+
// entirely when there are none (AC-3).
|
|
453
|
+
if (analytics.failureCategories.length > 0) {
|
|
454
|
+
const failureRunCount = analytics.failureCategories.reduce((sum, b) => sum + b.count, 0);
|
|
455
|
+
const runNoun = failureRunCount === 1 ? "run with a failure" : "runs with a failure";
|
|
456
|
+
console.log(ui.sectionHeader(`Failure Categories (${failureRunCount} ${runNoun})`));
|
|
457
|
+
const pad = Math.max(...analytics.failureCategories.map((b) => b.category.length));
|
|
458
|
+
for (const bucket of analytics.failureCategories) {
|
|
459
|
+
const pct = ((bucket.count / failureRunCount) * 100).toFixed(0);
|
|
460
|
+
console.log(` ${bucket.category.padEnd(pad)} ${bucket.count} (${pct}%)`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
418
463
|
// Averages table
|
|
419
464
|
console.log(ui.sectionHeader("Averages"));
|
|
420
465
|
const avgData = {};
|
|
@@ -653,6 +698,8 @@ export async function statsCommand(options) {
|
|
|
653
698
|
partialCount: analytics.partialCount,
|
|
654
699
|
failedCount: analytics.failedCount,
|
|
655
700
|
successRate: analytics.successRate,
|
|
701
|
+
// Failure-category breakdown over failed runs (#783 AC-5)
|
|
702
|
+
failureCategories: analytics.failureCategories,
|
|
656
703
|
avgTokensPerRun: analytics.avgTokensPerRun,
|
|
657
704
|
avgFilesChanged: analytics.avgFilesChanged,
|
|
658
705
|
avgLinesAdded: analytics.avgLinesAdded,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* sequant status - Show version, configuration, and workflow state
|
|
3
3
|
*/
|
|
4
|
+
import { type IssueState } from "../lib/workflow/state-schema.js";
|
|
4
5
|
export interface StatusCommandOptions {
|
|
5
6
|
/** Show only issues state */
|
|
6
7
|
issues?: boolean;
|
|
@@ -21,4 +22,12 @@ export interface StatusCommandOptions {
|
|
|
21
22
|
/** Skip GitHub API queries (offline mode) */
|
|
22
23
|
offline?: boolean;
|
|
23
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Format a single issue state for display
|
|
27
|
+
*
|
|
28
|
+
* @internal Exported for testing only (#892: the windowHalt/autoWait pause
|
|
29
|
+
* lines are display contract — a halted issue must read as resumable, not as
|
|
30
|
+
* a bare failure).
|
|
31
|
+
*/
|
|
32
|
+
export declare function formatIssueState(issue: IssueState): string;
|
|
24
33
|
export declare function statusCommand(options?: StatusCommandOptions): Promise<void>;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* sequant status - Show version, configuration, and workflow state
|
|
3
3
|
*/
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
+
import { formatResetTime } from "../lib/errors.js";
|
|
6
|
+
import { MAX_RESUME_REENTRIES } from "../lib/workflow/state-schema.js";
|
|
5
7
|
import { ui, colors } from "../lib/cli-ui.js";
|
|
6
8
|
import { getManifest, getPackageVersion } from "../lib/manifest.js";
|
|
7
9
|
import { fileExists } from "../lib/fs.js";
|
|
@@ -102,8 +104,12 @@ function getPhaseSymbol(phaseState) {
|
|
|
102
104
|
}
|
|
103
105
|
/**
|
|
104
106
|
* Format a single issue state for display
|
|
107
|
+
*
|
|
108
|
+
* @internal Exported for testing only (#892: the windowHalt/autoWait pause
|
|
109
|
+
* lines are display contract — a halted issue must read as resumable, not as
|
|
110
|
+
* a bare failure).
|
|
105
111
|
*/
|
|
106
|
-
function formatIssueState(issue) {
|
|
112
|
+
export function formatIssueState(issue) {
|
|
107
113
|
const lines = [];
|
|
108
114
|
// Issue header
|
|
109
115
|
lines.push(chalk.bold(` #${issue.number}: ${issue.title.substring(0, 50)}${issue.title.length > 50 ? "..." : ""}`));
|
|
@@ -128,6 +134,16 @@ function formatIssueState(issue) {
|
|
|
128
134
|
.join(" ");
|
|
129
135
|
lines.push(` Phases: ${phaseProgress}`);
|
|
130
136
|
lines.push(` ${phases.map((p) => p.charAt(0).toUpperCase()).join(" ")}`);
|
|
137
|
+
// #860: an auto-waiting issue is paused on purpose, not stalled — name the
|
|
138
|
+
// wake time so a multi-hour rate-limit pause is distinguishable from a hang.
|
|
139
|
+
if (issue.autoWait) {
|
|
140
|
+
lines.push(chalk.yellow(` ⏸ Auto-wait: ${issue.autoWait.phase} paused — resuming at ${formatResetTime(new Date(issue.autoWait.wakeAt).getTime())}`));
|
|
141
|
+
}
|
|
142
|
+
// #892: a window-halted issue exited cleanly (lock released) and is waiting
|
|
143
|
+
// for `sequant resume` — show the resume time instead of a bare failure.
|
|
144
|
+
if (issue.windowHalt) {
|
|
145
|
+
lines.push(chalk.yellow(` ⏸ Halted: ${issue.windowHalt.phase} hit a rate-limit window — resumable at ${formatResetTime(new Date(issue.windowHalt.resumeAt).getTime())} via \`sequant resume\` (re-entries ${issue.windowHalt.reentries}/${MAX_RESUME_REENTRIES})`));
|
|
146
|
+
}
|
|
131
147
|
// PR info
|
|
132
148
|
if (issue.pr) {
|
|
133
149
|
lines.push(chalk.gray(` PR: #${issue.pr.number} ${issue.pr.url}`));
|
|
@@ -419,6 +435,7 @@ async function displayIssueState(options) {
|
|
|
419
435
|
else {
|
|
420
436
|
console.log(chalk.red(`\nError reading state: ${error}`));
|
|
421
437
|
}
|
|
438
|
+
process.exitCode = 1;
|
|
422
439
|
}
|
|
423
440
|
}
|
|
424
441
|
/**
|
|
@@ -431,6 +448,8 @@ async function handleRebuild(options) {
|
|
|
431
448
|
const result = await rebuildStateFromLogs({ verbose: !options.json });
|
|
432
449
|
if (options.json) {
|
|
433
450
|
console.log(JSON.stringify(result, null, 2));
|
|
451
|
+
if (!result.success)
|
|
452
|
+
process.exitCode = 1;
|
|
434
453
|
return;
|
|
435
454
|
}
|
|
436
455
|
if (result.success) {
|
|
@@ -443,6 +462,7 @@ async function handleRebuild(options) {
|
|
|
443
462
|
}
|
|
444
463
|
else {
|
|
445
464
|
console.log(chalk.red(`✗ Rebuild failed: ${result.error}`));
|
|
465
|
+
process.exitCode = 1;
|
|
446
466
|
}
|
|
447
467
|
}
|
|
448
468
|
/**
|
|
@@ -470,6 +490,8 @@ async function handleCleanup(options) {
|
|
|
470
490
|
});
|
|
471
491
|
if (options.json) {
|
|
472
492
|
console.log(JSON.stringify(result, null, 2));
|
|
493
|
+
if (!result.success)
|
|
494
|
+
process.exitCode = 1;
|
|
473
495
|
return;
|
|
474
496
|
}
|
|
475
497
|
if (result.success) {
|
|
@@ -511,5 +533,6 @@ async function handleCleanup(options) {
|
|
|
511
533
|
}
|
|
512
534
|
else {
|
|
513
535
|
console.log(chalk.red(`✗ Cleanup failed: ${result.error}`));
|
|
536
|
+
process.exitCode = 1;
|
|
514
537
|
}
|
|
515
538
|
}
|
|
@@ -8,8 +8,10 @@ import chalk from "chalk";
|
|
|
8
8
|
import { join } from "path";
|
|
9
9
|
import { createHash } from "crypto";
|
|
10
10
|
import { getManifest, updateManifest, getPackageVersion, } from "../lib/manifest.js";
|
|
11
|
-
import { copyTemplates, computeTemplateChanges, listTemplateFiles, getTemplatesDir, } from "../lib/templates.js";
|
|
11
|
+
import { copyTemplates, computeTemplateChanges, listTemplateFiles, getTemplatesDir, assertTemplatesDirExists, isCustomizableFile, } from "../lib/templates.js";
|
|
12
12
|
import { getConfig } from "../lib/config.js";
|
|
13
|
+
import { resolveCliInvocation } from "../lib/version-check.js";
|
|
14
|
+
import { syncSequantMcpPin } from "../lib/mcp-config.js";
|
|
13
15
|
import { writeFile, readFile, fileExists, getFileStats } from "../lib/fs.js";
|
|
14
16
|
import { generateAgentsMd, writeAgentsMd, AGENTS_MD_PATH, } from "../lib/agents-md.js";
|
|
15
17
|
import { getProjectName } from "../lib/project-name.js";
|
|
@@ -195,6 +197,18 @@ export async function syncCommand(options = {}) {
|
|
|
195
197
|
process.exitCode = 1;
|
|
196
198
|
return;
|
|
197
199
|
}
|
|
200
|
+
// Fail loudly on a missing templates root before anything reads or writes.
|
|
201
|
+
// This must precede the version fast path below: that path diffs against the
|
|
202
|
+
// same (missing) directory, sees no drift, and reports "already up to date"
|
|
203
|
+
// over an empty tree — the second-run trap in #822.
|
|
204
|
+
try {
|
|
205
|
+
await assertTemplatesDirExists();
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
console.log(chalk.red(`❌ ${error.message}`));
|
|
209
|
+
process.exitCode = 1;
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
198
212
|
const packageVersion = getPackageVersion();
|
|
199
213
|
const skillsVersion = await getSkillsVersion();
|
|
200
214
|
if (!quiet) {
|
|
@@ -205,6 +219,18 @@ export async function syncCommand(options = {}) {
|
|
|
205
219
|
// Get config tokens for template processing
|
|
206
220
|
const config = await getConfig();
|
|
207
221
|
const tokens = config?.tokens || {};
|
|
222
|
+
// Re-pin the project .mcp.json MCP server to the installed version (#793).
|
|
223
|
+
// `syncSequantMcpPin`'s contract has always named this the `update`/`sync`
|
|
224
|
+
// path, but only `update` ever called it, so a `sync` left the pin stale.
|
|
225
|
+
// Placed here for the same reason update.ts states: it must precede the
|
|
226
|
+
// "already up to date" fast path below, because a version-only upgrade
|
|
227
|
+
// leaves every template byte-identical and returns early — and that is
|
|
228
|
+
// exactly the case where the pin most needs refreshing.
|
|
229
|
+
const mcpPin = syncSequantMcpPin(process.cwd(), { dryRun });
|
|
230
|
+
if (mcpPin.updated && !quiet) {
|
|
231
|
+
const verb = dryRun ? "Would update" : "Updated";
|
|
232
|
+
console.log(chalk.blue(`${verb} .mcp.json MCP pin: ${mcpPin.from} → ${mcpPin.to}`));
|
|
233
|
+
}
|
|
208
234
|
// The version marker is only a fast-path hint — verify actual content before
|
|
209
235
|
// claiming "up to date". On a version match we still diff bundled templates
|
|
210
236
|
// against installed content (rendered with the same variables) so we never
|
|
@@ -233,25 +259,36 @@ export async function syncCommand(options = {}) {
|
|
|
233
259
|
}
|
|
234
260
|
// Preview path: report exactly what the apply would write, then stop without
|
|
235
261
|
// mutating (#722). This branch is only reached when `force` is set or the
|
|
236
|
-
// version marker mismatches — i.e. the path that runs `copyTemplates
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
// is about
|
|
262
|
+
// version marker mismatches — i.e. the path that runs `copyTemplates` and
|
|
263
|
+
// rewrites the whole tree. (A matching-version, non-force dry-run already
|
|
264
|
+
// returned at the report-only short-circuit above, which never mutates.)
|
|
265
|
+
//
|
|
266
|
+
// Since #814 the apply path PRESERVES in-place customizations (files in
|
|
267
|
+
// CUSTOMIZABLE_FILES) under a plain sync and only overwrites them under an
|
|
268
|
+
// explicit `--force`. The preview must mirror that split exactly, or it would
|
|
269
|
+
// re-introduce the dry-run/apply divergence #722 is about: partition the
|
|
270
|
+
// `local-override` set into preserved (plain sync) vs overwritten (--force,
|
|
271
|
+
// or a non-customizable `.local`-twin the tree copy still rewrites).
|
|
244
272
|
if (dryRun) {
|
|
245
273
|
const changes = await computeTemplateChanges(manifest.stack, tokens);
|
|
246
274
|
const newFiles = changes.filter((c) => c.status === "new");
|
|
247
275
|
const modifiedFiles = changes.filter((c) => c.status === "modified");
|
|
248
276
|
const localOverrides = changes.filter((c) => c.status === "local-override");
|
|
249
|
-
const
|
|
277
|
+
const preservedOverrides = force
|
|
278
|
+
? []
|
|
279
|
+
: localOverrides.filter((c) => isCustomizableFile(c.path));
|
|
280
|
+
const overwrittenOverrides = localOverrides.filter((c) => force || !isCustomizableFile(c.path));
|
|
281
|
+
const toWrite = [...newFiles, ...modifiedFiles, ...overwrittenOverrides];
|
|
250
282
|
if (!quiet) {
|
|
251
283
|
console.log(chalk.bold("Summary (dry-run):"));
|
|
252
284
|
console.log(chalk.green(` New files: ${newFiles.length}`));
|
|
253
285
|
console.log(chalk.yellow(` Modified: ${modifiedFiles.length}`));
|
|
254
|
-
|
|
286
|
+
if (preservedOverrides.length > 0) {
|
|
287
|
+
console.log(chalk.blue(` Customizable (preserved): ${preservedOverrides.length}`));
|
|
288
|
+
}
|
|
289
|
+
if (overwrittenOverrides.length > 0) {
|
|
290
|
+
console.log(chalk.blue(` Local overrides (overwritten): ${overwrittenOverrides.length}`));
|
|
291
|
+
}
|
|
255
292
|
if (modifiedFiles.length > 0) {
|
|
256
293
|
console.log(chalk.bold("\nModified files:"));
|
|
257
294
|
for (const file of modifiedFiles) {
|
|
@@ -264,9 +301,15 @@ export async function syncCommand(options = {}) {
|
|
|
264
301
|
console.log(chalk.green(` ${file.path}`));
|
|
265
302
|
}
|
|
266
303
|
}
|
|
267
|
-
if (
|
|
268
|
-
console.log(chalk.bold("\
|
|
269
|
-
for (const file of
|
|
304
|
+
if (preservedOverrides.length > 0) {
|
|
305
|
+
console.log(chalk.bold("\nCustomizable files (preserved — run `sync --force` to replace):"));
|
|
306
|
+
for (const file of preservedOverrides) {
|
|
307
|
+
console.log(chalk.blue(` ${file.path}`));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (overwrittenOverrides.length > 0) {
|
|
311
|
+
console.log(chalk.bold("\nLocal overrides (will be overwritten):"));
|
|
312
|
+
for (const file of overwrittenOverrides) {
|
|
270
313
|
console.log(chalk.blue(` ${file.path}`));
|
|
271
314
|
}
|
|
272
315
|
}
|
|
@@ -286,14 +329,39 @@ export async function syncCommand(options = {}) {
|
|
|
286
329
|
}
|
|
287
330
|
return;
|
|
288
331
|
}
|
|
289
|
-
// Copy templates
|
|
332
|
+
// Copy templates: always refresh the managed trees, but only overwrite
|
|
333
|
+
// user-owned CUSTOMIZABLE_FILES (e.g. the constitution) when the user
|
|
334
|
+
// explicitly passed --force. These are two distinct notions of "force" and
|
|
335
|
+
// conflating them is what silently ate the constitution (#814).
|
|
290
336
|
const copyOptions = {
|
|
291
|
-
force: true, // Always overwrite
|
|
337
|
+
force: true, // Always overwrite the managed skills/agents/hooks trees
|
|
338
|
+
overwriteCustomizable: force, // Clobber user-owned files only on explicit --force
|
|
292
339
|
};
|
|
340
|
+
// AC-3: under --force, announce which customizable files are about to be
|
|
341
|
+
// overwritten *before* writing them, so the destructive action is never
|
|
342
|
+
// silent. Reuse the diff path's `local-override` classification (the same
|
|
343
|
+
// set --dry-run reports), narrowed to CUSTOMIZABLE_FILES.
|
|
344
|
+
if (force && !quiet) {
|
|
345
|
+
const changes = await computeTemplateChanges(manifest.stack, tokens);
|
|
346
|
+
const overwrites = changes.filter((c) => c.status === "local-override" && isCustomizableFile(c.path));
|
|
347
|
+
if (overwrites.length > 0) {
|
|
348
|
+
console.log(chalk.yellow(`Overwriting ${overwrites.length} customizable file(s) (--force):`));
|
|
349
|
+
for (const file of overwrites) {
|
|
350
|
+
console.log(chalk.yellow(` ${file.path}`));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
293
354
|
if (!quiet) {
|
|
294
355
|
console.log(chalk.blue("Copying templates..."));
|
|
295
356
|
}
|
|
296
|
-
await copyTemplates(manifest.stack, tokens, copyOptions);
|
|
357
|
+
const { preservedCustomizable } = await copyTemplates(manifest.stack, tokens, copyOptions);
|
|
358
|
+
// AC-4: on a plain sync, report each customizable file that was preserved so
|
|
359
|
+
// the apply path's output matches what --dry-run already promises (#722).
|
|
360
|
+
if (!quiet && preservedCustomizable.length > 0) {
|
|
361
|
+
for (const file of preservedCustomizable) {
|
|
362
|
+
console.log(chalk.blue(` preserved: ${file} — run \`sync --force\` to replace`));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
297
365
|
// Update version markers
|
|
298
366
|
await updateSkillsVersion();
|
|
299
367
|
await updateManifest();
|
|
@@ -340,9 +408,12 @@ export async function syncCommand(options = {}) {
|
|
|
340
408
|
*/
|
|
341
409
|
export async function checkAndWarnSkillsOutdated(status) {
|
|
342
410
|
const { outdated, currentVersion, packageVersion, contentDrift } = status ?? (await areSkillsOutdated());
|
|
411
|
+
// Name the invocation that reaches *this* build rather than assuming the
|
|
412
|
+
// documented `npm install sequant` shape — see resolveCliInvocation.
|
|
413
|
+
const cli = resolveCliInvocation();
|
|
343
414
|
if (outdated) {
|
|
344
415
|
console.log(chalk.yellow(`\n! Skills are outdated (${currentVersion || "unknown"} → ${packageVersion})`));
|
|
345
|
-
console.log(chalk.yellow(
|
|
416
|
+
console.log(chalk.yellow(` Run: ${cli} sync\n`));
|
|
346
417
|
return true;
|
|
347
418
|
}
|
|
348
419
|
if (contentDrift > 0) {
|
|
@@ -350,7 +421,7 @@ export async function checkAndWarnSkillsOutdated(status) {
|
|
|
350
421
|
// version is report-only (it won't copy), so point at the commands that
|
|
351
422
|
// actually resolve in-place drift — `sync --force` or `update`.
|
|
352
423
|
console.log(chalk.yellow(`\n! Version current, but ${contentDrift} file(s) differ from bundled content`));
|
|
353
|
-
console.log(chalk.yellow(
|
|
424
|
+
console.log(chalk.yellow(` Run: ${cli} sync --force (or ${cli} update)\n`));
|
|
354
425
|
return true;
|
|
355
426
|
}
|
|
356
427
|
return false;
|
|
@@ -7,9 +7,11 @@ import { spawnSync } from "child_process";
|
|
|
7
7
|
import { getManifest, updateManifest, getPackageVersion, } from "../lib/manifest.js";
|
|
8
8
|
import { computeTemplateChanges } from "../lib/templates.js";
|
|
9
9
|
import { getConfig, saveConfig } from "../lib/config.js";
|
|
10
|
-
import { getStackConfig, PM_CONFIG, getPackageManagerCommands, } from "../lib/stacks.js";
|
|
10
|
+
import { getStackConfig, PM_CONFIG, getPackageManagerCommands, resolvePackageManager, } from "../lib/stacks.js";
|
|
11
11
|
import { writeFile } from "../lib/fs.js";
|
|
12
|
+
import { chmod } from "fs/promises";
|
|
12
13
|
import { isStdinTTY, isCI, getNonInteractiveReason } from "../lib/tty.js";
|
|
14
|
+
import { syncSequantMcpPin } from "../lib/mcp-config.js";
|
|
13
15
|
/**
|
|
14
16
|
* True when `update` must not prompt: stdin is not a terminal (piped input) or
|
|
15
17
|
* we are running in a recognized CI environment. CI is checked explicitly
|
|
@@ -41,8 +43,18 @@ export async function updateCommand(options) {
|
|
|
41
43
|
const manifest = await getManifest();
|
|
42
44
|
if (!manifest) {
|
|
43
45
|
console.log(chalk.red("❌ Sequant is not initialized. Run `sequant init` first."));
|
|
46
|
+
process.exitCode = 1;
|
|
44
47
|
return;
|
|
45
48
|
}
|
|
49
|
+
// Resolved once for the whole command. The manifest's packageManager is a
|
|
50
|
+
// snapshot taken at `sequant init`, so it is absent on pre-1.3.0 installs —
|
|
51
|
+
// exactly the population the PM_RUN backfill below exists to serve. The
|
|
52
|
+
// three sites that used to spell this `(manifest.packageManager as keyof
|
|
53
|
+
// typeof PM_CONFIG) || "npm"` therefore assumed npm for those installs, and
|
|
54
|
+
// wrote an npm PM_RUN token into a pnpm/yarn/bun project's config (#870).
|
|
55
|
+
// The root is the cwd because `update` operates on the project in place,
|
|
56
|
+
// the same directory `getManifest` and `syncSequantMcpPin` read.
|
|
57
|
+
const pm = resolvePackageManager(manifest.packageManager, process.cwd());
|
|
46
58
|
const packageVersion = getPackageVersion();
|
|
47
59
|
console.log(chalk.gray(`Current version: ${manifest.version}`));
|
|
48
60
|
console.log(chalk.gray(`Stack: ${manifest.stack}`));
|
|
@@ -64,7 +76,6 @@ export async function updateCommand(options) {
|
|
|
64
76
|
console.log(chalk.gray(`Dev URL: ${tokens.DEV_URL || "(not set)"}\n`));
|
|
65
77
|
// Add PM_RUN if missing (for existing installs before v1.3.0)
|
|
66
78
|
if (!tokens.PM_RUN) {
|
|
67
|
-
const pm = manifest.packageManager || "npm";
|
|
68
79
|
const pmConfig = getPackageManagerCommands(pm);
|
|
69
80
|
tokens.PM_RUN = pmConfig.run;
|
|
70
81
|
config.tokens = tokens;
|
|
@@ -78,7 +89,6 @@ export async function updateCommand(options) {
|
|
|
78
89
|
const stackConfig = getStackConfig(manifest.stack);
|
|
79
90
|
const defaultDevUrl = stackConfig.devUrl;
|
|
80
91
|
// Get package manager run command
|
|
81
|
-
const pm = manifest.packageManager || "npm";
|
|
82
92
|
const pmConfig = getPackageManagerCommands(pm);
|
|
83
93
|
if (options.force || options.yes) {
|
|
84
94
|
tokens = { DEV_URL: defaultDevUrl, PM_RUN: pmConfig.run };
|
|
@@ -116,6 +126,16 @@ export async function updateCommand(options) {
|
|
|
116
126
|
console.log(chalk.green("✔ Configuration saved\n"));
|
|
117
127
|
}
|
|
118
128
|
}
|
|
129
|
+
// Re-pin the project .mcp.json MCP server to the installed version (#793).
|
|
130
|
+
// Independent of template changes: a version-only upgrade leaves every
|
|
131
|
+
// template unchanged (so the flow would short-circuit at "Everything is up to
|
|
132
|
+
// date" below) but should still refresh the pin so the MCP server tracks the
|
|
133
|
+
// release the user just updated to. Runs here, before that short-circuit.
|
|
134
|
+
const mcpPin = syncSequantMcpPin(process.cwd(), { dryRun: options.dryRun });
|
|
135
|
+
if (mcpPin.updated) {
|
|
136
|
+
const verb = options.dryRun ? "Would update" : "Updated";
|
|
137
|
+
console.log(chalk.blue(`${verb} .mcp.json MCP pin: ${mcpPin.from} → ${mcpPin.to}`));
|
|
138
|
+
}
|
|
119
139
|
// Compute changes using the shared, variable-aware comparison.
|
|
120
140
|
// Templates are rendered (PROJECT_NAME, STACK_NOTES, etc.) before diffing,
|
|
121
141
|
// and in-place-customizable files (constitution) are protected as overrides.
|
|
@@ -202,6 +222,11 @@ export async function updateCommand(options) {
|
|
|
202
222
|
let updated = 0;
|
|
203
223
|
for (const file of applySet) {
|
|
204
224
|
await writeFile(file.path, file.rendered);
|
|
225
|
+
// Shell scripts (hooks, scripts/dev) need the exec bit, matching the
|
|
226
|
+
// copyTemplates write path.
|
|
227
|
+
if (file.path.endsWith(".sh")) {
|
|
228
|
+
await chmod(file.path, 0o755);
|
|
229
|
+
}
|
|
205
230
|
updated++;
|
|
206
231
|
}
|
|
207
232
|
// Update manifest
|
|
@@ -210,8 +235,6 @@ export async function updateCommand(options) {
|
|
|
210
235
|
// Check if package.json was updated and run install
|
|
211
236
|
const packageJsonUpdated = applySet.some((f) => f.path === "package.json" || f.path.endsWith("/package.json"));
|
|
212
237
|
if (packageJsonUpdated) {
|
|
213
|
-
// Use detected package manager or default to npm
|
|
214
|
-
const pm = manifest.packageManager || "npm";
|
|
215
238
|
const pmConfig = PM_CONFIG[pm];
|
|
216
239
|
console.log(chalk.blue(`\npackage.json updated, running ${pmConfig.install}...`));
|
|
217
240
|
const [cmd, ...args] = pmConfig.install.split(" ");
|
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
* Also supports alternate formats: `- [ ] **B2:** Description`
|
|
7
7
|
* And bold-wrapped format: `- [ ] **AC-1: Description**`
|
|
8
8
|
*
|
|
9
|
+
* Bare checkbox items (`- [ ] Some requirement`, no ID prefix) are also
|
|
10
|
+
* honored, but ONLY under an explicit `## Acceptance Criteria` heading, where
|
|
11
|
+
* they receive synthesized stable IDs. Human-authored issues rarely prefix
|
|
12
|
+
* their ACs with IDs, so without this every hand-written issue parsed to zero
|
|
13
|
+
* ACs — blinding every downstream AC consumer (#850).
|
|
14
|
+
*
|
|
9
15
|
* @example
|
|
10
16
|
* ```typescript
|
|
11
17
|
* import { parseAcceptanceCriteria } from './ac-parser';
|
|
@@ -39,8 +45,16 @@ export declare function inferVerificationMethod(description: string): ACVerifica
|
|
|
39
45
|
* - `- [ ] **AC-1:** Description`
|
|
40
46
|
* - `- [ ] **B2:** Description`
|
|
41
47
|
* - `- [ ] **AC-1: Description**` (bold wraps ID + description)
|
|
48
|
+
* - `- [ ] **AC-1** Description` (bold ID, no colon)
|
|
49
|
+
* - `- [ ] **AC-1**: Description` (colon outside the bold)
|
|
42
50
|
* - `- [ ] AC-1: Description`
|
|
43
51
|
*
|
|
52
|
+
* Bare checkbox items without an ID prefix (`- [ ] Some requirement`) are also
|
|
53
|
+
* parsed, but ONLY under an explicit `## Acceptance Criteria` heading, where
|
|
54
|
+
* they receive synthesized stable IDs (`AC-1`, `AC-2`, ...). This keeps
|
|
55
|
+
* unrelated checklists elsewhere in the body (Open Questions, Test Plan) from
|
|
56
|
+
* being misread as ACs, while letting ordinary human-written issues parse.
|
|
57
|
+
*
|
|
44
58
|
* @param issueBody - The full GitHub issue body markdown
|
|
45
59
|
* @returns Array of parsed acceptance criteria
|
|
46
60
|
*/
|