sequant 2.9.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 +14 -3
- package/dist/bin/cli.js +97 -17
- 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 +56 -2
- package/dist/marketplace/external_plugins/sequant/hooks/parallel-marker.sh +50 -0
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +20 -7
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +75 -10
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/trust-model.md +18 -0
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +215 -390
- package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +1 -1
- 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 +3 -1
- 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/release/SKILL.md +1 -1
- 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/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 +14 -5
- 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.js +37 -4
- 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/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/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 +62 -0
- package/dist/src/lib/errors.d.ts +78 -6
- package/dist/src/lib/errors.js +133 -15
- 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/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 +45 -2
- package/dist/src/lib/workflow/batch-executor.d.ts +125 -6
- package/dist/src/lib/workflow/batch-executor.js +477 -44
- package/dist/src/lib/workflow/chain-resume.d.ts +13 -11
- package/dist/src/lib/workflow/chain-resume.js +24 -20
- 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/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 +14 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +20 -2
- 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 +75 -6
- package/dist/src/lib/workflow/metrics-schema.d.ts +3 -3
- package/dist/src/lib/workflow/phase-executor.d.ts +203 -14
- package/dist/src/lib/workflow/phase-executor.js +457 -52
- 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 +14 -0
- package/dist/src/lib/workflow/run-log-schema.js +47 -1
- package/dist/src/lib/workflow/run-orchestrator.d.ts +19 -0
- package/dist/src/lib/workflow/run-orchestrator.js +141 -20
- 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/types.d.ts +126 -4
- package/dist/src/lib/workflow/types.js +1 -0
- package/dist/src/lib/workflow/worktree-manager.d.ts +28 -2
- package/dist/src/lib/workflow/worktree-manager.js +102 -23
- package/dist/src/mcp/tools/run.d.ts +9 -1
- package/dist/src/mcp/tools/run.js +20 -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 +8 -7
- package/templates/hooks/parallel-marker.sh +50 -0
- package/templates/hooks/post-tool.sh +20 -7
- package/templates/hooks/pre-tool.sh +75 -10
- package/templates/scripts/cleanup-worktree.sh +182 -19
- package/templates/scripts/new-feature.sh +264 -16
- package/templates/skills/_shared/references/trust-model.md +18 -0
- package/templates/skills/assess/SKILL.md +215 -390
- package/templates/skills/assess/references/predicted-collision-detection.md +1 -1
- package/templates/skills/docs/SKILL.md +4 -4
- package/templates/skills/exec/SKILL.md +26 -6
- package/templates/skills/fullsolve/SKILL.md +3 -1
- 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/release/SKILL.md +1 -1
- package/templates/skills/setup/SKILL.md +5 -5
- package/templates/skills/spec/SKILL.md +2 -0
- 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
|
@@ -61,9 +61,9 @@ export function displayConfig(r) {
|
|
|
61
61
|
if (r.mergedOptions.chain) {
|
|
62
62
|
console.log(chalk.gray(` Chain mode: enabled (each issue branches from previous)`));
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
// #795 deliberately prints nothing for `--qa-gate`: the old
|
|
65
|
+
// `QA gate: enabled (chain waits for QA pass)` line advertised gating the
|
|
66
|
+
// flag never performed. `runCommand` prints a deprecation notice instead.
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
69
|
* Convert workflow `IssueResult` to renderer `IssueSummary`.
|
|
@@ -92,9 +92,12 @@ function toIssueSummary(r) {
|
|
|
92
92
|
prUrl: r.prUrl,
|
|
93
93
|
};
|
|
94
94
|
if (!r.success) {
|
|
95
|
+
// #879: a PR-creation failure has no failed phase — fall back to its error
|
|
96
|
+
// so the summary cell reads the real reason, not the useless "phase failed".
|
|
95
97
|
summary.failureReason =
|
|
96
98
|
failedPhase?.error ??
|
|
97
99
|
r.abortReason ??
|
|
100
|
+
r.prCreationError ??
|
|
98
101
|
`${failedPhase?.phase ?? "phase"} failed`;
|
|
99
102
|
if (failedPhase?.verdict) {
|
|
100
103
|
summary.qaVerdict = String(failedPhase.verdict);
|
|
@@ -159,7 +162,12 @@ export function displaySummary(result, renderer) {
|
|
|
159
162
|
if (results.length === 0)
|
|
160
163
|
return;
|
|
161
164
|
const issueSummaries = results.map(toIssueSummary);
|
|
162
|
-
|
|
165
|
+
// #867: run wall clock, computed once by the orchestrator (RunResult), NOT
|
|
166
|
+
// `results.reduce(sum + durationSeconds)`. Under --parallel that sum
|
|
167
|
+
// double-counts overlapping issues and over-reports by ~the concurrency
|
|
168
|
+
// factor (a 36m 18s run printed 1h 12m at concurrency 3). The orchestrator is
|
|
169
|
+
// the only component that brackets the whole run, so it owns this value.
|
|
170
|
+
const totalSeconds = result.wallClockDurationSeconds;
|
|
163
171
|
if (renderer) {
|
|
164
172
|
renderer.renderSummary({
|
|
165
173
|
issues: issueSummaries,
|
|
@@ -176,6 +184,31 @@ export function displaySummary(result, renderer) {
|
|
|
176
184
|
dryRun: config.dryRun,
|
|
177
185
|
});
|
|
178
186
|
}
|
|
187
|
+
// #817: when `--ready-gate` ran, surface each issue's gate outcome (threshold
|
|
188
|
+
// reached vs guard halt) in the summary the same way `sequant ready` reports
|
|
189
|
+
// it — the "never merged" contract and stop reason are the human's cue to
|
|
190
|
+
// review and merge manually. Only issues that actually ran the gate carry it.
|
|
191
|
+
//
|
|
192
|
+
// A gate that *failed* is listed here too. It is non-fatal (the PR still
|
|
193
|
+
// opens), but omitting it would make a run whose gate crashed look exactly
|
|
194
|
+
// like one that gated cleanly — the user opted into a second look and needs
|
|
195
|
+
// to know it never happened.
|
|
196
|
+
const gated = results.filter((r) => r.readyGate || r.readyGateError);
|
|
197
|
+
if (gated.length > 0) {
|
|
198
|
+
console.log(colors.muted(" Ready gate (#817) — never merged:"));
|
|
199
|
+
for (const r of gated) {
|
|
200
|
+
if (!r.readyGate) {
|
|
201
|
+
console.log(` #${r.issueNumber}: ${colors.warning("✗ gate did not run")} · ${r.readyGateError} · PR opened ungated — re-run \`sequant ready ${r.issueNumber}\``);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const g = r.readyGate;
|
|
205
|
+
const label = g.ready
|
|
206
|
+
? colors.success(`✓ ${g.reason}`)
|
|
207
|
+
: colors.warning(`⚠️ ${g.reason}`);
|
|
208
|
+
console.log(` #${r.issueNumber}: ${label} · policy \`${g.policy}\` · ${g.iterations} QA pass(es) · status \`${g.issueStatus}\``);
|
|
209
|
+
}
|
|
210
|
+
console.log("");
|
|
211
|
+
}
|
|
179
212
|
// #760: a chain link whose checkpoint commit failed keeps its own work but
|
|
180
213
|
// loses the recovery point resume depends on, and the per-issue warning has
|
|
181
214
|
// long scrolled past by now on a multi-hour chain. Restate it at the summary,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Run flag normalization (#705) — keeps run.ts thin (#503 AC-2: <200 LOC).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Pure resolvers for the `run` command's flag surface:
|
|
5
5
|
* - `normalizeQualityLoop`: ORs the hidden `-q` alias into `--quality-loop`.
|
|
6
6
|
* - `resolveTuiEnabled`: decides whether the boxed Ink TUI mounts.
|
|
7
|
+
* - `deprecatedFlagNotices`: messages for flags kept only for compatibility.
|
|
7
8
|
*
|
|
8
9
|
* Extracted as pure functions so the flag behavior is unit-testable without
|
|
9
10
|
* driving the full `runCommand` side effects.
|
|
@@ -29,3 +30,43 @@ export declare function normalizeQualityLoop(options: RunOptions): boolean;
|
|
|
29
30
|
* it, so it is intentionally not consulted here.
|
|
30
31
|
*/
|
|
31
32
|
export declare function resolveTuiEnabled(options: RunOptions, isTTY: boolean): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* #795: notices for flags that still parse but no longer do anything.
|
|
35
|
+
*
|
|
36
|
+
* `--qa-gate` promised to "wait for QA pass before starting the next issue in
|
|
37
|
+
* chain", but the chain loop halts on any failed link unconditionally, so the
|
|
38
|
+
* flag never changed behavior and no runtime path ever wrote the
|
|
39
|
+
* `waiting_for_qa_gate` status it advertised. It is kept parseable so existing
|
|
40
|
+
* scripts do not hard-error — the previous `--qa-gate requires --chain` check
|
|
41
|
+
* aborted the whole run, which is exactly the breakage a deprecation window
|
|
42
|
+
* exists to avoid — and it no longer requires `--chain`.
|
|
43
|
+
*
|
|
44
|
+
* Returned rather than printed so the wording is unit-testable without driving
|
|
45
|
+
* `runCommand`'s side effects.
|
|
46
|
+
*/
|
|
47
|
+
export declare function deprecatedFlagNotices(options: RunOptions): string[];
|
|
48
|
+
/**
|
|
49
|
+
* Prints every notice from {@link deprecatedFlagNotices}.
|
|
50
|
+
*
|
|
51
|
+
* Lives here rather than inline in `run.ts` to keep that adapter under the
|
|
52
|
+
* #503 AC-2 200-LOC budget, alongside the pure resolver it wraps.
|
|
53
|
+
*
|
|
54
|
+
* Two deliberate choices, both flagged because a future reader may mistake
|
|
55
|
+
* them for oversights:
|
|
56
|
+
*
|
|
57
|
+
* 1. **stderr, not stdout.** Deprecation notices are warnings, and warnings
|
|
58
|
+
* go on stderr (same as `abort.ts`). This keeps a script's piped stdout
|
|
59
|
+
* clean while still surfacing the notice on a terminal.
|
|
60
|
+
* 2. **Not gated on `options.quiet`.** `--quiet` suppresses progress and
|
|
61
|
+
* version chatter; it is not a warning switch. The scripts most likely to
|
|
62
|
+
* still pass `--qa-gate` are CI scripts, which are also the most likely to
|
|
63
|
+
* pass `--quiet` — suppressing there would defeat the deprecation window
|
|
64
|
+
* for exactly its target audience. Silencing warnings should be an
|
|
65
|
+
* explicit opt-out, not a side effect of asking for less progress output.
|
|
66
|
+
*
|
|
67
|
+
* `runCommand` calls this immediately after the header box, ahead of the
|
|
68
|
+
* manifest and settings checks: a dead flag is a fact about argv, not about
|
|
69
|
+
* project state, so it should be reported even from an uninitialized
|
|
70
|
+
* directory where those checks would return early.
|
|
71
|
+
*/
|
|
72
|
+
export declare function warnDeprecatedFlags(options: RunOptions): void;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Run flag normalization (#705) — keeps run.ts thin (#503 AC-2: <200 LOC).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Pure resolvers for the `run` command's flag surface:
|
|
5
5
|
* - `normalizeQualityLoop`: ORs the hidden `-q` alias into `--quality-loop`.
|
|
6
6
|
* - `resolveTuiEnabled`: decides whether the boxed Ink TUI mounts.
|
|
7
|
+
* - `deprecatedFlagNotices`: messages for flags kept only for compatibility.
|
|
7
8
|
*
|
|
8
9
|
* Extracted as pure functions so the flag behavior is unit-testable without
|
|
9
10
|
* driving the full `runCommand` side effects.
|
|
10
11
|
*/
|
|
12
|
+
import chalk from "chalk";
|
|
11
13
|
/**
|
|
12
14
|
* #705: `-q` is a hidden alias for the quality loop (it no longer maps to
|
|
13
15
|
* `--quiet`, which moved to `-s`). Returns the effective quality-loop flag so
|
|
@@ -32,3 +34,53 @@ export function normalizeQualityLoop(options) {
|
|
|
32
34
|
export function resolveTuiEnabled(options, isTTY) {
|
|
33
35
|
return options.tui !== false && isTTY && !options.quiet;
|
|
34
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* #795: notices for flags that still parse but no longer do anything.
|
|
39
|
+
*
|
|
40
|
+
* `--qa-gate` promised to "wait for QA pass before starting the next issue in
|
|
41
|
+
* chain", but the chain loop halts on any failed link unconditionally, so the
|
|
42
|
+
* flag never changed behavior and no runtime path ever wrote the
|
|
43
|
+
* `waiting_for_qa_gate` status it advertised. It is kept parseable so existing
|
|
44
|
+
* scripts do not hard-error — the previous `--qa-gate requires --chain` check
|
|
45
|
+
* aborted the whole run, which is exactly the breakage a deprecation window
|
|
46
|
+
* exists to avoid — and it no longer requires `--chain`.
|
|
47
|
+
*
|
|
48
|
+
* Returned rather than printed so the wording is unit-testable without driving
|
|
49
|
+
* `runCommand`'s side effects.
|
|
50
|
+
*/
|
|
51
|
+
export function deprecatedFlagNotices(options) {
|
|
52
|
+
const notices = [];
|
|
53
|
+
if (options.qaGate) {
|
|
54
|
+
notices.push("--qa-gate is deprecated and has no effect (#795). --chain already halts the chain on any failed issue, QA included. Remove the flag; it will be deleted in a future major release.");
|
|
55
|
+
}
|
|
56
|
+
return notices;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Prints every notice from {@link deprecatedFlagNotices}.
|
|
60
|
+
*
|
|
61
|
+
* Lives here rather than inline in `run.ts` to keep that adapter under the
|
|
62
|
+
* #503 AC-2 200-LOC budget, alongside the pure resolver it wraps.
|
|
63
|
+
*
|
|
64
|
+
* Two deliberate choices, both flagged because a future reader may mistake
|
|
65
|
+
* them for oversights:
|
|
66
|
+
*
|
|
67
|
+
* 1. **stderr, not stdout.** Deprecation notices are warnings, and warnings
|
|
68
|
+
* go on stderr (same as `abort.ts`). This keeps a script's piped stdout
|
|
69
|
+
* clean while still surfacing the notice on a terminal.
|
|
70
|
+
* 2. **Not gated on `options.quiet`.** `--quiet` suppresses progress and
|
|
71
|
+
* version chatter; it is not a warning switch. The scripts most likely to
|
|
72
|
+
* still pass `--qa-gate` are CI scripts, which are also the most likely to
|
|
73
|
+
* pass `--quiet` — suppressing there would defeat the deprecation window
|
|
74
|
+
* for exactly its target audience. Silencing warnings should be an
|
|
75
|
+
* explicit opt-out, not a side effect of asking for less progress output.
|
|
76
|
+
*
|
|
77
|
+
* `runCommand` calls this immediately after the header box, ahead of the
|
|
78
|
+
* manifest and settings checks: a dead flag is a fact about argv, not about
|
|
79
|
+
* project state, so it should be reported even from an uninitialized
|
|
80
|
+
* directory where those checks would return early.
|
|
81
|
+
*/
|
|
82
|
+
export function warnDeprecatedFlags(options) {
|
|
83
|
+
for (const notice of deprecatedFlagNotices(options)) {
|
|
84
|
+
console.error(chalk.yellow(` ! ${notice}`));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -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;
|
|
@@ -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;
|