@gobing-ai/spur 0.4.23 → 0.4.25
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/rules/surface/check-cli-surface.yaml +5 -0
- package/package.json +1 -1
- package/plugins/sp/README.md +52 -0
- package/plugins/sp/agents/expert-spur.md +1 -0
- package/plugins/sp/agents/super-coder.md +1 -0
- package/plugins/sp/agents/super-planner.md +1 -0
- package/plugins/sp/agents/super-reviewer.md +1 -0
- package/plugins/sp/commands/dev-arch.md +1 -0
- package/plugins/sp/commands/dev-brainstorm.md +1 -0
- package/plugins/sp/commands/dev-changelog.md +1 -0
- package/plugins/sp/commands/dev-daily.md +1 -0
- package/plugins/sp/commands/dev-debug.md +1 -0
- package/plugins/sp/commands/dev-dogfood.md +1 -0
- package/plugins/sp/commands/dev-feature-change.md +1 -0
- package/plugins/sp/commands/dev-find-conflict.md +1 -0
- package/plugins/sp/commands/dev-find-issue.md +1 -0
- package/plugins/sp/commands/dev-find-next.md +1 -0
- package/plugins/sp/commands/dev-find-way.md +1 -0
- package/plugins/sp/commands/dev-fixall.md +1 -0
- package/plugins/sp/commands/dev-fixgha.md +1 -0
- package/plugins/sp/commands/dev-gitmsg.md +1 -0
- package/plugins/sp/commands/dev-gtd.md +1 -0
- package/plugins/sp/commands/dev-handover.md +1 -0
- package/plugins/sp/commands/dev-idea.md +1 -0
- package/plugins/sp/commands/dev-job-dump.md +1 -0
- package/plugins/sp/commands/dev-job-resume.md +1 -0
- package/plugins/sp/commands/dev-next.md +1 -0
- package/plugins/sp/commands/dev-plan.md +1 -0
- package/plugins/sp/commands/dev-pr-review.md +1 -0
- package/plugins/sp/commands/dev-refactor.md +1 -0
- package/plugins/sp/commands/dev-refine.md +1 -0
- package/plugins/sp/commands/dev-refineall.md +1 -0
- package/plugins/sp/commands/dev-refresh.md +1 -0
- package/plugins/sp/commands/dev-reverse.md +1 -0
- package/plugins/sp/commands/dev-review-session.md +1 -0
- package/plugins/sp/commands/dev-review.md +1 -0
- package/plugins/sp/commands/dev-run.md +1 -0
- package/plugins/sp/commands/dev-runall.md +1 -0
- package/plugins/sp/commands/dev-simplify.md +1 -0
- package/plugins/sp/commands/dev-unit.md +1 -0
- package/plugins/sp/commands/dev-verify.md +1 -0
- package/plugins/sp/commands/dev-verifyall.md +1 -0
- package/plugins/sp/commands/dev-wrap.md +1 -0
- package/plugins/sp/commands/dev-wrapall.md +1 -0
- package/plugins/sp/commands/rule-add.md +1 -0
- package/plugins/sp/commands/rule-refine.md +1 -0
- package/plugins/sp/commands/rule-scan.md +1 -0
- package/plugins/sp/commands/spur-init.md +1 -0
- package/plugins/sp/commands/workflow-add.md +1 -0
- package/plugins/sp/commands/workflow-refine.md +1 -0
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/brainstorm/SKILL.md +1 -0
- package/plugins/sp/skills/branch-workflow/SKILL.md +1 -0
- package/plugins/sp/skills/code-implementation/SKILL.md +1 -0
- package/plugins/sp/skills/code-improvement/SKILL.md +1 -0
- package/plugins/sp/skills/code-refactoring/SKILL.md +1 -0
- package/plugins/sp/skills/code-review/SKILL.md +1 -0
- package/plugins/sp/skills/code-simplification/SKILL.md +1 -0
- package/plugins/sp/skills/code-testing/SKILL.md +1 -0
- package/plugins/sp/skills/code-verification/SKILL.md +1 -0
- package/plugins/sp/skills/conflict-finding/SKILL.md +1 -0
- package/plugins/sp/skills/daily-summary/SKILL.md +1 -0
- package/plugins/sp/skills/doc-evolve/SKILL.md +1 -0
- package/plugins/sp/skills/dogfood-testing/SKILL.md +1 -0
- package/plugins/sp/skills/doubt-driven-development/SKILL.md +1 -0
- package/plugins/sp/skills/fleet-join/SKILL.md +1 -0
- package/plugins/sp/skills/functional-review/SKILL.md +1 -0
- package/plugins/sp/skills/history-anatomy/SKILL.md +1 -0
- package/plugins/sp/skills/indexed-context/SKILL.md +1 -0
- package/plugins/sp/skills/issue-finding/SKILL.md +1 -0
- package/plugins/sp/skills/next-feature/SKILL.md +1 -0
- package/plugins/sp/skills/next-router/SKILL.md +1 -0
- package/plugins/sp/skills/parallel-execution/SKILL.md +1 -0
- package/plugins/sp/skills/pr-reviewing/SKILL.md +1 -0
- package/plugins/sp/skills/redesign-web-ui/SKILL.md +1 -0
- package/plugins/sp/skills/reverse-engineering/SKILL.md +1 -0
- package/plugins/sp/skills/session-review/SKILL.md +1 -0
- package/plugins/sp/skills/source-driven-development/SKILL.md +1 -0
- package/plugins/sp/skills/sp-bot-bootstrap/SKILL.md +75 -0
- package/plugins/sp/skills/spec-decomposition/SKILL.md +1 -0
- package/plugins/sp/skills/spur-check/SKILL.md +1 -0
- package/plugins/sp/skills/spur-cli/SKILL.md +1 -0
- package/plugins/sp/skills/spur-cli/references/history.md +1 -1
- package/plugins/sp/skills/spur-composer/SKILL.md +1 -0
- package/plugins/sp/skills/spur-dev/SKILL.md +1 -0
- package/plugins/sp/skills/spur-doctor/SKILL.md +1 -0
- package/plugins/sp/skills/sys-architecture/SKILL.md +1 -0
- package/plugins/sp/skills/sys-debugging/SKILL.md +1 -0
- package/plugins/sp/skills/taste-refactoring-api/SKILL.md +1 -0
- package/plugins/sp/skills/taste-refactoring-architect/SKILL.md +1 -0
- package/plugins/sp/skills/taste-refactoring-tests/SKILL.md +1 -0
- package/plugins/sp/skills/taste-refactoring-ui/SKILL.md +1 -0
- package/plugins/sp/skills/test-driven-development/SKILL.md +1 -0
- package/plugins/sp/skills/wayfinder/SKILL.md +1 -0
- package/spur.js +807 -706
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();
|
|
@@ -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",
|
|
@@ -161004,6 +161008,98 @@ async function publishPackage(target, opts = {}) {
|
|
|
161004
161008
|
}
|
|
161005
161009
|
}
|
|
161006
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
|
+
|
|
161007
161103
|
// ../../packages/app/src/services/pr-review/index.ts
|
|
161008
161104
|
init_src();
|
|
161009
161105
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
@@ -161997,7 +162093,7 @@ function registerBuilderPrCommand(builder, context) {
|
|
|
161997
162093
|
"returns PENDING. Exit codes: 0 ok \xB7 2 hard failure \xB7 3 wait timeout",
|
|
161998
162094
|
"(verdict PENDING \u2014 pending, never failure)."
|
|
161999
162095
|
].join(`
|
|
162000
|
-
`)).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) => {
|
|
162001
162097
|
run("review", {
|
|
162002
162098
|
...options.force === true ? { force: true } : {},
|
|
162003
162099
|
...options.focus !== undefined ? { focus: options.focus } : {},
|
|
@@ -162010,6 +162106,27 @@ function registerBuilderPrCommand(builder, context) {
|
|
|
162010
162106
|
});
|
|
162011
162107
|
}
|
|
162012
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
|
+
}
|
|
162127
|
+
});
|
|
162128
|
+
}
|
|
162129
|
+
|
|
162013
162130
|
// ../../packages/app/src/services/branch-topology/render.ts
|
|
162014
162131
|
var INDENT = " ";
|
|
162015
162132
|
var MERMAID_DEFAULT_BRANCH = "main";
|
|
@@ -162500,7 +162617,7 @@ function registerBuilderShowBranchesCommand(builder, context) {
|
|
|
162500
162617
|
"Exit codes: 0 ok \xB7 1 usage (bad --format/--limit/--max-commits, unknown base ref,",
|
|
162501
162618
|
"--local with --remote) \xB7 2 git failure (not a repository, git missing)."
|
|
162502
162619
|
].join(`
|
|
162503
|
-
`)).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) => {
|
|
162504
162621
|
const format = resolveFormat(options.format, command.getOptionValueSource("format") === "cli", options.json === true, context);
|
|
162505
162622
|
if (format === null)
|
|
162506
162623
|
return;
|
|
@@ -162575,110 +162692,12 @@ function usageError(message, context) {
|
|
|
162575
162692
|
context.setExitCode(1);
|
|
162576
162693
|
}
|
|
162577
162694
|
|
|
162578
|
-
// src/commands/builder.ts
|
|
162695
|
+
// src/commands/builder/index.ts
|
|
162579
162696
|
function registerBuilderCommand(program, context) {
|
|
162580
162697
|
const noun = program.command("builder").summary("release plumbing: version bumps, tags, branch topology, and PR-review ops");
|
|
162581
|
-
noun
|
|
162582
|
-
|
|
162583
|
-
|
|
162584
|
-
" spur builder bump-ver 0.1.4 # every released package",
|
|
162585
|
-
" spur builder bump-ver spur 0.1.4 # one package by id",
|
|
162586
|
-
" spur builder bump-ver --check spur # pre-publish drift guard (read-only)",
|
|
162587
|
-
"--check fails (exit 1) when .claude-plugin/marketplace.json or a listed plugin.json",
|
|
162588
|
-
"drifted from the package version; target defaults to the aggregate package."
|
|
162589
|
-
].join(`
|
|
162590
|
-
`)).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) => {
|
|
162591
|
-
const args = [target, version].filter((value) => value !== undefined);
|
|
162592
|
-
if (options.all === true)
|
|
162593
|
-
args.push("--all");
|
|
162594
|
-
if (options.push === true)
|
|
162595
|
-
args.push("--push");
|
|
162596
|
-
try {
|
|
162597
|
-
if (options.check === true) {
|
|
162598
|
-
if (version !== undefined)
|
|
162599
|
-
throw new Error("bump-ver --check takes only a package id");
|
|
162600
|
-
if (options.all === true || options.push === true)
|
|
162601
|
-
throw new Error("bump-ver --check rejects incompatible write flags (--push/--all)");
|
|
162602
|
-
const code = await checkVersionDrift(target, context.cwd, context.output, context.spurConfig);
|
|
162603
|
-
if (options.json === true) {
|
|
162604
|
-
context.output.write(toEnvelopeJson({ ok: code === 0, verb: "bump-ver", check: true, target: target ?? "aggregate" }, { enveloped: options.jsonEnvelope }));
|
|
162605
|
-
}
|
|
162606
|
-
if (code !== 0)
|
|
162607
|
-
context.setExitCode(code);
|
|
162608
|
-
return;
|
|
162609
|
-
}
|
|
162610
|
-
await bumpVer(args, context.cwd, context.output, context.spurConfig);
|
|
162611
|
-
if (options.json === true) {
|
|
162612
|
-
context.output.write(toEnvelopeJson({ ok: true, verb: "bump-ver", target: target ?? "all", version: version ?? "" }, { enveloped: options.jsonEnvelope }));
|
|
162613
|
-
}
|
|
162614
|
-
} catch (err) {
|
|
162615
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
162616
|
-
if (options.json === true) {
|
|
162617
|
-
context.output.write(toEnvelopeJson({ ok: false, verb: "bump-ver", error: message }, {
|
|
162618
|
-
enveloped: options.jsonEnvelope,
|
|
162619
|
-
error: {
|
|
162620
|
-
code: "INTERNAL_ERROR",
|
|
162621
|
-
message,
|
|
162622
|
-
details: { cliCode: "bump-ver", verb: "bump-ver" }
|
|
162623
|
-
}
|
|
162624
|
-
}));
|
|
162625
|
-
context.setExitCode(1);
|
|
162626
|
-
return;
|
|
162627
|
-
}
|
|
162628
|
-
context.output.error(message);
|
|
162629
|
-
context.setExitCode(1);
|
|
162630
|
-
}
|
|
162631
|
-
});
|
|
162632
|
-
noun.command("drop-tags").summary("delete release tags locally and optionally on origin").description([
|
|
162633
|
-
"Delete one package release tag (or every released tag plus the aggregate",
|
|
162634
|
-
"with --all / a bare version). Use --remote to also delete on origin."
|
|
162635
|
-
].join(`
|
|
162636
|
-
`)).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) => {
|
|
162637
|
-
const args = [target, version].filter((value) => value !== undefined);
|
|
162638
|
-
if (options.all === true)
|
|
162639
|
-
args.push("--all");
|
|
162640
|
-
if (options.remote === true)
|
|
162641
|
-
args.push("--remote");
|
|
162642
|
-
try {
|
|
162643
|
-
await dropTags(args, context.cwd, context.output, context.spurConfig);
|
|
162644
|
-
if (options.json === true) {
|
|
162645
|
-
context.output.write(toEnvelopeJson({ ok: true, verb: "drop-tags", target: target ?? "all", version: version ?? "" }, { enveloped: options.jsonEnvelope }));
|
|
162646
|
-
}
|
|
162647
|
-
} catch (err) {
|
|
162648
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
162649
|
-
if (options.json === true) {
|
|
162650
|
-
context.output.write(toEnvelopeJson({ ok: false, verb: "drop-tags", error: message }, {
|
|
162651
|
-
enveloped: options.jsonEnvelope,
|
|
162652
|
-
error: {
|
|
162653
|
-
code: "INTERNAL_ERROR",
|
|
162654
|
-
message,
|
|
162655
|
-
details: { cliCode: "drop-tags", verb: "drop-tags" }
|
|
162656
|
-
}
|
|
162657
|
-
}));
|
|
162658
|
-
context.setExitCode(1);
|
|
162659
|
-
return;
|
|
162660
|
-
}
|
|
162661
|
-
context.output.error(message);
|
|
162662
|
-
context.setExitCode(1);
|
|
162663
|
-
}
|
|
162664
|
-
});
|
|
162665
|
-
noun.command("publish").summary("resolve workspace/catalog ranges, build, and npm publish a package (CI)").description([
|
|
162666
|
-
"CI publish for npm Trusted Publishing (OIDC): rewrite workspace:/catalog: ranges in",
|
|
162667
|
-
"<dir>/package.json, run `bun run build:bundle`, `npm publish --access public`, and",
|
|
162668
|
-
"restore the manifest on every exit path."
|
|
162669
|
-
].join(`
|
|
162670
|
-
`)).option("--otp <code>", "npm one-time password (local publishes only)").argument("<dir>", "package directory, relative to the workspace root").action(async (dir, options) => {
|
|
162671
|
-
try {
|
|
162672
|
-
await publishPackage(dir, {
|
|
162673
|
-
repoRoot: context.cwd,
|
|
162674
|
-
output: context.output,
|
|
162675
|
-
...options.otp !== undefined ? { otp: options.otp } : {}
|
|
162676
|
-
});
|
|
162677
|
-
} catch (err) {
|
|
162678
|
-
context.output.error(err instanceof Error ? err.message : String(err));
|
|
162679
|
-
context.setExitCode(1);
|
|
162680
|
-
}
|
|
162681
|
-
});
|
|
162698
|
+
registerBuilderBumpVerCommand(noun, context);
|
|
162699
|
+
registerBuilderDropTagsCommand(noun, context);
|
|
162700
|
+
registerBuilderPublishCommand(noun, context);
|
|
162682
162701
|
registerBuilderShowBranchesCommand(noun, context);
|
|
162683
162702
|
registerBuilderPrCommand(noun, context);
|
|
162684
162703
|
}
|
|
@@ -163114,161 +163133,35 @@ function fail2(context, options, error) {
|
|
|
163114
163133
|
context.setExitCode(1);
|
|
163115
163134
|
}
|
|
163116
163135
|
|
|
163117
|
-
// src/commands/feature.ts
|
|
163136
|
+
// src/commands/feature/advance.ts
|
|
163118
163137
|
init_src4();
|
|
163119
|
-
init_src2();
|
|
163120
163138
|
|
|
163121
|
-
// src/commands/feature/
|
|
163122
|
-
|
|
163123
|
-
|
|
163124
|
-
|
|
163125
|
-
"-idea-ready.json), applies dependencies, refreshes the roster, checks each task and writes",
|
|
163126
|
-
".spur/run/<runId>-idea-handoff.md with one recommended next command.",
|
|
163127
|
-
"Exit: 0 success; 1 failure (missing ids or inputs, a mapping or CLI error).",
|
|
163128
|
-
"Flags fall back to the workflow env names: featureId, __runId, spurBin."
|
|
163129
|
-
].join(`
|
|
163130
|
-
`)).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) => {
|
|
163131
|
-
const runId = options.runId ?? context.env.__runId;
|
|
163132
|
-
if (runId && !acceptArtifactId(runId, "run id", context))
|
|
163133
|
-
return;
|
|
163134
|
-
await Promise.resolve().then(() => init_src4());
|
|
163135
|
-
const env = context.env;
|
|
163136
|
-
const outcome = await runIdeaHandoffCli({
|
|
163137
|
-
__runId: options.runId ?? env.__runId,
|
|
163138
|
-
...options.spurBin === undefined && env.spurBin === undefined ? { spurArgv: resolveSpurArgv() } : {},
|
|
163139
|
-
featureId: id ?? env.featureId,
|
|
163140
|
-
spurBin: options.spurBin ?? env.spurBin ?? resolveSpurBin()
|
|
163141
|
-
});
|
|
163142
|
-
context.setExitCode(outcome.exitCode);
|
|
163143
|
-
});
|
|
163139
|
+
// src/commands/feature/shared.ts
|
|
163140
|
+
init_src4();
|
|
163141
|
+
async function makeReceiptRunPort(context) {
|
|
163142
|
+
return createFeatureReceiptRunPort(await context.getDb(), context.fs);
|
|
163144
163143
|
}
|
|
163145
|
-
|
|
163146
|
-
|
|
163147
|
-
|
|
163148
|
-
|
|
163149
|
-
|
|
163150
|
-
|
|
163151
|
-
|
|
163152
|
-
|
|
163153
|
-
|
|
163154
|
-
|
|
163155
|
-
|
|
163156
|
-
|
|
163157
|
-
|
|
163158
|
-
|
|
163159
|
-
|
|
163160
|
-
|
|
163161
|
-
workdir: context.cwd,
|
|
163162
|
-
featureId,
|
|
163163
|
-
runId: options.runId || env.__runId || "",
|
|
163164
|
-
cmdOverride: options.cmd || env.verificationCmd || ""
|
|
163165
|
-
});
|
|
163166
|
-
context.output.write(`feature verify: ${featureId} ${result.message}`);
|
|
163167
|
-
} catch (err) {
|
|
163168
|
-
context.output.error(`feature verify: ${String(err)}`);
|
|
163169
|
-
context.setExitCode(1);
|
|
163170
|
-
}
|
|
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
|
|
163171
163160
|
});
|
|
163172
163161
|
}
|
|
163173
163162
|
|
|
163174
|
-
// src/commands/feature.ts
|
|
163175
|
-
function
|
|
163176
|
-
const feature = program.command("feature").summary("manage features (hierarchical IDs)");
|
|
163177
|
-
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) => {
|
|
163178
|
-
const svc = await makeService2(context, options.folder);
|
|
163179
|
-
try {
|
|
163180
|
-
const result = await svc.create(name, options.parent);
|
|
163181
|
-
if (options.json) {
|
|
163182
|
-
context.output.write(toEnvelopeJson(result, { enveloped: options.jsonEnvelope }));
|
|
163183
|
-
} else {
|
|
163184
|
-
context.output.write(`Created feature ${result.ref.id}: ${result.ref.filePath}`);
|
|
163185
|
-
}
|
|
163186
|
-
} catch (err) {
|
|
163187
|
-
writeJsonError(context.output, options, String(err));
|
|
163188
|
-
context.setExitCode(1);
|
|
163189
|
-
}
|
|
163190
|
-
});
|
|
163191
|
-
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) => {
|
|
163192
|
-
const svc = await makeService2(context, options.folder);
|
|
163193
|
-
try {
|
|
163194
|
-
const result = await svc.show(id);
|
|
163195
|
-
if (result === null) {
|
|
163196
|
-
writeJsonError(context.output, options, `Feature ${id} not found`, "NOT_FOUND");
|
|
163197
|
-
context.setExitCode(1);
|
|
163198
|
-
return;
|
|
163199
|
-
}
|
|
163200
|
-
if (options.json) {
|
|
163201
|
-
const { content, ...rest } = result;
|
|
163202
|
-
context.output.write(toEnvelopeJson({ ...rest, content }, { enveloped: options.jsonEnvelope }));
|
|
163203
|
-
} else {
|
|
163204
|
-
context.output.write(result.content);
|
|
163205
|
-
}
|
|
163206
|
-
} catch (err) {
|
|
163207
|
-
writeJsonError(context.output, options, String(err));
|
|
163208
|
-
context.setExitCode(1);
|
|
163209
|
-
}
|
|
163210
|
-
});
|
|
163211
|
-
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", [
|
|
163212
|
-
"Section names are validated against the closed-world canonical set; a rejected",
|
|
163213
|
-
"`--section` costs a failed write. List valid names first:",
|
|
163214
|
-
"`spur task sections <wbs> list`."
|
|
163215
|
-
].join(`
|
|
163216
|
-
`)).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) => {
|
|
163217
|
-
const svc = await makeService2(context, options.folder);
|
|
163218
|
-
try {
|
|
163219
|
-
let result;
|
|
163220
|
-
if (options.section !== undefined) {
|
|
163221
|
-
if (options.fromFile === undefined) {
|
|
163222
|
-
writeJsonError(context.output, options, "--from-file is required with --section", "VALIDATION_FAILED");
|
|
163223
|
-
context.setExitCode(2);
|
|
163224
|
-
return;
|
|
163225
|
-
}
|
|
163226
|
-
result = await svc.updateSection(id, options.section, options.fromFile);
|
|
163227
|
-
if (!options.json) {
|
|
163228
|
-
for (const warning of result.warnings ?? []) {
|
|
163229
|
-
context.output.error(warning);
|
|
163230
|
-
}
|
|
163231
|
-
context.output.write(`Updated section '${options.section}' in feature ${result.ref.id}`);
|
|
163232
|
-
}
|
|
163233
|
-
} else if (options.fromFile !== undefined) {
|
|
163234
|
-
writeJsonError(context.output, options, "--section is required with --from-file", "VALIDATION_FAILED");
|
|
163235
|
-
context.setExitCode(2);
|
|
163236
|
-
return;
|
|
163237
|
-
}
|
|
163238
|
-
if (options.field !== undefined) {
|
|
163239
|
-
if (options.value === undefined) {
|
|
163240
|
-
writeJsonError(context.output, options, "--value is required with --field", "VALIDATION_FAILED");
|
|
163241
|
-
context.setExitCode(2);
|
|
163242
|
-
return;
|
|
163243
|
-
}
|
|
163244
|
-
result = await svc.update(id, options.field, options.value);
|
|
163245
|
-
if (!options.json) {
|
|
163246
|
-
context.output.write(`Updated ${options.field} on feature ${result.ref.id}`);
|
|
163247
|
-
}
|
|
163248
|
-
} else if (options.value !== undefined) {
|
|
163249
|
-
writeJsonError(context.output, options, "--field is required with --value", "VALIDATION_FAILED");
|
|
163250
|
-
context.setExitCode(2);
|
|
163251
|
-
return;
|
|
163252
|
-
}
|
|
163253
|
-
if (status !== undefined) {
|
|
163254
|
-
result = await svc.transition(id, status);
|
|
163255
|
-
if (!options.json) {
|
|
163256
|
-
context.output.write(`${result.ref.id}: ${result.fromStatus} \u2192 ${result.toStatus}`);
|
|
163257
|
-
}
|
|
163258
|
-
}
|
|
163259
|
-
if (result === undefined) {
|
|
163260
|
-
writeJsonError(context.output, options, "Either <status>, --field/--value, or --section/--from-file is required", "VALIDATION_FAILED");
|
|
163261
|
-
context.setExitCode(2);
|
|
163262
|
-
return;
|
|
163263
|
-
}
|
|
163264
|
-
if (options.json) {
|
|
163265
|
-
context.output.write(toEnvelopeJson(result, { enveloped: options.jsonEnvelope }));
|
|
163266
|
-
}
|
|
163267
|
-
} catch (err) {
|
|
163268
|
-
writeJsonError(context.output, options, String(err));
|
|
163269
|
-
context.setExitCode(1);
|
|
163270
|
-
}
|
|
163271
|
-
});
|
|
163163
|
+
// src/commands/feature/advance.ts
|
|
163164
|
+
function registerFeatureAdvanceCommand(feature, context) {
|
|
163272
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) => {
|
|
163273
163166
|
const svc = await makeService2(context, options.folder);
|
|
163274
163167
|
const target = options.to ?? "done";
|
|
@@ -163333,6 +163226,148 @@ function registerFeatureCommand(program, context) {
|
|
|
163333
163226
|
context.setExitCode(1);
|
|
163334
163227
|
}
|
|
163335
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) {
|
|
163336
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) => {
|
|
163337
163372
|
const svc = await makeService2(context, options.folder);
|
|
163338
163373
|
try {
|
|
@@ -163359,6 +163394,10 @@ function registerFeatureCommand(program, context) {
|
|
|
163359
163394
|
context.setExitCode(1);
|
|
163360
163395
|
}
|
|
163361
163396
|
});
|
|
163397
|
+
}
|
|
163398
|
+
|
|
163399
|
+
// src/commands/feature/move.ts
|
|
163400
|
+
function registerFeatureMoveCommand(feature, context) {
|
|
163362
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) => {
|
|
163363
163402
|
const svc = await makeService2(context, options.folder);
|
|
163364
163403
|
try {
|
|
@@ -163379,6 +163418,11 @@ function registerFeatureCommand(program, context) {
|
|
|
163379
163418
|
context.setExitCode(1);
|
|
163380
163419
|
}
|
|
163381
163420
|
});
|
|
163421
|
+
}
|
|
163422
|
+
|
|
163423
|
+
// src/commands/feature/refresh.ts
|
|
163424
|
+
init_src4();
|
|
163425
|
+
function registerFeatureRefreshCommand(feature, context) {
|
|
163382
163426
|
feature.command("refresh").summary("Rebuild INDEX.md and each feature ## Tasks table from task edges (docs only \u2014 no status change).").description([
|
|
163383
163427
|
"Regenerate derived views so feature markdown matches the task graph:",
|
|
163384
163428
|
" \u2022 docs/features/INDEX.md (ID-encoded tree)",
|
|
@@ -163424,69 +163468,34 @@ function registerFeatureCommand(program, context) {
|
|
|
163424
163468
|
context.setExitCode(1);
|
|
163425
163469
|
}
|
|
163426
163470
|
});
|
|
163427
|
-
|
|
163428
|
-
|
|
163429
|
-
|
|
163430
|
-
|
|
163431
|
-
|
|
163432
|
-
|
|
163433
|
-
tasksDirs.unshift(tasksDir);
|
|
163434
|
-
const svc = new FeatureCheckService(context.fs);
|
|
163435
|
-
const json = options.json === true;
|
|
163436
|
-
const strict = options.strict === true;
|
|
163437
|
-
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);
|
|
163438
163477
|
try {
|
|
163439
|
-
const
|
|
163440
|
-
|
|
163441
|
-
|
|
163442
|
-
|
|
163443
|
-
|
|
163444
|
-
if (!fileName) {
|
|
163445
|
-
writeJsonError(context.output, options, `Feature ${fid} not found`, "NOT_FOUND");
|
|
163446
|
-
context.setExitCode(1);
|
|
163447
|
-
return;
|
|
163448
|
-
}
|
|
163449
|
-
const result = await svc.check(`${featuresDir}/${fileName}`, fid, {
|
|
163450
|
-
strict,
|
|
163451
|
-
featuresDir,
|
|
163452
|
-
tasksDir,
|
|
163453
|
-
tasksDirs,
|
|
163454
|
-
runDir: context.fs.resolve(".spur/run"),
|
|
163455
|
-
receiptRunPort: await makeReceiptRunPort(context),
|
|
163456
|
-
severityOverrides: resolved.severityOverrides,
|
|
163457
|
-
asStatus: options.as,
|
|
163458
|
-
fix: options.fix === true,
|
|
163459
|
-
inventory: inventoryText,
|
|
163460
|
-
transitionPort: options.fix === true ? async (targetId, to) => {
|
|
163461
|
-
const featureSvc = await makeService2(context, options.folder);
|
|
163462
|
-
return featureSvc.transition(targetId, to);
|
|
163463
|
-
} : undefined
|
|
163464
|
-
});
|
|
163465
|
-
results.push(result);
|
|
163466
|
-
if (!json) {
|
|
163467
|
-
if (result.repairs !== undefined && result.repairs.length > 0) {
|
|
163468
|
-
for (const r of result.repairs) {
|
|
163469
|
-
context.output.write(` [FIX] ${r.kind} ${r.section}: ${r.detail}`);
|
|
163470
|
-
}
|
|
163471
|
-
}
|
|
163472
|
-
context.output.write(`
|
|
163473
|
-
${result.id} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
163474
|
-
for (const f of result.findings) {
|
|
163475
|
-
const tag = f.severity === "error" ? "ERR" : "WARN";
|
|
163476
|
-
context.output.write(` [${tag}] ${f.layer} ${f.section}: ${f.message}`);
|
|
163477
|
-
}
|
|
163478
|
-
}
|
|
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;
|
|
163479
163483
|
}
|
|
163480
|
-
if (json) {
|
|
163481
|
-
|
|
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);
|
|
163482
163489
|
}
|
|
163483
|
-
if (results.some((r) => !r.pass))
|
|
163484
|
-
context.setExitCode(1);
|
|
163485
163490
|
} catch (err) {
|
|
163486
163491
|
writeJsonError(context.output, options, String(err));
|
|
163487
163492
|
context.setExitCode(1);
|
|
163488
163493
|
}
|
|
163489
163494
|
});
|
|
163495
|
+
}
|
|
163496
|
+
|
|
163497
|
+
// src/commands/feature/sync.ts
|
|
163498
|
+
function registerFeatureSyncCommand(feature, context) {
|
|
163490
163499
|
feature.command("sync").summary("Align feature lifecycle status with linked task states (status only \u2014 runs transition guards).").description([
|
|
163491
163500
|
"Derive a feature status proposal from linked task statuses and apply legal hops",
|
|
163492
163501
|
"(e.g. all tasks done \u2192 advance toward done; reopened work \u2192 reopen the feature).",
|
|
@@ -163545,165 +163554,134 @@ ${result.id} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
163545
163554
|
context.setExitCode(1);
|
|
163546
163555
|
}
|
|
163547
163556
|
});
|
|
163548
|
-
registerFeatureVerifyCommand(feature, context);
|
|
163549
|
-
registerFeatureHandoffCommand(feature, context);
|
|
163550
163557
|
}
|
|
163551
|
-
|
|
163552
|
-
|
|
163553
|
-
|
|
163554
|
-
|
|
163555
|
-
|
|
163556
|
-
|
|
163557
|
-
|
|
163558
|
-
|
|
163559
|
-
|
|
163560
|
-
|
|
163561
|
-
|
|
163562
|
-
|
|
163563
|
-
|
|
163564
|
-
|
|
163565
|
-
|
|
163566
|
-
|
|
163567
|
-
|
|
163568
|
-
|
|
163569
|
-
|
|
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
|
+
}
|
|
163570
163621
|
});
|
|
163571
163622
|
}
|
|
163572
|
-
|
|
163573
|
-
|
|
163574
|
-
|
|
163575
|
-
|
|
163576
|
-
|
|
163577
|
-
|
|
163578
|
-
|
|
163579
|
-
|
|
163580
|
-
|
|
163581
|
-
|
|
163582
|
-
|
|
163583
|
-
|
|
163584
|
-
|
|
163585
|
-
|
|
163586
|
-
|
|
163587
|
-
|
|
163588
|
-
|
|
163589
|
-
|
|
163590
|
-
|
|
163591
|
-
|
|
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
|
+
}
|
|
163592
163649
|
});
|
|
163593
|
-
if (!result.pass) {
|
|
163594
|
-
const details = result.findings.map((f) => `${f.layer} ${f.section}: ${f.message}`).join("; ");
|
|
163595
|
-
throw new Error(`Feature ${id} check failed before advance${strict ? " (strict)" : ""}: ${details}`);
|
|
163596
|
-
}
|
|
163597
163650
|
}
|
|
163598
163651
|
|
|
163599
|
-
// src/commands/
|
|
163600
|
-
|
|
163601
|
-
|
|
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
|
|
163602
163669
|
init_src2();
|
|
163603
|
-
init_dist28();
|
|
163604
|
-
init_dist7();
|
|
163605
|
-
import { createRequire as createRequire3 } from "module";
|
|
163606
163670
|
|
|
163607
163671
|
// src/config.ts
|
|
163608
163672
|
var CLI_CONFIG = {
|
|
163609
163673
|
binaryName: "spur",
|
|
163610
163674
|
binaryLabel: "spur",
|
|
163611
|
-
binaryVersion: "0.4.
|
|
163675
|
+
binaryVersion: "0.4.25",
|
|
163612
163676
|
configDir: ".spur",
|
|
163613
163677
|
configFile: ".spur/config.yaml",
|
|
163614
163678
|
databaseFile: ".spur/spur.db"
|
|
163615
163679
|
};
|
|
163616
163680
|
|
|
163617
|
-
// src/commands/history/
|
|
163618
|
-
|
|
163619
|
-
|
|
163620
|
-
|
|
163621
|
-
}
|
|
163622
|
-
var OPS = {
|
|
163623
|
-
digest: "<artifact.json> \u2014 semantic artifact digest",
|
|
163624
|
-
check: "<report.md> \u2014 report-contract structure gate (PASS/FAIL)",
|
|
163625
|
-
paths: "--out <env> [--skill-dir d] ... \u2014 resolve the run paths into an env file",
|
|
163626
|
-
"assert-clean": "--baseline <porcelain> [--expect <path>]... \u2014 fail on undeclared writes",
|
|
163627
|
-
probe: "--artifact a --target t ... \u2014 cache disposition + provenance",
|
|
163628
|
-
stamp: "--candidate c --provenance p --out o \u2014 stamp provenance frontmatter",
|
|
163629
|
-
refresh: "--report r --out o \u2014 refresh a cache-hit report",
|
|
163630
|
-
publish: "<candidate.md> <target.md> \u2014 atomic publish"
|
|
163631
|
-
};
|
|
163632
|
-
function registerHistoryAnatomyCommand(history, context) {
|
|
163633
|
-
const run = async (argv) => {
|
|
163634
|
-
for (let i = 0;i < argv.length; i++) {
|
|
163635
|
-
if (argv[i] === "--run-id" && !acceptArtifactId(argv[i + 1] ?? "", "run id", context))
|
|
163636
|
-
return;
|
|
163637
|
-
}
|
|
163638
|
-
await Promise.resolve().then(() => init_history_anatomy_cli());
|
|
163639
|
-
const result = runCacheCli(argv, {
|
|
163640
|
-
helper: spurEntryPath(),
|
|
163641
|
-
cwd: context.cwd,
|
|
163642
|
-
...context.env.HOME !== undefined ? { home: context.env.HOME } : {}
|
|
163643
|
-
});
|
|
163644
|
-
writeStepText(context.output, "stdout", result.stdout);
|
|
163645
|
-
writeStepText(context.output, "stderr", result.stderr);
|
|
163646
|
-
context.setExitCode(result.exitCode);
|
|
163647
|
-
};
|
|
163648
|
-
for (const [op, synopsis] of Object.entries(OPS)) {
|
|
163649
|
-
history.command(`anatomy-${op}`).summary(`history-anatomy cache op: ${synopsis}`).description(`history-anatomy cache op (ADR-079): ${op} ${synopsis}
|
|
163650
|
-
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]));
|
|
163651
|
-
}
|
|
163652
|
-
}
|
|
163653
|
-
|
|
163654
|
-
// src/commands/history/summary.ts
|
|
163655
|
-
function registerHistorySummaryCommand(history, context) {
|
|
163656
|
-
history.command("summary").summary("Daily summary report: tokens, git activity, history health, annotations").description([
|
|
163657
|
-
"Generate the day\u2019s Markdown summary and write it to docs/daily/summary_<YYYYMMDD>.md",
|
|
163658
|
-
"(or --output). Prompts for learnings / issues / pending unless SP_DAILY_SUMMARY_NO_PROMPT=1",
|
|
163659
|
-
"(non-TTY stdin supplies the three answers as lines). Exit: 0 written, 1 generation failed."
|
|
163660
|
-
].join(`
|
|
163661
|
-
`)).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) => {
|
|
163662
|
-
await Promise.resolve().then(() => init_daily_summary());
|
|
163663
|
-
const argv = [];
|
|
163664
|
-
if (options.date !== undefined)
|
|
163665
|
-
argv.push("--date", options.date);
|
|
163666
|
-
if (options.dryRun === true)
|
|
163667
|
-
argv.push("--dry-run");
|
|
163668
|
-
if (options.output !== undefined)
|
|
163669
|
-
argv.push("--output", options.output);
|
|
163670
|
-
if (options.git === false)
|
|
163671
|
-
argv.push("--no-git");
|
|
163672
|
-
if (options.ccusage === false)
|
|
163673
|
-
argv.push("--no-ccusage");
|
|
163674
|
-
const code = await main(argv, {
|
|
163675
|
-
out: (line) => context.output.write(line),
|
|
163676
|
-
err: (line) => context.output.error(line)
|
|
163677
|
-
});
|
|
163678
|
-
context.setExitCode(code);
|
|
163679
|
-
});
|
|
163680
|
-
}
|
|
163681
|
-
|
|
163682
|
-
// src/commands/history/timeline.ts
|
|
163683
|
-
function registerHistoryTimelineCommand(history, context) {
|
|
163684
|
-
history.command("timeline").summary("Per-prompt work/wait time and tokens from the host transcript (JSON)").description([
|
|
163685
|
-
"Read the active host transcript read-only (CLAUDE_CODE_SESSION_ID under ~/.claude/projects,",
|
|
163686
|
-
"PI_SESSION_FILE, or --transcript) and print one JSON timeline: a segment per operator prompt,",
|
|
163687
|
-
'optional --group stages, and totals. No transcript \u2192 {"available":false,...}, exit 0.',
|
|
163688
|
-
"Exit: 0 timeline (or unavailable), 2 invalid --group or unreadable transcript."
|
|
163689
|
-
].join(`
|
|
163690
|
-
`)).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) => {
|
|
163691
|
-
await Promise.resolve().then(() => init_session_timeline());
|
|
163692
|
-
const argv = [];
|
|
163693
|
-
if (options.transcript !== undefined)
|
|
163694
|
-
argv.push("--transcript", options.transcript);
|
|
163695
|
-
if (options.group !== undefined)
|
|
163696
|
-
argv.push("--group", options.group);
|
|
163697
|
-
const code = runSessionTimelineCli(argv, {
|
|
163698
|
-
env: context.env,
|
|
163699
|
-
write: (s) => writeStepText(context.output, "stdout", s),
|
|
163700
|
-
writeErr: (s) => writeStepText(context.output, "stderr", s)
|
|
163701
|
-
});
|
|
163702
|
-
context.setExitCode(code);
|
|
163703
|
-
});
|
|
163704
|
-
}
|
|
163705
|
-
|
|
163706
|
-
// src/commands/history.ts
|
|
163681
|
+
// src/commands/history/shared.ts
|
|
163682
|
+
init_src4();
|
|
163683
|
+
init_dist7();
|
|
163684
|
+
import { createRequire as createRequire3 } from "module";
|
|
163707
163685
|
function resolveCliSourceTimeout(explicit, env) {
|
|
163708
163686
|
if (explicit !== undefined)
|
|
163709
163687
|
return parseTimeoutInput(explicit);
|
|
@@ -163722,10 +163700,6 @@ async function resolveImportProvenance(fs) {
|
|
|
163722
163700
|
} catch {}
|
|
163723
163701
|
return { binary, importer };
|
|
163724
163702
|
}
|
|
163725
|
-
function formatProvenance(provenance) {
|
|
163726
|
-
return `binary: ${provenance.binary}
|
|
163727
|
-
importer: @gobing-ai/ts-llm-jsonl-importer@${provenance.importer}`;
|
|
163728
|
-
}
|
|
163729
163703
|
var CAPABILITY_KINDS2 = new Set(["command", "subagent", "skill"]);
|
|
163730
163704
|
function parseCapabilityOriginSpec(spec) {
|
|
163731
163705
|
const parts = spec.split(":").map((part) => part.trim());
|
|
@@ -163749,132 +163723,42 @@ function parseCapabilityOriginSpec(spec) {
|
|
|
163749
163723
|
originIdentity
|
|
163750
163724
|
};
|
|
163751
163725
|
}
|
|
163752
|
-
function
|
|
163753
|
-
|
|
163754
|
-
|
|
163755
|
-
|
|
163756
|
-
|
|
163757
|
-
|
|
163758
|
-
|
|
163759
|
-
|
|
163760
|
-
|
|
163761
|
-
|
|
163762
|
-
|
|
163763
|
-
|
|
163764
|
-
|
|
163765
|
-
|
|
163766
|
-
|
|
163767
|
-
|
|
163768
|
-
|
|
163769
|
-
|
|
163770
|
-
|
|
163771
|
-
|
|
163772
|
-
fs: context.fs,
|
|
163773
|
-
cwd: context.cwd
|
|
163774
|
-
});
|
|
163775
|
-
};
|
|
163776
|
-
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) => {
|
|
163777
|
-
const source = options.source ?? "all";
|
|
163778
|
-
if (options.file && source === "all") {
|
|
163779
|
-
context.output.write(options.json ? toEnvelopeJson({ status: "error", message: '--file requires a single --source, not "all".' }, {
|
|
163780
|
-
enveloped: options.jsonEnvelope,
|
|
163781
|
-
error: {
|
|
163782
|
-
code: "INTERNAL_ERROR",
|
|
163783
|
-
message: '--file requires a single --source, not "all".',
|
|
163784
|
-
details: { cliCode: "usage" }
|
|
163785
|
-
}
|
|
163786
|
-
}) : 'spur history import: --file requires a single --source, not "all".');
|
|
163787
|
-
context.setExitCode(1);
|
|
163788
|
-
return;
|
|
163789
|
-
}
|
|
163790
|
-
let sourceTimeout;
|
|
163791
|
-
try {
|
|
163792
|
-
sourceTimeout = resolveCliSourceTimeout(options.sourceTimeout, getEnvVars());
|
|
163793
|
-
} catch (e) {
|
|
163794
|
-
const timeoutMsg = `spur history import: ${e instanceof Error ? e.message : String(e)}`;
|
|
163795
|
-
context.output.write(options.json ? toEnvelopeJson({ status: "error", message: timeoutMsg }, {
|
|
163796
|
-
enveloped: options.jsonEnvelope,
|
|
163797
|
-
error: { code: "INTERNAL_ERROR", message: timeoutMsg, details: { cliCode: "usage" } }
|
|
163798
|
-
}) : timeoutMsg);
|
|
163799
|
-
context.setExitCode(1);
|
|
163800
|
-
return;
|
|
163801
|
-
}
|
|
163802
|
-
const mode = options.mode ?? (options.file ? "force-file" : "incremental");
|
|
163803
|
-
if (mode !== "full" && mode !== "incremental" && mode !== "force-file") {
|
|
163804
|
-
const modeMsg = `Invalid history import mode "${mode}". Expected one of: full, incremental, force-file`;
|
|
163805
|
-
if (options.json) {
|
|
163806
|
-
context.output.write(toEnvelopeJson({ status: "error", message: modeMsg }, {
|
|
163807
|
-
enveloped: options.jsonEnvelope,
|
|
163808
|
-
error: { code: "INTERNAL_ERROR", message: modeMsg, details: { cliCode: "usage" } }
|
|
163809
|
-
}));
|
|
163810
|
-
} else {
|
|
163811
|
-
context.output.error(modeMsg);
|
|
163812
|
-
}
|
|
163813
|
-
context.setExitCode(1);
|
|
163814
|
-
return;
|
|
163815
|
-
}
|
|
163816
|
-
if (options.file && mode === "full" && options.dryRun !== true) {
|
|
163817
|
-
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.";
|
|
163818
|
-
context.output.write(options.json ? toEnvelopeJson({ status: "error", message: unsafeMsg }, {
|
|
163819
|
-
enveloped: options.jsonEnvelope,
|
|
163820
|
-
error: { code: "INTERNAL_ERROR", message: unsafeMsg, details: { cliCode: "usage" } }
|
|
163821
|
-
}) : unsafeMsg);
|
|
163822
|
-
context.setExitCode(1);
|
|
163823
|
-
return;
|
|
163824
|
-
}
|
|
163825
|
-
let capabilityOrigins;
|
|
163826
|
-
if (options.capabilityOrigin.length > 0) {
|
|
163827
|
-
try {
|
|
163828
|
-
capabilityOrigins = options.capabilityOrigin.map(parseCapabilityOriginSpec);
|
|
163829
|
-
} catch (err) {
|
|
163830
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
163831
|
-
context.output.write(options.json ? toEnvelopeJson({ status: "error", message }, {
|
|
163832
|
-
enveloped: options.jsonEnvelope,
|
|
163833
|
-
error: { code: "INTERNAL_ERROR", message, details: { cliCode: "usage" } }
|
|
163834
|
-
}) : `spur history import: ${message}`);
|
|
163835
|
-
context.setExitCode(1);
|
|
163836
|
-
return;
|
|
163837
|
-
}
|
|
163838
|
-
}
|
|
163839
|
-
const svc = await makeService(capabilityOrigins);
|
|
163840
|
-
let fanOut;
|
|
163841
|
-
try {
|
|
163842
|
-
fanOut = await svc.importAll({
|
|
163843
|
-
sources: source === "all" ? undefined : [source],
|
|
163844
|
-
file: options.file || undefined,
|
|
163845
|
-
root: options.root || undefined,
|
|
163846
|
-
mode,
|
|
163847
|
-
dryRun: options.dryRun === true,
|
|
163848
|
-
sourceTimeout,
|
|
163849
|
-
onScope: (line) => context.output.error(line)
|
|
163850
|
-
});
|
|
163851
|
-
} catch (e) {
|
|
163852
|
-
if (e instanceof UnsafeHistoryImporterError) {
|
|
163853
|
-
const msg = `spur history import: ${e.code}: ${e.message}`;
|
|
163854
|
-
context.output.write(options.json ? toEnvelopeJson({ status: "error", message: msg }, {
|
|
163855
|
-
enveloped: options.jsonEnvelope,
|
|
163856
|
-
error: {
|
|
163857
|
-
code: "INTERNAL_ERROR",
|
|
163858
|
-
message: msg,
|
|
163859
|
-
details: {
|
|
163860
|
-
cliCode: e.code,
|
|
163861
|
-
installedVersion: e.installedVersion,
|
|
163862
|
-
minSafeVersion: e.minSafeVersion
|
|
163863
|
-
}
|
|
163864
|
-
}
|
|
163865
|
-
}) : msg);
|
|
163866
|
-
context.setExitCode(1);
|
|
163867
|
-
return;
|
|
163868
|
-
}
|
|
163869
|
-
throw e;
|
|
163870
|
-
}
|
|
163871
|
-
const provenance = await resolveImportProvenance(context.fs);
|
|
163872
|
-
context.output.write(options.json ? toEnvelopeJson({ ...fanOut, provenance }, { enveloped: options.jsonEnvelope }) : `${formatProvenance(provenance)}
|
|
163873
|
-
${formatFanOutResult(fanOut)}`);
|
|
163874
|
-
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
|
|
163875
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) {
|
|
163876
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) => {
|
|
163877
|
-
const svc = await
|
|
163761
|
+
const svc = await makeHistoryService(context);
|
|
163878
163762
|
const source = options.source ?? "all";
|
|
163879
163763
|
const selector = {
|
|
163880
163764
|
since: options.since || null,
|
|
@@ -163900,64 +163784,50 @@ ${formatFanOutResult(fanOut)}`);
|
|
|
163900
163784
|
context.setExitCode(2);
|
|
163901
163785
|
}
|
|
163902
163786
|
});
|
|
163903
|
-
|
|
163904
|
-
|
|
163905
|
-
|
|
163906
|
-
|
|
163907
|
-
|
|
163908
|
-
|
|
163909
|
-
|
|
163910
|
-
|
|
163911
|
-
|
|
163912
|
-
|
|
163913
|
-
|
|
163914
|
-
|
|
163915
|
-
|
|
163916
|
-
|
|
163917
|
-
|
|
163918
|
-
|
|
163919
|
-
|
|
163920
|
-
|
|
163921
|
-
|
|
163922
|
-
|
|
163923
|
-
|
|
163924
|
-
}, { enveloped: options.jsonEnvelope }) : `cleared ${result.cleared.length} history tables` + (result.skipped.length > 0 ? ` (${result.skipped.length} not present)` : "") + note);
|
|
163925
|
-
});
|
|
163926
|
-
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) => {
|
|
163927
|
-
try {
|
|
163928
|
-
const top = parseTopOrThrow(options.top);
|
|
163929
|
-
const { report, artifactPath, resolution, artifact, banner } = runHistoryReport({
|
|
163930
|
-
path: pathArg,
|
|
163931
|
-
cwd: context.cwd,
|
|
163932
|
-
now: new Date,
|
|
163933
|
-
mode: options.mode,
|
|
163934
|
-
task: options.task,
|
|
163935
|
-
top
|
|
163936
|
-
});
|
|
163937
|
-
if (options.json) {
|
|
163938
|
-
context.output.write(toEnvelopeJson(artifact, { enveloped: options.jsonEnvelope }));
|
|
163939
|
-
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))
|
|
163940
163808
|
return;
|
|
163941
|
-
}
|
|
163942
|
-
if (resolution === "pointer") {
|
|
163943
|
-
const staleBanner = stalenessBanner(artifact.generatedAt, new Date);
|
|
163944
|
-
if (staleBanner !== null)
|
|
163945
|
-
context.output.write(staleBanner);
|
|
163946
|
-
}
|
|
163947
|
-
if (banner !== null)
|
|
163948
|
-
context.output.write(`${banner}
|
|
163949
|
-
`);
|
|
163950
|
-
context.output.write(report);
|
|
163951
|
-
context.output.write(`
|
|
163952
|
-
(artifact: ${artifactPath})
|
|
163953
|
-
`);
|
|
163954
|
-
context.setExitCode(0);
|
|
163955
|
-
} catch (e) {
|
|
163956
|
-
const message = `spur history report failed: ${e.message}`;
|
|
163957
|
-
context.output.write(options.json ? toEnvelopeJson({ status: "error", message }, { enveloped: options.jsonEnvelope, error: { code: "INTERNAL_ERROR", message } }) : message);
|
|
163958
|
-
context.setExitCode(1);
|
|
163959
163809
|
}
|
|
163960
|
-
|
|
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) {
|
|
163961
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) => {
|
|
163962
163832
|
let sourceTimeout;
|
|
163963
163833
|
try {
|
|
@@ -163971,7 +163841,7 @@ ${formatFanOutResult(fanOut)}`);
|
|
|
163971
163841
|
context.setExitCode(1);
|
|
163972
163842
|
return;
|
|
163973
163843
|
}
|
|
163974
|
-
const svc = await
|
|
163844
|
+
const svc = await makeHistoryService(context);
|
|
163975
163845
|
let refresh;
|
|
163976
163846
|
try {
|
|
163977
163847
|
refresh = parseHistoryRefreshContext(getEnvVar(HISTORY_REFRESH_CONTEXT_ENV));
|
|
@@ -164093,35 +163963,6 @@ ${formatFanOutResult(fanOut)}`);
|
|
|
164093
163963
|
}
|
|
164094
163964
|
context.output.write(options.json ? toEnvelopeJson(result, { enveloped: options.jsonEnvelope }) : formatDailyResult(result));
|
|
164095
163965
|
});
|
|
164096
|
-
registerHistoryAnatomyCommand(noun, context);
|
|
164097
|
-
registerHistoryTimelineCommand(noun, context);
|
|
164098
|
-
registerHistorySummaryCommand(noun, context);
|
|
164099
|
-
}
|
|
164100
|
-
function parseTopOrThrow(raw) {
|
|
164101
|
-
if (raw === undefined)
|
|
164102
|
-
return;
|
|
164103
|
-
const n = Number(raw);
|
|
164104
|
-
if (!Number.isInteger(n) || n < 1) {
|
|
164105
|
-
throw new Error(`--top must be a positive integer (got "${raw}")`);
|
|
164106
|
-
}
|
|
164107
|
-
return n;
|
|
164108
|
-
}
|
|
164109
|
-
function formatFanOutResult(r) {
|
|
164110
|
-
const lines = ["history import (fan-out)", "sources:"];
|
|
164111
|
-
for (const e of r.entries) {
|
|
164112
|
-
lines.push(` ${e.source}: ${e.status} (files=${e.files} scanned, new-messages=${e.messages}, tool-calls=${e.toolCalls})`);
|
|
164113
|
-
}
|
|
164114
|
-
const a = r.attribution;
|
|
164115
|
-
lines.push(`attribution: sessions=${a.sessionsEvaluated} links-created=${a.linksCreated} ` + `links-present=${a.linksAlreadyPresent} skipped=${a.skippedEvidence} ambiguous=${a.ambiguousEvidence}`);
|
|
164116
|
-
if (r.warnings.length > 0) {
|
|
164117
|
-
lines.push("warnings:");
|
|
164118
|
-
for (const w of r.warnings) {
|
|
164119
|
-
lines.push(` [${w.code}] ${w.source ?? "-"}: ${w.detail}`);
|
|
164120
|
-
}
|
|
164121
|
-
}
|
|
164122
|
-
lines.push(`exit_code: ${r.exitCode}`);
|
|
164123
|
-
return lines.join(`
|
|
164124
|
-
`);
|
|
164125
163966
|
}
|
|
164126
163967
|
function formatDailyResult(r) {
|
|
164127
163968
|
const lines = ["history daily"];
|
|
@@ -164148,6 +163989,266 @@ function formatDailyResult(r) {
|
|
|
164148
163989
|
`);
|
|
164149
163990
|
}
|
|
164150
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
|
+
|
|
164151
164252
|
// src/commands/init.ts
|
|
164152
164253
|
init_src4();
|
|
164153
164254
|
init_src();
|
|
@@ -164528,13 +164629,13 @@ ${entry}
|
|
|
164528
164629
|
}
|
|
164529
164630
|
function describeZgReadiness(status) {
|
|
164530
164631
|
if (!status.zg.available)
|
|
164531
|
-
return `zg not installed (optional) \u2014 install:
|
|
164632
|
+
return `zg not installed (optional) \u2014 install: ${ZG_INSTALL_HINT}`;
|
|
164532
164633
|
if (status.zg.index === "ready")
|
|
164533
164634
|
return "zg ready";
|
|
164534
164635
|
if (status.zg.index === "stale")
|
|
164535
|
-
return
|
|
164636
|
+
return `zg index stale \u2014 run: ${ZG_REFRESH_HINT}`;
|
|
164536
164637
|
if (status.zg.index === "missing")
|
|
164537
|
-
return
|
|
164638
|
+
return `zg index missing \u2014 run: ${ZG_INDEX_HINT}`;
|
|
164538
164639
|
return "zg status unknown";
|
|
164539
164640
|
}
|
|
164540
164641
|
|
|
@@ -171134,7 +171235,7 @@ init_dist5();
|
|
|
171134
171235
|
import { homedir as homedir17 } from "os";
|
|
171135
171236
|
import { basename as basename21, join as join94, relative as relative19, resolve as resolve37 } from "path";
|
|
171136
171237
|
// ../server/package.json
|
|
171137
|
-
var version4 = "0.4.
|
|
171238
|
+
var version4 = "0.4.25";
|
|
171138
171239
|
|
|
171139
171240
|
// ../server/src/modules/health/index.ts
|
|
171140
171241
|
var startedAt = Date.now();
|