@gobing-ai/spur 0.4.22 → 0.4.24
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/config/pipeline-budgets.json +1 -1
- package/config/rules/boundary/config-loading-ownership.yaml +1 -1
- package/config/rules/strict/runtime-boundaries.yaml +3 -3
- package/config/rules/surface/check-cli-surface.yaml +6 -1
- package/config/workflow-candidates.json +1 -1
- package/config/workflows/history-anatomy.yaml +11 -11
- package/package.json +1 -1
- package/plugins/sp/commands/dev-runall.md +1 -1
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/dogfood-testing/SKILL.md +7 -7
- package/plugins/sp/skills/dogfood-testing/references/report-template.md +1 -1
- package/plugins/sp/skills/history-anatomy/references/modes.md +1 -1
- package/plugins/sp/skills/spur-cli/references/agent.md +6 -5
- package/plugins/sp/skills/spur-cli/references/builder.md +1 -8
- package/plugins/sp/skills/spur-cli/references/history.md +9 -2
- package/plugins/sp/skills/spur-cli/references/tasks.md +2 -1
- package/plugins/sp/skills/spur-cli/references/workflows.md +6 -2
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +3 -3
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +3 -3
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +2 -2
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +4 -4
- package/spur.js +882 -789
package/spur.js
CHANGED
|
@@ -124703,40 +124703,40 @@ var init_src3 = __esm(() => {
|
|
|
124703
124703
|
|
|
124704
124704
|
// ../../packages/app/src/services/search-zg.ts
|
|
124705
124705
|
function buildZgArgs(req, globs) {
|
|
124706
|
-
return [
|
|
124706
|
+
return [
|
|
124707
|
+
"--hybrid",
|
|
124708
|
+
req.query,
|
|
124709
|
+
"--limit",
|
|
124710
|
+
String(req.limit),
|
|
124711
|
+
"--preview",
|
|
124712
|
+
"short",
|
|
124713
|
+
...globs.flatMap((glob) => ["-g", glob])
|
|
124714
|
+
];
|
|
124707
124715
|
}
|
|
124708
124716
|
function parseZgQuery(stdout, zgVersion) {
|
|
124709
|
-
if (!stdout.trim())
|
|
124710
|
-
return [];
|
|
124711
|
-
if (!stdout.includes("query groups (")) {
|
|
124712
|
-
throw new ZgOutputError('zg query output is missing the "query groups (" header', zgVersion, stdout);
|
|
124713
|
-
}
|
|
124714
|
-
const countMatch = stdout.match(/^hits: (\d+)$/m);
|
|
124715
|
-
if (!countMatch) {
|
|
124716
|
-
throw new ZgOutputError('zg query output is missing the "hits:" count line', zgVersion, stdout);
|
|
124717
|
-
}
|
|
124718
|
-
const declaredCount = Number(countMatch[1] ?? "0");
|
|
124719
124717
|
const hits = [];
|
|
124720
124718
|
let current = null;
|
|
124721
124719
|
const snippetLines = [];
|
|
124722
124720
|
for (const line of stdout.split(`
|
|
124723
124721
|
`)) {
|
|
124724
|
-
|
|
124722
|
+
const trimmed = line.trim();
|
|
124723
|
+
if (!trimmed || trimmed === "\u2026" || trimmed === "..." || GROUP_SEPARATOR_PATTERN.test(trimmed))
|
|
124724
|
+
continue;
|
|
124725
|
+
if (trimmed === "No matches." && hits.length === 0)
|
|
124725
124726
|
continue;
|
|
124726
124727
|
const header = line.match(HIT_HEADER_PATTERN);
|
|
124727
124728
|
if (header) {
|
|
124728
124729
|
if (current)
|
|
124729
124730
|
flushSnippet(current, snippetLines);
|
|
124730
|
-
|
|
124731
|
-
|
|
124732
|
-
|
|
124733
|
-
|
|
124734
|
-
throw new ZgOutputError("zg query hit header is missing path or line fields", zgVersion, stdout);
|
|
124731
|
+
if (Number(header[1]) !== hits.length + 1) {
|
|
124732
|
+
throw new ZgOutputError(`zg query hit rank #${header[1]} is out of sequence`, zgVersion, stdout);
|
|
124733
|
+
}
|
|
124734
|
+
const start = Number(header[3]);
|
|
124735
124735
|
current = {
|
|
124736
124736
|
rank: hits.length + 1,
|
|
124737
|
-
path:
|
|
124738
|
-
startLine:
|
|
124739
|
-
endLine:
|
|
124737
|
+
path: header[2] ?? "",
|
|
124738
|
+
startLine: start,
|
|
124739
|
+
endLine: header[4] ? Number(header[4]) : start,
|
|
124740
124740
|
snippet: "",
|
|
124741
124741
|
route: "fuzzy",
|
|
124742
124742
|
matchedBy: ["fuzzy"]
|
|
@@ -124745,25 +124745,23 @@ function parseZgQuery(stdout, zgVersion) {
|
|
|
124745
124745
|
snippetLines.length = 0;
|
|
124746
124746
|
continue;
|
|
124747
124747
|
}
|
|
124748
|
-
|
|
124748
|
+
const source = current ? line.match(SOURCE_LINE_PATTERN) : null;
|
|
124749
|
+
if (source) {
|
|
124750
|
+
snippetLines.push(source[2] ?? "");
|
|
124749
124751
|
continue;
|
|
124750
|
-
|
|
124751
|
-
|
|
124752
|
+
}
|
|
124753
|
+
const metadata = current ? line.match(METADATA_PATTERN) : null;
|
|
124754
|
+
if (current && metadata) {
|
|
124752
124755
|
if (metadata[1] === "heading")
|
|
124753
124756
|
current.heading = metadata[2] ?? "";
|
|
124754
124757
|
if (metadata[1] === "symbol")
|
|
124755
124758
|
current.symbol = metadata[2] ?? "";
|
|
124756
124759
|
continue;
|
|
124757
124760
|
}
|
|
124758
|
-
|
|
124759
|
-
if (source)
|
|
124760
|
-
snippetLines.push(source[2] ?? "");
|
|
124761
|
+
throw new ZgOutputError(`zg query output has an unrecognized line: ${line.slice(0, 80)}`, zgVersion, stdout);
|
|
124761
124762
|
}
|
|
124762
124763
|
if (current)
|
|
124763
124764
|
flushSnippet(current, snippetLines);
|
|
124764
|
-
if (hits.length !== declaredCount) {
|
|
124765
|
-
throw new ZgOutputError(`zg query output declares hits: ${declaredCount} but ${hits.length} hit blocks were parsed`, zgVersion, stdout);
|
|
124766
|
-
}
|
|
124767
124765
|
return hits;
|
|
124768
124766
|
}
|
|
124769
124767
|
function flushSnippet(hit, snippetLines) {
|
|
@@ -124771,6 +124769,8 @@ function flushSnippet(hit, snippetLines) {
|
|
|
124771
124769
|
`).slice(0, SNIPPET_MAX_CHARS);
|
|
124772
124770
|
}
|
|
124773
124771
|
function parseZgStatus(stdout) {
|
|
124772
|
+
if (/^\s*Coverage\s+Unavailable\b/m.test(stdout))
|
|
124773
|
+
return "missing";
|
|
124774
124774
|
const firstLine = stdout.split(`
|
|
124775
124775
|
`).find((line) => line.trim()) ?? "";
|
|
124776
124776
|
const glyph = firstLine.trim().charAt(0);
|
|
@@ -124782,7 +124782,7 @@ function parseZgStatus(stdout) {
|
|
|
124782
124782
|
return "unknown";
|
|
124783
124783
|
return "ready";
|
|
124784
124784
|
}
|
|
124785
|
-
var ZgOutputError, SNIPPET_MAX_CHARS = 300, HIT_HEADER_PATTERN, METADATA_PATTERN, SOURCE_LINE_PATTERN;
|
|
124785
|
+
var ZgOutputError, SNIPPET_MAX_CHARS = 300, HIT_HEADER_PATTERN, METADATA_PATTERN, SOURCE_LINE_PATTERN, GROUP_SEPARATOR_PATTERN;
|
|
124786
124786
|
var init_search_zg = __esm(() => {
|
|
124787
124787
|
ZgOutputError = class ZgOutputError extends Error {
|
|
124788
124788
|
zgVersion;
|
|
@@ -124794,9 +124794,10 @@ var init_search_zg = __esm(() => {
|
|
|
124794
124794
|
this.sample = sample.slice(0, 400);
|
|
124795
124795
|
}
|
|
124796
124796
|
};
|
|
124797
|
-
HIT_HEADER_PATTERN = /^#(\d+)
|
|
124798
|
-
METADATA_PATTERN = /^(
|
|
124799
|
-
SOURCE_LINE_PATTERN =
|
|
124797
|
+
HIT_HEADER_PATTERN = /^#(\d+) (.+?):(\d+)(?:-(\d+))?$/;
|
|
124798
|
+
METADATA_PATTERN = /^([a-z_]+): (.*)$/;
|
|
124799
|
+
SOURCE_LINE_PATTERN = /^\s+(\d+): ?(.*)$/;
|
|
124800
|
+
GROUP_SEPARATOR_PATTERN = /^-- query \d+\/\d+: .* --$/;
|
|
124800
124801
|
});
|
|
124801
124802
|
|
|
124802
124803
|
// ../../packages/app/src/services/search.ts
|
|
@@ -124814,10 +124815,10 @@ async function searchStatus(root, deps = defaultSearchDeps) {
|
|
|
124814
124815
|
if (!zgPath) {
|
|
124815
124816
|
return { rg, zg: { available: false, index: "unknown", hint: ZG_INSTALL_HINT } };
|
|
124816
124817
|
}
|
|
124817
|
-
const version = await probeVersion(deps, zgPath, ["version"]);
|
|
124818
|
+
const version = await probeVersion(deps, zgPath, ["--version"]);
|
|
124818
124819
|
let index = "unknown";
|
|
124819
124820
|
try {
|
|
124820
|
-
const result = await deps.run("zg", ["status"], { cwd: root, timeout: PROBE_TIMEOUT_MS });
|
|
124821
|
+
const result = await deps.run("zg", ["--status"], { cwd: root, timeout: PROBE_TIMEOUT_MS });
|
|
124821
124822
|
if (result.exitCode === 0)
|
|
124822
124823
|
index = parseZgStatus(result.stdout);
|
|
124823
124824
|
} catch {}
|
|
@@ -124959,7 +124960,7 @@ async function runFuzzy(req, limit, globs, root, status, deps) {
|
|
|
124959
124960
|
if (result.timedOut)
|
|
124960
124961
|
return { hits: null, skipped: [{ route: "fuzzy", reason: "zg-timeout" }] };
|
|
124961
124962
|
if (result.exitCode !== 0) {
|
|
124962
|
-
if (result.stderr
|
|
124963
|
+
if (/ZG\.ENGINE\.NOT_FOUND|\[INDEX_MISSING\]/.test(result.stderr)) {
|
|
124963
124964
|
return { hits: null, skipped: [{ route: "fuzzy", reason: "zg-index-missing", hint: ZG_INDEX_HINT }] };
|
|
124964
124965
|
}
|
|
124965
124966
|
return { hits: null, skipped: [{ route: "fuzzy", reason: "zg-failed" }] };
|
|
@@ -124968,13 +124969,13 @@ async function runFuzzy(req, limit, globs, root, status, deps) {
|
|
|
124968
124969
|
return { hits: parseZgQuery(result.stdout, status.zg.version ?? "unknown").slice(0, limit), skipped: [] };
|
|
124969
124970
|
} catch (error) {
|
|
124970
124971
|
if (error instanceof ZgOutputError) {
|
|
124971
|
-
const sample = `${error.zgVersion}: ${error.sample}`.slice(0, 300);
|
|
124972
|
+
const sample = `${error.zgVersion}: ${error.message} | ${error.sample}`.slice(0, 300);
|
|
124972
124973
|
return { hits: null, skipped: [{ route: "fuzzy", reason: "zg-output-error", hint: sample }] };
|
|
124973
124974
|
}
|
|
124974
124975
|
throw error;
|
|
124975
124976
|
}
|
|
124976
124977
|
}
|
|
124977
|
-
var RG_INSTALL_HINT = "brew install ripgrep", ZG_INSTALL_HINT = "bun add -g @zvec/zvec-grep", ZG_INDEX_HINT = "zg index --embedding local/potion-code-16m-v2", ZG_REFRESH_HINT = "zg index", SearchError, defaultSearchDeps, RG_TIMEOUT_MS = 1e4, ZG_QUERY_TIMEOUT_MS = 15000, PROBE_TIMEOUT_MS = 5000, SNIPPET_MAX_CHARS2 = 300;
|
|
124978
|
+
var RG_INSTALL_HINT = "brew install ripgrep", ZG_INSTALL_HINT = "bun add -g @zvec/zvec-grep", ZG_INDEX_HINT = "zg --index --embedding local/potion-code-16m-v2", ZG_REFRESH_HINT = "zg --index", SearchError, defaultSearchDeps, RG_TIMEOUT_MS = 1e4, ZG_QUERY_TIMEOUT_MS = 15000, PROBE_TIMEOUT_MS = 5000, SNIPPET_MAX_CHARS2 = 300;
|
|
124978
124979
|
var init_search2 = __esm(() => {
|
|
124979
124980
|
init_src3();
|
|
124980
124981
|
init_dist5();
|
|
@@ -143843,7 +143844,7 @@ async function runBatchPlan(req, read) {
|
|
|
143843
143844
|
}
|
|
143844
143845
|
return usage();
|
|
143845
143846
|
}
|
|
143846
|
-
var BATCH_PLAN_USAGE = "usage: spur task batch-plan waves --tasks <tasks.json> | order --tasks <tasks.json> [--concurrency <n>]
|
|
143847
|
+
var BATCH_PLAN_USAGE = "usage: spur task batch-plan waves --tasks <tasks.json> | order --tasks <tasks.json> [--concurrency <n>]; spur task batch-plan-children --letter <A-Z> --plan <plan.json>", usage = () => ({ exitCode: 2, stdout: "", stderr: `${BATCH_PLAN_USAGE}
|
|
143847
143848
|
` }), invalid = (message) => ({
|
|
143848
143849
|
exitCode: 1,
|
|
143849
143850
|
stdout: "",
|
|
@@ -146370,7 +146371,7 @@ function runCacheCli(argv, deps = {}) {
|
|
|
146370
146371
|
switch (cmd) {
|
|
146371
146372
|
case "digest": {
|
|
146372
146373
|
if (a === undefined) {
|
|
146373
|
-
return { exitCode: 1, stdout: "", stderr: `usage: spur history anatomy
|
|
146374
|
+
return { exitCode: 1, stdout: "", stderr: `usage: spur history anatomy-digest <artifact.json>
|
|
146374
146375
|
` };
|
|
146375
146376
|
}
|
|
146376
146377
|
let artifact;
|
|
@@ -146386,7 +146387,7 @@ function runCacheCli(argv, deps = {}) {
|
|
|
146386
146387
|
}
|
|
146387
146388
|
case "check": {
|
|
146388
146389
|
if (a === undefined) {
|
|
146389
|
-
return { exitCode: 1, stdout: "", stderr: `usage: spur history anatomy
|
|
146390
|
+
return { exitCode: 1, stdout: "", stderr: `usage: spur history anatomy-check <report.md>
|
|
146390
146391
|
` };
|
|
146391
146392
|
}
|
|
146392
146393
|
const result = checkReportStructure(readFileSync26(a, "utf8"));
|
|
@@ -146400,7 +146401,7 @@ ${result.problems.map((p) => `- ${p}
|
|
|
146400
146401
|
return {
|
|
146401
146402
|
exitCode: 1,
|
|
146402
146403
|
stdout: "",
|
|
146403
|
-
stderr: `usage: spur history anatomy
|
|
146404
|
+
stderr: `usage: spur history anatomy-publish <candidate.md> <target.md>
|
|
146404
146405
|
`
|
|
146405
146406
|
};
|
|
146406
146407
|
}
|
|
@@ -146413,7 +146414,7 @@ ${result.problems.map((p) => `- ${p}
|
|
|
146413
146414
|
return {
|
|
146414
146415
|
exitCode: 1,
|
|
146415
146416
|
stdout: "",
|
|
146416
|
-
stderr: `usage: spur history anatomy
|
|
146417
|
+
stderr: `usage: spur history anatomy-assert-clean --baseline <porcelain.txt> [--expect <path>]...
|
|
146417
146418
|
`
|
|
146418
146419
|
};
|
|
146419
146420
|
}
|
|
@@ -146447,7 +146448,7 @@ ${result.problems.map((p) => `- ${p}
|
|
|
146447
146448
|
return {
|
|
146448
146449
|
exitCode: 1,
|
|
146449
146450
|
stdout: "",
|
|
146450
|
-
stderr: `usage: spur history anatomy
|
|
146451
|
+
stderr: `usage: spur history anatomy-paths --out <env> [--skill-dir <d>] [--helper <p>] [--report-dir <d>] [--date <d>] [--output <p>] [--mode <m>] [--since <s>] [--until <u>] [--focus <text>] [--recompute true|false] [--run-id <id>]
|
|
146451
146452
|
`
|
|
146452
146453
|
};
|
|
146453
146454
|
}
|
|
@@ -146535,7 +146536,7 @@ ${reasons}`, stderr: "" };
|
|
|
146535
146536
|
return {
|
|
146536
146537
|
exitCode: 1,
|
|
146537
146538
|
stdout: "",
|
|
146538
|
-
stderr: `usage: spur history anatomy
|
|
146539
|
+
stderr: `usage: spur history anatomy-stamp --candidate <c.md> --provenance <p.json> --out <o.md>
|
|
146539
146540
|
`
|
|
146540
146541
|
};
|
|
146541
146542
|
}
|
|
@@ -146555,7 +146556,7 @@ ${reasons}`, stderr: "" };
|
|
|
146555
146556
|
return {
|
|
146556
146557
|
exitCode: 1,
|
|
146557
146558
|
stdout: "",
|
|
146558
|
-
stderr: `usage: spur history anatomy
|
|
146559
|
+
stderr: `usage: spur history anatomy-refresh --report <published.md> --out <o.md> [--disposition hit]
|
|
146559
146560
|
`
|
|
146560
146561
|
};
|
|
146561
146562
|
}
|
|
@@ -146580,7 +146581,7 @@ var VALID_COMMANDS = "digest, check, paths, assert-clean, probe, stamp, refresh,
|
|
|
146580
146581
|
var init_history_anatomy_cli = __esm(() => {
|
|
146581
146582
|
init_dist5();
|
|
146582
146583
|
init_history_anatomy();
|
|
146583
|
-
PROBE_USAGE = "spur history anatomy
|
|
146584
|
+
PROBE_USAGE = "spur history anatomy-probe --artifact <a.json> --target <report.md> [--baseline <b.json>] [--mode daily|ad-hoc] " + "[--date <YYYY-MM-DD>] [--recompute true] [--out <prov.json>] [--skill-dir <d>] [--contract <f>] [--workflow <f>] [--helper <f>]";
|
|
146584
146585
|
});
|
|
146585
146586
|
|
|
146586
146587
|
// ../../packages/app/src/services/daily-summary.ts
|
|
@@ -147044,7 +147045,7 @@ async function buildDailySummary(options) {
|
|
|
147044
147045
|
}
|
|
147045
147046
|
return result;
|
|
147046
147047
|
}
|
|
147047
|
-
async function
|
|
147048
|
+
async function main(argv, sink) {
|
|
147048
147049
|
const previous = io2;
|
|
147049
147050
|
io2 = sink;
|
|
147050
147051
|
try {
|
|
@@ -155545,6 +155546,7 @@ var SHARED_OPTIONS = {
|
|
|
155545
155546
|
jsonProjectsResponse: ["--json", "Output JSON response"],
|
|
155546
155547
|
jsonMessageStream: ["--json", "Output one JSON object per new message (machine-consumable)"],
|
|
155547
155548
|
jsonServePortUrl: ["--json", "Output { port, url, pid } and exit"],
|
|
155549
|
+
jsonShowBranches: ["--json", "alias of --format json (prints the raw BranchTopology object)"],
|
|
155548
155550
|
fromFileOutcomeRows: ["--from-file <path>", "Path to JSON array of {wbs,outcome[,reason]} rows"],
|
|
155549
155551
|
asFeature0418: [
|
|
155550
155552
|
"--as <status>",
|
|
@@ -155564,6 +155566,7 @@ var SHARED_OPTIONS = {
|
|
|
155564
155566
|
dryRunWorkflowValidate: ["--dry-run", "Validate and walk transitions without executing actions"],
|
|
155565
155567
|
execDryRun: ["--dry-run", "Print the resolved command(s) and run nothing"],
|
|
155566
155568
|
dryRunWorkflowClean: ["--dry-run", "List what would be cleaned without writing (applies to both scopes)"],
|
|
155569
|
+
dryRunHistorySummary: ["--dry-run", "print the summary without writing the file"],
|
|
155567
155570
|
featureTrace: ["--feature <id>", "Feature ID for traceability and Goal\u2192Background derivation"],
|
|
155568
155571
|
featureFrontmatter: ["--feature <id>", "Set the feature_id frontmatter field (traceability edge)"],
|
|
155569
155572
|
featureFilterEdge: ["--feature <id>", "Filter by linked feature ID (feature_id edge)"],
|
|
@@ -155580,6 +155583,7 @@ var SHARED_OPTIONS = {
|
|
|
155580
155583
|
"--force",
|
|
155581
155584
|
"Force applying reopen proposals without confirmation; also bypasses repeated-BLOCKED suppression"
|
|
155582
155585
|
],
|
|
155586
|
+
forceBuilderPr: ["--force", "request a fresh review even when the current HEAD is already reviewed"],
|
|
155583
155587
|
forceInitRecreate: ["--force", "Recreate files that already exist"],
|
|
155584
155588
|
forceTaskPreflight: [
|
|
155585
155589
|
"--force",
|
|
@@ -155765,7 +155769,7 @@ function parseDogfoodGateArgs(argv) {
|
|
|
155765
155769
|
return { testee, maxRetryPresent, steps, json, help };
|
|
155766
155770
|
}
|
|
155767
155771
|
var DOGFOOD_GATE_USAGE = `Usage:
|
|
155768
|
-
spur agent dogfood
|
|
155772
|
+
spur agent dogfood-gate --testee "<testee string>" [--max-retry-present] [--steps "s1||s2"] [--json]
|
|
155769
155773
|
|
|
155770
155774
|
Exit codes:
|
|
155771
155775
|
0 proceed (stdout may carry implement-heavy advisory)
|
|
@@ -155999,7 +156003,7 @@ function parseDogfoodValidateArgs(argv) {
|
|
|
155999
156003
|
return { file, json, help };
|
|
156000
156004
|
}
|
|
156001
156005
|
var DOGFOOD_VALIDATE_USAGE = `Usage:
|
|
156002
|
-
spur agent dogfood
|
|
156006
|
+
spur agent dogfood-validate-report --file <report.md> [--json]
|
|
156003
156007
|
|
|
156004
156008
|
Exit codes:
|
|
156005
156009
|
0 report validates clean (complete-report shape)
|
|
@@ -156058,8 +156062,7 @@ function writeResult(context, result) {
|
|
|
156058
156062
|
context.setExitCode(result.exitCode);
|
|
156059
156063
|
}
|
|
156060
156064
|
function registerAgentDogfoodCommand(agent, context) {
|
|
156061
|
-
|
|
156062
|
-
dogfood.command("gate").summary("Refuse a pipeline-driving testee without --max-retry (dogfood Phase 1.0 / W8)").description(DOGFOOD_GATE_USAGE).option("--testee <string>", "the testee invocation under evaluation").option("--max-retry-present", "operator already supplied an explicit --max-retry").option("--steps <labels>", "derived step labels joined by || (Phase 1 W8 re-check)").option(...SHARED_OPTIONS.json).argument("[testee]", "positional alias for --testee").action((positional, options) => {
|
|
156065
|
+
agent.command("dogfood-gate").summary("Refuse a pipeline-driving testee without --max-retry (dogfood Phase 1.0 / W8)").description(DOGFOOD_GATE_USAGE).option("--testee <string>", "the testee invocation under evaluation").option("--max-retry-present", "operator already supplied an explicit --max-retry").option("--steps <labels>", "derived step labels joined by || (Phase 1 W8 re-check)").option(...SHARED_OPTIONS.json).argument("[testee]", "positional alias for --testee").action((positional, options) => {
|
|
156063
156066
|
const argv = [];
|
|
156064
156067
|
const testee = options.testee ?? positional;
|
|
156065
156068
|
if (testee)
|
|
@@ -156072,7 +156075,7 @@ function registerAgentDogfoodCommand(agent, context) {
|
|
|
156072
156075
|
argv.push("--json");
|
|
156073
156076
|
writeResult(context, runDogfoodGateCli(argv));
|
|
156074
156077
|
});
|
|
156075
|
-
|
|
156078
|
+
agent.command("dogfood-validate-report").summary("Validate a dogfood report against the @1.2 contract").description(DOGFOOD_VALIDATE_USAGE).option("--file <path>", "report markdown path").option(...SHARED_OPTIONS.json).argument("[file]", "positional alias for --file").action(async (positional, options) => {
|
|
156076
156079
|
const argv = [];
|
|
156077
156080
|
const file = options.file ?? positional;
|
|
156078
156081
|
if (file)
|
|
@@ -157401,6 +157404,9 @@ Recover with: ${err.recoveryCommand}` : err.message;
|
|
|
157401
157404
|
});
|
|
157402
157405
|
}
|
|
157403
157406
|
|
|
157407
|
+
// src/commands/task/batch-plan.ts
|
|
157408
|
+
init_pipeline_steps();
|
|
157409
|
+
|
|
157404
157410
|
// src/commands/task/step-output.ts
|
|
157405
157411
|
function writeStepText(output, stream, text) {
|
|
157406
157412
|
if (text === "")
|
|
@@ -157415,26 +157421,42 @@ function writeStepText(output, stream, text) {
|
|
|
157415
157421
|
|
|
157416
157422
|
// src/commands/task/batch-plan.ts
|
|
157417
157423
|
function registerTaskBatchPlanCommand(task, context) {
|
|
157418
|
-
|
|
157424
|
+
const run = async (req) => {
|
|
157425
|
+
await Promise.resolve().then(() => init_pipeline_steps());
|
|
157426
|
+
const outcome = await runBatchPlan(req, (path) => context.fs.readFile(context.fs.resolve(path)));
|
|
157427
|
+
writeStepText(context.output, "stdout", outcome.stdout);
|
|
157428
|
+
writeStepText(context.output, "stderr", outcome.stderr);
|
|
157429
|
+
context.setExitCode(outcome.exitCode);
|
|
157430
|
+
};
|
|
157431
|
+
const childrenReq = (options) => ({
|
|
157432
|
+
mode: "task-children",
|
|
157433
|
+
...options.letter !== undefined ? { letter: options.letter } : {},
|
|
157434
|
+
...options.plan !== undefined ? { plan: options.plan } : {}
|
|
157435
|
+
});
|
|
157436
|
+
task.command("batch-plan").summary("Project a task batch: visible plan waves or dependency order.").description([
|
|
157419
157437
|
"Batch plan projection (JSON on stdout):",
|
|
157420
157438
|
" waves --tasks <file> {wbs,name}[] \u2192 { waves } (A Prepare batch, B..Y tasks, Z report)",
|
|
157421
157439
|
" order --tasks <file> [--concurrency n] {wbs,dependencies?,files?}[] \u2192 { order, waves, cycle, overlaps, mode, reason }",
|
|
157422
|
-
" task-children --letter <A-Z> --plan <file> todo --json payload (or bare plan) \u2192 phase digits",
|
|
157423
157440
|
"Exit: 0 success, 1 unreadable/invalid input, 2 usage error."
|
|
157424
157441
|
].join(`
|
|
157425
|
-
`)).argument("<mode>", "waves | order
|
|
157426
|
-
|
|
157427
|
-
|
|
157442
|
+
`)).argument("<mode>", "waves | order").option("--tasks <file>", "tasks JSON file").option("--concurrency <n>", "parallel slots for order (default 2)").action(async (mode, options) => {
|
|
157443
|
+
if (mode !== "waves" && mode !== "order") {
|
|
157444
|
+
context.output.error(BATCH_PLAN_USAGE);
|
|
157445
|
+
context.setExitCode(2);
|
|
157446
|
+
return;
|
|
157447
|
+
}
|
|
157448
|
+
await run({
|
|
157428
157449
|
mode,
|
|
157429
157450
|
...options.tasks !== undefined ? { tasks: options.tasks } : {},
|
|
157430
|
-
...options.concurrency !== undefined ? { concurrency: options.concurrency } : {}
|
|
157431
|
-
|
|
157432
|
-
...options.plan !== undefined ? { plan: options.plan } : {}
|
|
157433
|
-
}, (path) => context.fs.readFile(context.fs.resolve(path)));
|
|
157434
|
-
writeStepText(context.output, "stdout", outcome.stdout);
|
|
157435
|
-
writeStepText(context.output, "stderr", outcome.stderr);
|
|
157436
|
-
context.setExitCode(outcome.exitCode);
|
|
157451
|
+
...options.concurrency !== undefined ? { concurrency: options.concurrency } : {}
|
|
157452
|
+
});
|
|
157437
157453
|
});
|
|
157454
|
+
task.command("batch-plan-children").summary("Project one task row\u2019s phase digits from the visible batch plan.").description([
|
|
157455
|
+
"Task-row phase digits (JSON on stdout) from a `spur workflow show --format todo --json`",
|
|
157456
|
+
"payload or a bare plan array.",
|
|
157457
|
+
"Exit: 0 success, 1 unreadable/invalid input, 2 usage error."
|
|
157458
|
+
].join(`
|
|
157459
|
+
`)).option("--letter <letter>", "task row letter A-Z").option("--plan <file>", "todo-format workflow plan payload or bare plan array").allowExcessArguments(false).action((options) => run(childrenReq(options)));
|
|
157438
157460
|
}
|
|
157439
157461
|
|
|
157440
157462
|
// src/commands/task/check.ts
|
|
@@ -160986,6 +161008,98 @@ async function publishPackage(target, opts = {}) {
|
|
|
160986
161008
|
}
|
|
160987
161009
|
}
|
|
160988
161010
|
|
|
161011
|
+
// src/commands/builder/bump-ver.ts
|
|
161012
|
+
function registerBuilderBumpVerCommand(noun, context) {
|
|
161013
|
+
noun.command("bump-ver").summary("bump a package version, commit, tag, optionally push").description([
|
|
161014
|
+
"Bump one workspace package (or every released package with --all),",
|
|
161015
|
+
"rewrite workspace pins, commit, and tag. A bare version bumps all:",
|
|
161016
|
+
" spur builder bump-ver 0.1.4 # every released package",
|
|
161017
|
+
" spur builder bump-ver spur 0.1.4 # one package by id",
|
|
161018
|
+
" spur builder bump-ver --check spur # pre-publish drift guard (read-only)",
|
|
161019
|
+
"--check fails (exit 1) when .claude-plugin/marketplace.json or a listed plugin.json",
|
|
161020
|
+
"drifted from the package version; target defaults to the aggregate package."
|
|
161021
|
+
].join(`
|
|
161022
|
+
`)).option("--check", "only verify marketplace/plugin versions match the package version (no writes)").option("--all", "bump every released package in one commit with per-package + aggregate tags").option("--push", "push the branch and release tag to origin").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).argument("[target]", "package id, or the version itself when bumping all").argument("[version]", "target semver version").action(async (target, version, options) => {
|
|
161023
|
+
const args = [target, version].filter((value) => value !== undefined);
|
|
161024
|
+
if (options.all === true)
|
|
161025
|
+
args.push("--all");
|
|
161026
|
+
if (options.push === true)
|
|
161027
|
+
args.push("--push");
|
|
161028
|
+
try {
|
|
161029
|
+
if (options.check === true) {
|
|
161030
|
+
if (version !== undefined)
|
|
161031
|
+
throw new Error("bump-ver --check takes only a package id");
|
|
161032
|
+
if (options.all === true || options.push === true)
|
|
161033
|
+
throw new Error("bump-ver --check rejects incompatible write flags (--push/--all)");
|
|
161034
|
+
const code = await checkVersionDrift(target, context.cwd, context.output, context.spurConfig);
|
|
161035
|
+
if (options.json === true) {
|
|
161036
|
+
context.output.write(toEnvelopeJson({ ok: code === 0, verb: "bump-ver", check: true, target: target ?? "aggregate" }, { enveloped: options.jsonEnvelope }));
|
|
161037
|
+
}
|
|
161038
|
+
if (code !== 0)
|
|
161039
|
+
context.setExitCode(code);
|
|
161040
|
+
return;
|
|
161041
|
+
}
|
|
161042
|
+
await bumpVer(args, context.cwd, context.output, context.spurConfig);
|
|
161043
|
+
if (options.json === true) {
|
|
161044
|
+
context.output.write(toEnvelopeJson({ ok: true, verb: "bump-ver", target: target ?? "all", version: version ?? "" }, { enveloped: options.jsonEnvelope }));
|
|
161045
|
+
}
|
|
161046
|
+
} catch (err) {
|
|
161047
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
161048
|
+
if (options.json === true) {
|
|
161049
|
+
context.output.write(toEnvelopeJson({ ok: false, verb: "bump-ver", error: message }, {
|
|
161050
|
+
enveloped: options.jsonEnvelope,
|
|
161051
|
+
error: {
|
|
161052
|
+
code: "INTERNAL_ERROR",
|
|
161053
|
+
message,
|
|
161054
|
+
details: { cliCode: "bump-ver", verb: "bump-ver" }
|
|
161055
|
+
}
|
|
161056
|
+
}));
|
|
161057
|
+
context.setExitCode(1);
|
|
161058
|
+
return;
|
|
161059
|
+
}
|
|
161060
|
+
context.output.error(message);
|
|
161061
|
+
context.setExitCode(1);
|
|
161062
|
+
}
|
|
161063
|
+
});
|
|
161064
|
+
}
|
|
161065
|
+
|
|
161066
|
+
// src/commands/builder/drop-tags.ts
|
|
161067
|
+
function registerBuilderDropTagsCommand(noun, context) {
|
|
161068
|
+
noun.command("drop-tags").summary("delete release tags locally and optionally on origin").description([
|
|
161069
|
+
"Delete one package release tag (or every released tag plus the aggregate",
|
|
161070
|
+
"with --all / a bare version). Use --remote to also delete on origin."
|
|
161071
|
+
].join(`
|
|
161072
|
+
`)).option("--all", "drop every released tag plus the aggregate tag").option("--remote", "also delete the tag(s) on origin").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).argument("[target]", "package id, or the version itself when dropping all").argument("[version]", "semver version of the tags to drop").action(async (target, version, options) => {
|
|
161073
|
+
const args = [target, version].filter((value) => value !== undefined);
|
|
161074
|
+
if (options.all === true)
|
|
161075
|
+
args.push("--all");
|
|
161076
|
+
if (options.remote === true)
|
|
161077
|
+
args.push("--remote");
|
|
161078
|
+
try {
|
|
161079
|
+
await dropTags(args, context.cwd, context.output, context.spurConfig);
|
|
161080
|
+
if (options.json === true) {
|
|
161081
|
+
context.output.write(toEnvelopeJson({ ok: true, verb: "drop-tags", target: target ?? "all", version: version ?? "" }, { enveloped: options.jsonEnvelope }));
|
|
161082
|
+
}
|
|
161083
|
+
} catch (err) {
|
|
161084
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
161085
|
+
if (options.json === true) {
|
|
161086
|
+
context.output.write(toEnvelopeJson({ ok: false, verb: "drop-tags", error: message }, {
|
|
161087
|
+
enveloped: options.jsonEnvelope,
|
|
161088
|
+
error: {
|
|
161089
|
+
code: "INTERNAL_ERROR",
|
|
161090
|
+
message,
|
|
161091
|
+
details: { cliCode: "drop-tags", verb: "drop-tags" }
|
|
161092
|
+
}
|
|
161093
|
+
}));
|
|
161094
|
+
context.setExitCode(1);
|
|
161095
|
+
return;
|
|
161096
|
+
}
|
|
161097
|
+
context.output.error(message);
|
|
161098
|
+
context.setExitCode(1);
|
|
161099
|
+
}
|
|
161100
|
+
});
|
|
161101
|
+
}
|
|
161102
|
+
|
|
160989
161103
|
// ../../packages/app/src/services/pr-review/index.ts
|
|
160990
161104
|
init_src();
|
|
160991
161105
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
@@ -161606,9 +161720,9 @@ function cmdStatus(rt, args) {
|
|
|
161606
161720
|
].join(`
|
|
161607
161721
|
`));
|
|
161608
161722
|
}
|
|
161609
|
-
var PR_REVIEW_HELP = `
|
|
161723
|
+
var PR_REVIEW_HELP = `pr-review spine \u2014 internal ops composed by the flat spur builder pr-status|pr-check|pr-open|pr-review verbs
|
|
161610
161724
|
|
|
161611
|
-
Usage:
|
|
161725
|
+
Usage: main([<op>, ...flags]) \u2014 not a CLI verb (the builder pr <op> alias was removed in 1290)
|
|
161612
161726
|
|
|
161613
161727
|
Subcommands:
|
|
161614
161728
|
preflight git/gh/repo checks; FAILs on detached HEAD or a dirty tree
|
|
@@ -161625,15 +161739,6 @@ Subcommands:
|
|
|
161625
161739
|
|
|
161626
161740
|
Global flags: --json (single JSON object) --status-file <path> (one-word verdict)
|
|
161627
161741
|
Exit codes: 0 ok \xB7 1 usage \xB7 2 hard failure \xB7 3 wait timeout`;
|
|
161628
|
-
function main(argv, sink, context = {}) {
|
|
161629
|
-
const previous = io;
|
|
161630
|
-
io = sink;
|
|
161631
|
-
try {
|
|
161632
|
-
return runSpine(argv, createRuntime(context));
|
|
161633
|
-
} finally {
|
|
161634
|
-
io = previous;
|
|
161635
|
-
}
|
|
161636
|
-
}
|
|
161637
161742
|
function runSpine(argv, rt) {
|
|
161638
161743
|
let args = null;
|
|
161639
161744
|
try {
|
|
@@ -161929,22 +162034,7 @@ function runPrVerb(verb, opts, sink, context = {}) {
|
|
|
161929
162034
|
}
|
|
161930
162035
|
|
|
161931
162036
|
// src/commands/builder/pr.ts
|
|
161932
|
-
|
|
161933
|
-
preflight: "pr-check",
|
|
161934
|
-
hygiene: "pr-check",
|
|
161935
|
-
push: "pr-open",
|
|
161936
|
-
ensure: "pr-open",
|
|
161937
|
-
"ensure-pr": "pr-open",
|
|
161938
|
-
request: "pr-review",
|
|
161939
|
-
wait: "pr-review",
|
|
161940
|
-
collect: "pr-status",
|
|
161941
|
-
status: "pr-status"
|
|
161942
|
-
};
|
|
161943
|
-
function prDeprecationLine(op) {
|
|
161944
|
-
const replacement = op === undefined ? undefined : PR_VERB_FOR_OP[op];
|
|
161945
|
-
return replacement !== undefined ? `spur builder pr ${op} is deprecated; use spur builder ${replacement}` : "spur builder pr is deprecated; use spur builder pr-status | pr-check | pr-open | pr-review";
|
|
161946
|
-
}
|
|
161947
|
-
function registerBuilderPrCommands(builder, context) {
|
|
162037
|
+
function registerBuilderPrCommand(builder, context) {
|
|
161948
162038
|
const io = () => ({
|
|
161949
162039
|
out: (line) => context.output.write(line),
|
|
161950
162040
|
err: (line) => context.output.error(line)
|
|
@@ -162003,7 +162093,7 @@ function registerBuilderPrCommands(builder, context) {
|
|
|
162003
162093
|
"returns PENDING. Exit codes: 0 ok \xB7 2 hard failure \xB7 3 wait timeout",
|
|
162004
162094
|
"(verdict PENDING \u2014 pending, never failure)."
|
|
162005
162095
|
].join(`
|
|
162006
|
-
`)).option(
|
|
162096
|
+
`)).option(...SHARED_OPTIONS.forceBuilderPr).option("--focus <text>", "extra review focus appended to the request").option("--no-wait", "post the request and return PENDING without polling").option("--timeout <s>", "wait budget in seconds (service default 600)").option("--interval <s>", "poll interval in seconds (service default 30)").option(...SHARED_OPTIONS.json).option("--status-file <path>", "write the one-word verdict (CLEAN | FINDINGS | PENDING | FAIL)").action((options) => {
|
|
162007
162097
|
run("review", {
|
|
162008
162098
|
...options.force === true ? { force: true } : {},
|
|
162009
162099
|
...options.focus !== undefined ? { focus: options.focus } : {},
|
|
@@ -162015,13 +162105,25 @@ function registerBuilderPrCommands(builder, context) {
|
|
|
162015
162105
|
});
|
|
162016
162106
|
});
|
|
162017
162107
|
}
|
|
162018
|
-
|
|
162019
|
-
|
|
162020
|
-
|
|
162021
|
-
|
|
162022
|
-
|
|
162023
|
-
|
|
162024
|
-
|
|
162108
|
+
|
|
162109
|
+
// src/commands/builder/publish.ts
|
|
162110
|
+
function registerBuilderPublishCommand(noun, context) {
|
|
162111
|
+
noun.command("publish").summary("resolve workspace/catalog ranges, build, and npm publish a package (CI)").description([
|
|
162112
|
+
"CI publish for npm Trusted Publishing (OIDC): rewrite workspace:/catalog: ranges in",
|
|
162113
|
+
"<dir>/package.json, run `bun run build:bundle`, `npm publish --access public`, and",
|
|
162114
|
+
"restore the manifest on every exit path."
|
|
162115
|
+
].join(`
|
|
162116
|
+
`)).option("--otp <code>", "npm one-time password (local publishes only)").argument("<dir>", "package directory, relative to the workspace root").action(async (dir, options) => {
|
|
162117
|
+
try {
|
|
162118
|
+
await publishPackage(dir, {
|
|
162119
|
+
repoRoot: context.cwd,
|
|
162120
|
+
output: context.output,
|
|
162121
|
+
...options.otp !== undefined ? { otp: options.otp } : {}
|
|
162122
|
+
});
|
|
162123
|
+
} catch (err) {
|
|
162124
|
+
context.output.error(err instanceof Error ? err.message : String(err));
|
|
162125
|
+
context.setExitCode(1);
|
|
162126
|
+
}
|
|
162025
162127
|
});
|
|
162026
162128
|
}
|
|
162027
162129
|
|
|
@@ -162515,7 +162617,7 @@ function registerBuilderShowBranchesCommand(builder, context) {
|
|
|
162515
162617
|
"Exit codes: 0 ok \xB7 1 usage (bad --format/--limit/--max-commits, unknown base ref,",
|
|
162516
162618
|
"--local with --remote) \xB7 2 git failure (not a repository, git missing)."
|
|
162517
162619
|
].join(`
|
|
162518
|
-
`)).option("--format <format>", "output format: markdown | json | text (default markdown)", "markdown").option(
|
|
162620
|
+
`)).option("--format <format>", "output format: markdown | json | text (default markdown)", "markdown").option(...SHARED_OPTIONS.jsonShowBranches).option("--base <ref>", "base ref to measure every branch against (default: origin/HEAD, else main)").option("--local", "only local branches").option("--remote", "only remote branches").option("--pr", "enrich rows with open pull-request state (one gh call; degrades with a warning)").option("--limit <n>", "maximum branch rows kept, most recent last-commit first (default 30)", "30").option("--max-commits <n>", "maximum commit nodes per branch line in the graph (default 3)", "3").option("--hide-contained", "drop contained branches from the graph instead of drawing their join").option("--theme <name>", "mermaid theme name for the graph init directive").action((options) => {
|
|
162519
162621
|
const format = resolveFormat(options.format, command.getOptionValueSource("format") === "cli", options.json === true, context);
|
|
162520
162622
|
if (format === null)
|
|
162521
162623
|
return;
|
|
@@ -162590,113 +162692,14 @@ function usageError(message, context) {
|
|
|
162590
162692
|
context.setExitCode(1);
|
|
162591
162693
|
}
|
|
162592
162694
|
|
|
162593
|
-
// src/commands/builder.ts
|
|
162695
|
+
// src/commands/builder/index.ts
|
|
162594
162696
|
function registerBuilderCommand(program, context) {
|
|
162595
162697
|
const noun = program.command("builder").summary("release plumbing: version bumps, tags, branch topology, and PR-review ops");
|
|
162596
|
-
noun
|
|
162597
|
-
|
|
162598
|
-
|
|
162599
|
-
" spur builder bump-ver 0.1.4 # every released package",
|
|
162600
|
-
" spur builder bump-ver spur 0.1.4 # one package by id",
|
|
162601
|
-
" spur builder bump-ver --check spur # pre-publish drift guard (read-only)",
|
|
162602
|
-
"--check fails (exit 1) when .claude-plugin/marketplace.json or a listed plugin.json",
|
|
162603
|
-
"drifted from the package version; target defaults to the aggregate package."
|
|
162604
|
-
].join(`
|
|
162605
|
-
`)).option("--check", "only verify marketplace/plugin versions match the package version (no writes)").option("--all", "bump every released package in one commit with per-package + aggregate tags").option("--push", "push the branch and release tag to origin").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).argument("[target]", "package id, or the version itself when bumping all").argument("[version]", "target semver version").action(async (target, version, options) => {
|
|
162606
|
-
const args = [target, version].filter((value) => value !== undefined);
|
|
162607
|
-
if (options.all === true)
|
|
162608
|
-
args.push("--all");
|
|
162609
|
-
if (options.push === true)
|
|
162610
|
-
args.push("--push");
|
|
162611
|
-
try {
|
|
162612
|
-
if (options.check === true) {
|
|
162613
|
-
if (version !== undefined)
|
|
162614
|
-
throw new Error("bump-ver --check takes only a package id");
|
|
162615
|
-
if (options.all === true || options.push === true)
|
|
162616
|
-
throw new Error("bump-ver --check rejects incompatible write flags (--push/--all)");
|
|
162617
|
-
const code = await checkVersionDrift(target, context.cwd, context.output, context.spurConfig);
|
|
162618
|
-
if (options.json === true) {
|
|
162619
|
-
context.output.write(toEnvelopeJson({ ok: code === 0, verb: "bump-ver", check: true, target: target ?? "aggregate" }, { enveloped: options.jsonEnvelope }));
|
|
162620
|
-
}
|
|
162621
|
-
if (code !== 0)
|
|
162622
|
-
context.setExitCode(code);
|
|
162623
|
-
return;
|
|
162624
|
-
}
|
|
162625
|
-
await bumpVer(args, context.cwd, context.output, context.spurConfig);
|
|
162626
|
-
if (options.json === true) {
|
|
162627
|
-
context.output.write(toEnvelopeJson({ ok: true, verb: "bump-ver", target: target ?? "all", version: version ?? "" }, { enveloped: options.jsonEnvelope }));
|
|
162628
|
-
}
|
|
162629
|
-
} catch (err) {
|
|
162630
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
162631
|
-
if (options.json === true) {
|
|
162632
|
-
context.output.write(toEnvelopeJson({ ok: false, verb: "bump-ver", error: message }, {
|
|
162633
|
-
enveloped: options.jsonEnvelope,
|
|
162634
|
-
error: {
|
|
162635
|
-
code: "INTERNAL_ERROR",
|
|
162636
|
-
message,
|
|
162637
|
-
details: { cliCode: "bump-ver", verb: "bump-ver" }
|
|
162638
|
-
}
|
|
162639
|
-
}));
|
|
162640
|
-
context.setExitCode(1);
|
|
162641
|
-
return;
|
|
162642
|
-
}
|
|
162643
|
-
context.output.error(message);
|
|
162644
|
-
context.setExitCode(1);
|
|
162645
|
-
}
|
|
162646
|
-
});
|
|
162647
|
-
noun.command("drop-tags").summary("delete release tags locally and optionally on origin").description([
|
|
162648
|
-
"Delete one package release tag (or every released tag plus the aggregate",
|
|
162649
|
-
"with --all / a bare version). Use --remote to also delete on origin."
|
|
162650
|
-
].join(`
|
|
162651
|
-
`)).option("--all", "drop every released tag plus the aggregate tag").option("--remote", "also delete the tag(s) on origin").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).argument("[target]", "package id, or the version itself when dropping all").argument("[version]", "semver version of the tags to drop").action(async (target, version, options) => {
|
|
162652
|
-
const args = [target, version].filter((value) => value !== undefined);
|
|
162653
|
-
if (options.all === true)
|
|
162654
|
-
args.push("--all");
|
|
162655
|
-
if (options.remote === true)
|
|
162656
|
-
args.push("--remote");
|
|
162657
|
-
try {
|
|
162658
|
-
await dropTags(args, context.cwd, context.output, context.spurConfig);
|
|
162659
|
-
if (options.json === true) {
|
|
162660
|
-
context.output.write(toEnvelopeJson({ ok: true, verb: "drop-tags", target: target ?? "all", version: version ?? "" }, { enveloped: options.jsonEnvelope }));
|
|
162661
|
-
}
|
|
162662
|
-
} catch (err) {
|
|
162663
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
162664
|
-
if (options.json === true) {
|
|
162665
|
-
context.output.write(toEnvelopeJson({ ok: false, verb: "drop-tags", error: message }, {
|
|
162666
|
-
enveloped: options.jsonEnvelope,
|
|
162667
|
-
error: {
|
|
162668
|
-
code: "INTERNAL_ERROR",
|
|
162669
|
-
message,
|
|
162670
|
-
details: { cliCode: "drop-tags", verb: "drop-tags" }
|
|
162671
|
-
}
|
|
162672
|
-
}));
|
|
162673
|
-
context.setExitCode(1);
|
|
162674
|
-
return;
|
|
162675
|
-
}
|
|
162676
|
-
context.output.error(message);
|
|
162677
|
-
context.setExitCode(1);
|
|
162678
|
-
}
|
|
162679
|
-
});
|
|
162680
|
-
noun.command("publish").summary("resolve workspace/catalog ranges, build, and npm publish a package (CI)").description([
|
|
162681
|
-
"CI publish for npm Trusted Publishing (OIDC): rewrite workspace:/catalog: ranges in",
|
|
162682
|
-
"<dir>/package.json, run `bun run build:bundle`, `npm publish --access public`, and",
|
|
162683
|
-
"restore the manifest on every exit path."
|
|
162684
|
-
].join(`
|
|
162685
|
-
`)).option("--otp <code>", "npm one-time password (local publishes only)").argument("<dir>", "package directory, relative to the workspace root").action(async (dir, options) => {
|
|
162686
|
-
try {
|
|
162687
|
-
await publishPackage(dir, {
|
|
162688
|
-
repoRoot: context.cwd,
|
|
162689
|
-
output: context.output,
|
|
162690
|
-
...options.otp !== undefined ? { otp: options.otp } : {}
|
|
162691
|
-
});
|
|
162692
|
-
} catch (err) {
|
|
162693
|
-
context.output.error(err instanceof Error ? err.message : String(err));
|
|
162694
|
-
context.setExitCode(1);
|
|
162695
|
-
}
|
|
162696
|
-
});
|
|
162698
|
+
registerBuilderBumpVerCommand(noun, context);
|
|
162699
|
+
registerBuilderDropTagsCommand(noun, context);
|
|
162700
|
+
registerBuilderPublishCommand(noun, context);
|
|
162697
162701
|
registerBuilderShowBranchesCommand(noun, context);
|
|
162698
|
-
|
|
162699
|
-
registerBuilderPrAliasCommand(noun, context);
|
|
162702
|
+
registerBuilderPrCommand(noun, context);
|
|
162700
162703
|
}
|
|
162701
162704
|
|
|
162702
162705
|
// src/commands/decision.ts
|
|
@@ -163130,161 +163133,35 @@ function fail2(context, options, error) {
|
|
|
163130
163133
|
context.setExitCode(1);
|
|
163131
163134
|
}
|
|
163132
163135
|
|
|
163133
|
-
// src/commands/feature.ts
|
|
163136
|
+
// src/commands/feature/advance.ts
|
|
163134
163137
|
init_src4();
|
|
163135
|
-
init_src2();
|
|
163136
163138
|
|
|
163137
|
-
// src/commands/feature/
|
|
163138
|
-
|
|
163139
|
-
|
|
163140
|
-
|
|
163141
|
-
"-idea-ready.json), applies dependencies, refreshes the roster, checks each task and writes",
|
|
163142
|
-
".spur/run/<runId>-idea-handoff.md with one recommended next command.",
|
|
163143
|
-
"Exit: 0 success; 1 failure (missing ids or inputs, a mapping or CLI error).",
|
|
163144
|
-
"Flags fall back to the workflow env names: featureId, __runId, spurBin."
|
|
163145
|
-
].join(`
|
|
163146
|
-
`)).argument("[id]", "Feature ID (env: featureId)").option("--run-id <id>", "idea-pipeline run id (env: __runId)").option("--spur-bin <cmd>", "spur invocation for the nested task/feature calls (env: spurBin; default: this spur)").action(async (id, options) => {
|
|
163147
|
-
const runId = options.runId ?? context.env.__runId;
|
|
163148
|
-
if (runId && !acceptArtifactId(runId, "run id", context))
|
|
163149
|
-
return;
|
|
163150
|
-
await Promise.resolve().then(() => init_src4());
|
|
163151
|
-
const env = context.env;
|
|
163152
|
-
const outcome = await runIdeaHandoffCli({
|
|
163153
|
-
__runId: options.runId ?? env.__runId,
|
|
163154
|
-
...options.spurBin === undefined && env.spurBin === undefined ? { spurArgv: resolveSpurArgv() } : {},
|
|
163155
|
-
featureId: id ?? env.featureId,
|
|
163156
|
-
spurBin: options.spurBin ?? env.spurBin ?? resolveSpurBin()
|
|
163157
|
-
});
|
|
163158
|
-
context.setExitCode(outcome.exitCode);
|
|
163159
|
-
});
|
|
163139
|
+
// src/commands/feature/shared.ts
|
|
163140
|
+
init_src4();
|
|
163141
|
+
async function makeReceiptRunPort(context) {
|
|
163142
|
+
return createFeatureReceiptRunPort(await context.getDb(), context.fs);
|
|
163160
163143
|
}
|
|
163161
|
-
|
|
163162
|
-
|
|
163163
|
-
|
|
163164
|
-
|
|
163165
|
-
|
|
163166
|
-
|
|
163167
|
-
|
|
163168
|
-
|
|
163169
|
-
|
|
163170
|
-
|
|
163171
|
-
|
|
163172
|
-
|
|
163173
|
-
|
|
163174
|
-
|
|
163175
|
-
|
|
163176
|
-
|
|
163177
|
-
workdir: context.cwd,
|
|
163178
|
-
featureId,
|
|
163179
|
-
runId: options.runId || env.__runId || "",
|
|
163180
|
-
cmdOverride: options.cmd || env.verificationCmd || ""
|
|
163181
|
-
});
|
|
163182
|
-
context.output.write(`feature verify: ${featureId} ${result.message}`);
|
|
163183
|
-
} catch (err) {
|
|
163184
|
-
context.output.error(`feature verify: ${String(err)}`);
|
|
163185
|
-
context.setExitCode(1);
|
|
163186
|
-
}
|
|
163144
|
+
async function makeService2(context, folderOverride) {
|
|
163145
|
+
const resolved = await resolvePlanningFolders(context.fs);
|
|
163146
|
+
const featuresDir = folderOverride ?? context.fs.resolve(resolved.featuresDir);
|
|
163147
|
+
const tasksDir = context.fs.resolve(resolved.tasksDir);
|
|
163148
|
+
const lifecycle = makeLifecycleAdapter(context, FEATURE_LIFECYCLE_PROFILE);
|
|
163149
|
+
const writeService = new PlanningWriteService({
|
|
163150
|
+
fs: context.fs,
|
|
163151
|
+
...lifecycle ? { lifecycle } : {},
|
|
163152
|
+
emitter: makePlanningEmitter(context)
|
|
163153
|
+
});
|
|
163154
|
+
return new FeatureService({
|
|
163155
|
+
fs: context.fs,
|
|
163156
|
+
writeService,
|
|
163157
|
+
featuresDir,
|
|
163158
|
+
tasksDir,
|
|
163159
|
+
foldersConfig: resolved.foldersConfig
|
|
163187
163160
|
});
|
|
163188
163161
|
}
|
|
163189
163162
|
|
|
163190
|
-
// src/commands/feature.ts
|
|
163191
|
-
function
|
|
163192
|
-
const feature = program.command("feature").summary("manage features (hierarchical IDs)");
|
|
163193
|
-
feature.command("create").summary("Create a feature; allocates a hierarchical ID (DD-14) under the create-lock.").argument("<name>", "Feature name").option("--parent <id>", "Parent feature ID (child gets the next free digit 1-9)").option(...SHARED_OPTIONS.folderFeatures).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (name, options) => {
|
|
163194
|
-
const svc = await makeService2(context, options.folder);
|
|
163195
|
-
try {
|
|
163196
|
-
const result = await svc.create(name, options.parent);
|
|
163197
|
-
if (options.json) {
|
|
163198
|
-
context.output.write(toEnvelopeJson(result, { enveloped: options.jsonEnvelope }));
|
|
163199
|
-
} else {
|
|
163200
|
-
context.output.write(`Created feature ${result.ref.id}: ${result.ref.filePath}`);
|
|
163201
|
-
}
|
|
163202
|
-
} catch (err) {
|
|
163203
|
-
writeJsonError(context.output, options, String(err));
|
|
163204
|
-
context.setExitCode(1);
|
|
163205
|
-
}
|
|
163206
|
-
});
|
|
163207
|
-
feature.command("show").alias("get").summary("Show a feature by ID.").argument("<id>", "Feature ID").option(...SHARED_OPTIONS.folderFeatures).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (id, options) => {
|
|
163208
|
-
const svc = await makeService2(context, options.folder);
|
|
163209
|
-
try {
|
|
163210
|
-
const result = await svc.show(id);
|
|
163211
|
-
if (result === null) {
|
|
163212
|
-
writeJsonError(context.output, options, `Feature ${id} not found`, "NOT_FOUND");
|
|
163213
|
-
context.setExitCode(1);
|
|
163214
|
-
return;
|
|
163215
|
-
}
|
|
163216
|
-
if (options.json) {
|
|
163217
|
-
const { content, ...rest } = result;
|
|
163218
|
-
context.output.write(toEnvelopeJson({ ...rest, content }, { enveloped: options.jsonEnvelope }));
|
|
163219
|
-
} else {
|
|
163220
|
-
context.output.write(result.content);
|
|
163221
|
-
}
|
|
163222
|
-
} catch (err) {
|
|
163223
|
-
writeJsonError(context.output, options, String(err));
|
|
163224
|
-
context.setExitCode(1);
|
|
163225
|
-
}
|
|
163226
|
-
});
|
|
163227
|
-
feature.command("update").summary("Update a feature status, scalar frontmatter field, or section body.").argument("<id>", "Feature ID").argument("[status]", "New lifecycle status").addHelpText("after", [
|
|
163228
|
-
"Section names are validated against the closed-world canonical set; a rejected",
|
|
163229
|
-
"`--section` costs a failed write. List valid names first:",
|
|
163230
|
-
"`spur task sections <wbs> list`."
|
|
163231
|
-
].join(`
|
|
163232
|
-
`)).option("--field <key>", "Frontmatter field to set (e.g. priority)").option("--value <value>", "New value for --field").option(...SHARED_OPTIONS.section).option(...SHARED_OPTIONS.fromFile).option(...SHARED_OPTIONS.folderFeatures).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (id, status, options) => {
|
|
163233
|
-
const svc = await makeService2(context, options.folder);
|
|
163234
|
-
try {
|
|
163235
|
-
let result;
|
|
163236
|
-
if (options.section !== undefined) {
|
|
163237
|
-
if (options.fromFile === undefined) {
|
|
163238
|
-
writeJsonError(context.output, options, "--from-file is required with --section", "VALIDATION_FAILED");
|
|
163239
|
-
context.setExitCode(2);
|
|
163240
|
-
return;
|
|
163241
|
-
}
|
|
163242
|
-
result = await svc.updateSection(id, options.section, options.fromFile);
|
|
163243
|
-
if (!options.json) {
|
|
163244
|
-
for (const warning of result.warnings ?? []) {
|
|
163245
|
-
context.output.error(warning);
|
|
163246
|
-
}
|
|
163247
|
-
context.output.write(`Updated section '${options.section}' in feature ${result.ref.id}`);
|
|
163248
|
-
}
|
|
163249
|
-
} else if (options.fromFile !== undefined) {
|
|
163250
|
-
writeJsonError(context.output, options, "--section is required with --from-file", "VALIDATION_FAILED");
|
|
163251
|
-
context.setExitCode(2);
|
|
163252
|
-
return;
|
|
163253
|
-
}
|
|
163254
|
-
if (options.field !== undefined) {
|
|
163255
|
-
if (options.value === undefined) {
|
|
163256
|
-
writeJsonError(context.output, options, "--value is required with --field", "VALIDATION_FAILED");
|
|
163257
|
-
context.setExitCode(2);
|
|
163258
|
-
return;
|
|
163259
|
-
}
|
|
163260
|
-
result = await svc.update(id, options.field, options.value);
|
|
163261
|
-
if (!options.json) {
|
|
163262
|
-
context.output.write(`Updated ${options.field} on feature ${result.ref.id}`);
|
|
163263
|
-
}
|
|
163264
|
-
} else if (options.value !== undefined) {
|
|
163265
|
-
writeJsonError(context.output, options, "--field is required with --value", "VALIDATION_FAILED");
|
|
163266
|
-
context.setExitCode(2);
|
|
163267
|
-
return;
|
|
163268
|
-
}
|
|
163269
|
-
if (status !== undefined) {
|
|
163270
|
-
result = await svc.transition(id, status);
|
|
163271
|
-
if (!options.json) {
|
|
163272
|
-
context.output.write(`${result.ref.id}: ${result.fromStatus} \u2192 ${result.toStatus}`);
|
|
163273
|
-
}
|
|
163274
|
-
}
|
|
163275
|
-
if (result === undefined) {
|
|
163276
|
-
writeJsonError(context.output, options, "Either <status>, --field/--value, or --section/--from-file is required", "VALIDATION_FAILED");
|
|
163277
|
-
context.setExitCode(2);
|
|
163278
|
-
return;
|
|
163279
|
-
}
|
|
163280
|
-
if (options.json) {
|
|
163281
|
-
context.output.write(toEnvelopeJson(result, { enveloped: options.jsonEnvelope }));
|
|
163282
|
-
}
|
|
163283
|
-
} catch (err) {
|
|
163284
|
-
writeJsonError(context.output, options, String(err));
|
|
163285
|
-
context.setExitCode(1);
|
|
163286
|
-
}
|
|
163287
|
-
});
|
|
163163
|
+
// src/commands/feature/advance.ts
|
|
163164
|
+
function registerFeatureAdvanceCommand(feature, context) {
|
|
163288
163165
|
feature.command("advance").summary("Walk a feature through the legal forward lifecycle path.").argument("<id>", "Feature ID to advance").option("--to <status>", "Target status (default: 'done')").option(...SHARED_OPTIONS.folderFeatures).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (id, options) => {
|
|
163289
163166
|
const svc = await makeService2(context, options.folder);
|
|
163290
163167
|
const target = options.to ?? "done";
|
|
@@ -163349,6 +163226,148 @@ function registerFeatureCommand(program, context) {
|
|
|
163349
163226
|
context.setExitCode(1);
|
|
163350
163227
|
}
|
|
163351
163228
|
});
|
|
163229
|
+
}
|
|
163230
|
+
async function assertFeatureCheckPass(context, id, folderOverride, strict, asStatus) {
|
|
163231
|
+
const resolved = await resolvePlanningFolders(context.fs);
|
|
163232
|
+
const featuresDir = folderOverride ?? context.fs.resolve(resolved.featuresDir);
|
|
163233
|
+
const tasksDir = context.fs.resolve(resolved.tasksDir);
|
|
163234
|
+
const tasksDirs = Object.keys(resolved.foldersConfig.folders).map((p) => context.fs.resolve(p));
|
|
163235
|
+
if (!tasksDirs.includes(tasksDir))
|
|
163236
|
+
tasksDirs.unshift(tasksDir);
|
|
163237
|
+
const entries = await context.fs.readDir(featuresDir);
|
|
163238
|
+
const fileName = entries.find((name) => isFeatureFile(name, id));
|
|
163239
|
+
if (fileName === undefined) {
|
|
163240
|
+
throw new Error(`Feature ${id} not found`);
|
|
163241
|
+
}
|
|
163242
|
+
const result = await new FeatureCheckService(context.fs).check(`${featuresDir}/${fileName}`, id, {
|
|
163243
|
+
strict,
|
|
163244
|
+
featuresDir,
|
|
163245
|
+
tasksDir,
|
|
163246
|
+
tasksDirs,
|
|
163247
|
+
runDir: context.fs.resolve(".spur/run"),
|
|
163248
|
+
receiptRunPort: await makeReceiptRunPort(context),
|
|
163249
|
+
asStatus
|
|
163250
|
+
});
|
|
163251
|
+
if (!result.pass) {
|
|
163252
|
+
const details = result.findings.map((f) => `${f.layer} ${f.section}: ${f.message}`).join("; ");
|
|
163253
|
+
throw new Error(`Feature ${id} check failed before advance${strict ? " (strict)" : ""}: ${details}`);
|
|
163254
|
+
}
|
|
163255
|
+
}
|
|
163256
|
+
|
|
163257
|
+
// src/commands/feature/check.ts
|
|
163258
|
+
init_src4();
|
|
163259
|
+
function registerFeatureCheckCommand(feature, context) {
|
|
163260
|
+
feature.command("check").summary("Validate feature file(s) through the four-layer check (design \xA73).").argument("[id]", "Feature ID (validates all features in the folder when omitted)").option(...SHARED_OPTIONS.strictFeature).option(...SHARED_OPTIONS.asFeature0418).option(...SHARED_OPTIONS.folderFeatures).option("--fix", "repair structural findings in place (heading presence/level/order, R-item checkboxes)").option("--inventory <report>", "Cross-check the report requirement inventory against the AC covers lines (errors on uncovered items)").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (id, options) => {
|
|
163261
|
+
const resolved = await resolvePlanningFolders(context.fs);
|
|
163262
|
+
const featuresDir = options.folder ?? context.fs.resolve(resolved.featuresDir);
|
|
163263
|
+
const tasksDir = context.fs.resolve(resolved.tasksDir);
|
|
163264
|
+
const tasksDirs = Object.keys(resolved.foldersConfig.folders).map((p) => context.fs.resolve(p));
|
|
163265
|
+
if (!tasksDirs.includes(tasksDir))
|
|
163266
|
+
tasksDirs.unshift(tasksDir);
|
|
163267
|
+
const svc = new FeatureCheckService(context.fs);
|
|
163268
|
+
const json = options.json === true;
|
|
163269
|
+
const strict = options.strict === true;
|
|
163270
|
+
const inventoryText = typeof options.inventory === "string" ? await Promise.resolve(context.fs.readFile(context.fs.resolve(options.inventory))).catch(() => "") : undefined;
|
|
163271
|
+
try {
|
|
163272
|
+
const entries = await context.fs.readDir(featuresDir);
|
|
163273
|
+
const ids = id ? [id] : entries.map((n) => n.match(/^([A-Z][1-9]*)_.+\.md$/)?.[1]).filter((n) => n !== undefined);
|
|
163274
|
+
const results = [];
|
|
163275
|
+
for (const fid of ids) {
|
|
163276
|
+
const fileName = entries.find((n) => isFeatureFile(n, fid));
|
|
163277
|
+
if (!fileName) {
|
|
163278
|
+
writeJsonError(context.output, options, `Feature ${fid} not found`, "NOT_FOUND");
|
|
163279
|
+
context.setExitCode(1);
|
|
163280
|
+
return;
|
|
163281
|
+
}
|
|
163282
|
+
const result = await svc.check(`${featuresDir}/${fileName}`, fid, {
|
|
163283
|
+
strict,
|
|
163284
|
+
featuresDir,
|
|
163285
|
+
tasksDir,
|
|
163286
|
+
tasksDirs,
|
|
163287
|
+
runDir: context.fs.resolve(".spur/run"),
|
|
163288
|
+
receiptRunPort: await makeReceiptRunPort(context),
|
|
163289
|
+
severityOverrides: resolved.severityOverrides,
|
|
163290
|
+
asStatus: options.as,
|
|
163291
|
+
fix: options.fix === true,
|
|
163292
|
+
inventory: inventoryText,
|
|
163293
|
+
transitionPort: options.fix === true ? async (targetId, to) => {
|
|
163294
|
+
const featureSvc = await makeService2(context, options.folder);
|
|
163295
|
+
return featureSvc.transition(targetId, to);
|
|
163296
|
+
} : undefined
|
|
163297
|
+
});
|
|
163298
|
+
results.push(result);
|
|
163299
|
+
if (!json) {
|
|
163300
|
+
if (result.repairs !== undefined && result.repairs.length > 0) {
|
|
163301
|
+
for (const r of result.repairs) {
|
|
163302
|
+
context.output.write(` [FIX] ${r.kind} ${r.section}: ${r.detail}`);
|
|
163303
|
+
}
|
|
163304
|
+
}
|
|
163305
|
+
context.output.write(`
|
|
163306
|
+
${result.id} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
163307
|
+
for (const f of result.findings) {
|
|
163308
|
+
const tag = f.severity === "error" ? "ERR" : "WARN";
|
|
163309
|
+
context.output.write(` [${tag}] ${f.layer} ${f.section}: ${f.message}`);
|
|
163310
|
+
}
|
|
163311
|
+
}
|
|
163312
|
+
}
|
|
163313
|
+
if (json) {
|
|
163314
|
+
context.output.write(toEnvelopeJson(results, { enveloped: options.jsonEnvelope, kind: "list" }));
|
|
163315
|
+
}
|
|
163316
|
+
if (results.some((r) => !r.pass))
|
|
163317
|
+
context.setExitCode(1);
|
|
163318
|
+
} catch (err) {
|
|
163319
|
+
writeJsonError(context.output, options, String(err));
|
|
163320
|
+
context.setExitCode(1);
|
|
163321
|
+
}
|
|
163322
|
+
});
|
|
163323
|
+
}
|
|
163324
|
+
|
|
163325
|
+
// src/commands/feature/create.ts
|
|
163326
|
+
function registerFeatureCreateCommand(feature, context) {
|
|
163327
|
+
feature.command("create").summary("Create a feature; allocates a hierarchical ID (DD-14) under the create-lock.").argument("<name>", "Feature name").option("--parent <id>", "Parent feature ID (child gets the next free digit 1-9)").option(...SHARED_OPTIONS.folderFeatures).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (name, options) => {
|
|
163328
|
+
const svc = await makeService2(context, options.folder);
|
|
163329
|
+
try {
|
|
163330
|
+
const result = await svc.create(name, options.parent);
|
|
163331
|
+
if (options.json) {
|
|
163332
|
+
context.output.write(toEnvelopeJson(result, { enveloped: options.jsonEnvelope }));
|
|
163333
|
+
} else {
|
|
163334
|
+
context.output.write(`Created feature ${result.ref.id}: ${result.ref.filePath}`);
|
|
163335
|
+
}
|
|
163336
|
+
} catch (err) {
|
|
163337
|
+
writeJsonError(context.output, options, String(err));
|
|
163338
|
+
context.setExitCode(1);
|
|
163339
|
+
}
|
|
163340
|
+
});
|
|
163341
|
+
}
|
|
163342
|
+
|
|
163343
|
+
// src/commands/feature/handoff.ts
|
|
163344
|
+
function registerFeatureHandoffCommand(feature, context) {
|
|
163345
|
+
feature.command("handoff").summary("Finalize the idea pipeline: task ordering, roster refresh and the handoff report.").description([
|
|
163346
|
+
"Reads .spur/run/<runId>-idea-{task-batch,batch-create-result,task-order}.json (and the optional",
|
|
163347
|
+
"-idea-ready.json), applies dependencies, refreshes the roster, checks each task and writes",
|
|
163348
|
+
".spur/run/<runId>-idea-handoff.md with one recommended next command.",
|
|
163349
|
+
"Exit: 0 success; 1 failure (missing ids or inputs, a mapping or CLI error).",
|
|
163350
|
+
"Flags fall back to the workflow env names: featureId, __runId, spurBin."
|
|
163351
|
+
].join(`
|
|
163352
|
+
`)).argument("[id]", "Feature ID (env: featureId)").option("--run-id <id>", "idea-pipeline run id (env: __runId)").option("--spur-bin <cmd>", "spur invocation for the nested task/feature calls (env: spurBin; default: this spur)").action(async (id, options) => {
|
|
163353
|
+
const runId = options.runId ?? context.env.__runId;
|
|
163354
|
+
if (runId && !acceptArtifactId(runId, "run id", context))
|
|
163355
|
+
return;
|
|
163356
|
+
await Promise.resolve().then(() => init_src4());
|
|
163357
|
+
const env = context.env;
|
|
163358
|
+
const outcome = await runIdeaHandoffCli({
|
|
163359
|
+
__runId: options.runId ?? env.__runId,
|
|
163360
|
+
...options.spurBin === undefined && env.spurBin === undefined ? { spurArgv: resolveSpurArgv() } : {},
|
|
163361
|
+
featureId: id ?? env.featureId,
|
|
163362
|
+
spurBin: options.spurBin ?? env.spurBin ?? resolveSpurBin()
|
|
163363
|
+
});
|
|
163364
|
+
context.setExitCode(outcome.exitCode);
|
|
163365
|
+
});
|
|
163366
|
+
}
|
|
163367
|
+
|
|
163368
|
+
// src/commands/feature/list.ts
|
|
163369
|
+
init_src2();
|
|
163370
|
+
function registerFeatureListCommand(feature, context) {
|
|
163352
163371
|
feature.command("list").summary("List features with optional status/priority filters.").option(...SHARED_OPTIONS.statusFilter).option(...SHARED_OPTIONS.priorityFilter).option(...SHARED_OPTIONS.folderFeatures).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options) => {
|
|
163353
163372
|
const svc = await makeService2(context, options.folder);
|
|
163354
163373
|
try {
|
|
@@ -163375,6 +163394,10 @@ function registerFeatureCommand(program, context) {
|
|
|
163375
163394
|
context.setExitCode(1);
|
|
163376
163395
|
}
|
|
163377
163396
|
});
|
|
163397
|
+
}
|
|
163398
|
+
|
|
163399
|
+
// src/commands/feature/move.ts
|
|
163400
|
+
function registerFeatureMoveCommand(feature, context) {
|
|
163378
163401
|
feature.command("move").summary("Move a feature to a new parent \u2014 cascade rename of the subtree (DD-14).").argument("<id>", "Feature ID to move").option("--parent <id>", "New parent feature ID (omit to move to a top-level group)").option(...SHARED_OPTIONS.dryRunFeatureMap).option(...SHARED_OPTIONS.folderFeatures).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (id, options) => {
|
|
163379
163402
|
const svc = await makeService2(context, options.folder);
|
|
163380
163403
|
try {
|
|
@@ -163395,6 +163418,11 @@ function registerFeatureCommand(program, context) {
|
|
|
163395
163418
|
context.setExitCode(1);
|
|
163396
163419
|
}
|
|
163397
163420
|
});
|
|
163421
|
+
}
|
|
163422
|
+
|
|
163423
|
+
// src/commands/feature/refresh.ts
|
|
163424
|
+
init_src4();
|
|
163425
|
+
function registerFeatureRefreshCommand(feature, context) {
|
|
163398
163426
|
feature.command("refresh").summary("Rebuild INDEX.md and each feature ## Tasks table from task edges (docs only \u2014 no status change).").description([
|
|
163399
163427
|
"Regenerate derived views so feature markdown matches the task graph:",
|
|
163400
163428
|
" \u2022 docs/features/INDEX.md (ID-encoded tree)",
|
|
@@ -163440,69 +163468,34 @@ function registerFeatureCommand(program, context) {
|
|
|
163440
163468
|
context.setExitCode(1);
|
|
163441
163469
|
}
|
|
163442
163470
|
});
|
|
163443
|
-
|
|
163444
|
-
|
|
163445
|
-
|
|
163446
|
-
|
|
163447
|
-
|
|
163448
|
-
|
|
163449
|
-
tasksDirs.unshift(tasksDir);
|
|
163450
|
-
const svc = new FeatureCheckService(context.fs);
|
|
163451
|
-
const json = options.json === true;
|
|
163452
|
-
const strict = options.strict === true;
|
|
163453
|
-
const inventoryText = typeof options.inventory === "string" ? await Promise.resolve(context.fs.readFile(context.fs.resolve(options.inventory))).catch(() => "") : undefined;
|
|
163471
|
+
}
|
|
163472
|
+
|
|
163473
|
+
// src/commands/feature/show.ts
|
|
163474
|
+
function registerFeatureShowCommand(feature, context) {
|
|
163475
|
+
feature.command("show").alias("get").summary("Show a feature by ID.").argument("<id>", "Feature ID").option(...SHARED_OPTIONS.folderFeatures).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (id, options) => {
|
|
163476
|
+
const svc = await makeService2(context, options.folder);
|
|
163454
163477
|
try {
|
|
163455
|
-
const
|
|
163456
|
-
|
|
163457
|
-
|
|
163458
|
-
|
|
163459
|
-
|
|
163460
|
-
if (!fileName) {
|
|
163461
|
-
writeJsonError(context.output, options, `Feature ${fid} not found`, "NOT_FOUND");
|
|
163462
|
-
context.setExitCode(1);
|
|
163463
|
-
return;
|
|
163464
|
-
}
|
|
163465
|
-
const result = await svc.check(`${featuresDir}/${fileName}`, fid, {
|
|
163466
|
-
strict,
|
|
163467
|
-
featuresDir,
|
|
163468
|
-
tasksDir,
|
|
163469
|
-
tasksDirs,
|
|
163470
|
-
runDir: context.fs.resolve(".spur/run"),
|
|
163471
|
-
receiptRunPort: await makeReceiptRunPort(context),
|
|
163472
|
-
severityOverrides: resolved.severityOverrides,
|
|
163473
|
-
asStatus: options.as,
|
|
163474
|
-
fix: options.fix === true,
|
|
163475
|
-
inventory: inventoryText,
|
|
163476
|
-
transitionPort: options.fix === true ? async (targetId, to) => {
|
|
163477
|
-
const featureSvc = await makeService2(context, options.folder);
|
|
163478
|
-
return featureSvc.transition(targetId, to);
|
|
163479
|
-
} : undefined
|
|
163480
|
-
});
|
|
163481
|
-
results.push(result);
|
|
163482
|
-
if (!json) {
|
|
163483
|
-
if (result.repairs !== undefined && result.repairs.length > 0) {
|
|
163484
|
-
for (const r of result.repairs) {
|
|
163485
|
-
context.output.write(` [FIX] ${r.kind} ${r.section}: ${r.detail}`);
|
|
163486
|
-
}
|
|
163487
|
-
}
|
|
163488
|
-
context.output.write(`
|
|
163489
|
-
${result.id} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
163490
|
-
for (const f of result.findings) {
|
|
163491
|
-
const tag = f.severity === "error" ? "ERR" : "WARN";
|
|
163492
|
-
context.output.write(` [${tag}] ${f.layer} ${f.section}: ${f.message}`);
|
|
163493
|
-
}
|
|
163494
|
-
}
|
|
163478
|
+
const result = await svc.show(id);
|
|
163479
|
+
if (result === null) {
|
|
163480
|
+
writeJsonError(context.output, options, `Feature ${id} not found`, "NOT_FOUND");
|
|
163481
|
+
context.setExitCode(1);
|
|
163482
|
+
return;
|
|
163495
163483
|
}
|
|
163496
|
-
if (json) {
|
|
163497
|
-
|
|
163484
|
+
if (options.json) {
|
|
163485
|
+
const { content, ...rest } = result;
|
|
163486
|
+
context.output.write(toEnvelopeJson({ ...rest, content }, { enveloped: options.jsonEnvelope }));
|
|
163487
|
+
} else {
|
|
163488
|
+
context.output.write(result.content);
|
|
163498
163489
|
}
|
|
163499
|
-
if (results.some((r) => !r.pass))
|
|
163500
|
-
context.setExitCode(1);
|
|
163501
163490
|
} catch (err) {
|
|
163502
163491
|
writeJsonError(context.output, options, String(err));
|
|
163503
163492
|
context.setExitCode(1);
|
|
163504
163493
|
}
|
|
163505
163494
|
});
|
|
163495
|
+
}
|
|
163496
|
+
|
|
163497
|
+
// src/commands/feature/sync.ts
|
|
163498
|
+
function registerFeatureSyncCommand(feature, context) {
|
|
163506
163499
|
feature.command("sync").summary("Align feature lifecycle status with linked task states (status only \u2014 runs transition guards).").description([
|
|
163507
163500
|
"Derive a feature status proposal from linked task statuses and apply legal hops",
|
|
163508
163501
|
"(e.g. all tasks done \u2192 advance toward done; reopened work \u2192 reopen the feature).",
|
|
@@ -163561,163 +163554,134 @@ ${result.id} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
163561
163554
|
context.setExitCode(1);
|
|
163562
163555
|
}
|
|
163563
163556
|
});
|
|
163564
|
-
registerFeatureVerifyCommand(feature, context);
|
|
163565
|
-
registerFeatureHandoffCommand(feature, context);
|
|
163566
|
-
}
|
|
163567
|
-
async function makeReceiptRunPort(context) {
|
|
163568
|
-
return createFeatureReceiptRunPort(await context.getDb(), context.fs);
|
|
163569
163557
|
}
|
|
163570
|
-
|
|
163571
|
-
|
|
163572
|
-
|
|
163573
|
-
|
|
163574
|
-
|
|
163575
|
-
|
|
163576
|
-
|
|
163577
|
-
|
|
163578
|
-
|
|
163579
|
-
|
|
163580
|
-
|
|
163581
|
-
|
|
163582
|
-
|
|
163583
|
-
|
|
163584
|
-
|
|
163585
|
-
|
|
163558
|
+
|
|
163559
|
+
// src/commands/feature/update.ts
|
|
163560
|
+
function registerFeatureUpdateCommand(feature, context) {
|
|
163561
|
+
feature.command("update").summary("Update a feature status, scalar frontmatter field, or section body.").argument("<id>", "Feature ID").argument("[status]", "New lifecycle status").addHelpText("after", [
|
|
163562
|
+
"Section names are validated against the closed-world canonical set; a rejected",
|
|
163563
|
+
"`--section` costs a failed write. List valid names first:",
|
|
163564
|
+
"`spur task sections <wbs> list`."
|
|
163565
|
+
].join(`
|
|
163566
|
+
`)).option("--field <key>", "Frontmatter field to set (e.g. priority)").option("--value <value>", "New value for --field").option(...SHARED_OPTIONS.section).option(...SHARED_OPTIONS.fromFile).option(...SHARED_OPTIONS.folderFeatures).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (id, status, options) => {
|
|
163567
|
+
const svc = await makeService2(context, options.folder);
|
|
163568
|
+
try {
|
|
163569
|
+
let result;
|
|
163570
|
+
if (options.section !== undefined) {
|
|
163571
|
+
if (options.fromFile === undefined) {
|
|
163572
|
+
writeJsonError(context.output, options, "--from-file is required with --section", "VALIDATION_FAILED");
|
|
163573
|
+
context.setExitCode(2);
|
|
163574
|
+
return;
|
|
163575
|
+
}
|
|
163576
|
+
result = await svc.updateSection(id, options.section, options.fromFile);
|
|
163577
|
+
if (!options.json) {
|
|
163578
|
+
for (const warning of result.warnings ?? []) {
|
|
163579
|
+
context.output.error(warning);
|
|
163580
|
+
}
|
|
163581
|
+
context.output.write(`Updated section '${options.section}' in feature ${result.ref.id}`);
|
|
163582
|
+
}
|
|
163583
|
+
} else if (options.fromFile !== undefined) {
|
|
163584
|
+
writeJsonError(context.output, options, "--section is required with --from-file", "VALIDATION_FAILED");
|
|
163585
|
+
context.setExitCode(2);
|
|
163586
|
+
return;
|
|
163587
|
+
}
|
|
163588
|
+
if (options.field !== undefined) {
|
|
163589
|
+
if (options.value === undefined) {
|
|
163590
|
+
writeJsonError(context.output, options, "--value is required with --field", "VALIDATION_FAILED");
|
|
163591
|
+
context.setExitCode(2);
|
|
163592
|
+
return;
|
|
163593
|
+
}
|
|
163594
|
+
result = await svc.update(id, options.field, options.value);
|
|
163595
|
+
if (!options.json) {
|
|
163596
|
+
context.output.write(`Updated ${options.field} on feature ${result.ref.id}`);
|
|
163597
|
+
}
|
|
163598
|
+
} else if (options.value !== undefined) {
|
|
163599
|
+
writeJsonError(context.output, options, "--field is required with --value", "VALIDATION_FAILED");
|
|
163600
|
+
context.setExitCode(2);
|
|
163601
|
+
return;
|
|
163602
|
+
}
|
|
163603
|
+
if (status !== undefined) {
|
|
163604
|
+
result = await svc.transition(id, status);
|
|
163605
|
+
if (!options.json) {
|
|
163606
|
+
context.output.write(`${result.ref.id}: ${result.fromStatus} \u2192 ${result.toStatus}`);
|
|
163607
|
+
}
|
|
163608
|
+
}
|
|
163609
|
+
if (result === undefined) {
|
|
163610
|
+
writeJsonError(context.output, options, "Either <status>, --field/--value, or --section/--from-file is required", "VALIDATION_FAILED");
|
|
163611
|
+
context.setExitCode(2);
|
|
163612
|
+
return;
|
|
163613
|
+
}
|
|
163614
|
+
if (options.json) {
|
|
163615
|
+
context.output.write(toEnvelopeJson(result, { enveloped: options.jsonEnvelope }));
|
|
163616
|
+
}
|
|
163617
|
+
} catch (err) {
|
|
163618
|
+
writeJsonError(context.output, options, String(err));
|
|
163619
|
+
context.setExitCode(1);
|
|
163620
|
+
}
|
|
163586
163621
|
});
|
|
163587
163622
|
}
|
|
163588
|
-
|
|
163589
|
-
|
|
163590
|
-
|
|
163591
|
-
|
|
163592
|
-
|
|
163593
|
-
|
|
163594
|
-
|
|
163595
|
-
|
|
163596
|
-
|
|
163597
|
-
|
|
163598
|
-
|
|
163599
|
-
|
|
163600
|
-
|
|
163601
|
-
|
|
163602
|
-
|
|
163603
|
-
|
|
163604
|
-
|
|
163605
|
-
|
|
163606
|
-
|
|
163607
|
-
|
|
163623
|
+
|
|
163624
|
+
// src/commands/feature/verify.ts
|
|
163625
|
+
function registerFeatureVerifyCommand(feature, context) {
|
|
163626
|
+
feature.command("verify").summary("Run the feature-verification pass and record its bound PASS/FAIL evidence receipt.").description([
|
|
163627
|
+
"Runs the selected feature-verification definition\u2019s command once (or --cmd) and writes the",
|
|
163628
|
+
"run-scoped + feature-latest receipts and .spur/run/<runId>-feature-verification.status.",
|
|
163629
|
+
"Exit: 0 after a completed pass (PASS or FAIL); 1 when the pass could not run.",
|
|
163630
|
+
"Without --run-id the pass opens and finalizes its own feature-lifecycle run row.",
|
|
163631
|
+
"Flags fall back to the workflow env names: featureId, __runId."
|
|
163632
|
+
].join(`
|
|
163633
|
+
`)).argument("[id]", "Feature ID (env: featureId)").option("--run-id <id>", "workflow run id the receipt binds to (env: __runId)").option("--cmd <command>", "verification command override (env: verificationCmd)").action(async (id, options) => {
|
|
163634
|
+
await Promise.resolve().then(() => init_src4());
|
|
163635
|
+
const env = context.env;
|
|
163636
|
+
const featureId = id || env.featureId || "";
|
|
163637
|
+
try {
|
|
163638
|
+
const result = await runFeatureVerification({
|
|
163639
|
+
workdir: context.cwd,
|
|
163640
|
+
featureId,
|
|
163641
|
+
runId: options.runId || env.__runId || "",
|
|
163642
|
+
cmdOverride: options.cmd || env.verificationCmd || ""
|
|
163643
|
+
});
|
|
163644
|
+
context.output.write(`feature verify: ${featureId} ${result.message}`);
|
|
163645
|
+
} catch (err) {
|
|
163646
|
+
context.output.error(`feature verify: ${String(err)}`);
|
|
163647
|
+
context.setExitCode(1);
|
|
163648
|
+
}
|
|
163608
163649
|
});
|
|
163609
|
-
if (!result.pass) {
|
|
163610
|
-
const details = result.findings.map((f) => `${f.layer} ${f.section}: ${f.message}`).join("; ");
|
|
163611
|
-
throw new Error(`Feature ${id} check failed before advance${strict ? " (strict)" : ""}: ${details}`);
|
|
163612
|
-
}
|
|
163613
163650
|
}
|
|
163614
163651
|
|
|
163615
|
-
// src/commands/
|
|
163616
|
-
|
|
163617
|
-
|
|
163652
|
+
// src/commands/feature/index.ts
|
|
163653
|
+
function registerFeatureCommand(program, context) {
|
|
163654
|
+
const feature = program.command("feature").summary("manage features (hierarchical IDs)");
|
|
163655
|
+
registerFeatureCreateCommand(feature, context);
|
|
163656
|
+
registerFeatureShowCommand(feature, context);
|
|
163657
|
+
registerFeatureUpdateCommand(feature, context);
|
|
163658
|
+
registerFeatureAdvanceCommand(feature, context);
|
|
163659
|
+
registerFeatureListCommand(feature, context);
|
|
163660
|
+
registerFeatureMoveCommand(feature, context);
|
|
163661
|
+
registerFeatureRefreshCommand(feature, context);
|
|
163662
|
+
registerFeatureCheckCommand(feature, context);
|
|
163663
|
+
registerFeatureSyncCommand(feature, context);
|
|
163664
|
+
registerFeatureVerifyCommand(feature, context);
|
|
163665
|
+
registerFeatureHandoffCommand(feature, context);
|
|
163666
|
+
}
|
|
163667
|
+
|
|
163668
|
+
// src/commands/history/analyze.ts
|
|
163618
163669
|
init_src2();
|
|
163619
|
-
init_dist28();
|
|
163620
|
-
init_dist7();
|
|
163621
|
-
import { createRequire as createRequire3 } from "module";
|
|
163622
163670
|
|
|
163623
163671
|
// src/config.ts
|
|
163624
163672
|
var CLI_CONFIG = {
|
|
163625
163673
|
binaryName: "spur",
|
|
163626
163674
|
binaryLabel: "spur",
|
|
163627
|
-
binaryVersion: "0.4.
|
|
163675
|
+
binaryVersion: "0.4.24",
|
|
163628
163676
|
configDir: ".spur",
|
|
163629
163677
|
configFile: ".spur/config.yaml",
|
|
163630
163678
|
databaseFile: ".spur/spur.db"
|
|
163631
163679
|
};
|
|
163632
163680
|
|
|
163633
|
-
// src/commands/history/
|
|
163634
|
-
|
|
163635
|
-
|
|
163636
|
-
|
|
163637
|
-
}
|
|
163638
|
-
function registerHistoryAnatomyCommand(history, context) {
|
|
163639
|
-
history.command("anatomy").summary("history-anatomy report cache ops (digest | check | paths | assert-clean | probe | stamp | refresh | publish)").description([
|
|
163640
|
-
"Deterministic cache ops behind the history-anatomy workflow (ADR-079):",
|
|
163641
|
-
" digest <artifact.json> semantic artifact digest",
|
|
163642
|
-
" check <report.md> report-contract structure gate (PASS/FAIL)",
|
|
163643
|
-
" paths --out <env> [--skill-dir d] ... resolve the run paths into an env file",
|
|
163644
|
-
" assert-clean --baseline <porcelain> fail on undeclared writes (--expect=<path>)",
|
|
163645
|
-
" probe --artifact a --target t ... cache disposition + provenance",
|
|
163646
|
-
" stamp --candidate c --provenance p --out o stamp provenance frontmatter",
|
|
163647
|
-
" refresh --report r --out o refresh a cache-hit report",
|
|
163648
|
-
" publish <candidate.md> <target.md> atomic publish",
|
|
163649
|
-
"Exit: 0 ok, 1 usage or op failure."
|
|
163650
|
-
].join(`
|
|
163651
|
-
`)).allowUnknownOption(true).allowExcessArguments(true).argument("[argv...]", "op and its flags (see description)").action(async (argv) => {
|
|
163652
|
-
for (let i = 0;i < argv.length; i++) {
|
|
163653
|
-
if (argv[i] === "--run-id" && !acceptArtifactId(argv[i + 1] ?? "", "run id", context))
|
|
163654
|
-
return;
|
|
163655
|
-
}
|
|
163656
|
-
await Promise.resolve().then(() => init_history_anatomy_cli());
|
|
163657
|
-
const result = runCacheCli(argv, {
|
|
163658
|
-
helper: spurEntryPath(),
|
|
163659
|
-
cwd: context.cwd,
|
|
163660
|
-
...context.env.HOME !== undefined ? { home: context.env.HOME } : {}
|
|
163661
|
-
});
|
|
163662
|
-
writeStepText(context.output, "stdout", result.stdout);
|
|
163663
|
-
writeStepText(context.output, "stderr", result.stderr);
|
|
163664
|
-
context.setExitCode(result.exitCode);
|
|
163665
|
-
});
|
|
163666
|
-
}
|
|
163667
|
-
|
|
163668
|
-
// src/commands/history/summary.ts
|
|
163669
|
-
function registerHistorySummaryCommand(history, context) {
|
|
163670
|
-
history.command("summary").summary("Daily summary report: tokens, git activity, history health, annotations").description([
|
|
163671
|
-
"Generate the day\u2019s Markdown summary and write it to docs/daily/summary_<YYYYMMDD>.md",
|
|
163672
|
-
"(or --output). Prompts for learnings / issues / pending unless SP_DAILY_SUMMARY_NO_PROMPT=1",
|
|
163673
|
-
"(non-TTY stdin supplies the three answers as lines). Exit: 0 written, 1 generation failed."
|
|
163674
|
-
].join(`
|
|
163675
|
-
`)).option("--date <date>", "YYYY-MM-DD, today or yesterday (default: today)").option("--dry-run", "print the summary without writing the file").option("--output <path>", "write to a custom path").option("--no-git", "skip git history collection").option("--no-ccusage", "skip token usage collection").action(async (options) => {
|
|
163676
|
-
await Promise.resolve().then(() => init_daily_summary());
|
|
163677
|
-
const argv = [];
|
|
163678
|
-
if (options.date !== undefined)
|
|
163679
|
-
argv.push("--date", options.date);
|
|
163680
|
-
if (options.dryRun === true)
|
|
163681
|
-
argv.push("--dry-run");
|
|
163682
|
-
if (options.output !== undefined)
|
|
163683
|
-
argv.push("--output", options.output);
|
|
163684
|
-
if (options.git === false)
|
|
163685
|
-
argv.push("--no-git");
|
|
163686
|
-
if (options.ccusage === false)
|
|
163687
|
-
argv.push("--no-ccusage");
|
|
163688
|
-
const code = await main2(argv, {
|
|
163689
|
-
out: (line) => context.output.write(line),
|
|
163690
|
-
err: (line) => context.output.error(line)
|
|
163691
|
-
});
|
|
163692
|
-
context.setExitCode(code);
|
|
163693
|
-
});
|
|
163694
|
-
}
|
|
163695
|
-
|
|
163696
|
-
// src/commands/history/timeline.ts
|
|
163697
|
-
function registerHistoryTimelineCommand(history, context) {
|
|
163698
|
-
history.command("timeline").summary("Per-prompt work/wait time and tokens from the host transcript (JSON)").description([
|
|
163699
|
-
"Read the active host transcript read-only (CLAUDE_CODE_SESSION_ID under ~/.claude/projects,",
|
|
163700
|
-
"PI_SESSION_FILE, or --transcript) and print one JSON timeline: a segment per operator prompt,",
|
|
163701
|
-
'optional --group stages, and totals. No transcript \u2192 {"available":false,...}, exit 0.',
|
|
163702
|
-
"Exit: 0 timeline (or unavailable), 2 invalid --group or unreadable transcript."
|
|
163703
|
-
].join(`
|
|
163704
|
-
`)).option("--transcript <path>", "transcript JSONL path (default: the active host session)").option("--group <spec>", 'sum segments into stages, e.g. "1-3,4"').action(async (options) => {
|
|
163705
|
-
await Promise.resolve().then(() => init_session_timeline());
|
|
163706
|
-
const argv = [];
|
|
163707
|
-
if (options.transcript !== undefined)
|
|
163708
|
-
argv.push("--transcript", options.transcript);
|
|
163709
|
-
if (options.group !== undefined)
|
|
163710
|
-
argv.push("--group", options.group);
|
|
163711
|
-
const code = runSessionTimelineCli(argv, {
|
|
163712
|
-
env: context.env,
|
|
163713
|
-
write: (s) => writeStepText(context.output, "stdout", s),
|
|
163714
|
-
writeErr: (s) => writeStepText(context.output, "stderr", s)
|
|
163715
|
-
});
|
|
163716
|
-
context.setExitCode(code);
|
|
163717
|
-
});
|
|
163718
|
-
}
|
|
163719
|
-
|
|
163720
|
-
// src/commands/history.ts
|
|
163681
|
+
// src/commands/history/shared.ts
|
|
163682
|
+
init_src4();
|
|
163683
|
+
init_dist7();
|
|
163684
|
+
import { createRequire as createRequire3 } from "module";
|
|
163721
163685
|
function resolveCliSourceTimeout(explicit, env) {
|
|
163722
163686
|
if (explicit !== undefined)
|
|
163723
163687
|
return parseTimeoutInput(explicit);
|
|
@@ -163736,10 +163700,6 @@ async function resolveImportProvenance(fs) {
|
|
|
163736
163700
|
} catch {}
|
|
163737
163701
|
return { binary, importer };
|
|
163738
163702
|
}
|
|
163739
|
-
function formatProvenance(provenance) {
|
|
163740
|
-
return `binary: ${provenance.binary}
|
|
163741
|
-
importer: @gobing-ai/ts-llm-jsonl-importer@${provenance.importer}`;
|
|
163742
|
-
}
|
|
163743
163703
|
var CAPABILITY_KINDS2 = new Set(["command", "subagent", "skill"]);
|
|
163744
163704
|
function parseCapabilityOriginSpec(spec) {
|
|
163745
163705
|
const parts = spec.split(":").map((part) => part.trim());
|
|
@@ -163763,132 +163723,42 @@ function parseCapabilityOriginSpec(spec) {
|
|
|
163763
163723
|
originIdentity
|
|
163764
163724
|
};
|
|
163765
163725
|
}
|
|
163766
|
-
function
|
|
163767
|
-
|
|
163768
|
-
|
|
163769
|
-
|
|
163770
|
-
|
|
163771
|
-
|
|
163772
|
-
|
|
163773
|
-
|
|
163774
|
-
|
|
163775
|
-
|
|
163776
|
-
|
|
163777
|
-
|
|
163778
|
-
|
|
163779
|
-
|
|
163780
|
-
|
|
163781
|
-
|
|
163782
|
-
|
|
163783
|
-
|
|
163784
|
-
|
|
163785
|
-
|
|
163786
|
-
fs: context.fs,
|
|
163787
|
-
cwd: context.cwd
|
|
163788
|
-
});
|
|
163789
|
-
};
|
|
163790
|
-
noun.command("import").description("Import agent conversation JSONL. `--source all` fans out across all sources with " + "per-source failure isolation (task 0470). A single source is the n=1 case of " + "the same contract \u2014 never two import paths. For one session's evidence, use " + "`--file`; a full replay can exceed the per-source budget.").option(...SHARED_OPTIONS.sourceHistory, "all").option(...SHARED_OPTIONS.fileHistoryJsonl).option("--root <path>", "Scan a history root").option(...SHARED_OPTIONS.modeHistory).option(...SHARED_OPTIONS.dryRunHistoryScan).option("--source-timeout <ms|none>", `Per-source timeout in milliseconds or 'none' (default ${DEFAULT_SOURCE_TIMEOUT_MS} = 10 min)`).option(...SHARED_OPTIONS.jsonSupported).option(...SHARED_OPTIONS.jsonEnvelope).option("--capability-origin <spec>", "Classify skill calls against a released artifact: " + "source:skillName:artifactDigest:capabilityKind:originIdentity (repeatable).", (value, previous) => [...previous, value], []).action(async (options) => {
|
|
163791
|
-
const source = options.source ?? "all";
|
|
163792
|
-
if (options.file && source === "all") {
|
|
163793
|
-
context.output.write(options.json ? toEnvelopeJson({ status: "error", message: '--file requires a single --source, not "all".' }, {
|
|
163794
|
-
enveloped: options.jsonEnvelope,
|
|
163795
|
-
error: {
|
|
163796
|
-
code: "INTERNAL_ERROR",
|
|
163797
|
-
message: '--file requires a single --source, not "all".',
|
|
163798
|
-
details: { cliCode: "usage" }
|
|
163799
|
-
}
|
|
163800
|
-
}) : 'spur history import: --file requires a single --source, not "all".');
|
|
163801
|
-
context.setExitCode(1);
|
|
163802
|
-
return;
|
|
163803
|
-
}
|
|
163804
|
-
let sourceTimeout;
|
|
163805
|
-
try {
|
|
163806
|
-
sourceTimeout = resolveCliSourceTimeout(options.sourceTimeout, getEnvVars());
|
|
163807
|
-
} catch (e) {
|
|
163808
|
-
const timeoutMsg = `spur history import: ${e instanceof Error ? e.message : String(e)}`;
|
|
163809
|
-
context.output.write(options.json ? toEnvelopeJson({ status: "error", message: timeoutMsg }, {
|
|
163810
|
-
enveloped: options.jsonEnvelope,
|
|
163811
|
-
error: { code: "INTERNAL_ERROR", message: timeoutMsg, details: { cliCode: "usage" } }
|
|
163812
|
-
}) : timeoutMsg);
|
|
163813
|
-
context.setExitCode(1);
|
|
163814
|
-
return;
|
|
163815
|
-
}
|
|
163816
|
-
const mode = options.mode ?? (options.file ? "force-file" : "incremental");
|
|
163817
|
-
if (mode !== "full" && mode !== "incremental" && mode !== "force-file") {
|
|
163818
|
-
const modeMsg = `Invalid history import mode "${mode}". Expected one of: full, incremental, force-file`;
|
|
163819
|
-
if (options.json) {
|
|
163820
|
-
context.output.write(toEnvelopeJson({ status: "error", message: modeMsg }, {
|
|
163821
|
-
enveloped: options.jsonEnvelope,
|
|
163822
|
-
error: { code: "INTERNAL_ERROR", message: modeMsg, details: { cliCode: "usage" } }
|
|
163823
|
-
}));
|
|
163824
|
-
} else {
|
|
163825
|
-
context.output.error(modeMsg);
|
|
163826
|
-
}
|
|
163827
|
-
context.setExitCode(1);
|
|
163828
|
-
return;
|
|
163829
|
-
}
|
|
163830
|
-
if (options.file && mode === "full" && options.dryRun !== true) {
|
|
163831
|
-
const unsafeMsg = "spur history import: --file <path> --mode full without --dry-run is unsafe \u2014 full mode " + "treats the file as the authoritative source for reconciliation. Preview with --dry-run, " + "or import a single file with --mode force-file.";
|
|
163832
|
-
context.output.write(options.json ? toEnvelopeJson({ status: "error", message: unsafeMsg }, {
|
|
163833
|
-
enveloped: options.jsonEnvelope,
|
|
163834
|
-
error: { code: "INTERNAL_ERROR", message: unsafeMsg, details: { cliCode: "usage" } }
|
|
163835
|
-
}) : unsafeMsg);
|
|
163836
|
-
context.setExitCode(1);
|
|
163837
|
-
return;
|
|
163838
|
-
}
|
|
163839
|
-
let capabilityOrigins;
|
|
163840
|
-
if (options.capabilityOrigin.length > 0) {
|
|
163841
|
-
try {
|
|
163842
|
-
capabilityOrigins = options.capabilityOrigin.map(parseCapabilityOriginSpec);
|
|
163843
|
-
} catch (err) {
|
|
163844
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
163845
|
-
context.output.write(options.json ? toEnvelopeJson({ status: "error", message }, {
|
|
163846
|
-
enveloped: options.jsonEnvelope,
|
|
163847
|
-
error: { code: "INTERNAL_ERROR", message, details: { cliCode: "usage" } }
|
|
163848
|
-
}) : `spur history import: ${message}`);
|
|
163849
|
-
context.setExitCode(1);
|
|
163850
|
-
return;
|
|
163851
|
-
}
|
|
163852
|
-
}
|
|
163853
|
-
const svc = await makeService(capabilityOrigins);
|
|
163854
|
-
let fanOut;
|
|
163855
|
-
try {
|
|
163856
|
-
fanOut = await svc.importAll({
|
|
163857
|
-
sources: source === "all" ? undefined : [source],
|
|
163858
|
-
file: options.file || undefined,
|
|
163859
|
-
root: options.root || undefined,
|
|
163860
|
-
mode,
|
|
163861
|
-
dryRun: options.dryRun === true,
|
|
163862
|
-
sourceTimeout,
|
|
163863
|
-
onScope: (line) => context.output.error(line)
|
|
163864
|
-
});
|
|
163865
|
-
} catch (e) {
|
|
163866
|
-
if (e instanceof UnsafeHistoryImporterError) {
|
|
163867
|
-
const msg = `spur history import: ${e.code}: ${e.message}`;
|
|
163868
|
-
context.output.write(options.json ? toEnvelopeJson({ status: "error", message: msg }, {
|
|
163869
|
-
enveloped: options.jsonEnvelope,
|
|
163870
|
-
error: {
|
|
163871
|
-
code: "INTERNAL_ERROR",
|
|
163872
|
-
message: msg,
|
|
163873
|
-
details: {
|
|
163874
|
-
cliCode: e.code,
|
|
163875
|
-
installedVersion: e.installedVersion,
|
|
163876
|
-
minSafeVersion: e.minSafeVersion
|
|
163877
|
-
}
|
|
163878
|
-
}
|
|
163879
|
-
}) : msg);
|
|
163880
|
-
context.setExitCode(1);
|
|
163881
|
-
return;
|
|
163882
|
-
}
|
|
163883
|
-
throw e;
|
|
163884
|
-
}
|
|
163885
|
-
const provenance = await resolveImportProvenance(context.fs);
|
|
163886
|
-
context.output.write(options.json ? toEnvelopeJson({ ...fanOut, provenance }, { enveloped: options.jsonEnvelope }) : `${formatProvenance(provenance)}
|
|
163887
|
-
${formatFanOutResult(fanOut)}`);
|
|
163888
|
-
context.setExitCode(fanOut.exitCode);
|
|
163726
|
+
async function makeHistoryService(context, capabilityOrigins) {
|
|
163727
|
+
let taskLocator;
|
|
163728
|
+
try {
|
|
163729
|
+
const { foldersConfig } = await resolvePlanningFolders(context.fs);
|
|
163730
|
+
taskLocator = TaskLocator.forDirs(context.fs, [
|
|
163731
|
+
context.fs.resolve(foldersConfig.active_folder),
|
|
163732
|
+
...Object.keys(foldersConfig.folders ?? {}).map((f) => context.fs.resolve(f))
|
|
163733
|
+
]);
|
|
163734
|
+
} catch {
|
|
163735
|
+
taskLocator = undefined;
|
|
163736
|
+
}
|
|
163737
|
+
const provenance = await resolveImportProvenance(context.fs);
|
|
163738
|
+
return new HistoryService({
|
|
163739
|
+
getDb: () => context.getDb(),
|
|
163740
|
+
agentConfig: context.agentConfig,
|
|
163741
|
+
...taskLocator ? { taskLocator } : {},
|
|
163742
|
+
importerVersion: provenance.importer,
|
|
163743
|
+
...capabilityOrigins ? { capabilityOrigins } : {},
|
|
163744
|
+
fs: context.fs,
|
|
163745
|
+
cwd: context.cwd
|
|
163889
163746
|
});
|
|
163747
|
+
}
|
|
163748
|
+
function parseTopOrThrow(raw) {
|
|
163749
|
+
if (raw === undefined)
|
|
163750
|
+
return;
|
|
163751
|
+
const n = Number(raw);
|
|
163752
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
163753
|
+
throw new Error(`--top must be a positive integer (got "${raw}")`);
|
|
163754
|
+
}
|
|
163755
|
+
return n;
|
|
163756
|
+
}
|
|
163757
|
+
|
|
163758
|
+
// src/commands/history/analyze.ts
|
|
163759
|
+
function registerHistoryAnalyzeCommand(noun, context) {
|
|
163890
163760
|
noun.command("analyze").description("Aggregate imported history with SQL and write a versioned JSON artifact (Q1-Q10 forensic query set).").option("--since <iso>", "Inclusive lower bound on message timestamp").option("--until <iso>", "Inclusive upper bound on message timestamp").option(...SHARED_OPTIONS.sourceHistory, "all").option("--session <id>", "Narrow to a single session id").option(...SHARED_OPTIONS.runHistory).option("--task <wbs>", "Narrow to a single task WBS").option("--top <n>", "Leaderboard depth for byTool/bySession", "20").option("--out <path>", "Write the artifact to this path instead of the dated reports dir").option(...SHARED_OPTIONS.jsonArtifact).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options) => {
|
|
163891
|
-
const svc = await
|
|
163761
|
+
const svc = await makeHistoryService(context);
|
|
163892
163762
|
const source = options.source ?? "all";
|
|
163893
163763
|
const selector = {
|
|
163894
163764
|
since: options.since || null,
|
|
@@ -163914,64 +163784,50 @@ ${formatFanOutResult(fanOut)}`);
|
|
|
163914
163784
|
context.setExitCode(2);
|
|
163915
163785
|
}
|
|
163916
163786
|
});
|
|
163917
|
-
|
|
163918
|
-
|
|
163919
|
-
|
|
163920
|
-
|
|
163921
|
-
|
|
163922
|
-
|
|
163923
|
-
|
|
163924
|
-
|
|
163925
|
-
|
|
163926
|
-
|
|
163927
|
-
|
|
163928
|
-
|
|
163929
|
-
|
|
163930
|
-
|
|
163931
|
-
|
|
163932
|
-
|
|
163933
|
-
|
|
163934
|
-
|
|
163935
|
-
|
|
163936
|
-
|
|
163937
|
-
|
|
163938
|
-
}, { enveloped: options.jsonEnvelope }) : `cleared ${result.cleared.length} history tables` + (result.skipped.length > 0 ? ` (${result.skipped.length} not present)` : "") + note);
|
|
163939
|
-
});
|
|
163940
|
-
noun.command("report").description("Render a previously-generated history artifact as a spend + forensic report. " + "Never opens the database \u2014 pure renderer of the analyze JSON. " + "--task / --top narrow the already-loaded artifact client-side (0564 R3).").argument("[path]", "Artifact JSON path (defaults to the latest.json pointer)").option(...SHARED_OPTIONS.jsonParsedArtifact).option(...SHARED_OPTIONS.jsonEnvelope).option("--mode <name>", "Report mode: default | forensics (registry-resolved; unknown names fail)").option("--task <wbs>", "Narrow to a single task WBS the artifact was analyzed with").option("--top <n>", "Leaderboard depth for byTool/bySession (re-slices the artifact)").action(async (pathArg, options) => {
|
|
163941
|
-
try {
|
|
163942
|
-
const top = parseTopOrThrow(options.top);
|
|
163943
|
-
const { report, artifactPath, resolution, artifact, banner } = runHistoryReport({
|
|
163944
|
-
path: pathArg,
|
|
163945
|
-
cwd: context.cwd,
|
|
163946
|
-
now: new Date,
|
|
163947
|
-
mode: options.mode,
|
|
163948
|
-
task: options.task,
|
|
163949
|
-
top
|
|
163950
|
-
});
|
|
163951
|
-
if (options.json) {
|
|
163952
|
-
context.output.write(toEnvelopeJson(artifact, { enveloped: options.jsonEnvelope }));
|
|
163953
|
-
context.setExitCode(0);
|
|
163787
|
+
}
|
|
163788
|
+
|
|
163789
|
+
// src/commands/history/anatomy.ts
|
|
163790
|
+
function spurEntryPath(argv = process.argv, execPath = process.execPath) {
|
|
163791
|
+
const entry = argv[1];
|
|
163792
|
+
return entry !== undefined && /\.(?:[cm]?[jt]s)$/.test(entry) ? entry : execPath;
|
|
163793
|
+
}
|
|
163794
|
+
var OPS = {
|
|
163795
|
+
digest: "<artifact.json> \u2014 semantic artifact digest",
|
|
163796
|
+
check: "<report.md> \u2014 report-contract structure gate (PASS/FAIL)",
|
|
163797
|
+
paths: "--out <env> [--skill-dir d] ... \u2014 resolve the run paths into an env file",
|
|
163798
|
+
"assert-clean": "--baseline <porcelain> [--expect <path>]... \u2014 fail on undeclared writes",
|
|
163799
|
+
probe: "--artifact a --target t ... \u2014 cache disposition + provenance",
|
|
163800
|
+
stamp: "--candidate c --provenance p --out o \u2014 stamp provenance frontmatter",
|
|
163801
|
+
refresh: "--report r --out o \u2014 refresh a cache-hit report",
|
|
163802
|
+
publish: "<candidate.md> <target.md> \u2014 atomic publish"
|
|
163803
|
+
};
|
|
163804
|
+
function registerHistoryAnatomyCommand(history, context) {
|
|
163805
|
+
const run = async (argv) => {
|
|
163806
|
+
for (let i = 0;i < argv.length; i++) {
|
|
163807
|
+
if (argv[i] === "--run-id" && !acceptArtifactId(argv[i + 1] ?? "", "run id", context))
|
|
163954
163808
|
return;
|
|
163955
|
-
}
|
|
163956
|
-
if (resolution === "pointer") {
|
|
163957
|
-
const staleBanner = stalenessBanner(artifact.generatedAt, new Date);
|
|
163958
|
-
if (staleBanner !== null)
|
|
163959
|
-
context.output.write(staleBanner);
|
|
163960
|
-
}
|
|
163961
|
-
if (banner !== null)
|
|
163962
|
-
context.output.write(`${banner}
|
|
163963
|
-
`);
|
|
163964
|
-
context.output.write(report);
|
|
163965
|
-
context.output.write(`
|
|
163966
|
-
(artifact: ${artifactPath})
|
|
163967
|
-
`);
|
|
163968
|
-
context.setExitCode(0);
|
|
163969
|
-
} catch (e) {
|
|
163970
|
-
const message = `spur history report failed: ${e.message}`;
|
|
163971
|
-
context.output.write(options.json ? toEnvelopeJson({ status: "error", message }, { enveloped: options.jsonEnvelope, error: { code: "INTERNAL_ERROR", message } }) : message);
|
|
163972
|
-
context.setExitCode(1);
|
|
163973
163809
|
}
|
|
163974
|
-
|
|
163810
|
+
await Promise.resolve().then(() => init_history_anatomy_cli());
|
|
163811
|
+
const result = runCacheCli(argv, {
|
|
163812
|
+
helper: spurEntryPath(),
|
|
163813
|
+
cwd: context.cwd,
|
|
163814
|
+
...context.env.HOME !== undefined ? { home: context.env.HOME } : {}
|
|
163815
|
+
});
|
|
163816
|
+
writeStepText(context.output, "stdout", result.stdout);
|
|
163817
|
+
writeStepText(context.output, "stderr", result.stderr);
|
|
163818
|
+
context.setExitCode(result.exitCode);
|
|
163819
|
+
};
|
|
163820
|
+
for (const [op, synopsis] of Object.entries(OPS)) {
|
|
163821
|
+
history.command(`anatomy-${op}`).summary(`history-anatomy cache op: ${synopsis}`).description(`history-anatomy cache op (ADR-079): ${op} ${synopsis}
|
|
163822
|
+
Exit: 0 ok, 1 usage or op failure.`).allowUnknownOption(true).allowExcessArguments(true).argument("[argv...]", "the op flags and operands (see description)").action((argv) => run([op, ...argv]));
|
|
163823
|
+
}
|
|
163824
|
+
}
|
|
163825
|
+
|
|
163826
|
+
// src/commands/history/daily.ts
|
|
163827
|
+
init_src4();
|
|
163828
|
+
init_src();
|
|
163829
|
+
init_dist28();
|
|
163830
|
+
function registerHistoryDailyCommand(noun, context) {
|
|
163975
163831
|
noun.command("daily").description("Run-once daily pipeline: import-all (fan-out, per-source isolation) \u2192 analyze \u2192 write " + "artifact \u2192 prune reports older than 90 days (task 0470 R6). Import uses checkpoint " + "resume, so a missed night self-heals on the next run with no gap and no double-count.").option("--since <iso>", "Inclusive lower bound on message timestamp for the report (not the import)").option("--until <iso>", "Inclusive upper bound on message timestamp for the report").option("--source-timeout <ms|none>", `Per-source import timeout in milliseconds or 'none' (default ${DEFAULT_SOURCE_TIMEOUT_MS} = 10 min)`).option("--root <path>", "History root override (default: per-source platform dir)").option(...SHARED_OPTIONS.jsonDaily).option(...SHARED_OPTIONS.jsonEnvelope).option("--mode <name>", "Render the artifact as a .md sidecar in this mode after analyze (e.g. forensics)").action(async (options) => {
|
|
163976
163832
|
let sourceTimeout;
|
|
163977
163833
|
try {
|
|
@@ -163985,7 +163841,7 @@ ${formatFanOutResult(fanOut)}`);
|
|
|
163985
163841
|
context.setExitCode(1);
|
|
163986
163842
|
return;
|
|
163987
163843
|
}
|
|
163988
|
-
const svc = await
|
|
163844
|
+
const svc = await makeHistoryService(context);
|
|
163989
163845
|
let refresh;
|
|
163990
163846
|
try {
|
|
163991
163847
|
refresh = parseHistoryRefreshContext(getEnvVar(HISTORY_REFRESH_CONTEXT_ENV));
|
|
@@ -164107,35 +163963,6 @@ ${formatFanOutResult(fanOut)}`);
|
|
|
164107
163963
|
}
|
|
164108
163964
|
context.output.write(options.json ? toEnvelopeJson(result, { enveloped: options.jsonEnvelope }) : formatDailyResult(result));
|
|
164109
163965
|
});
|
|
164110
|
-
registerHistoryAnatomyCommand(noun, context);
|
|
164111
|
-
registerHistoryTimelineCommand(noun, context);
|
|
164112
|
-
registerHistorySummaryCommand(noun, context);
|
|
164113
|
-
}
|
|
164114
|
-
function parseTopOrThrow(raw) {
|
|
164115
|
-
if (raw === undefined)
|
|
164116
|
-
return;
|
|
164117
|
-
const n = Number(raw);
|
|
164118
|
-
if (!Number.isInteger(n) || n < 1) {
|
|
164119
|
-
throw new Error(`--top must be a positive integer (got "${raw}")`);
|
|
164120
|
-
}
|
|
164121
|
-
return n;
|
|
164122
|
-
}
|
|
164123
|
-
function formatFanOutResult(r) {
|
|
164124
|
-
const lines = ["history import (fan-out)", "sources:"];
|
|
164125
|
-
for (const e of r.entries) {
|
|
164126
|
-
lines.push(` ${e.source}: ${e.status} (files=${e.files} scanned, new-messages=${e.messages}, tool-calls=${e.toolCalls})`);
|
|
164127
|
-
}
|
|
164128
|
-
const a = r.attribution;
|
|
164129
|
-
lines.push(`attribution: sessions=${a.sessionsEvaluated} links-created=${a.linksCreated} ` + `links-present=${a.linksAlreadyPresent} skipped=${a.skippedEvidence} ambiguous=${a.ambiguousEvidence}`);
|
|
164130
|
-
if (r.warnings.length > 0) {
|
|
164131
|
-
lines.push("warnings:");
|
|
164132
|
-
for (const w of r.warnings) {
|
|
164133
|
-
lines.push(` [${w.code}] ${w.source ?? "-"}: ${w.detail}`);
|
|
164134
|
-
}
|
|
164135
|
-
}
|
|
164136
|
-
lines.push(`exit_code: ${r.exitCode}`);
|
|
164137
|
-
return lines.join(`
|
|
164138
|
-
`);
|
|
164139
163966
|
}
|
|
164140
163967
|
function formatDailyResult(r) {
|
|
164141
163968
|
const lines = ["history daily"];
|
|
@@ -164162,6 +163989,266 @@ function formatDailyResult(r) {
|
|
|
164162
163989
|
`);
|
|
164163
163990
|
}
|
|
164164
163991
|
|
|
163992
|
+
// src/commands/history/import.ts
|
|
163993
|
+
init_src4();
|
|
163994
|
+
init_src();
|
|
163995
|
+
function registerHistoryImportCommand(noun, context) {
|
|
163996
|
+
noun.command("import").description("Import agent conversation JSONL. `--source all` fans out across all sources with " + "per-source failure isolation (task 0470). A single source is the n=1 case of " + "the same contract \u2014 never two import paths. For one session's evidence, use " + "`--file`; a full replay can exceed the per-source budget.").option(...SHARED_OPTIONS.sourceHistory, "all").option(...SHARED_OPTIONS.fileHistoryJsonl).option("--root <path>", "Scan a history root").option(...SHARED_OPTIONS.modeHistory).option(...SHARED_OPTIONS.dryRunHistoryScan).option("--source-timeout <ms|none>", `Per-source timeout in milliseconds or 'none' (default ${DEFAULT_SOURCE_TIMEOUT_MS} = 10 min)`).option(...SHARED_OPTIONS.jsonSupported).option(...SHARED_OPTIONS.jsonEnvelope).option("--capability-origin <spec>", "Classify skill calls against a released artifact: " + "source:skillName:artifactDigest:capabilityKind:originIdentity (repeatable).", (value, previous) => [...previous, value], []).action(async (options) => {
|
|
163997
|
+
const source = options.source ?? "all";
|
|
163998
|
+
if (options.file && source === "all") {
|
|
163999
|
+
context.output.write(options.json ? toEnvelopeJson({ status: "error", message: '--file requires a single --source, not "all".' }, {
|
|
164000
|
+
enveloped: options.jsonEnvelope,
|
|
164001
|
+
error: {
|
|
164002
|
+
code: "INTERNAL_ERROR",
|
|
164003
|
+
message: '--file requires a single --source, not "all".',
|
|
164004
|
+
details: { cliCode: "usage" }
|
|
164005
|
+
}
|
|
164006
|
+
}) : 'spur history import: --file requires a single --source, not "all".');
|
|
164007
|
+
context.setExitCode(1);
|
|
164008
|
+
return;
|
|
164009
|
+
}
|
|
164010
|
+
let sourceTimeout;
|
|
164011
|
+
try {
|
|
164012
|
+
sourceTimeout = resolveCliSourceTimeout(options.sourceTimeout, getEnvVars());
|
|
164013
|
+
} catch (e) {
|
|
164014
|
+
const timeoutMsg = `spur history import: ${e instanceof Error ? e.message : String(e)}`;
|
|
164015
|
+
context.output.write(options.json ? toEnvelopeJson({ status: "error", message: timeoutMsg }, {
|
|
164016
|
+
enveloped: options.jsonEnvelope,
|
|
164017
|
+
error: { code: "INTERNAL_ERROR", message: timeoutMsg, details: { cliCode: "usage" } }
|
|
164018
|
+
}) : timeoutMsg);
|
|
164019
|
+
context.setExitCode(1);
|
|
164020
|
+
return;
|
|
164021
|
+
}
|
|
164022
|
+
const mode = options.mode ?? (options.file ? "force-file" : "incremental");
|
|
164023
|
+
if (mode !== "full" && mode !== "incremental" && mode !== "force-file") {
|
|
164024
|
+
const modeMsg = `Invalid history import mode "${mode}". Expected one of: full, incremental, force-file`;
|
|
164025
|
+
if (options.json) {
|
|
164026
|
+
context.output.write(toEnvelopeJson({ status: "error", message: modeMsg }, {
|
|
164027
|
+
enveloped: options.jsonEnvelope,
|
|
164028
|
+
error: { code: "INTERNAL_ERROR", message: modeMsg, details: { cliCode: "usage" } }
|
|
164029
|
+
}));
|
|
164030
|
+
} else {
|
|
164031
|
+
context.output.error(modeMsg);
|
|
164032
|
+
}
|
|
164033
|
+
context.setExitCode(1);
|
|
164034
|
+
return;
|
|
164035
|
+
}
|
|
164036
|
+
if (options.file && mode === "full" && options.dryRun !== true) {
|
|
164037
|
+
const unsafeMsg = "spur history import: --file <path> --mode full without --dry-run is unsafe \u2014 full mode " + "treats the file as the authoritative source for reconciliation. Preview with --dry-run, " + "or import a single file with --mode force-file.";
|
|
164038
|
+
context.output.write(options.json ? toEnvelopeJson({ status: "error", message: unsafeMsg }, {
|
|
164039
|
+
enveloped: options.jsonEnvelope,
|
|
164040
|
+
error: { code: "INTERNAL_ERROR", message: unsafeMsg, details: { cliCode: "usage" } }
|
|
164041
|
+
}) : unsafeMsg);
|
|
164042
|
+
context.setExitCode(1);
|
|
164043
|
+
return;
|
|
164044
|
+
}
|
|
164045
|
+
let capabilityOrigins;
|
|
164046
|
+
if (options.capabilityOrigin.length > 0) {
|
|
164047
|
+
try {
|
|
164048
|
+
capabilityOrigins = options.capabilityOrigin.map(parseCapabilityOriginSpec);
|
|
164049
|
+
} catch (err) {
|
|
164050
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
164051
|
+
context.output.write(options.json ? toEnvelopeJson({ status: "error", message }, {
|
|
164052
|
+
enveloped: options.jsonEnvelope,
|
|
164053
|
+
error: { code: "INTERNAL_ERROR", message, details: { cliCode: "usage" } }
|
|
164054
|
+
}) : `spur history import: ${message}`);
|
|
164055
|
+
context.setExitCode(1);
|
|
164056
|
+
return;
|
|
164057
|
+
}
|
|
164058
|
+
}
|
|
164059
|
+
const svc = await makeHistoryService(context, capabilityOrigins);
|
|
164060
|
+
let fanOut;
|
|
164061
|
+
try {
|
|
164062
|
+
fanOut = await svc.importAll({
|
|
164063
|
+
sources: source === "all" ? undefined : [source],
|
|
164064
|
+
file: options.file || undefined,
|
|
164065
|
+
root: options.root || undefined,
|
|
164066
|
+
mode,
|
|
164067
|
+
dryRun: options.dryRun === true,
|
|
164068
|
+
sourceTimeout,
|
|
164069
|
+
onScope: (line) => context.output.error(line)
|
|
164070
|
+
});
|
|
164071
|
+
} catch (e) {
|
|
164072
|
+
if (e instanceof UnsafeHistoryImporterError) {
|
|
164073
|
+
const msg = `spur history import: ${e.code}: ${e.message}`;
|
|
164074
|
+
context.output.write(options.json ? toEnvelopeJson({ status: "error", message: msg }, {
|
|
164075
|
+
enveloped: options.jsonEnvelope,
|
|
164076
|
+
error: {
|
|
164077
|
+
code: "INTERNAL_ERROR",
|
|
164078
|
+
message: msg,
|
|
164079
|
+
details: {
|
|
164080
|
+
cliCode: e.code,
|
|
164081
|
+
installedVersion: e.installedVersion,
|
|
164082
|
+
minSafeVersion: e.minSafeVersion
|
|
164083
|
+
}
|
|
164084
|
+
}
|
|
164085
|
+
}) : msg);
|
|
164086
|
+
context.setExitCode(1);
|
|
164087
|
+
return;
|
|
164088
|
+
}
|
|
164089
|
+
throw e;
|
|
164090
|
+
}
|
|
164091
|
+
const provenance = await resolveImportProvenance(context.fs);
|
|
164092
|
+
context.output.write(options.json ? toEnvelopeJson({ ...fanOut, provenance }, { enveloped: options.jsonEnvelope }) : `${formatProvenance(provenance)}
|
|
164093
|
+
${formatFanOutResult(fanOut)}`);
|
|
164094
|
+
context.setExitCode(fanOut.exitCode);
|
|
164095
|
+
});
|
|
164096
|
+
}
|
|
164097
|
+
function formatProvenance(provenance) {
|
|
164098
|
+
return `binary: ${provenance.binary}
|
|
164099
|
+
importer: @gobing-ai/ts-llm-jsonl-importer@${provenance.importer}`;
|
|
164100
|
+
}
|
|
164101
|
+
function formatFanOutResult(r) {
|
|
164102
|
+
const lines = ["history import (fan-out)", "sources:"];
|
|
164103
|
+
for (const e of r.entries) {
|
|
164104
|
+
lines.push(` ${e.source}: ${e.status} (files=${e.files} scanned, new-messages=${e.messages}, tool-calls=${e.toolCalls})`);
|
|
164105
|
+
}
|
|
164106
|
+
const a = r.attribution;
|
|
164107
|
+
lines.push(`attribution: sessions=${a.sessionsEvaluated} links-created=${a.linksCreated} ` + `links-present=${a.linksAlreadyPresent} skipped=${a.skippedEvidence} ambiguous=${a.ambiguousEvidence}`);
|
|
164108
|
+
if (r.warnings.length > 0) {
|
|
164109
|
+
lines.push("warnings:");
|
|
164110
|
+
for (const w of r.warnings) {
|
|
164111
|
+
lines.push(` [${w.code}] ${w.source ?? "-"}: ${w.detail}`);
|
|
164112
|
+
}
|
|
164113
|
+
}
|
|
164114
|
+
lines.push(`exit_code: ${r.exitCode}`);
|
|
164115
|
+
return lines.join(`
|
|
164116
|
+
`);
|
|
164117
|
+
}
|
|
164118
|
+
|
|
164119
|
+
// src/commands/history/report.ts
|
|
164120
|
+
init_src4();
|
|
164121
|
+
init_src2();
|
|
164122
|
+
function registerHistoryReportCommand(noun, context) {
|
|
164123
|
+
noun.command("report").description("Render a previously-generated history artifact as a spend + forensic report. " + "Never opens the database \u2014 pure renderer of the analyze JSON. " + "--task / --top narrow the already-loaded artifact client-side (0564 R3).").argument("[path]", "Artifact JSON path (defaults to the latest.json pointer)").option(...SHARED_OPTIONS.jsonParsedArtifact).option(...SHARED_OPTIONS.jsonEnvelope).option("--mode <name>", "Report mode: default | forensics (registry-resolved; unknown names fail)").option("--task <wbs>", "Narrow to a single task WBS the artifact was analyzed with").option("--top <n>", "Leaderboard depth for byTool/bySession (re-slices the artifact)").action(async (pathArg, options) => {
|
|
164124
|
+
try {
|
|
164125
|
+
const top = parseTopOrThrow(options.top);
|
|
164126
|
+
const { report, artifactPath, resolution, artifact, banner } = runHistoryReport({
|
|
164127
|
+
path: pathArg,
|
|
164128
|
+
cwd: context.cwd,
|
|
164129
|
+
now: new Date,
|
|
164130
|
+
mode: options.mode,
|
|
164131
|
+
task: options.task,
|
|
164132
|
+
top
|
|
164133
|
+
});
|
|
164134
|
+
if (options.json) {
|
|
164135
|
+
context.output.write(toEnvelopeJson(artifact, { enveloped: options.jsonEnvelope }));
|
|
164136
|
+
context.setExitCode(0);
|
|
164137
|
+
return;
|
|
164138
|
+
}
|
|
164139
|
+
if (resolution === "pointer") {
|
|
164140
|
+
const staleBanner = stalenessBanner(artifact.generatedAt, new Date);
|
|
164141
|
+
if (staleBanner !== null)
|
|
164142
|
+
context.output.write(staleBanner);
|
|
164143
|
+
}
|
|
164144
|
+
if (banner !== null)
|
|
164145
|
+
context.output.write(`${banner}
|
|
164146
|
+
`);
|
|
164147
|
+
context.output.write(report);
|
|
164148
|
+
context.output.write(`
|
|
164149
|
+
(artifact: ${artifactPath})
|
|
164150
|
+
`);
|
|
164151
|
+
context.setExitCode(0);
|
|
164152
|
+
} catch (e) {
|
|
164153
|
+
const message = `spur history report failed: ${e.message}`;
|
|
164154
|
+
context.output.write(options.json ? toEnvelopeJson({ status: "error", message }, { enveloped: options.jsonEnvelope, error: { code: "INTERNAL_ERROR", message } }) : message);
|
|
164155
|
+
context.setExitCode(1);
|
|
164156
|
+
}
|
|
164157
|
+
});
|
|
164158
|
+
}
|
|
164159
|
+
|
|
164160
|
+
// src/commands/history/reset.ts
|
|
164161
|
+
function registerHistoryResetCommand(noun, context) {
|
|
164162
|
+
noun.command("reset").description("Destructively wipe every history_* table: normalized rows, per-source ETL, board rollups, " + "daily stats, and importer checkpoints/ledger. Requires --yes. Task corpus and run " + "provenance (task_run_links) are untouched; a full `spur history import` rebuilds everything.").option("--yes", "Confirm the destructive wipe", false).option(...SHARED_OPTIONS.jsonSupported).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options) => {
|
|
164163
|
+
if (!options.yes) {
|
|
164164
|
+
context.output.write(options.json ? toEnvelopeJson({ status: "error", message: "Refusing to wipe history tables without --yes." }, {
|
|
164165
|
+
enveloped: options.jsonEnvelope,
|
|
164166
|
+
error: {
|
|
164167
|
+
code: "INTERNAL_ERROR",
|
|
164168
|
+
message: "Refusing to wipe history tables without --yes.",
|
|
164169
|
+
details: { cliCode: "usage" }
|
|
164170
|
+
}
|
|
164171
|
+
}) : "spur history reset: refusing to wipe history tables without --yes.");
|
|
164172
|
+
context.setExitCode(1);
|
|
164173
|
+
return;
|
|
164174
|
+
}
|
|
164175
|
+
const svc = await makeHistoryService(context);
|
|
164176
|
+
const result = await svc.resetHistory();
|
|
164177
|
+
const note = result.unknown.length > 0 ? ` WARNING: ${result.unknown.length} unlisted history_* table(s) left intact: ${result.unknown.join(", ")}` : "";
|
|
164178
|
+
context.output.write(options.json ? toEnvelopeJson({
|
|
164179
|
+
cleared: result.cleared,
|
|
164180
|
+
skipped: result.skipped,
|
|
164181
|
+
unknown: result.unknown,
|
|
164182
|
+
clearedCount: result.cleared.length
|
|
164183
|
+
}, { enveloped: options.jsonEnvelope }) : `cleared ${result.cleared.length} history tables` + (result.skipped.length > 0 ? ` (${result.skipped.length} not present)` : "") + note);
|
|
164184
|
+
});
|
|
164185
|
+
}
|
|
164186
|
+
|
|
164187
|
+
// src/commands/history/summary.ts
|
|
164188
|
+
function registerHistorySummaryCommand(history, context) {
|
|
164189
|
+
history.command("summary").summary("Daily summary report: tokens, git activity, history health, annotations").description([
|
|
164190
|
+
"Generate the day\u2019s Markdown summary and write it to docs/daily/summary_<YYYYMMDD>.md",
|
|
164191
|
+
"(or --output). Prompts for learnings / issues / pending unless SP_DAILY_SUMMARY_NO_PROMPT=1",
|
|
164192
|
+
"(non-TTY stdin supplies the three answers as lines). Exit: 0 written, 1 generation failed."
|
|
164193
|
+
].join(`
|
|
164194
|
+
`)).option("--date <date>", "YYYY-MM-DD, today or yesterday (default: today)").option(...SHARED_OPTIONS.dryRunHistorySummary).option("--output <path>", "write to a custom path").option("--no-git", "skip git history collection").option("--no-ccusage", "skip token usage collection").action(async (options) => {
|
|
164195
|
+
await Promise.resolve().then(() => init_daily_summary());
|
|
164196
|
+
const argv = [];
|
|
164197
|
+
if (options.date !== undefined)
|
|
164198
|
+
argv.push("--date", options.date);
|
|
164199
|
+
if (options.dryRun === true)
|
|
164200
|
+
argv.push("--dry-run");
|
|
164201
|
+
if (options.output !== undefined)
|
|
164202
|
+
argv.push("--output", options.output);
|
|
164203
|
+
if (options.git === false)
|
|
164204
|
+
argv.push("--no-git");
|
|
164205
|
+
if (options.ccusage === false)
|
|
164206
|
+
argv.push("--no-ccusage");
|
|
164207
|
+
const code = await main(argv, {
|
|
164208
|
+
out: (line) => context.output.write(line),
|
|
164209
|
+
err: (line) => context.output.error(line)
|
|
164210
|
+
});
|
|
164211
|
+
context.setExitCode(code);
|
|
164212
|
+
});
|
|
164213
|
+
}
|
|
164214
|
+
|
|
164215
|
+
// src/commands/history/timeline.ts
|
|
164216
|
+
function registerHistoryTimelineCommand(history, context) {
|
|
164217
|
+
history.command("timeline").summary("Per-prompt work/wait time and tokens from the host transcript (JSON)").description([
|
|
164218
|
+
"Read the active host transcript read-only (CLAUDE_CODE_SESSION_ID under ~/.claude/projects,",
|
|
164219
|
+
"PI_SESSION_FILE, or --transcript) and print one JSON timeline: a segment per operator prompt,",
|
|
164220
|
+
'optional --group stages, and totals. No transcript \u2192 {"available":false,...}, exit 0.',
|
|
164221
|
+
"Exit: 0 timeline (or unavailable), 2 invalid --group or unreadable transcript."
|
|
164222
|
+
].join(`
|
|
164223
|
+
`)).option("--transcript <path>", "transcript JSONL path (default: the active host session)").option("--group <spec>", 'sum segments into stages, e.g. "1-3,4"').action(async (options) => {
|
|
164224
|
+
await Promise.resolve().then(() => init_session_timeline());
|
|
164225
|
+
const argv = [];
|
|
164226
|
+
if (options.transcript !== undefined)
|
|
164227
|
+
argv.push("--transcript", options.transcript);
|
|
164228
|
+
if (options.group !== undefined)
|
|
164229
|
+
argv.push("--group", options.group);
|
|
164230
|
+
const code = runSessionTimelineCli(argv, {
|
|
164231
|
+
env: context.env,
|
|
164232
|
+
write: (s) => writeStepText(context.output, "stdout", s),
|
|
164233
|
+
writeErr: (s) => writeStepText(context.output, "stderr", s)
|
|
164234
|
+
});
|
|
164235
|
+
context.setExitCode(code);
|
|
164236
|
+
});
|
|
164237
|
+
}
|
|
164238
|
+
|
|
164239
|
+
// src/commands/history/index.ts
|
|
164240
|
+
function registerHistoryCommand(program, context) {
|
|
164241
|
+
const noun = program.command("history").summary("import and analyze coding-agent history");
|
|
164242
|
+
registerHistoryImportCommand(noun, context);
|
|
164243
|
+
registerHistoryAnalyzeCommand(noun, context);
|
|
164244
|
+
registerHistoryResetCommand(noun, context);
|
|
164245
|
+
registerHistoryReportCommand(noun, context);
|
|
164246
|
+
registerHistoryDailyCommand(noun, context);
|
|
164247
|
+
registerHistoryAnatomyCommand(noun, context);
|
|
164248
|
+
registerHistoryTimelineCommand(noun, context);
|
|
164249
|
+
registerHistorySummaryCommand(noun, context);
|
|
164250
|
+
}
|
|
164251
|
+
|
|
164165
164252
|
// src/commands/init.ts
|
|
164166
164253
|
init_src4();
|
|
164167
164254
|
init_src();
|
|
@@ -164542,13 +164629,13 @@ ${entry}
|
|
|
164542
164629
|
}
|
|
164543
164630
|
function describeZgReadiness(status) {
|
|
164544
164631
|
if (!status.zg.available)
|
|
164545
|
-
return `zg not installed (optional) \u2014 install:
|
|
164632
|
+
return `zg not installed (optional) \u2014 install: ${ZG_INSTALL_HINT}`;
|
|
164546
164633
|
if (status.zg.index === "ready")
|
|
164547
164634
|
return "zg ready";
|
|
164548
164635
|
if (status.zg.index === "stale")
|
|
164549
|
-
return
|
|
164636
|
+
return `zg index stale \u2014 run: ${ZG_REFRESH_HINT}`;
|
|
164550
164637
|
if (status.zg.index === "missing")
|
|
164551
|
-
return
|
|
164638
|
+
return `zg index missing \u2014 run: ${ZG_INDEX_HINT}`;
|
|
164552
164639
|
return "zg status unknown";
|
|
164553
164640
|
}
|
|
164554
164641
|
|
|
@@ -171148,7 +171235,7 @@ init_dist5();
|
|
|
171148
171235
|
import { homedir as homedir17 } from "os";
|
|
171149
171236
|
import { basename as basename21, join as join94, relative as relative19, resolve as resolve37 } from "path";
|
|
171150
171237
|
// ../server/package.json
|
|
171151
|
-
var version4 = "0.4.
|
|
171238
|
+
var version4 = "0.4.24";
|
|
171152
171239
|
|
|
171153
171240
|
// ../server/src/modules/health/index.ts
|
|
171154
171241
|
var startedAt = Date.now();
|
|
@@ -176358,7 +176445,7 @@ init_src();
|
|
|
176358
176445
|
init_dist5();
|
|
176359
176446
|
import { mkdirSync as mkdirSync23, readFileSync as readFileSync28, renameSync as renameSync10, writeFileSync as writeFileSync20 } from "fs";
|
|
176360
176447
|
import { join as join100 } from "path";
|
|
176361
|
-
var COMMIT_GUARD_USAGE = "usage: spur workflow commit-guard
|
|
176448
|
+
var COMMIT_GUARD_USAGE = "usage: spur workflow commit-guard-start --run <id> | commit-guard-stage --run <id> [--base <sha>] -- <paths\u2026> | commit-guard-check --run <id> [--base <sha>]";
|
|
176362
176449
|
var isBookkeeping = (path) => path === ".spur" || path.startsWith(".spur/");
|
|
176363
176450
|
var at2 = (options, rel) => options.cwd ? join100(options.cwd, rel) : rel;
|
|
176364
176451
|
var artifactPath = (run) => join100(".spur", "run", `${run}-tree-start.json`);
|
|
@@ -176449,7 +176536,7 @@ var report = (exitCode, stdout = [], stderr = []) => ({
|
|
|
176449
176536
|
stderr
|
|
176450
176537
|
});
|
|
176451
176538
|
function missingStart(run) {
|
|
176452
|
-
return report(1, [], [`commit-guard: no ${artifactPath(run)} \u2014 run \`spur workflow commit-guard
|
|
176539
|
+
return report(1, [], [`commit-guard: no ${artifactPath(run)} \u2014 run \`spur workflow commit-guard-start --run ${run}\` first`]);
|
|
176453
176540
|
}
|
|
176454
176541
|
function commitGuardUsage() {
|
|
176455
176542
|
return report(1, [], [COMMIT_GUARD_USAGE]);
|
|
@@ -176580,21 +176667,21 @@ function registerWorkflowCommitGuardCommand(workflow, context) {
|
|
|
176580
176667
|
return;
|
|
176581
176668
|
emit(guarded(run));
|
|
176582
176669
|
};
|
|
176583
|
-
|
|
176584
|
-
|
|
176585
|
-
|
|
176586
|
-
|
|
176587
|
-
|
|
176588
|
-
|
|
176589
|
-
|
|
176590
|
-
|
|
176591
|
-
|
|
176592
|
-
|
|
176593
|
-
|
|
176594
|
-
|
|
176595
|
-
|
|
176596
|
-
|
|
176597
|
-
})
|
|
176670
|
+
workflow.command("commit-guard-start").description("Fingerprint HEAD and the dirty working files into .spur/run/<run>-tree-start.json").option("--run <id>", "Run id the snapshot belongs to").allowExcessArguments(false).action((options) => {
|
|
176671
|
+
withRun(options.run, (run) => commitGuardStart(run, { cwd: context.cwd }));
|
|
176672
|
+
});
|
|
176673
|
+
workflow.command("commit-guard-stage").description("Stage only the listed paths this run wrote; print {staged, foreign}").argument("[paths...]", "Candidate paths (after `--`)").option("--run <id>", "Run id whose tree-start snapshot partitions the paths").option("--base <sha>", "Diff base (default: the snapshot HEAD)").action((paths, options) => {
|
|
176674
|
+
withRun(options.run, (run) => commitGuardStage(run, paths, {
|
|
176675
|
+
cwd: context.cwd,
|
|
176676
|
+
...options.base === undefined ? {} : { base: options.base }
|
|
176677
|
+
}));
|
|
176678
|
+
});
|
|
176679
|
+
workflow.command("commit-guard-check").description("Report working-tree changes this run did not write; stage nothing").option("--run <id>", 'Run id whose tree-start snapshot defines "written by this run"').option("--base <sha>", "Diff base (default: the snapshot HEAD)").allowExcessArguments(false).action((options) => {
|
|
176680
|
+
withRun(options.run, (run) => commitGuardCheck(run, {
|
|
176681
|
+
cwd: context.cwd,
|
|
176682
|
+
...options.base === undefined ? {} : { base: options.base }
|
|
176683
|
+
}));
|
|
176684
|
+
});
|
|
176598
176685
|
}
|
|
176599
176686
|
|
|
176600
176687
|
// src/commands/workflow/continue.ts
|
|
@@ -177089,7 +177176,7 @@ async function runBudgetGate(opts) {
|
|
|
177089
177176
|
// src/commands/workflow/cost.ts
|
|
177090
177177
|
var collect3 = (value, previous) => [...previous, value];
|
|
177091
177178
|
function registerWorkflowCostCommand(workflow, context, deps = {}) {
|
|
177092
|
-
workflow.command("cost").description([
|
|
177179
|
+
workflow.command("cost").summary("Report per-workflow wall-clock and cost from real runs (or --budget gate)").description([
|
|
177093
177180
|
"Report per-workflow wall-clock and cost from REAL (terminal, non-dry) runs in .spur/spur.db.",
|
|
177094
177181
|
"The cohort defaults to every definition in .spur/workflows/ (bookkeeping workflows excluded).",
|
|
177095
177182
|
"--json emits the byte-stable baseline payload (workflow + state rows, key-sorted).",
|
|
@@ -178503,7 +178590,13 @@ function registerWorkflowCommand(program, context) {
|
|
|
178503
178590
|
}
|
|
178504
178591
|
|
|
178505
178592
|
// src/workflow-transport-argv.ts
|
|
178506
|
-
var WORKFLOW_TRANSPORT_VERBS = [
|
|
178593
|
+
var WORKFLOW_TRANSPORT_VERBS = [
|
|
178594
|
+
"commit-guard-start",
|
|
178595
|
+
"commit-guard-stage",
|
|
178596
|
+
"commit-guard-check",
|
|
178597
|
+
"persist-out",
|
|
178598
|
+
"persist-check"
|
|
178599
|
+
];
|
|
178507
178600
|
function isWorkflowTransportInvocation(argv) {
|
|
178508
178601
|
const tokens = argv.filter((token) => !["--no-logo", "--cli-verbose", "-v"].includes(token));
|
|
178509
178602
|
return tokens[0] === "workflow" && WORKFLOW_TRANSPORT_VERBS.includes(tokens[1] ?? "");
|
|
@@ -178516,7 +178609,7 @@ function registerWorkflowTransportCommands(workflow, context) {
|
|
|
178516
178609
|
}
|
|
178517
178610
|
|
|
178518
178611
|
// src/index.ts
|
|
178519
|
-
async function
|
|
178612
|
+
async function main2(argv = process.argv.slice(2), options = {}) {
|
|
178520
178613
|
const output = options.output ?? consoleOutput;
|
|
178521
178614
|
let exitCode = 0;
|
|
178522
178615
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -178713,7 +178806,7 @@ async function runCli() {
|
|
|
178713
178806
|
consoleOutput.write(bannerText());
|
|
178714
178807
|
}
|
|
178715
178808
|
try {
|
|
178716
|
-
return await
|
|
178809
|
+
return await main2();
|
|
178717
178810
|
} finally {
|
|
178718
178811
|
await disposeDecisionServices();
|
|
178719
178812
|
}
|
|
@@ -178724,7 +178817,7 @@ if (import.meta.main) {
|
|
|
178724
178817
|
export {
|
|
178725
178818
|
bannerText,
|
|
178726
178819
|
buildProgram,
|
|
178727
|
-
|
|
178820
|
+
main2 as main,
|
|
178728
178821
|
runCli,
|
|
178729
178822
|
shouldRenderBanner
|
|
178730
178823
|
};
|