@gobing-ai/spur 0.3.65 → 0.3.66
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/workflows/history-anatomy.yaml +5 -4
- package/package.json +1 -1
- package/plugins/sp/commands/dev-review-session.md +11 -6
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +4 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +4 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +1 -1
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +3 -2
- package/plugins/sp/skills/dogfood-testing/SKILL.md +11 -11
- package/plugins/sp/skills/session-review/SKILL.md +39 -6
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +7 -1
- package/plugins/sp/skills/spur-dev/SKILL.md +5 -0
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +2 -2
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +71 -15
- package/spur.js +333 -121
package/spur.js
CHANGED
|
@@ -39046,6 +39046,7 @@ var init_finding_codes = __esm(() => {
|
|
|
39046
39046
|
"L3.scope-delineation",
|
|
39047
39047
|
"L3.one-active-goal",
|
|
39048
39048
|
"L3.children-limit",
|
|
39049
|
+
"L3.review-testing-contradiction",
|
|
39049
39050
|
"L4.design-placeholder",
|
|
39050
39051
|
"L4.feature-not-found",
|
|
39051
39052
|
"L4.feature-terminal",
|
|
@@ -39055,6 +39056,7 @@ var init_finding_codes = __esm(() => {
|
|
|
39055
39056
|
"L4.rollup-subtasks-open",
|
|
39056
39057
|
"L4.rollup-parent-open",
|
|
39057
39058
|
"L4.rollup-missing-roster",
|
|
39059
|
+
"L4.rollup-roster-not-declared-dependency",
|
|
39058
39060
|
"L4.readiness-blocked",
|
|
39059
39061
|
"L4.prose-prerequisite-unlisted",
|
|
39060
39062
|
"L4.prerequisite-cycle",
|
|
@@ -39064,6 +39066,7 @@ var init_finding_codes = __esm(() => {
|
|
|
39064
39066
|
"L4.orphan-scenarios",
|
|
39065
39067
|
"L4.uncovered-task-scenario",
|
|
39066
39068
|
"L4.uncovered-feature-scenario",
|
|
39069
|
+
"L4.verdict-rows-match-no-scenario",
|
|
39067
39070
|
"L4.verifying-incomplete-tasks",
|
|
39068
39071
|
"L4.dogfood-missing",
|
|
39069
39072
|
"L4.stale-line-anchor",
|
|
@@ -39098,6 +39101,7 @@ var init_finding_codes = __esm(() => {
|
|
|
39098
39101
|
L3_SCOPE_DELINEATION: "L3.scope-delineation",
|
|
39099
39102
|
L3_ONE_ACTIVE_GOAL: "L3.one-active-goal",
|
|
39100
39103
|
L3_CHILDREN_LIMIT: "L3.children-limit",
|
|
39104
|
+
L3_REVIEW_TESTING_CONTRADICTION: "L3.review-testing-contradiction",
|
|
39101
39105
|
L4_DESIGN_PLACEHOLDER: "L4.design-placeholder",
|
|
39102
39106
|
L4_FEATURE_NOT_FOUND: "L4.feature-not-found",
|
|
39103
39107
|
L4_FEATURE_TERMINAL: "L4.feature-terminal",
|
|
@@ -39107,6 +39111,7 @@ var init_finding_codes = __esm(() => {
|
|
|
39107
39111
|
L4_ROLLUP_SUBTASKS_OPEN: "L4.rollup-subtasks-open",
|
|
39108
39112
|
L4_ROLLUP_PARENT_OPEN: "L4.rollup-parent-open",
|
|
39109
39113
|
L4_ROLLUP_MISSING_ROSTER: "L4.rollup-missing-roster",
|
|
39114
|
+
L4_ROLLUP_ROSTER_NOT_DECLARED_DEPENDENCY: "L4.rollup-roster-not-declared-dependency",
|
|
39110
39115
|
L4_READINESS_BLOCKED: "L4.readiness-blocked",
|
|
39111
39116
|
L4_PROSE_PREREQUISITE_UNLISTED: "L4.prose-prerequisite-unlisted",
|
|
39112
39117
|
L4_PREREQUISITE_CYCLE: "L4.prerequisite-cycle",
|
|
@@ -39116,6 +39121,7 @@ var init_finding_codes = __esm(() => {
|
|
|
39116
39121
|
L4_ORPHAN_SCENARIOS: "L4.orphan-scenarios",
|
|
39117
39122
|
L4_UNCOVERED_TASK_SCENARIO: "L4.uncovered-task-scenario",
|
|
39118
39123
|
L4_UNCOVERED_FEATURE_SCENARIO: "L4.uncovered-feature-scenario",
|
|
39124
|
+
L4_VERDICT_ROWS_MATCH_NO_SCENARIO: "L4.verdict-rows-match-no-scenario",
|
|
39119
39125
|
L4_DOGFOOD_MISSING: "L4.dogfood-missing",
|
|
39120
39126
|
L4_VERIFYING_INCOMPLETE_TASKS: "L4.verifying-incomplete-tasks",
|
|
39121
39127
|
L4_STALE_LINE_ANCHOR: "L4.stale-line-anchor",
|
|
@@ -50346,6 +50352,42 @@ var init_artifact_digest = __esm(() => {
|
|
|
50346
50352
|
RANKED_ARTIFACT_KEYS = new Set(Object.entries(ARTIFACT_ARRAY_CLASSIFICATION).filter(([, kind]) => kind === "ranked").map(([key]) => key));
|
|
50347
50353
|
});
|
|
50348
50354
|
|
|
50355
|
+
// ../../packages/domain/src/analytics/assistant-duration.ts
|
|
50356
|
+
async function deriveAssistantDurations(db2) {
|
|
50357
|
+
const candidateSql = `
|
|
50358
|
+
WITH ordered AS (
|
|
50359
|
+
SELECT record_hash,
|
|
50360
|
+
role,
|
|
50361
|
+
duration_ms,
|
|
50362
|
+
ts,
|
|
50363
|
+
LAG(ts) OVER (PARTITION BY source, session_id ORDER BY seq) AS prev_ts
|
|
50364
|
+
FROM history_message
|
|
50365
|
+
WHERE ts IS NOT NULL AND ts LIKE '____-__-__T%'
|
|
50366
|
+
)
|
|
50367
|
+
SELECT record_hash AS recordHash,
|
|
50368
|
+
CAST(ROUND((unixepoch(ts, 'subsec') - unixepoch(prev_ts, 'subsec')) * 1000) AS INTEGER) AS deltaMs
|
|
50369
|
+
FROM ordered
|
|
50370
|
+
WHERE role = 'assistant' AND duration_ms IS NULL AND prev_ts IS NOT NULL`;
|
|
50371
|
+
const rows = await db2.queryAll(candidateSql);
|
|
50372
|
+
let derived = 0;
|
|
50373
|
+
let skippedOverCeiling = 0;
|
|
50374
|
+
for (const row of rows) {
|
|
50375
|
+
if (row.deltaMs === null || row.deltaMs <= 0)
|
|
50376
|
+
continue;
|
|
50377
|
+
if (row.deltaMs > DERIVED_DURATION_CEILING_MS) {
|
|
50378
|
+
skippedOverCeiling += 1;
|
|
50379
|
+
continue;
|
|
50380
|
+
}
|
|
50381
|
+
await db2.run("UPDATE history_message SET duration_ms = ?, duration_source = ? WHERE record_hash = ? AND duration_ms IS NULL", row.deltaMs, DURATION_SOURCE_DERIVED, row.recordHash);
|
|
50382
|
+
derived += 1;
|
|
50383
|
+
}
|
|
50384
|
+
return { derived, skippedOverCeiling };
|
|
50385
|
+
}
|
|
50386
|
+
var DURATION_SOURCE_DERIVED = "derived", DERIVED_DURATION_CEILING_MS;
|
|
50387
|
+
var init_assistant_duration = __esm(() => {
|
|
50388
|
+
DERIVED_DURATION_CEILING_MS = 30 * 60 * 1000;
|
|
50389
|
+
});
|
|
50390
|
+
|
|
50349
50391
|
// ../../packages/domain/src/analytics/models.ts
|
|
50350
50392
|
function resolvePricing(model) {
|
|
50351
50393
|
if (model === undefined || model.length === 0)
|
|
@@ -51083,6 +51125,7 @@ async function stepSupport(db2, sel, opts) {
|
|
|
51083
51125
|
COUNT(*) AS assistantSteps,
|
|
51084
51126
|
SUM(m.input_tokens IS NOT NULL OR m.output_tokens IS NOT NULL) AS stepsWithUsage,
|
|
51085
51127
|
SUM(m.duration_ms IS NOT NULL) AS stepsWithDuration,
|
|
51128
|
+
SUM(m.duration_source IS 'derived') AS stepsWithDerivedDuration,
|
|
51086
51129
|
SUM(m.cache_read_tokens IS NOT NULL) AS stepsWithCacheRead
|
|
51087
51130
|
FROM history_message m
|
|
51088
51131
|
${withStepPredicates(wm.where, "m.role = 'assistant'")}
|
|
@@ -52190,7 +52233,7 @@ async function pairingSummary(db2, spec = {}) {
|
|
|
52190
52233
|
failures: d.failures,
|
|
52191
52234
|
unknownOutcomes: d.dispatches - known,
|
|
52192
52235
|
escalations: {},
|
|
52193
|
-
totalCostUsd:
|
|
52236
|
+
totalCostUsd: null,
|
|
52194
52237
|
meanDurationMs: d.durationCount > 0 ? d.durationTotal / d.durationCount : 0
|
|
52195
52238
|
});
|
|
52196
52239
|
}
|
|
@@ -52204,7 +52247,7 @@ async function pairingSummary(db2, spec = {}) {
|
|
|
52204
52247
|
const entry = byKey.get(pairingKey(f.executor, f.role));
|
|
52205
52248
|
if (entry === undefined)
|
|
52206
52249
|
continue;
|
|
52207
|
-
entry.totalCostUsd
|
|
52250
|
+
entry.totalCostUsd = entry.totalCostUsd === null ? f.totalCostUsd : f.totalCostUsd === null ? entry.totalCostUsd : entry.totalCostUsd + f.totalCostUsd;
|
|
52208
52251
|
}
|
|
52209
52252
|
return [...byKey.values()].sort((a2, b) => a2.executor.localeCompare(b.executor) || a2.role.localeCompare(b.role));
|
|
52210
52253
|
}
|
|
@@ -52346,7 +52389,7 @@ async function loadFolds(db2, spec) {
|
|
|
52346
52389
|
WHERE m.session_id IS NOT NULL
|
|
52347
52390
|
)
|
|
52348
52391
|
SELECT m.executor AS executor, m.role AS role,
|
|
52349
|
-
|
|
52392
|
+
SUM(h.cost_usd) AS totalCostUsd
|
|
52350
52393
|
FROM mapped m
|
|
52351
52394
|
LEFT JOIN history_message h
|
|
52352
52395
|
ON h.source = m.source AND h.session_id = m.session_id
|
|
@@ -52658,7 +52701,7 @@ function renderPerStep(artifact) {
|
|
|
52658
52701
|
if ((artifact.stepSupport?.length ?? 0) > 0) {
|
|
52659
52702
|
lines.push("### Section Support", "", "| Source | Assistant steps | Tokens | Time | Cache |", "| --- | ---: | --- | --- | --- |");
|
|
52660
52703
|
for (const e of artifact.stepSupport ?? []) {
|
|
52661
|
-
lines.push(`| ${e.source} | ${e.assistantSteps.toLocaleString("en-US")} | ${e.stepsWithUsage > 0 ? "yes" : "no"} | ${e
|
|
52704
|
+
lines.push(`| ${e.source} | ${e.assistantSteps.toLocaleString("en-US")} | ${e.stepsWithUsage > 0 ? "yes" : "no"} | ${timeSupport(e)} | ${e.stepsWithCacheRead > 0 ? "yes" : "no"} |`);
|
|
52662
52705
|
}
|
|
52663
52706
|
lines.push("");
|
|
52664
52707
|
}
|
|
@@ -52691,6 +52734,15 @@ function renderTopStepsByTokens(artifact) {
|
|
|
52691
52734
|
lines.push("");
|
|
52692
52735
|
return lines;
|
|
52693
52736
|
}
|
|
52737
|
+
function timeSupport(e) {
|
|
52738
|
+
if (e.stepsWithDuration === 0)
|
|
52739
|
+
return "no";
|
|
52740
|
+
if (e.stepsWithDerivedDuration === 0)
|
|
52741
|
+
return "yes";
|
|
52742
|
+
if (e.stepsWithDerivedDuration >= e.stepsWithDuration)
|
|
52743
|
+
return "derived";
|
|
52744
|
+
return "yes (mixed)";
|
|
52745
|
+
}
|
|
52694
52746
|
function renderTopStepsByDuration(artifact) {
|
|
52695
52747
|
const lines = ["### Top Steps by Duration", ""];
|
|
52696
52748
|
if (artifact.topStepsByDuration === undefined) {
|
|
@@ -52852,7 +52904,16 @@ function renderPairings(artifact) {
|
|
|
52852
52904
|
`);
|
|
52853
52905
|
}
|
|
52854
52906
|
function comparePairings(a2, b) {
|
|
52855
|
-
return b.successRate - a2.successRate || totalEscalations(a2) - totalEscalations(b) || a2.totalCostUsd
|
|
52907
|
+
return b.successRate - a2.successRate || totalEscalations(a2) - totalEscalations(b) || compareCost(a2.totalCostUsd, b.totalCostUsd);
|
|
52908
|
+
}
|
|
52909
|
+
function compareCost(a2, b) {
|
|
52910
|
+
if (a2 === null && b === null)
|
|
52911
|
+
return 0;
|
|
52912
|
+
if (a2 === null)
|
|
52913
|
+
return 1;
|
|
52914
|
+
if (b === null)
|
|
52915
|
+
return -1;
|
|
52916
|
+
return a2 - b;
|
|
52856
52917
|
}
|
|
52857
52918
|
function renderPairingsSection(artifact) {
|
|
52858
52919
|
const lines = ["## Pairings", ""];
|
|
@@ -52895,11 +52956,11 @@ function measureExecutor(entry, pairings) {
|
|
|
52895
52956
|
dispatches,
|
|
52896
52957
|
successRate: dispatches > 0 ? successes / dispatches : 0,
|
|
52897
52958
|
escalations: owned.reduce((sum, p) => sum + totalEscalations(p), 0),
|
|
52898
|
-
costUsd: owned.reduce((sum, p) => sum + p.totalCostUsd,
|
|
52959
|
+
costUsd: owned.reduce((sum, p) => sum === null ? p.totalCostUsd : p.totalCostUsd === null ? sum : sum + p.totalCostUsd, null)
|
|
52899
52960
|
};
|
|
52900
52961
|
}
|
|
52901
52962
|
function compareMeasured(a2, b) {
|
|
52902
|
-
return b.successRate - a2.successRate || a2.escalations - b.escalations || a2.costUsd
|
|
52963
|
+
return b.successRate - a2.successRate || a2.escalations - b.escalations || compareCost(a2.costUsd, b.costUsd);
|
|
52903
52964
|
}
|
|
52904
52965
|
function renderLadderDiffSection(artifact) {
|
|
52905
52966
|
const lines = ["## Ladder diff", ""];
|
|
@@ -52945,7 +53006,7 @@ function pct(rate) {
|
|
|
52945
53006
|
return `${(rate * 100).toFixed(1)}%`;
|
|
52946
53007
|
}
|
|
52947
53008
|
function usd(cost) {
|
|
52948
|
-
return `$${cost.toFixed(2)}`;
|
|
53009
|
+
return cost === null ? "not available" : `$${cost.toFixed(2)}`;
|
|
52949
53010
|
}
|
|
52950
53011
|
function fmtDuration(ms) {
|
|
52951
53012
|
return ms > 0 ? fmtDur(ms) : "n/a";
|
|
@@ -53637,6 +53698,7 @@ var init_run_cost = __esm(() => {
|
|
|
53637
53698
|
var init_analytics = __esm(() => {
|
|
53638
53699
|
init_artifact();
|
|
53639
53700
|
init_artifact_digest();
|
|
53701
|
+
init_assistant_duration();
|
|
53640
53702
|
init_costs();
|
|
53641
53703
|
init_forensic_query();
|
|
53642
53704
|
init_models();
|
|
@@ -53669,7 +53731,7 @@ function parseChecklist(content) {
|
|
|
53669
53731
|
const checked = (match[1] ?? "").toLowerCase() === "x";
|
|
53670
53732
|
const rawText = (match[2] ?? "").trim();
|
|
53671
53733
|
const line = i2 + 1;
|
|
53672
|
-
const reqIdMatch = rawText.match(/^(R\d+)\s*[:\-\u2014]?\s*(.*)$/);
|
|
53734
|
+
const reqIdMatch = rawText.match(/^[*_]{0,2}\s*((?:AC|R)\d+)\.?[*_]{0,2}\s*[:\-\u2014]?\s*(.*)$/);
|
|
53673
53735
|
if (reqIdMatch) {
|
|
53674
53736
|
items.push({
|
|
53675
53737
|
text: (reqIdMatch[2] ?? "").trim(),
|
|
@@ -53857,6 +53919,20 @@ function stripScenarioPrefixes(title) {
|
|
|
53857
53919
|
function normalizeTitle(title) {
|
|
53858
53920
|
return stripScenarioPrefixes(title).replace(/^(R\d+)\s*[:\-\u2014]?\s*/, "").trim().toLowerCase().replace(/[\u0027\u2018\u2019\u201c\u201d]/g, "").replace(/\s+/g, " ").trim();
|
|
53859
53921
|
}
|
|
53922
|
+
function stripCoversClause(text3) {
|
|
53923
|
+
return text3.replace(new RegExp(COVERS_RE_SOURCE, "gi"), " ").replace(/\s+/g, " ").trim();
|
|
53924
|
+
}
|
|
53925
|
+
function extractCoversAliases(text3) {
|
|
53926
|
+
const out = [];
|
|
53927
|
+
for (const m of text3.matchAll(new RegExp(COVERS_RE_SOURCE, "gi"))) {
|
|
53928
|
+
for (const part of (m[1] ?? "").split(";")) {
|
|
53929
|
+
const alias3 = part.trim();
|
|
53930
|
+
if (alias3.length > 0)
|
|
53931
|
+
out.push(alias3);
|
|
53932
|
+
}
|
|
53933
|
+
}
|
|
53934
|
+
return out;
|
|
53935
|
+
}
|
|
53860
53936
|
function checkAcCoverage(featureAc, taskAc, taskChecklist, acAltitude) {
|
|
53861
53937
|
if (acAltitude === "task-local") {
|
|
53862
53938
|
return { covered: true, orphans: [], uncovered: [], issues: [] };
|
|
@@ -53868,7 +53944,10 @@ function checkAcCoverage(featureAc, taskAc, taskChecklist, acAltitude) {
|
|
|
53868
53944
|
const checklistTitles = new Set;
|
|
53869
53945
|
if (taskChecklist) {
|
|
53870
53946
|
for (const item of taskChecklist) {
|
|
53871
|
-
checklistTitles.add(normalizeTitle(item.text));
|
|
53947
|
+
checklistTitles.add(normalizeTitle(stripCoversClause(item.text)));
|
|
53948
|
+
for (const alias3 of extractCoversAliases(item.text)) {
|
|
53949
|
+
checklistTitles.add(normalizeTitle(alias3));
|
|
53950
|
+
}
|
|
53872
53951
|
}
|
|
53873
53952
|
}
|
|
53874
53953
|
const uncovered = [];
|
|
@@ -53886,6 +53965,15 @@ function checkAcCoverage(featureAc, taskAc, taskChecklist, acAltitude) {
|
|
|
53886
53965
|
}
|
|
53887
53966
|
if (taskChecklist) {
|
|
53888
53967
|
for (const item of taskChecklist) {
|
|
53968
|
+
const aliases = extractCoversAliases(item.text);
|
|
53969
|
+
if (aliases.length > 0) {
|
|
53970
|
+
for (const alias3 of aliases) {
|
|
53971
|
+
if (!featureTitles.has(normalizeTitle(alias3))) {
|
|
53972
|
+
uncovered.push(item.text);
|
|
53973
|
+
}
|
|
53974
|
+
}
|
|
53975
|
+
continue;
|
|
53976
|
+
}
|
|
53889
53977
|
const normalized = normalizeTitle(item.text);
|
|
53890
53978
|
if (!featureTitles.has(normalized) && !taskTitles.has(normalized)) {
|
|
53891
53979
|
uncovered.push(item.text);
|
|
@@ -53933,6 +54021,7 @@ function tryParseFeature(content) {
|
|
|
53933
54021
|
${content}`;
|
|
53934
54022
|
return parseFeature(toParse);
|
|
53935
54023
|
}
|
|
54024
|
+
var COVERS_RE_SOURCE = "\\(covers:\\s*([^)]*)\\)";
|
|
53936
54025
|
var init_coverage = () => {};
|
|
53937
54026
|
|
|
53938
54027
|
// ../../packages/domain/src/bdd/fence.ts
|
|
@@ -64318,6 +64407,11 @@ ${HISTORY_RUN_SESSION_SCHEMA_SQL}
|
|
|
64318
64407
|
id: "0025_spur_cli_history_checkpoint_identity_mtime",
|
|
64319
64408
|
sql: "ALTER TABLE history_import_checkpoint ADD COLUMN source_mtime_ms REAL",
|
|
64320
64409
|
addColumnIfMissing: { table: "history_import_checkpoint", column: "source_mtime_ms" }
|
|
64410
|
+
},
|
|
64411
|
+
{
|
|
64412
|
+
id: "0026_spur_cli_history_message_duration_source",
|
|
64413
|
+
sql: "ALTER TABLE history_message ADD COLUMN duration_source TEXT",
|
|
64414
|
+
addColumnIfMissing: { table: "history_message", column: "duration_source" }
|
|
64321
64415
|
}
|
|
64322
64416
|
];
|
|
64323
64417
|
});
|
|
@@ -65925,7 +66019,7 @@ class MarkdownDocument {
|
|
|
65925
66019
|
_frontmatter;
|
|
65926
66020
|
_preamble;
|
|
65927
66021
|
_sections;
|
|
65928
|
-
|
|
66022
|
+
_demotedHeadings = [];
|
|
65929
66023
|
_duplicateSectionNames = [];
|
|
65930
66024
|
constructor(domain2, frontmatterBlock, frontmatter, preamble, sections) {
|
|
65931
66025
|
this._domain = domain2;
|
|
@@ -66008,8 +66102,8 @@ class MarkdownDocument {
|
|
|
66008
66102
|
get sectionNames() {
|
|
66009
66103
|
return this._sections.map((s2) => s2.name);
|
|
66010
66104
|
}
|
|
66011
|
-
get
|
|
66012
|
-
return this.
|
|
66105
|
+
get demotedHeadings() {
|
|
66106
|
+
return this._demotedHeadings;
|
|
66013
66107
|
}
|
|
66014
66108
|
get duplicateSectionNames() {
|
|
66015
66109
|
return this._duplicateSectionNames;
|
|
@@ -66067,7 +66161,8 @@ ${withTrailer}`;
|
|
|
66067
66161
|
continue;
|
|
66068
66162
|
}
|
|
66069
66163
|
if (!inCodeBlock && line.startsWith(prefix)) {
|
|
66070
|
-
this.
|
|
66164
|
+
this._demotedHeadings.push(line);
|
|
66165
|
+
kept.push(`#${line}`);
|
|
66071
66166
|
continue;
|
|
66072
66167
|
}
|
|
66073
66168
|
kept.push(line);
|
|
@@ -66978,6 +67073,7 @@ __export(exports_src, {
|
|
|
66978
67073
|
drift: () => drift,
|
|
66979
67074
|
deterministicExecutionSchema: () => deterministicExecutionSchema,
|
|
66980
67075
|
derivedWarnings: () => derivedWarnings,
|
|
67076
|
+
deriveAssistantDurations: () => deriveAssistantDurations,
|
|
66981
67077
|
dbHealthCheck: () => dbHealthCheck,
|
|
66982
67078
|
dataWindow: () => dataWindow,
|
|
66983
67079
|
dailyTokenMatrix: () => dailyTokenMatrix,
|
|
@@ -67087,7 +67183,9 @@ __export(exports_src, {
|
|
|
67087
67183
|
FEATURE_ID_PATTERN: () => FEATURE_ID_PATTERN,
|
|
67088
67184
|
FEATURE_CANONICAL_SECTIONS: () => FEATURE_CANONICAL_SECTIONS,
|
|
67089
67185
|
EXECUTION_KINDS: () => EXECUTION_KINDS,
|
|
67186
|
+
DURATION_SOURCE_DERIVED: () => DURATION_SOURCE_DERIVED,
|
|
67090
67187
|
DOMAIN_SCHEMA_SQL: () => DOMAIN_SCHEMA_SQL,
|
|
67188
|
+
DERIVED_DURATION_CEILING_MS: () => DERIVED_DURATION_CEILING_MS,
|
|
67091
67189
|
DEFAULT_TASK_VARIANT: () => DEFAULT_TASK_VARIANT,
|
|
67092
67190
|
DEFAULT_LOCK_TTL_MS: () => DEFAULT_LOCK_TTL_MS,
|
|
67093
67191
|
DEFAULT_DISCLOSURE_BUDGET_BYTES: () => DEFAULT_DISCLOSURE_BUDGET_BYTES,
|
|
@@ -67885,12 +67983,14 @@ function toEnvelopeJson(value, opts = {}) {
|
|
|
67885
67983
|
function toEnvelopeError(code, message, details) {
|
|
67886
67984
|
return toJson(details !== undefined ? { ok: false, error: { code, message, details } } : { ok: false, error: { code, message } });
|
|
67887
67985
|
}
|
|
67888
|
-
function writeJsonError(output2, options, message) {
|
|
67986
|
+
function writeJsonError(output2, options, message, code = "INTERNAL_ERROR", details) {
|
|
67987
|
+
const text4 = typeof message === "string" ? message : String(message);
|
|
67889
67988
|
if (options.json && envelopeEnabled(options.jsonEnvelope)) {
|
|
67890
|
-
|
|
67989
|
+
const bare = text4.startsWith("Error: ") ? text4.slice("Error: ".length) : text4;
|
|
67990
|
+
output2.write(toEnvelopeError(code, bare, details));
|
|
67891
67991
|
return;
|
|
67892
67992
|
}
|
|
67893
|
-
output2.error(
|
|
67993
|
+
output2.error(text4);
|
|
67894
67994
|
}
|
|
67895
67995
|
|
|
67896
67996
|
// ../../packages/app/src/services/agent-instance-store.ts
|
|
@@ -70771,8 +70871,18 @@ function splitTableRow(line) {
|
|
|
70771
70871
|
function unescapeTablePipe(s2) {
|
|
70772
70872
|
return s2.replace(/\\\|/g, "|");
|
|
70773
70873
|
}
|
|
70874
|
+
function isRecordAuthoredReview(body) {
|
|
70875
|
+
if (body === null)
|
|
70876
|
+
return false;
|
|
70877
|
+
const trimmed = body.trim();
|
|
70878
|
+
if (trimmed.startsWith(RECORD_REVIEW_MARKER))
|
|
70879
|
+
return true;
|
|
70880
|
+
return LEGACY_RECORD_REVIEW_RE.test(trimmed);
|
|
70881
|
+
}
|
|
70774
70882
|
function renderReview(v) {
|
|
70775
70883
|
const lines = [];
|
|
70884
|
+
lines.push(RECORD_REVIEW_MARKER);
|
|
70885
|
+
lines.push("");
|
|
70776
70886
|
lines.push(`**SECU findings** (pipeline verify step \u2014 verdict: ${v.verdict})`);
|
|
70777
70887
|
lines.push("");
|
|
70778
70888
|
lines.push("| Priority | Dimension | Location | Finding |");
|
|
@@ -70864,10 +70974,12 @@ function gitDiffU0(cwd) {
|
|
|
70864
70974
|
return "";
|
|
70865
70975
|
}
|
|
70866
70976
|
}
|
|
70977
|
+
var RECORD_REVIEW_MARKER = "<!-- spur:record-review -->", LEGACY_RECORD_REVIEW_RE;
|
|
70867
70978
|
var init_task_record = __esm(() => {
|
|
70868
70979
|
init_src2();
|
|
70869
70980
|
init_dist5();
|
|
70870
70981
|
init_verify_verdict();
|
|
70982
|
+
LEGACY_RECORD_REVIEW_RE = /^\*\*SECU findings\*\* \(pipeline verify step \u2014 verdict: [A-Z]+\)/;
|
|
70871
70983
|
});
|
|
70872
70984
|
|
|
70873
70985
|
// ../../packages/app/src/services/feature-check.ts
|
|
@@ -71330,7 +71442,14 @@ var init_feature_check = __esm(() => {
|
|
|
71330
71442
|
if (touchesSelfRef && dogfoodDir !== undefined) {
|
|
71331
71443
|
let hasDogfood = false;
|
|
71332
71444
|
try {
|
|
71333
|
-
|
|
71445
|
+
let entries;
|
|
71446
|
+
try {
|
|
71447
|
+
const ledger = await this.fs.readFile(`${dogfoodDir}/INDEX.md`);
|
|
71448
|
+
entries = ledger.split(`
|
|
71449
|
+
`);
|
|
71450
|
+
} catch {
|
|
71451
|
+
entries = await this.fs.readDir(dogfoodDir);
|
|
71452
|
+
}
|
|
71334
71453
|
const segmentRe = new RegExp(`(^|[^A-Za-z0-9])${featureId2}([^A-Za-z0-9]|$)`, "i");
|
|
71335
71454
|
hasDogfood = entries.some((f) => segmentRe.test(f));
|
|
71336
71455
|
} catch {}
|
|
@@ -71438,6 +71557,23 @@ var init_feature_check = __esm(() => {
|
|
|
71438
71557
|
});
|
|
71439
71558
|
}
|
|
71440
71559
|
}
|
|
71560
|
+
for (const [taskWbs, artifact] of artifacts2) {
|
|
71561
|
+
if (artifact.diagnostics.artifactError === "artifact is missing")
|
|
71562
|
+
continue;
|
|
71563
|
+
const rows = [...artifact.requirements, ...artifact.acceptanceCriteria];
|
|
71564
|
+
if (rows.length === 0)
|
|
71565
|
+
continue;
|
|
71566
|
+
const anyMatch = rows.some((r) => scenarioAliases.some((sc) => rowMatchesScenario(r.id, sc)));
|
|
71567
|
+
if (!anyMatch) {
|
|
71568
|
+
findings.push({
|
|
71569
|
+
layer: "L4",
|
|
71570
|
+
code: FINDING_CODES.L4_VERDICT_ROWS_MATCH_NO_SCENARIO,
|
|
71571
|
+
severity: "warning",
|
|
71572
|
+
section: "Acceptance Criteria",
|
|
71573
|
+
message: `Task ${taskWbs} verdict evidence (${artifact.path}) carries ${rows.length} row(s) matching no scenario of this feature \u2014 key rows by scenario title or AC-N alias (repair: /sp:dev-verify ${taskWbs})`
|
|
71574
|
+
});
|
|
71575
|
+
}
|
|
71576
|
+
}
|
|
71441
71577
|
for (const sc of scenarioAliases) {
|
|
71442
71578
|
const linked = covers[sc.title] ?? [];
|
|
71443
71579
|
if (linked.length === 0)
|
|
@@ -71904,15 +72040,23 @@ var init_task_check = __esm(() => {
|
|
|
71904
72040
|
const reqBody = doc2.getSection("Requirements");
|
|
71905
72041
|
if (reqBody !== null && !isPlaceholderBody(reqBody)) {
|
|
71906
72042
|
const blocks = reqBody.trim().split(/\n\s*\n/).filter((b) => b.trim().length > 0);
|
|
72043
|
+
const R_ITEM_RE = /^\s*[-*]?\s*(?:\[[ xX]\]\s*)?[*_]{0,2}R\d+\.?[*_]{0,2}\s/;
|
|
71907
72044
|
let numbered = 0;
|
|
72045
|
+
let proseBlocks = 0;
|
|
72046
|
+
let seenItem = false;
|
|
71908
72047
|
for (const block of blocks) {
|
|
71909
|
-
const
|
|
71910
|
-
`)
|
|
71911
|
-
if (
|
|
71912
|
-
numbered
|
|
72048
|
+
const itemLines = block.split(`
|
|
72049
|
+
`).filter((l) => R_ITEM_RE.test(l)).length;
|
|
72050
|
+
if (itemLines > 0) {
|
|
72051
|
+
numbered += itemLines;
|
|
72052
|
+
seenItem = true;
|
|
72053
|
+
continue;
|
|
72054
|
+
}
|
|
72055
|
+
if (!seenItem || /^\s*[*_]{1,2}[^*_\s]/.test(block.trimStart())) {
|
|
72056
|
+
proseBlocks++;
|
|
71913
72057
|
}
|
|
71914
72058
|
}
|
|
71915
|
-
if (numbered === 0 ||
|
|
72059
|
+
if (numbered === 0 || proseBlocks > numbered) {
|
|
71916
72060
|
findings.push({
|
|
71917
72061
|
layer: "L3",
|
|
71918
72062
|
code: FINDING_CODES.L3_REQUIREMENTS_FORMAT,
|
|
@@ -72041,6 +72185,26 @@ var init_task_check = __esm(() => {
|
|
|
72041
72185
|
message: `Task is ${status} but carries ${openBoxes} unchecked checklist box(es) \u2014 flip to [x] or remove before closing`
|
|
72042
72186
|
});
|
|
72043
72187
|
}
|
|
72188
|
+
const reviewBody = doc2.getSection("Review");
|
|
72189
|
+
const testingBody = doc2.getSection("Testing");
|
|
72190
|
+
if (reviewBody !== null && testingBody !== null) {
|
|
72191
|
+
const lastVerdict = (body) => {
|
|
72192
|
+
const lines = [...body.matchAll(/\**\s*Verdict\b\**\s*[:\uFF1A]?\s*([^*()\n]+)/gi)];
|
|
72193
|
+
return (lines[lines.length - 1]?.[1] ?? "").replace(/\*/g, "").trim();
|
|
72194
|
+
};
|
|
72195
|
+
const reviewVerdict = lastVerdict(reviewBody);
|
|
72196
|
+
const nonPassing = /\bPARTIAL\b|\bFAIL\b|request-changes/i.test(reviewVerdict);
|
|
72197
|
+
const testingVerdict = lastVerdict(testingBody);
|
|
72198
|
+
if (nonPassing && /^PASS\b/i.test(testingVerdict)) {
|
|
72199
|
+
findings.push({
|
|
72200
|
+
layer: "L3",
|
|
72201
|
+
code: FINDING_CODES.L3_REVIEW_TESTING_CONTRADICTION,
|
|
72202
|
+
severity: "error",
|
|
72203
|
+
section: "Review",
|
|
72204
|
+
message: `Review verdict is "${reviewVerdict}" while Testing records PASS \u2014 reconcile the stale Review via /sp:dev-review (semantics: the last Verdict line in the section is authoritative)`
|
|
72205
|
+
});
|
|
72206
|
+
}
|
|
72207
|
+
}
|
|
72044
72208
|
}
|
|
72045
72209
|
}
|
|
72046
72210
|
async runL4(doc2, fm, status, findings, featuresDir, tasksDir, wbs) {
|
|
@@ -72178,6 +72342,18 @@ var init_task_check = __esm(() => {
|
|
|
72178
72342
|
section: "Plan",
|
|
72179
72343
|
message: "Parent task has sub-tasks but its Plan has no sub-task roster (decomposition.md)"
|
|
72180
72344
|
});
|
|
72345
|
+
return;
|
|
72346
|
+
}
|
|
72347
|
+
const declared = new Set(this.extractDependencyWbs(doc2.frontmatterData?.dependencies));
|
|
72348
|
+
const undeclared = openKids.filter((kid) => !declared.has(kid.wbs)).map((kid) => kid.wbs);
|
|
72349
|
+
if (undeclared.length > 0) {
|
|
72350
|
+
findings.push({
|
|
72351
|
+
layer: "L4",
|
|
72352
|
+
code: FINDING_CODES.L4_ROLLUP_ROSTER_NOT_DECLARED_DEPENDENCY,
|
|
72353
|
+
severity: "warning",
|
|
72354
|
+
section: "Plan",
|
|
72355
|
+
message: `Plan rosters open sub-task(s) ${undeclared.join(", ")} that frontmatter dependencies[] does not declare \u2014 batch ordering reads dependencies[], not the Plan (repair: spur task deps ${wbs} add ${undeclared.join(" ")})`
|
|
72356
|
+
});
|
|
72181
72357
|
}
|
|
72182
72358
|
}
|
|
72183
72359
|
hasSubtaskRoster(planBody, kids) {
|
|
@@ -72297,6 +72473,9 @@ var init_task_check = __esm(() => {
|
|
|
72297
72473
|
}
|
|
72298
72474
|
}
|
|
72299
72475
|
checkGateLanguage(doc2, findings) {
|
|
72476
|
+
const declaredDeps = doc2.frontmatterData?.dependencies;
|
|
72477
|
+
if (Array.isArray(declaredDeps) && declaredDeps.length > 0)
|
|
72478
|
+
return;
|
|
72300
72479
|
for (const section of ["Background", "Requirements", "Design", "Acceptance Criteria", "Plan"]) {
|
|
72301
72480
|
const body = doc2.getSection(section);
|
|
72302
72481
|
if (body === null)
|
|
@@ -72656,7 +72835,7 @@ async function structuralSweep(projectRoot) {
|
|
|
72656
72835
|
});
|
|
72657
72836
|
const taskService = new TaskCheckService(fs3, await loadTaskMatrix(projectRoot), locator);
|
|
72658
72837
|
const findings = [];
|
|
72659
|
-
for (const tasksDir of
|
|
72838
|
+
for (const tasksDir of [activeTasksDir]) {
|
|
72660
72839
|
if (!await fs3.exists(tasksDir))
|
|
72661
72840
|
continue;
|
|
72662
72841
|
for (const fileName of await fs3.readDir(tasksDir)) {
|
|
@@ -78058,6 +78237,7 @@ class HistoryService {
|
|
|
78058
78237
|
});
|
|
78059
78238
|
}
|
|
78060
78239
|
await dao2.alignMessageProvenance();
|
|
78240
|
+
await deriveAssistantDurations(db2);
|
|
78061
78241
|
}
|
|
78062
78242
|
return result;
|
|
78063
78243
|
}
|
|
@@ -79111,7 +79291,7 @@ class PlanningWriteService {
|
|
|
79111
79291
|
} : {}
|
|
79112
79292
|
};
|
|
79113
79293
|
await this.emitter.emit(event);
|
|
79114
|
-
const warnings = doc2.
|
|
79294
|
+
const warnings = doc2.demotedHeadings.map((line) => `Demoted same-level heading one level deeper in section body (would become a phantom section): "${line}". ` + "Use bullet lists, tables, or **bold** labels for sub-structure instead.");
|
|
79115
79295
|
for (const dup of doc2.duplicateSectionNames) {
|
|
79116
79296
|
warnings.push(`Dropped duplicate "${dup}" section (appeared more than once in the file \u2014 ` + "likely a copy-paste or double-write error). The first occurrence was kept.");
|
|
79117
79297
|
}
|
|
@@ -79142,6 +79322,10 @@ function applyMutation(doc2, mutation) {
|
|
|
79142
79322
|
case "updateSection":
|
|
79143
79323
|
if (mutation.sectionName !== undefined && mutation.sectionBody !== undefined) {
|
|
79144
79324
|
const body = mutation.sectionName.toLowerCase() === "acceptance criteria" ? normalizeAcFence(mutation.sectionBody) : mutation.sectionBody;
|
|
79325
|
+
if (mutation.sectionName.toLowerCase() === "q&a") {
|
|
79326
|
+
appendQaEntry(doc2, body);
|
|
79327
|
+
break;
|
|
79328
|
+
}
|
|
79145
79329
|
doc2.replaceSection(mutation.sectionName, body);
|
|
79146
79330
|
}
|
|
79147
79331
|
break;
|
|
@@ -79172,6 +79356,22 @@ ${line}
|
|
|
79172
79356
|
`;
|
|
79173
79357
|
doc2.replaceSection("History", updated);
|
|
79174
79358
|
}
|
|
79359
|
+
function appendQaEntry(doc2, body) {
|
|
79360
|
+
const REPLACE_MARKER = "<!-- qa:replace -->";
|
|
79361
|
+
if (body.trimStart().startsWith(REPLACE_MARKER)) {
|
|
79362
|
+
doc2.replaceSection("Q&A", body.trimStart().slice(REPLACE_MARKER.length).trimStart());
|
|
79363
|
+
return;
|
|
79364
|
+
}
|
|
79365
|
+
const existing = doc2.getSection("Q&A") ?? "";
|
|
79366
|
+
const entry = `#### Q&A entry \u2014 ${new Date().toISOString()}
|
|
79367
|
+
|
|
79368
|
+
${body.trim()}
|
|
79369
|
+
`;
|
|
79370
|
+
const updated = existing.trim().length > 0 ? `${existing.trimEnd()}
|
|
79371
|
+
|
|
79372
|
+
${entry}` : entry;
|
|
79373
|
+
doc2.replaceSection("Q&A", updated);
|
|
79374
|
+
}
|
|
79175
79375
|
function resolveEventName(kind, domain2, statusChanged) {
|
|
79176
79376
|
if (kind === "create") {
|
|
79177
79377
|
return domain2 === "task" ? "task.created" : "feature.created";
|
|
@@ -81838,7 +82038,7 @@ class TaskService {
|
|
|
81838
82038
|
await this.writeService.updateSection(ref, "Testing", testingBody);
|
|
81839
82039
|
result.testingWritten = true;
|
|
81840
82040
|
}
|
|
81841
|
-
if (sectionIsBare(doc2, "Review")) {
|
|
82041
|
+
if (sectionIsBare(doc2, "Review") || isRecordAuthoredReview(doc2.getSection("Review"))) {
|
|
81842
82042
|
const reviewBody = renderReview(verdict);
|
|
81843
82043
|
await this.writeService.updateSection(ref, "Review", reviewBody);
|
|
81844
82044
|
result.reviewWritten = true;
|
|
@@ -82163,8 +82363,11 @@ ${block}` : block);
|
|
|
82163
82363
|
}
|
|
82164
82364
|
async resolveTaskFile(wbs) {
|
|
82165
82365
|
const result = await this.findTaskFileName(wbs);
|
|
82166
|
-
if (!result)
|
|
82167
|
-
|
|
82366
|
+
if (!result) {
|
|
82367
|
+
const err = new Error(`Task ${wbs} not found in any registered task folder`);
|
|
82368
|
+
err.cliCode = "NOT_FOUND";
|
|
82369
|
+
throw err;
|
|
82370
|
+
}
|
|
82168
82371
|
return result.filePath;
|
|
82169
82372
|
}
|
|
82170
82373
|
async findTaskFileName(wbs) {
|
|
@@ -87564,6 +87767,7 @@ __export(exports_src2, {
|
|
|
87564
87767
|
looksLikeOpaqueId: () => looksLikeOpaqueId,
|
|
87565
87768
|
loadAcceptedFindings: () => loadAcceptedFindings,
|
|
87566
87769
|
isSystemEventEnvelopeV2: () => isSystemEventEnvelopeV2,
|
|
87770
|
+
isRecordAuthoredReview: () => isRecordAuthoredReview,
|
|
87567
87771
|
isPortLive: () => isPortLive,
|
|
87568
87772
|
isPortAvailable: () => isPortAvailable,
|
|
87569
87773
|
isFindingCode: () => isFindingCode,
|
|
@@ -95318,12 +95522,12 @@ async function runAgentList(svc, context4, opts) {
|
|
|
95318
95522
|
}
|
|
95319
95523
|
async function runAgentCreate(id, context4, flags) {
|
|
95320
95524
|
if (id === undefined) {
|
|
95321
|
-
context4.output
|
|
95525
|
+
writeJsonError(context4.output, jsonFlags(flags), "agent create requires <id>", "VALIDATION_FAILED");
|
|
95322
95526
|
return 2;
|
|
95323
95527
|
}
|
|
95324
95528
|
const type = typeof flags.type === "string" ? flags.type : "";
|
|
95325
95529
|
if (type === "") {
|
|
95326
|
-
context4.output
|
|
95530
|
+
writeJsonError(context4.output, jsonFlags(flags), "agent create requires --type <agent-type>", "VALIDATION_FAILED");
|
|
95327
95531
|
return 2;
|
|
95328
95532
|
}
|
|
95329
95533
|
const tags = typeof flags.tags === "string" ? flags.tags : "";
|
|
@@ -95423,6 +95627,13 @@ async function runAgentDelete(id, context4, flags) {
|
|
|
95423
95627
|
return 1;
|
|
95424
95628
|
}
|
|
95425
95629
|
}
|
|
95630
|
+
function jsonFlags(flags) {
|
|
95631
|
+
const envelope2 = flags.jsonEnvelope ?? flags["json-envelope"];
|
|
95632
|
+
return {
|
|
95633
|
+
json: flags.json === true,
|
|
95634
|
+
jsonEnvelope: typeof envelope2 === "boolean" ? envelope2 : undefined
|
|
95635
|
+
};
|
|
95636
|
+
}
|
|
95426
95637
|
async function runAgentRun(prompt, context4, flags, deps) {
|
|
95427
95638
|
const bus = new EventBus;
|
|
95428
95639
|
const ledger = await attachSystemEventLedger(bus, context4);
|
|
@@ -95431,19 +95642,19 @@ async function runAgentRun(prompt, context4, flags, deps) {
|
|
|
95431
95642
|
if (flags.drain === true || typeof flags.spec === "string") {
|
|
95432
95643
|
const { prompt: drained, flags: rewritten } = await drainIntoPrompt(prompt, context4, flags);
|
|
95433
95644
|
if (typeof flags.spec === "string" && flags.spec !== "" && rewritten["spec-id"] !== flags.spec) {
|
|
95434
|
-
context4.output
|
|
95645
|
+
writeJsonError(context4.output, jsonFlags(flags), `--spec "${flags.spec}" does not match a team agent spec`, "VALIDATION_FAILED");
|
|
95435
95646
|
return 2;
|
|
95436
95647
|
}
|
|
95437
95648
|
const invalid2 = validateAgentSelector(rewritten, context4);
|
|
95438
95649
|
if (invalid2 !== null) {
|
|
95439
|
-
context4.output
|
|
95650
|
+
writeJsonError(context4.output, jsonFlags(flags), invalid2, "VALIDATION_FAILED");
|
|
95440
95651
|
return 2;
|
|
95441
95652
|
}
|
|
95442
95653
|
return await svc.run(drained, rewritten, deps);
|
|
95443
95654
|
}
|
|
95444
95655
|
const invalid = validateAgentSelector(flags, context4);
|
|
95445
95656
|
if (invalid !== null) {
|
|
95446
|
-
context4.output
|
|
95657
|
+
writeJsonError(context4.output, jsonFlags(flags), invalid, "VALIDATION_FAILED");
|
|
95447
95658
|
return 2;
|
|
95448
95659
|
}
|
|
95449
95660
|
return await svc.run(prompt, flags, deps);
|
|
@@ -96299,7 +96510,7 @@ function registerFeatureCommand(program2, context4) {
|
|
|
96299
96510
|
try {
|
|
96300
96511
|
const result = await svc.show(id);
|
|
96301
96512
|
if (result === null) {
|
|
96302
|
-
writeJsonError(context4.output, options, `Feature ${id} not found
|
|
96513
|
+
writeJsonError(context4.output, options, `Feature ${id} not found`, "NOT_FOUND");
|
|
96303
96514
|
context4.setExitCode(1);
|
|
96304
96515
|
return;
|
|
96305
96516
|
}
|
|
@@ -96325,7 +96536,7 @@ function registerFeatureCommand(program2, context4) {
|
|
|
96325
96536
|
let result;
|
|
96326
96537
|
if (options.section !== undefined) {
|
|
96327
96538
|
if (options.fromFile === undefined) {
|
|
96328
|
-
context4.output
|
|
96539
|
+
writeJsonError(context4.output, options, "--from-file is required with --section", "VALIDATION_FAILED");
|
|
96329
96540
|
context4.setExitCode(2);
|
|
96330
96541
|
return;
|
|
96331
96542
|
}
|
|
@@ -96337,13 +96548,13 @@ function registerFeatureCommand(program2, context4) {
|
|
|
96337
96548
|
context4.output.write(`Updated section '${options.section}' in feature ${result.ref.id}`);
|
|
96338
96549
|
}
|
|
96339
96550
|
} else if (options.fromFile !== undefined) {
|
|
96340
|
-
context4.output
|
|
96551
|
+
writeJsonError(context4.output, options, "--section is required with --from-file", "VALIDATION_FAILED");
|
|
96341
96552
|
context4.setExitCode(2);
|
|
96342
96553
|
return;
|
|
96343
96554
|
}
|
|
96344
96555
|
if (options.field !== undefined) {
|
|
96345
96556
|
if (options.value === undefined) {
|
|
96346
|
-
context4.output
|
|
96557
|
+
writeJsonError(context4.output, options, "--value is required with --field", "VALIDATION_FAILED");
|
|
96347
96558
|
context4.setExitCode(2);
|
|
96348
96559
|
return;
|
|
96349
96560
|
}
|
|
@@ -96352,7 +96563,7 @@ function registerFeatureCommand(program2, context4) {
|
|
|
96352
96563
|
context4.output.write(`Updated ${options.field} on feature ${result.ref.id}`);
|
|
96353
96564
|
}
|
|
96354
96565
|
} else if (options.value !== undefined) {
|
|
96355
|
-
context4.output
|
|
96566
|
+
writeJsonError(context4.output, options, "--field is required with --value", "VALIDATION_FAILED");
|
|
96356
96567
|
context4.setExitCode(2);
|
|
96357
96568
|
return;
|
|
96358
96569
|
}
|
|
@@ -96363,7 +96574,7 @@ function registerFeatureCommand(program2, context4) {
|
|
|
96363
96574
|
}
|
|
96364
96575
|
}
|
|
96365
96576
|
if (result === undefined) {
|
|
96366
|
-
context4.output
|
|
96577
|
+
writeJsonError(context4.output, options, "Either <status>, --field/--value, or --section/--from-file is required", "VALIDATION_FAILED");
|
|
96367
96578
|
context4.setExitCode(2);
|
|
96368
96579
|
return;
|
|
96369
96580
|
}
|
|
@@ -96387,7 +96598,7 @@ function registerFeatureCommand(program2, context4) {
|
|
|
96387
96598
|
try {
|
|
96388
96599
|
const initial = await svc.show(id);
|
|
96389
96600
|
if (initial === null) {
|
|
96390
|
-
writeJsonError(context4.output, options, `Feature ${id} not found
|
|
96601
|
+
writeJsonError(context4.output, options, `Feature ${id} not found`, "NOT_FOUND");
|
|
96391
96602
|
context4.setExitCode(1);
|
|
96392
96603
|
return;
|
|
96393
96604
|
}
|
|
@@ -96419,7 +96630,7 @@ function registerFeatureCommand(program2, context4) {
|
|
|
96419
96630
|
next = forwardPath[current];
|
|
96420
96631
|
}
|
|
96421
96632
|
if (current !== target) {
|
|
96422
|
-
context4.output
|
|
96633
|
+
writeJsonError(context4.output, options, `${id}: cannot reach '${target}' from '${current}' along the forward path`, "GUARD_DENIED");
|
|
96423
96634
|
context4.setExitCode(1);
|
|
96424
96635
|
return;
|
|
96425
96636
|
}
|
|
@@ -96495,12 +96706,12 @@ function registerFeatureCommand(program2, context4) {
|
|
|
96495
96706
|
const svc = await makeService(context4, options.folder);
|
|
96496
96707
|
try {
|
|
96497
96708
|
if (options.all && options.feature) {
|
|
96498
|
-
context4.output
|
|
96709
|
+
writeJsonError(context4.output, options, "--feature <id> and --all are mutually exclusive", "VALIDATION_FAILED");
|
|
96499
96710
|
context4.setExitCode(2);
|
|
96500
96711
|
return;
|
|
96501
96712
|
}
|
|
96502
96713
|
if (!options.all && !options.feature) {
|
|
96503
|
-
context4.output
|
|
96714
|
+
writeJsonError(context4.output, options, "--feature <id> or --all is required (refusing silent all-features sweep)", "VALIDATION_FAILED");
|
|
96504
96715
|
context4.setExitCode(2);
|
|
96505
96716
|
return;
|
|
96506
96717
|
}
|
|
@@ -96534,9 +96745,9 @@ function registerFeatureCommand(program2, context4) {
|
|
|
96534
96745
|
for (const fid of ids) {
|
|
96535
96746
|
const fileName = entries.find((n3) => n3.match(new RegExp(`^${fid}_.+\\.md$`)));
|
|
96536
96747
|
if (!fileName) {
|
|
96537
|
-
context4.output
|
|
96748
|
+
writeJsonError(context4.output, options, `Feature ${fid} not found`, "NOT_FOUND");
|
|
96538
96749
|
context4.setExitCode(1);
|
|
96539
|
-
|
|
96750
|
+
return;
|
|
96540
96751
|
}
|
|
96541
96752
|
const result = await svc.check(`${featuresDir}/${fileName}`, fid, {
|
|
96542
96753
|
strict,
|
|
@@ -96586,7 +96797,7 @@ ${result.id} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
96586
96797
|
const svc = await makeService(context4, options.folder);
|
|
96587
96798
|
try {
|
|
96588
96799
|
if (!options.all && !id) {
|
|
96589
|
-
context4.output
|
|
96800
|
+
writeJsonError(context4.output, options, "Feature ID is required unless --all is passed", "VALIDATION_FAILED");
|
|
96590
96801
|
context4.setExitCode(2);
|
|
96591
96802
|
return;
|
|
96592
96803
|
}
|
|
@@ -96675,7 +96886,7 @@ import { createRequire } from "module";
|
|
|
96675
96886
|
var CLI_CONFIG = {
|
|
96676
96887
|
binaryName: "spur",
|
|
96677
96888
|
binaryLabel: "spur",
|
|
96678
|
-
binaryVersion: "0.3.
|
|
96889
|
+
binaryVersion: "0.3.66",
|
|
96679
96890
|
configDir: ".spur",
|
|
96680
96891
|
configFile: ".spur/config.yaml",
|
|
96681
96892
|
databaseFile: ".spur/spur.db"
|
|
@@ -97362,7 +97573,7 @@ function registerMessageCommand(program2, context4) {
|
|
|
97362
97573
|
const svc = new TeamService(context4);
|
|
97363
97574
|
const intervalMs = parseInterval2(options.interval);
|
|
97364
97575
|
if (intervalMs === null) {
|
|
97365
|
-
context4.output
|
|
97576
|
+
writeJsonError(context4.output, options, `invalid --interval "${options.interval}" (expected a positive integer ms)`, "VALIDATION_FAILED");
|
|
97366
97577
|
context4.setExitCode(2);
|
|
97367
97578
|
return;
|
|
97368
97579
|
}
|
|
@@ -97527,7 +97738,7 @@ async function runMessageInbox(svc, context4, options) {
|
|
|
97527
97738
|
async function runMessageReply(svc, context4, msgId, body, options) {
|
|
97528
97739
|
const trimmed = body.trim();
|
|
97529
97740
|
if (trimmed === "") {
|
|
97530
|
-
context4.output
|
|
97741
|
+
writeJsonError(context4.output, options, "message reply requires a non-empty body", "VALIDATION_FAILED");
|
|
97531
97742
|
return 2;
|
|
97532
97743
|
}
|
|
97533
97744
|
const result = await svc.replyToMessage(msgId, trimmed);
|
|
@@ -97930,17 +98141,17 @@ function registerRuleCommand(program2, context4) {
|
|
|
97930
98141
|
rule.command("trace").summary("Show persisted rule run history.").argument("[run-id]", "Run ID for per-run detail").option("--preset <name>", "Filter by preset name").option(...SHARED_OPTIONS.statusDoneFailed).option(...SHARED_OPTIONS.since).option(...SHARED_OPTIONS.last, "20").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (runId, options) => {
|
|
97931
98142
|
const last = parseInt(options.last, 10);
|
|
97932
98143
|
if (!Number.isInteger(last) || last < 1) {
|
|
97933
|
-
context4.output
|
|
98144
|
+
writeJsonError(context4.output, options, "--last must be a positive integer", "VALIDATION_FAILED");
|
|
97934
98145
|
context4.setExitCode(1);
|
|
97935
98146
|
return;
|
|
97936
98147
|
}
|
|
97937
98148
|
if (options.status !== undefined && !["done", "failed"].includes(options.status)) {
|
|
97938
|
-
context4.output
|
|
98149
|
+
writeJsonError(context4.output, options, "--status must be one of: done, failed", "VALIDATION_FAILED");
|
|
97939
98150
|
context4.setExitCode(1);
|
|
97940
98151
|
return;
|
|
97941
98152
|
}
|
|
97942
98153
|
if (options.since !== undefined && Number.isNaN(Date.parse(options.since))) {
|
|
97943
|
-
context4.output
|
|
98154
|
+
writeJsonError(context4.output, options, "--since must be a valid ISO date", "VALIDATION_FAILED");
|
|
97944
98155
|
context4.setExitCode(1);
|
|
97945
98156
|
return;
|
|
97946
98157
|
}
|
|
@@ -108141,7 +108352,7 @@ function registerServeCommand(program2, context4, options = {}) {
|
|
|
108141
108352
|
} catch (err) {
|
|
108142
108353
|
writeJsonError(context4.output, options2, err instanceof Error ? err.message : String(err));
|
|
108143
108354
|
if (context4.env?.SPUR_DEBUG === "1" && err instanceof Error && err.stack) {
|
|
108144
|
-
context4.output
|
|
108355
|
+
writeJsonError(context4.output, options2, err.stack, "INTERNAL_ERROR");
|
|
108145
108356
|
}
|
|
108146
108357
|
context4.setExitCode(1);
|
|
108147
108358
|
}
|
|
@@ -109168,12 +109379,12 @@ function registerTaskCommand(program2, context4) {
|
|
|
109168
109379
|
const task = program2.command("task").summary("manage tasks");
|
|
109169
109380
|
task.command("create").summary("Create a new task with race-safe WBS allocation.").argument("<title>", "Task title").option(...SHARED_OPTIONS.featureTrace).option("--parent <wbs>", "Parent WBS for sub-task grouping").option("--template <variant>", `Template variant (${TASK_VARIANTS.join("|")})`).option(...SHARED_OPTIONS.folderTasks).option("--dedupe-within <seconds>", "Override the default dedup window (seconds). Guard is on (300s) by default when --feature is set.", Number).option("--allow-duplicate-name", "Disable the dedup guard entirely (creates anyway)").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (title2, options) => {
|
|
109170
109381
|
if (options.template !== undefined && !TASK_VARIANTS.includes(options.template)) {
|
|
109171
|
-
context4.output
|
|
109382
|
+
writeJsonError(context4.output, options, `Unknown template variant "${options.template}". Valid: ${TASK_VARIANTS.join(", ")}`, "VALIDATION_FAILED");
|
|
109172
109383
|
context4.setExitCode(2);
|
|
109173
109384
|
return;
|
|
109174
109385
|
}
|
|
109175
109386
|
if (options.dedupeWithin !== undefined && (!Number.isInteger(options.dedupeWithin) || options.dedupeWithin <= 0)) {
|
|
109176
|
-
context4.output
|
|
109387
|
+
writeJsonError(context4.output, options, "--dedupe-within must be a positive integer", "VALIDATION_FAILED");
|
|
109177
109388
|
context4.setExitCode(2);
|
|
109178
109389
|
return;
|
|
109179
109390
|
}
|
|
@@ -109267,7 +109478,8 @@ function registerTaskCommand(program2, context4) {
|
|
|
109267
109478
|
${result.content}`);
|
|
109268
109479
|
}
|
|
109269
109480
|
} catch (err) {
|
|
109270
|
-
|
|
109481
|
+
const cliCode = err?.cliCode;
|
|
109482
|
+
writeJsonError(context4.output, options, String(err), "INTERNAL_ERROR", cliCode !== undefined ? { cliCode } : undefined);
|
|
109271
109483
|
context4.setExitCode(1);
|
|
109272
109484
|
}
|
|
109273
109485
|
});
|
|
@@ -109287,7 +109499,7 @@ ${result.content}`);
|
|
|
109287
109499
|
try {
|
|
109288
109500
|
if (options.section !== undefined) {
|
|
109289
109501
|
if (options.fromFile === undefined) {
|
|
109290
|
-
context4.output
|
|
109502
|
+
writeJsonError(context4.output, options, "--from-file is required with --section", "VALIDATION_FAILED");
|
|
109291
109503
|
context4.setExitCode(2);
|
|
109292
109504
|
return;
|
|
109293
109505
|
}
|
|
@@ -109319,7 +109531,7 @@ ${result.content}`);
|
|
|
109319
109531
|
}
|
|
109320
109532
|
const ok = await runDoneGateCheck(context4, wbs, options.folder, status);
|
|
109321
109533
|
if (!ok) {
|
|
109322
|
-
context4.output
|
|
109534
|
+
writeJsonError(context4.output, options, `Lifecycle transition blocked: \`spur task check ${wbs}\` failed. Fix the findings before transitioning to ${status}.`, "GUARD_DENIED");
|
|
109323
109535
|
context4.setExitCode(1);
|
|
109324
109536
|
return;
|
|
109325
109537
|
}
|
|
@@ -109350,7 +109562,7 @@ ${result.content}`);
|
|
|
109350
109562
|
return;
|
|
109351
109563
|
}
|
|
109352
109564
|
if (guardOutcome.kind === "deny") {
|
|
109353
|
-
context4.output
|
|
109565
|
+
writeJsonError(context4.output, options, guardOutcome.message, "GUARD_DENIED");
|
|
109354
109566
|
context4.setExitCode(1);
|
|
109355
109567
|
return;
|
|
109356
109568
|
}
|
|
@@ -109397,7 +109609,7 @@ ${result.content}`);
|
|
|
109397
109609
|
}
|
|
109398
109610
|
} catch (err) {
|
|
109399
109611
|
if (err instanceof SectionMutationError) {
|
|
109400
|
-
context4.output
|
|
109612
|
+
writeJsonError(context4.output, options, `[${err.code}] ${err.message}`, "INTERNAL_ERROR");
|
|
109401
109613
|
context4.setExitCode(err.code === "usage" ? 2 : 3);
|
|
109402
109614
|
} else {
|
|
109403
109615
|
writeJsonError(context4.output, options, String(err));
|
|
@@ -109415,7 +109627,7 @@ ${result.content}`);
|
|
|
109415
109627
|
` + "2 usage error, 3 validation error.").argument("<wbs>", "Task WBS number to mutate").argument("<op>", "Operation: set | add | remove | clear").argument("[values...]", "WBS values (required for set/add/remove; forbidden for clear)").option(...SHARED_OPTIONS.folderTasks).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (wbs, op, values, options) => {
|
|
109416
109628
|
const allowedOps = ["set", "add", "remove", "clear"];
|
|
109417
109629
|
if (!allowedOps.includes(op)) {
|
|
109418
|
-
context4.output
|
|
109630
|
+
writeJsonError(context4.output, options, `Unknown op "${op}". Allowed: ${allowedOps.join(", ")}.`, "VALIDATION_FAILED");
|
|
109419
109631
|
context4.setExitCode(2);
|
|
109420
109632
|
return;
|
|
109421
109633
|
}
|
|
@@ -109431,7 +109643,7 @@ ${result.content}`);
|
|
|
109431
109643
|
}
|
|
109432
109644
|
} catch (err) {
|
|
109433
109645
|
if (err instanceof DependencyMutationError) {
|
|
109434
|
-
context4.output
|
|
109646
|
+
writeJsonError(context4.output, options, `[${err.code}] ${err.message}`, "INTERNAL_ERROR");
|
|
109435
109647
|
context4.setExitCode(err.code === "usage" ? 2 : 3);
|
|
109436
109648
|
} else {
|
|
109437
109649
|
writeJsonError(context4.output, options, String(err));
|
|
@@ -109455,18 +109667,18 @@ ${result.content}`);
|
|
|
109455
109667
|
` + "1 generic error, 2 usage error, 3 validation error.").argument("<wbs>", "Task WBS number to mutate").argument("<op>", "Operation: init | add | list").argument("[name]", "Canonical section name (required for add; forbidden for init/list)").option(...SHARED_OPTIONS.folderTasks).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (wbs, op, name, options) => {
|
|
109456
109668
|
const allowedOps = ["init", "add", "list"];
|
|
109457
109669
|
if (!allowedOps.includes(op)) {
|
|
109458
|
-
context4.output
|
|
109670
|
+
writeJsonError(context4.output, options, `Unknown op "${op}". Allowed: ${allowedOps.join(", ")}.`, "VALIDATION_FAILED");
|
|
109459
109671
|
context4.setExitCode(2);
|
|
109460
109672
|
return;
|
|
109461
109673
|
}
|
|
109462
109674
|
const typedOp = op;
|
|
109463
109675
|
if (typedOp === "add" && typeof name !== "string") {
|
|
109464
|
-
context4.output
|
|
109676
|
+
writeJsonError(context4.output, options, 'op "add" requires a section name argument.', "VALIDATION_FAILED");
|
|
109465
109677
|
context4.setExitCode(2);
|
|
109466
109678
|
return;
|
|
109467
109679
|
}
|
|
109468
109680
|
if ((typedOp === "init" || typedOp === "list") && name !== undefined) {
|
|
109469
|
-
context4.output
|
|
109681
|
+
writeJsonError(context4.output, options, `op "${typedOp}" takes no section name argument.`, "VALIDATION_FAILED");
|
|
109470
109682
|
context4.setExitCode(2);
|
|
109471
109683
|
return;
|
|
109472
109684
|
}
|
|
@@ -109491,7 +109703,7 @@ ${result.content}`);
|
|
|
109491
109703
|
}
|
|
109492
109704
|
} catch (err) {
|
|
109493
109705
|
if (err instanceof SectionMutationError) {
|
|
109494
|
-
context4.output
|
|
109706
|
+
writeJsonError(context4.output, options, `[${err.code}] ${err.message}`, "INTERNAL_ERROR");
|
|
109495
109707
|
context4.setExitCode(err.code === "usage" ? 2 : 3);
|
|
109496
109708
|
} else {
|
|
109497
109709
|
writeJsonError(context4.output, options, String(err));
|
|
@@ -109704,7 +109916,7 @@ ${result.content}`);
|
|
|
109704
109916
|
}
|
|
109705
109917
|
});
|
|
109706
109918
|
task.command("verdict").summary("Derive PASS/PARTIAL/FAIL/UNKNOWN verdict from verify answer text (replaces pipeline grep/shell).").argument("<wbs>", "Task WBS number").option("--from-answer <path>", "Path to verify answer text file").option(...SHARED_OPTIONS.folderTasks).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (wbs, options) => {
|
|
109707
|
-
const { deriveVerdict: deriveVerdict2
|
|
109919
|
+
const { deriveVerdict: deriveVerdict2 } = await init_src3().then(() => exports_src2);
|
|
109708
109920
|
const answerPath = options.fromAnswer ?? `.spur/run/${wbs}-verify-answer.txt`;
|
|
109709
109921
|
let answerText;
|
|
109710
109922
|
try {
|
|
@@ -109716,22 +109928,6 @@ ${result.content}`);
|
|
|
109716
109928
|
}
|
|
109717
109929
|
const taskCheckPassed = true;
|
|
109718
109930
|
const result = deriveVerdict2(answerText, taskCheckPassed);
|
|
109719
|
-
try {
|
|
109720
|
-
const svc = await makeService2(context4, options.folder);
|
|
109721
|
-
const task2 = await svc.show(wbs);
|
|
109722
|
-
const featureId2 = task2.frontmatter.feature_id;
|
|
109723
|
-
if (typeof featureId2 === "string" && featureId2.length > 0) {
|
|
109724
|
-
const resolved = await resolvePlanningFolders(context4.fs);
|
|
109725
|
-
const names = await context4.fs.readDir(context4.fs.resolve(resolved.featuresDir));
|
|
109726
|
-
const name = names.find((n3) => n3.startsWith(`${featureId2}_`) && n3.endsWith(".md"));
|
|
109727
|
-
if (name !== undefined) {
|
|
109728
|
-
const raw2 = await context4.fs.readFile(context4.fs.resolve(`${resolved.featuresDir}/${name}`));
|
|
109729
|
-
if (!verdictRowsMatchScenarios2(result.requirements, raw2)) {
|
|
109730
|
-
context4.output.error(`warning: no verdict row matches any scenario in feature ${featureId2} \u2014 key rows by scenario title or AC-N alias, or the feature done gate reports L4.scenario-unverified`);
|
|
109731
|
-
}
|
|
109732
|
-
}
|
|
109733
|
-
}
|
|
109734
|
-
} catch {}
|
|
109735
109931
|
const jsonOut = JSON.stringify({ wbs, ...result, source: "spur-task-verdict" }, null, 2);
|
|
109736
109932
|
await context4.fs.ensureDir(".spur/run");
|
|
109737
109933
|
await context4.fs.writeFile(`.spur/run/${wbs}-verdict.json`, `${jsonOut}
|
|
@@ -109792,34 +109988,34 @@ ${result.content}`);
|
|
|
109792
109988
|
context4.setExitCode(1);
|
|
109793
109989
|
}
|
|
109794
109990
|
});
|
|
109795
|
-
task.command("check").summary("Validate a task file through the four-layer check (design \xA73).").argument("[wbs]", "Task WBS number (validates all tasks in the folder when omitted)").option(...SHARED_OPTIONS.strictTaskAll).option("--strict-core", "Compatibility alias (F92 R2): historically the done-gate label; kept so installed plugins/workflows that call it keep working. No longer meaningful on its own \u2014 target-state selection (`--as`) supplies the real done semantics.").option(...SHARED_OPTIONS.asTaskF92).option("--corpus", "Sweep every task and feature against config/corpus-baseline.json").option("--since <ref>", "Scope the corpus fog check to changes since a git ref (requires --corpus)").option("--fix", "repair structural findings in place (heading presence/level/order
|
|
109991
|
+
task.command("check").summary("Validate a task file through the four-layer check (design \xA73).").argument("[wbs]", "Task WBS number (validates all tasks in the folder when omitted)").option(...SHARED_OPTIONS.strictTaskAll).option("--strict-core", "Compatibility alias (F92 R2): historically the done-gate label; kept so installed plugins/workflows that call it keep working. No longer meaningful on its own \u2014 target-state selection (`--as`) supplies the real done semantics.").option(...SHARED_OPTIONS.asTaskF92).option("--corpus", "Sweep every task and feature against config/corpus-baseline.json").option("--since <ref>", "Scope the corpus fog check to changes since a git ref (requires --corpus)").option("--fix", "repair structural findings in place (heading presence/level/order; adds missing R-item checkbox markers \u2014 flipping verified boxes is task record's job, not --fix)").option(...SHARED_OPTIONS.folderTasks).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (wbs, options) => {
|
|
109796
109992
|
const json3 = options.json === true;
|
|
109797
109993
|
const strict = options.strict === true;
|
|
109798
109994
|
const asStatus = options.as === undefined ? undefined : canonicalStatusOrRaw(options.as);
|
|
109799
109995
|
if (options.as !== undefined && !TASK_STATUSES.includes(asStatus ?? "")) {
|
|
109800
|
-
context4.output
|
|
109996
|
+
writeJsonError(context4.output, options, `invalid --as status "${options.as}" (canonical: ${TASK_STATUSES.join(", ")})`, "VALIDATION_FAILED");
|
|
109801
109997
|
context4.setExitCode(2);
|
|
109802
109998
|
return;
|
|
109803
109999
|
}
|
|
109804
110000
|
if (asStatus !== undefined && options.corpus === true) {
|
|
109805
|
-
context4.output
|
|
110001
|
+
writeJsonError(context4.output, options, "--as <status> is a single-task target projection and cannot be combined with --corpus", "VALIDATION_FAILED");
|
|
109806
110002
|
context4.setExitCode(2);
|
|
109807
110003
|
return;
|
|
109808
110004
|
}
|
|
109809
110005
|
if (options.fix === true && options.corpus === true) {
|
|
109810
|
-
context4.output
|
|
110006
|
+
writeJsonError(context4.output, options, "--fix repairs files in place and cannot be combined with --corpus", "VALIDATION_FAILED");
|
|
109811
110007
|
context4.setExitCode(2);
|
|
109812
110008
|
return;
|
|
109813
110009
|
}
|
|
109814
110010
|
try {
|
|
109815
110011
|
if (options.corpus === true) {
|
|
109816
110012
|
if (wbs !== undefined) {
|
|
109817
|
-
context4.output
|
|
110013
|
+
writeJsonError(context4.output, options, "--corpus validates the whole corpus and cannot be combined with a WBS", "VALIDATION_FAILED");
|
|
109818
110014
|
context4.setExitCode(2);
|
|
109819
110015
|
return;
|
|
109820
110016
|
}
|
|
109821
110017
|
if (String(options.since ?? "").startsWith("-")) {
|
|
109822
|
-
context4.output
|
|
110018
|
+
writeJsonError(context4.output, options, "--since requires a git ref value (e.g. --since HEAD~1)", "VALIDATION_FAILED");
|
|
109823
110019
|
context4.setExitCode(2);
|
|
109824
110020
|
return;
|
|
109825
110021
|
}
|
|
@@ -109852,7 +110048,7 @@ ${result.content}`);
|
|
|
109852
110048
|
return;
|
|
109853
110049
|
}
|
|
109854
110050
|
if (options.since !== undefined) {
|
|
109855
|
-
context4.output
|
|
110051
|
+
writeJsonError(context4.output, options, "--since requires --corpus", "VALIDATION_FAILED");
|
|
109856
110052
|
context4.setExitCode(2);
|
|
109857
110053
|
return;
|
|
109858
110054
|
}
|
|
@@ -109884,8 +110080,9 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
109884
110080
|
const locator = options.folder !== undefined ? TaskLocator.forSingleDir(context4.fs, tasksDir) : await makeTaskLocator(context4);
|
|
109885
110081
|
const hit = await locator.findByWbs(wbs);
|
|
109886
110082
|
if (hit === null) {
|
|
109887
|
-
context4.output
|
|
110083
|
+
writeJsonError(context4.output, options, `Task ${wbs} not found`, "NOT_FOUND");
|
|
109888
110084
|
context4.setExitCode(1);
|
|
110085
|
+
return;
|
|
109889
110086
|
} else {
|
|
109890
110087
|
const result = await svc.check(hit.filePath, wbs, {
|
|
109891
110088
|
strict,
|
|
@@ -109903,7 +110100,7 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
109903
110100
|
for (const w of wbsPattern) {
|
|
109904
110101
|
const fileName = entries.find((n3) => n3.startsWith(`${w}_`) && n3.endsWith(".md"));
|
|
109905
110102
|
if (!fileName) {
|
|
109906
|
-
context4.output
|
|
110103
|
+
writeJsonError(context4.output, options, `Task ${w} not found`, "NOT_FOUND");
|
|
109907
110104
|
context4.setExitCode(1);
|
|
109908
110105
|
continue;
|
|
109909
110106
|
}
|
|
@@ -109957,7 +110154,7 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
109957
110154
|
context4.output.write(`${result.wbs} ${result.filePath}`);
|
|
109958
110155
|
}
|
|
109959
110156
|
} else {
|
|
109960
|
-
context4.output
|
|
110157
|
+
writeJsonError(context4.output, options, `No owning task found for ${filePath}`, "NOT_FOUND");
|
|
109961
110158
|
context4.setExitCode(1);
|
|
109962
110159
|
}
|
|
109963
110160
|
} catch (err) {
|
|
@@ -109976,7 +110173,7 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
109976
110173
|
context4.output.write(filePath);
|
|
109977
110174
|
}
|
|
109978
110175
|
} else {
|
|
109979
|
-
context4.output
|
|
110176
|
+
writeJsonError(context4.output, options, `Task ${wbs} not found`, "NOT_FOUND");
|
|
109980
110177
|
context4.setExitCode(1);
|
|
109981
110178
|
}
|
|
109982
110179
|
} catch (err) {
|
|
@@ -110253,7 +110450,8 @@ async function performTeamStart(agentId, options) {
|
|
|
110253
110450
|
const body = await res.json();
|
|
110254
110451
|
if (res.ok)
|
|
110255
110452
|
return { ok: true, body };
|
|
110256
|
-
|
|
110453
|
+
const detail = errorText(body.error);
|
|
110454
|
+
return { ok: false, error: detail ?? `start failed: ${res.status}`, status: res.status };
|
|
110257
110455
|
} catch (err) {
|
|
110258
110456
|
return { ok: false, transportError: err };
|
|
110259
110457
|
}
|
|
@@ -110262,11 +110460,11 @@ async function runTeamStart(agentId, options, context4) {
|
|
|
110262
110460
|
const result = await performTeamStart(agentId, options);
|
|
110263
110461
|
if ("transportError" in result) {
|
|
110264
110462
|
const err = result.transportError;
|
|
110265
|
-
context4.output
|
|
110463
|
+
writeJsonError(context4.output, options, `Cannot reach server at ${options.server} \u2014 is spur serve running? (${err instanceof Error ? err.message : String(err)})`, "INTERNAL_ERROR");
|
|
110266
110464
|
return 1;
|
|
110267
110465
|
}
|
|
110268
110466
|
if (!result.ok) {
|
|
110269
|
-
context4.output
|
|
110467
|
+
writeJsonError(context4.output, options, result.error, "INTERNAL_ERROR");
|
|
110270
110468
|
return 1;
|
|
110271
110469
|
}
|
|
110272
110470
|
if (options.json) {
|
|
@@ -110276,6 +110474,19 @@ async function runTeamStart(agentId, options, context4) {
|
|
|
110276
110474
|
}
|
|
110277
110475
|
return 0;
|
|
110278
110476
|
}
|
|
110477
|
+
function errorText(raw2) {
|
|
110478
|
+
if (typeof raw2 === "string")
|
|
110479
|
+
return raw2;
|
|
110480
|
+
if (raw2 !== null && typeof raw2 === "object" && "message" in raw2) {
|
|
110481
|
+
const message = raw2.message;
|
|
110482
|
+
if (typeof message === "string" && message !== "")
|
|
110483
|
+
return message;
|
|
110484
|
+
if ("code" in raw2 && typeof raw2.code === "string") {
|
|
110485
|
+
return raw2.code;
|
|
110486
|
+
}
|
|
110487
|
+
}
|
|
110488
|
+
return raw2 === undefined ? undefined : JSON.stringify(raw2);
|
|
110489
|
+
}
|
|
110279
110490
|
async function performTeamStop(agentId, options) {
|
|
110280
110491
|
try {
|
|
110281
110492
|
const url2 = `${options.server}/team/agents/${encodeURIComponent(agentId)}/stop`;
|
|
@@ -110283,7 +110494,8 @@ async function performTeamStop(agentId, options) {
|
|
|
110283
110494
|
const body = await res.json();
|
|
110284
110495
|
if (res.ok)
|
|
110285
110496
|
return { ok: true, body };
|
|
110286
|
-
|
|
110497
|
+
const detail = errorText(body.error);
|
|
110498
|
+
return { ok: false, error: detail ?? `stop failed: ${res.status}`, status: res.status };
|
|
110287
110499
|
} catch (err) {
|
|
110288
110500
|
return { ok: false, transportError: err };
|
|
110289
110501
|
}
|
|
@@ -110292,11 +110504,11 @@ async function runTeamStop(agentId, options, context4) {
|
|
|
110292
110504
|
const result = await performTeamStop(agentId, options);
|
|
110293
110505
|
if ("transportError" in result) {
|
|
110294
110506
|
const err = result.transportError;
|
|
110295
|
-
context4.output
|
|
110507
|
+
writeJsonError(context4.output, options, `Cannot reach server at ${options.server} \u2014 is spur serve running? (${err instanceof Error ? err.message : String(err)})`, "INTERNAL_ERROR");
|
|
110296
110508
|
return 1;
|
|
110297
110509
|
}
|
|
110298
110510
|
if (!result.ok) {
|
|
110299
|
-
context4.output
|
|
110511
|
+
writeJsonError(context4.output, options, result.error, "INTERNAL_ERROR");
|
|
110300
110512
|
return 1;
|
|
110301
110513
|
}
|
|
110302
110514
|
if (options.json) {
|
|
@@ -110627,32 +110839,32 @@ ${result.errors.map((m) => ` - ${m}`).join(`
|
|
|
110627
110839
|
const silent = !json3 && options.silent === true;
|
|
110628
110840
|
const quiet = !json3 && options.quiet === true;
|
|
110629
110841
|
if (!json3 && options.quiet === true && options.verbose === true) {
|
|
110630
|
-
context4.output
|
|
110842
|
+
writeJsonError(context4.output, options, "--quiet and --verbose are mutually exclusive", "VALIDATION_FAILED");
|
|
110631
110843
|
context4.setExitCode(2);
|
|
110632
110844
|
return;
|
|
110633
110845
|
}
|
|
110634
110846
|
if (!json3 && options.silent === true && (options.quiet === true || options.verbose === true)) {
|
|
110635
|
-
context4.output
|
|
110847
|
+
writeJsonError(context4.output, options, "--silent cannot be combined with --quiet or --verbose", "VALIDATION_FAILED");
|
|
110636
110848
|
context4.setExitCode(2);
|
|
110637
110849
|
return;
|
|
110638
110850
|
}
|
|
110639
110851
|
if (options.steer === true && (json3 || options.async === true)) {
|
|
110640
|
-
context4.output
|
|
110852
|
+
writeJsonError(context4.output, options, "--steer is synchronous and in-process; it cannot be combined with --json or --async", "VALIDATION_FAILED");
|
|
110641
110853
|
context4.setExitCode(2);
|
|
110642
110854
|
return;
|
|
110643
110855
|
}
|
|
110644
110856
|
const requestedDetail = options.detail;
|
|
110645
110857
|
if (requestedDetail !== undefined && !["minimal", "invocation", "full"].includes(requestedDetail)) {
|
|
110646
|
-
context4.output
|
|
110858
|
+
writeJsonError(context4.output, options, "--detail must be one of: minimal, invocation, full", "VALIDATION_FAILED");
|
|
110647
110859
|
context4.setExitCode(2);
|
|
110648
110860
|
return;
|
|
110649
110861
|
}
|
|
110650
110862
|
const detail = options.verbose === true ? "full" : requestedDetail ?? "invocation";
|
|
110651
110863
|
if (process.env[WORKFLOW_RUN_ACTIVE_ENV] === "1") {
|
|
110652
|
-
context4.output
|
|
110864
|
+
writeJsonError(context4.output, options, `workflow run: refusing to start \u2014 already inside an active workflow run (${WORKFLOW_RUN_ACTIVE_ENV}=1).
|
|
110653
110865
|
` + `A pipeline that starts another pipeline forks a worktree and an agent run per level, without bound.
|
|
110654
110866
|
` + `If you are an agent running inside a pipeline step: do NOT start a pipeline here. Report what you
|
|
110655
|
-
` + "needed and let the operator run it from a clean shell.");
|
|
110867
|
+
` + "needed and let the operator run it from a clean shell.", "VALIDATION_FAILED");
|
|
110656
110868
|
context4.setExitCode(1);
|
|
110657
110869
|
return;
|
|
110658
110870
|
}
|
|
@@ -110847,7 +111059,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
110847
111059
|
if (options.answer !== undefined) {
|
|
110848
111060
|
const v = String(options.answer).toLowerCase();
|
|
110849
111061
|
if (v !== "yes" && v !== "no" && v !== "cancel") {
|
|
110850
|
-
context4.output
|
|
111062
|
+
writeJsonError(context4.output, options, `Invalid --answer value "${options.answer}" - must be yes, no, or cancel.`, "VALIDATION_FAILED");
|
|
110851
111063
|
context4.setExitCode(2);
|
|
110852
111064
|
return;
|
|
110853
111065
|
}
|
|
@@ -110861,7 +111073,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
110861
111073
|
if (targetId === undefined) {
|
|
110862
111074
|
const latest = await svc.latestPausedRun();
|
|
110863
111075
|
if (latest === null) {
|
|
110864
|
-
context4.output
|
|
111076
|
+
writeJsonError(context4.output, options, "No paused workflow run to continue.", "NOT_FOUND");
|
|
110865
111077
|
context4.setExitCode(1);
|
|
110866
111078
|
return;
|
|
110867
111079
|
}
|
|
@@ -110874,7 +111086,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
110874
111086
|
node: "continue"
|
|
110875
111087
|
});
|
|
110876
111088
|
if (answer.value !== "yes") {
|
|
110877
|
-
context4.output
|
|
111089
|
+
writeJsonError(context4.output, options, `Aborted - run ${latest.runId} not resumed.`, "GUARD_DENIED");
|
|
110878
111090
|
context4.setExitCode(1);
|
|
110879
111091
|
return;
|
|
110880
111092
|
}
|
|
@@ -110900,7 +111112,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
110900
111112
|
const force = options.force === true;
|
|
110901
111113
|
const minutes = force ? 0 : Number.parseInt(options.olderThan ?? "30", 10);
|
|
110902
111114
|
if (!Number.isFinite(minutes) || minutes < 0) {
|
|
110903
|
-
context4.output
|
|
111115
|
+
writeJsonError(context4.output, options, `Invalid --older-than value: ${options.olderThan}`, "VALIDATION_FAILED");
|
|
110904
111116
|
context4.setExitCode(2);
|
|
110905
111117
|
return;
|
|
110906
111118
|
}
|
|
@@ -110945,7 +111157,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
110945
111157
|
return;
|
|
110946
111158
|
}
|
|
110947
111159
|
if (result.status === "not_found") {
|
|
110948
|
-
context4.output
|
|
111160
|
+
writeJsonError(context4.output, options, `Run ${runId} not found.`, "NOT_FOUND");
|
|
110949
111161
|
context4.setExitCode(1);
|
|
110950
111162
|
return;
|
|
110951
111163
|
}
|
|
@@ -110967,14 +111179,14 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
110967
111179
|
});
|
|
110968
111180
|
workflow.command("show").description("Render a workflow definition: mermaid FSM diagram (default) or declared-step todo checklist.").argument("<file>", "Workflow YAML file").option("--format <name>", "Projection to render: mermaid (default) or todo", "mermaid").option(...SHARED_OPTIONS.jsonSupported).action(async (file2, options) => {
|
|
110969
111181
|
if (options.format !== "mermaid" && options.format !== "todo") {
|
|
110970
|
-
context4.output
|
|
111182
|
+
writeJsonError(context4.output, options, `workflow show: unknown --format '${options.format}' \u2014 expected mermaid or todo`, "VALIDATION_FAILED");
|
|
110971
111183
|
context4.setExitCode(1);
|
|
110972
111184
|
return;
|
|
110973
111185
|
}
|
|
110974
111186
|
const resolved = resolveWorkflowFile(context4.cwd, file2);
|
|
110975
111187
|
if (resolved.path === null) {
|
|
110976
111188
|
const [probedProject, probedBundled] = resolved.probed;
|
|
110977
|
-
context4.output
|
|
111189
|
+
writeJsonError(context4.output, options, `workflow show: file not found: ${probedProject}${probedBundled !== null ? ` (bundled: ${probedBundled})` : ""}`, "NOT_FOUND");
|
|
110978
111190
|
context4.setExitCode(1);
|
|
110979
111191
|
return;
|
|
110980
111192
|
}
|
|
@@ -110983,7 +111195,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
110983
111195
|
try {
|
|
110984
111196
|
def = await loadWorkflowDef(filePath, { validateSchema: true });
|
|
110985
111197
|
} catch (err) {
|
|
110986
|
-
context4.output
|
|
111198
|
+
writeJsonError(context4.output, options, `workflow show: cannot read or parse ${file2} \u2014 ${err instanceof Error ? err.message : String(err)}`, "VALIDATION_FAILED");
|
|
110987
111199
|
context4.setExitCode(1);
|
|
110988
111200
|
return;
|
|
110989
111201
|
}
|
|
@@ -111011,38 +111223,38 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
111011
111223
|
const svc = makeSvc();
|
|
111012
111224
|
const last = parseInt(options.last, 10);
|
|
111013
111225
|
if (Number.isNaN(last) || last < 1) {
|
|
111014
|
-
context4.output
|
|
111226
|
+
writeJsonError(context4.output, options, "--last must be a positive integer", "VALIDATION_FAILED");
|
|
111015
111227
|
context4.setExitCode(1);
|
|
111016
111228
|
return;
|
|
111017
111229
|
}
|
|
111018
111230
|
const pollMs = parseInt(options.poll, 10);
|
|
111019
111231
|
if (Number.isNaN(pollMs) || pollMs < 50) {
|
|
111020
|
-
context4.output
|
|
111232
|
+
writeJsonError(context4.output, options, "--poll must be an integer of at least 50ms", "VALIDATION_FAILED");
|
|
111021
111233
|
context4.setExitCode(1);
|
|
111022
111234
|
return;
|
|
111023
111235
|
}
|
|
111024
111236
|
if (options.follow === true && runId === undefined) {
|
|
111025
|
-
context4.output
|
|
111237
|
+
writeJsonError(context4.output, options, "--follow requires a run-id", "VALIDATION_FAILED");
|
|
111026
111238
|
context4.setExitCode(1);
|
|
111027
111239
|
return;
|
|
111028
111240
|
}
|
|
111029
111241
|
if (options.follow === true && options.json === true) {
|
|
111030
|
-
context4.output
|
|
111242
|
+
writeJsonError(context4.output, options, "--follow is a human streaming mode and cannot be combined with --json", "VALIDATION_FAILED");
|
|
111031
111243
|
context4.setExitCode(1);
|
|
111032
111244
|
return;
|
|
111033
111245
|
}
|
|
111034
111246
|
if (options.output === true && options.follow !== true) {
|
|
111035
|
-
context4.output
|
|
111247
|
+
writeJsonError(context4.output, options, "--output requires --follow", "VALIDATION_FAILED");
|
|
111036
111248
|
context4.setExitCode(1);
|
|
111037
111249
|
return;
|
|
111038
111250
|
}
|
|
111039
111251
|
if (options.output === true && options.json === true) {
|
|
111040
|
-
context4.output
|
|
111252
|
+
writeJsonError(context4.output, options, "--output is a human streaming mode and cannot be combined with --json", "VALIDATION_FAILED");
|
|
111041
111253
|
context4.setExitCode(1);
|
|
111042
111254
|
return;
|
|
111043
111255
|
}
|
|
111044
111256
|
if (options.status !== undefined && !["done", "failed", "running"].includes(options.status)) {
|
|
111045
|
-
context4.output
|
|
111257
|
+
writeJsonError(context4.output, options, "--status must be one of: done, failed, running", "VALIDATION_FAILED");
|
|
111046
111258
|
context4.setExitCode(1);
|
|
111047
111259
|
return;
|
|
111048
111260
|
}
|