sequant 2.9.0 → 2.11.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 +20 -5
- package/dist/bin/cli.js +143 -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 +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.d.ts +20 -1
- package/dist/src/commands/locks.js +208 -4
- 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 +31 -3
- package/dist/src/commands/ready.js +53 -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 +38 -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/commands/worktree.d.ts +31 -0
- package/dist/src/commands/worktree.js +95 -0
- 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 +56 -0
- package/dist/src/lib/cli-flags.js +89 -0
- package/dist/src/lib/cli-ui/run-renderer-types.d.ts +23 -7
- package/dist/src/lib/cli-ui/run-renderer.d.ts +10 -0
- package/dist/src/lib/cli-ui/run-renderer.js +69 -1
- package/dist/src/lib/errors.d.ts +78 -6
- package/dist/src/lib/errors.js +133 -15
- package/dist/src/lib/locks/checkout-lock.d.ts +193 -0
- package/dist/src/lib/locks/checkout-lock.js +389 -0
- package/dist/src/lib/locks/index.d.ts +7 -4
- package/dist/src/lib/locks/index.js +5 -3
- package/dist/src/lib/locks/lock-manager.d.ts +101 -3
- package/dist/src/lib/locks/lock-manager.js +277 -8
- package/dist/src/lib/locks/types.d.ts +106 -3
- package/dist/src/lib/locks/types.js +44 -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 +87 -0
- package/dist/src/lib/settings.js +106 -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 +10 -2
- package/dist/src/lib/test-tautology-detector.js +213 -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 +552 -60
- 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 +51 -0
- package/dist/src/lib/workflow/config-resolver.js +143 -2
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +22 -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 +25 -2
- package/dist/src/lib/workflow/effort-escalation.d.ts +73 -0
- package/dist/src/lib/workflow/effort-escalation.js +82 -0
- package/dist/src/lib/workflow/error-classifier.d.ts +4 -1
- package/dist/src/lib/workflow/error-classifier.js +4 -0
- 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 +54 -2
- package/dist/src/lib/workflow/log-writer.js +95 -6
- package/dist/src/lib/workflow/metrics-schema.d.ts +52 -9
- package/dist/src/lib/workflow/metrics-schema.js +33 -0
- package/dist/src/lib/workflow/metrics-writer.d.ts +11 -0
- package/dist/src/lib/workflow/phase-detection.d.ts +12 -0
- package/dist/src/lib/workflow/phase-detection.js +5 -1
- package/dist/src/lib/workflow/phase-executor.d.ts +203 -14
- package/dist/src/lib/workflow/phase-executor.js +467 -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 +45 -4
- package/dist/src/lib/workflow/ready-gate.js +49 -12
- package/dist/src/lib/workflow/run-log-schema.d.ts +69 -0
- package/dist/src/lib/workflow/run-log-schema.js +78 -2
- package/dist/src/lib/workflow/run-orchestrator.d.ts +19 -0
- package/dist/src/lib/workflow/run-orchestrator.js +168 -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/spec-recommendation.d.ts +71 -0
- package/dist/src/lib/workflow/spec-recommendation.js +142 -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 +190 -4
- package/dist/src/lib/workflow/types.js +1 -0
- package/dist/src/lib/workflow/worktree-manager.d.ts +36 -3
- package/dist/src/lib/workflow/worktree-manager.js +111 -24
- package/dist/src/lib/workflow/worktree-resolver.d.ts +73 -0
- package/dist/src/lib/workflow/worktree-resolver.js +126 -0
- 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 +10 -8
- package/templates/hooks/parallel-marker.sh +50 -0
- package/templates/hooks/post-tool.sh +20 -7
- package/templates/hooks/pre-tool.sh +303 -10
- package/templates/scripts/cleanup-worktree.sh +217 -33
- package/templates/scripts/new-feature.sh +289 -35
- package/templates/skills/_shared/references/subagent-types.md +7 -18
- package/templates/skills/_shared/references/trust-model.md +18 -0
- package/templates/skills/assess/SKILL.md +220 -391
- 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 +87 -13
- package/templates/skills/fullsolve/SKILL.md +130 -22
- package/templates/skills/loop/SKILL.md +94 -13
- package/templates/skills/merger/SKILL.md +100 -12
- package/templates/skills/qa/SKILL.md +396 -63
- 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 +80 -1
- package/templates/skills/setup/SKILL.md +5 -5
- package/templates/skills/spec/SKILL.md +33 -15
- package/templates/skills/spec/references/recommended-workflow.md +14 -1
- package/templates/skills/test/SKILL.md +1 -1
- package/templates/skills/testgen/SKILL.md +23 -6
- 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
- package/templates/agents/sequant-explorer.md +0 -24
|
@@ -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(" ");
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sequant worktree` — repo-scoped worktree resolution for skill bodies (#899).
|
|
3
|
+
*
|
|
4
|
+
* `/fullsolve` uses `resolve` to turn an issue number into a real absolute
|
|
5
|
+
* path before exporting `SEQUANT_WORKTREE`; `/exec` uses `verify` to refuse a
|
|
6
|
+
* path that does not exist or belongs to another repository. Both fail closed:
|
|
7
|
+
* a non-zero exit means the caller must halt, not continue where it stands.
|
|
8
|
+
*/
|
|
9
|
+
export interface WorktreeResolveOptions {
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface WorktreeVerifyOptions {
|
|
13
|
+
issue?: string;
|
|
14
|
+
json?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `sequant worktree resolve <issue>` — print the absolute path of this
|
|
18
|
+
* repository's worktree for an issue.
|
|
19
|
+
*
|
|
20
|
+
* On success the path is the *only* thing written to stdout, so callers can
|
|
21
|
+
* capture it with `WT="$(sequant worktree resolve 123)"`. Everything else goes
|
|
22
|
+
* to stderr.
|
|
23
|
+
*/
|
|
24
|
+
export declare function worktreeResolveCommand(issueArg: string, options?: WorktreeResolveOptions): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* `sequant worktree verify <path>` — confirm a path is a worktree of this
|
|
27
|
+
* repository (and, with `--issue`, that it belongs to that issue).
|
|
28
|
+
*
|
|
29
|
+
* Exit 1 with a named error is the signal for `/exec` to halt.
|
|
30
|
+
*/
|
|
31
|
+
export declare function worktreeVerifyCommand(pathArg: string, options?: WorktreeVerifyOptions): Promise<void>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sequant worktree` — repo-scoped worktree resolution for skill bodies (#899).
|
|
3
|
+
*
|
|
4
|
+
* `/fullsolve` uses `resolve` to turn an issue number into a real absolute
|
|
5
|
+
* path before exporting `SEQUANT_WORKTREE`; `/exec` uses `verify` to refuse a
|
|
6
|
+
* path that does not exist or belongs to another repository. Both fail closed:
|
|
7
|
+
* a non-zero exit means the caller must halt, not continue where it stands.
|
|
8
|
+
*/
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import { resolveIssueWorktree, verifyWorktreePath, } from "../lib/workflow/worktree-resolver.js";
|
|
11
|
+
function parseIssue(arg) {
|
|
12
|
+
const issue = Number.parseInt(arg, 10);
|
|
13
|
+
if (!Number.isInteger(issue) || issue <= 0) {
|
|
14
|
+
console.error(chalk.red(`Invalid issue number: ${arg}`));
|
|
15
|
+
process.exitCode = 2;
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return issue;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* `sequant worktree resolve <issue>` — print the absolute path of this
|
|
22
|
+
* repository's worktree for an issue.
|
|
23
|
+
*
|
|
24
|
+
* On success the path is the *only* thing written to stdout, so callers can
|
|
25
|
+
* capture it with `WT="$(sequant worktree resolve 123)"`. Everything else goes
|
|
26
|
+
* to stderr.
|
|
27
|
+
*/
|
|
28
|
+
export async function worktreeResolveCommand(issueArg, options = {}) {
|
|
29
|
+
const issue = parseIssue(issueArg);
|
|
30
|
+
if (issue === null)
|
|
31
|
+
return;
|
|
32
|
+
const result = resolveIssueWorktree(issue);
|
|
33
|
+
if (!result.ok) {
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
if (options.json) {
|
|
36
|
+
console.error(JSON.stringify({
|
|
37
|
+
issue,
|
|
38
|
+
ok: false,
|
|
39
|
+
error: result.error,
|
|
40
|
+
message: result.message,
|
|
41
|
+
candidates: result.candidates,
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
console.error(chalk.red(result.message));
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (options.json) {
|
|
50
|
+
console.log(JSON.stringify({
|
|
51
|
+
issue,
|
|
52
|
+
ok: true,
|
|
53
|
+
path: result.path,
|
|
54
|
+
branch: result.branch,
|
|
55
|
+
}));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Bare path only — this is consumed by `$(...)` in skill bodies.
|
|
59
|
+
console.log(result.path);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* `sequant worktree verify <path>` — confirm a path is a worktree of this
|
|
63
|
+
* repository (and, with `--issue`, that it belongs to that issue).
|
|
64
|
+
*
|
|
65
|
+
* Exit 1 with a named error is the signal for `/exec` to halt.
|
|
66
|
+
*/
|
|
67
|
+
export async function worktreeVerifyCommand(pathArg, options = {}) {
|
|
68
|
+
let issue;
|
|
69
|
+
if (options.issue !== undefined) {
|
|
70
|
+
const parsed = parseIssue(options.issue);
|
|
71
|
+
if (parsed === null)
|
|
72
|
+
return;
|
|
73
|
+
issue = parsed;
|
|
74
|
+
}
|
|
75
|
+
const result = verifyWorktreePath(pathArg, { issue });
|
|
76
|
+
if (!result.ok) {
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
if (options.json) {
|
|
79
|
+
console.error(JSON.stringify({
|
|
80
|
+
ok: false,
|
|
81
|
+
error: result.error,
|
|
82
|
+
message: result.message,
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.error(chalk.red(result.message));
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (options.json) {
|
|
91
|
+
console.log(JSON.stringify({ ok: true, path: result.path, branch: result.branch }));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
console.log(chalk.green(`✓ ${result.path} (${result.branch})`));
|
|
95
|
+
}
|
|
@@ -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
|
*/
|
|
@@ -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';
|
|
@@ -33,6 +39,9 @@ import { createAcceptanceCriterion, createAcceptanceCriteria, } from "./workflow
|
|
|
33
39
|
* - `- [ ] **B2:** Description`
|
|
34
40
|
* - `- [ ] **AC1:** Description`
|
|
35
41
|
* - `- [ ] **AC-1: Description**` (bold wraps ID + description)
|
|
42
|
+
* - `- [ ] **AC-1** Description` (bold ID, no colon)
|
|
43
|
+
* - `- [ ] **AC-1**: Description` (colon outside the bold)
|
|
44
|
+
* - `- [ ] AC-1: Description`
|
|
36
45
|
*/
|
|
37
46
|
const AC_PATTERNS = [
|
|
38
47
|
// Pattern 1: `- [ ] **AC-1:** Description` or `- [x] **AC-1:** Description`
|
|
@@ -43,7 +52,41 @@ const AC_PATTERNS = [
|
|
|
43
52
|
/^-\s*\[[x\s]\]\s*\*\*([A-Za-z]+-?\d+):\s*(.+?)\*\*\s*(.*)$/gim,
|
|
44
53
|
// Pattern 4: `- [ ] AC-1: Description` (no bold)
|
|
45
54
|
/^-\s*\[[x\s]\]\s*([A-Za-z]+-?\d+):\s*(.+)$/gim,
|
|
55
|
+
// Pattern 5: `- [ ] **AC-1** Description` / `- [ ] **AC-1**: Description` (#808)
|
|
56
|
+
//
|
|
57
|
+
// The colon-less form is a very common authoring style, and before this
|
|
58
|
+
// pattern existed it matched nothing at all — an issue written this way
|
|
59
|
+
// parsed as ZERO acceptance criteria, silently, with no warning anywhere
|
|
60
|
+
// downstream (#803's five ACs were written exactly like this).
|
|
61
|
+
//
|
|
62
|
+
// The trailing `:?` also absorbs a colon placed *outside* the bold, which
|
|
63
|
+
// would otherwise land in the description as a leading ": ".
|
|
64
|
+
//
|
|
65
|
+
// Deliberately requires the bold markers. A bare `- [ ] AC1 something` is
|
|
66
|
+
// ambiguous against ordinary checklist prose, so widening that far would
|
|
67
|
+
// trade a silent miss for silent false positives. The `\d+` at the end of
|
|
68
|
+
// the ID keeps non-identifier bold labels (`**Note**`, `**Verify**`) out.
|
|
69
|
+
/^-\s*\[[x\s]\]\s*\*\*([A-Za-z]+-?\d+)\*\*:?\s*(.+)$/gim,
|
|
46
70
|
];
|
|
71
|
+
/**
|
|
72
|
+
* Matches an `## Acceptance Criteria` heading (any level, case-insensitive).
|
|
73
|
+
*
|
|
74
|
+
* Real issues vary the casing — #703 uses `## Acceptance criteria` (lowercase
|
|
75
|
+
* "criteria") — so the match must be case-insensitive. The trailing `\b` lets
|
|
76
|
+
* a suffix follow ("Acceptance Criteria (v2)") without demanding an exact end.
|
|
77
|
+
*/
|
|
78
|
+
const AC_HEADING_RE = /^#{1,6}\s+acceptance\s+criteria\b/i;
|
|
79
|
+
/**
|
|
80
|
+
* Matches any markdown ATX heading. Used to detect when the AC section ends —
|
|
81
|
+
* the next heading of any level closes it, mirroring `parseNonGoals` in
|
|
82
|
+
* `ready-gate.ts`.
|
|
83
|
+
*/
|
|
84
|
+
const HEADING_RE = /^#{1,6}\s+/;
|
|
85
|
+
/**
|
|
86
|
+
* Matches a bare checkbox item with no ID prefix: `- [ ] Some requirement`.
|
|
87
|
+
* Honored only inside the AC section (see {@link parseAcceptanceCriteria}).
|
|
88
|
+
*/
|
|
89
|
+
const BARE_CHECKBOX_RE = /^-\s*\[[x\s]\]\s*(.+)$/i;
|
|
47
90
|
/**
|
|
48
91
|
* Keywords that suggest verification method
|
|
49
92
|
*/
|
|
@@ -122,22 +165,73 @@ function parseACLine(line) {
|
|
|
122
165
|
* - `- [ ] **AC-1:** Description`
|
|
123
166
|
* - `- [ ] **B2:** Description`
|
|
124
167
|
* - `- [ ] **AC-1: Description**` (bold wraps ID + description)
|
|
168
|
+
* - `- [ ] **AC-1** Description` (bold ID, no colon)
|
|
169
|
+
* - `- [ ] **AC-1**: Description` (colon outside the bold)
|
|
125
170
|
* - `- [ ] AC-1: Description`
|
|
126
171
|
*
|
|
172
|
+
* Bare checkbox items without an ID prefix (`- [ ] Some requirement`) are also
|
|
173
|
+
* parsed, but ONLY under an explicit `## Acceptance Criteria` heading, where
|
|
174
|
+
* they receive synthesized stable IDs (`AC-1`, `AC-2`, ...). This keeps
|
|
175
|
+
* unrelated checklists elsewhere in the body (Open Questions, Test Plan) from
|
|
176
|
+
* being misread as ACs, while letting ordinary human-written issues parse.
|
|
177
|
+
*
|
|
127
178
|
* @param issueBody - The full GitHub issue body markdown
|
|
128
179
|
* @returns Array of parsed acceptance criteria
|
|
129
180
|
*/
|
|
130
181
|
export function parseAcceptanceCriteria(issueBody) {
|
|
131
182
|
const criteria = [];
|
|
132
183
|
const seenIds = new Set();
|
|
133
|
-
|
|
184
|
+
const push = (id, description) => {
|
|
185
|
+
if (seenIds.has(id))
|
|
186
|
+
return;
|
|
187
|
+
seenIds.add(id);
|
|
188
|
+
criteria.push(createAcceptanceCriterion(id, description, inferVerificationMethod(description)));
|
|
189
|
+
};
|
|
190
|
+
// Split into lines and process each. `inAcSection` tracks whether the
|
|
191
|
+
// current line falls under an `## Acceptance Criteria` heading; it toggles on
|
|
192
|
+
// every heading and closes at the next heading of any level (same boundary
|
|
193
|
+
// rule as `parseNonGoals` in ready-gate.ts). `bareCount` numbers synthesized
|
|
194
|
+
// IDs for bare checkboxes in appearance order.
|
|
134
195
|
const lines = issueBody.split("\n");
|
|
196
|
+
let inAcSection = false;
|
|
197
|
+
let bareCount = 0;
|
|
198
|
+
// Pre-scan for explicit IDs anywhere in the body. Synthesis must skip IDs
|
|
199
|
+
// owned by explicit markers even when the marker appears on a LATER line —
|
|
200
|
+
// otherwise a bare checkbox listed before `**AC-1:**` under the same heading
|
|
201
|
+
// synthesizes AC-1 first and the author's explicit AC-1 is silently dropped
|
|
202
|
+
// by the first-occurrence dedupe.
|
|
203
|
+
const explicitIds = new Set();
|
|
135
204
|
for (const line of lines) {
|
|
136
205
|
const parsed = parseACLine(line);
|
|
137
|
-
if (parsed
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
206
|
+
if (parsed)
|
|
207
|
+
explicitIds.add(parsed.id);
|
|
208
|
+
}
|
|
209
|
+
for (const line of lines) {
|
|
210
|
+
if (HEADING_RE.test(line)) {
|
|
211
|
+
inAcSection = AC_HEADING_RE.test(line);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
// Existing ID-prefixed patterns run on every line, regardless of section,
|
|
215
|
+
// so previously-parsable issues are unaffected (AC-4).
|
|
216
|
+
const parsed = parseACLine(line);
|
|
217
|
+
if (parsed) {
|
|
218
|
+
push(parsed.id, parsed.description);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
// Bare-checkbox fallback: only inside the AC section (AC-1/AC-2).
|
|
222
|
+
if (inAcSection) {
|
|
223
|
+
const bare = line.match(BARE_CHECKBOX_RE);
|
|
224
|
+
const description = bare?.[1].trim();
|
|
225
|
+
if (description) {
|
|
226
|
+
// Synthesize `AC-<n>`, skipping any ID already taken by an explicit
|
|
227
|
+
// marker — before OR after this line — so a synthesized ID can never
|
|
228
|
+
// collide with (and be silently dropped against) a hand-written one.
|
|
229
|
+
let id;
|
|
230
|
+
do {
|
|
231
|
+
id = `AC-${++bareCount}`;
|
|
232
|
+
} while (seenIds.has(id) || explicitIds.has(id));
|
|
233
|
+
push(id, description);
|
|
234
|
+
}
|
|
141
235
|
}
|
|
142
236
|
}
|
|
143
237
|
return criteria;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic rendering for `/assess` output (#823).
|
|
3
|
+
*
|
|
4
|
+
* `types.ts` owns the contract, `renderer.ts` owns the geometry. Neither touches
|
|
5
|
+
* TTY state, ANSI, or I/O — see the header comment in `renderer.ts` for why that
|
|
6
|
+
* rules out the `cli-ui.ts` helpers.
|
|
7
|
+
*/
|
|
8
|
+
export { AssessActionSchema, AssessChainSchema, AssessCleanupSchema, AssessCommandSchema, AssessFlagSchema, AssessIssueSchema, AssessResultSchema, AssessResultValidationError, AssessWarningSchema, parseAssessResult, } from "./types.js";
|
|
9
|
+
export type { AssessAction, AssessChain, AssessCleanup, AssessCommand, AssessFlag, AssessIssue, AssessResult, AssessWarning, } from "./types.js";
|
|
10
|
+
export { GEOMETRY, render, renderBatch, renderSingle } from "./renderer.js";
|