@gobing-ai/spur 0.3.36 → 0.3.38
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/config/corpus-baseline.json +0 -28
- package/config/templates/AGENTS.md +1 -1
- package/config/workflows/task-pipeline.yaml +32 -7
- package/package.json +1 -1
- package/spur.js +272 -84
|
@@ -15,34 +15,6 @@
|
|
|
15
15
|
"reason": "GENUINE PRE-FIX BYPASS. Closed 2026-08-06, well after the rule existed (2026-07-24), via the `--no-lifecycle` hole closed on 2026-08-07. Its Solution needs one file:line citation. The only entry here representing real unpaid debt.",
|
|
16
16
|
"since": "2026-08-07"
|
|
17
17
|
},
|
|
18
|
-
{
|
|
19
|
-
"kind": "feature",
|
|
20
|
-
"id": "M",
|
|
21
|
-
"code": "L3.ac-bdd-error",
|
|
22
|
-
"reason": "CHECKER GAP, not corpus debt. M is a wayfinder MAP; its Acceptance Criteria section carries an explicit prose disclaimer ('This feature is a wayfinder MAP... It has no acceptance criteria — its target is the ## Goal'). The BDD validator requires a `Feature:` declaration and has no notion of the map feature type. Correct fix is to teach `feature check` about wayfinder maps, not to bolt fake Gherkin onto a map.",
|
|
23
|
-
"since": "2026-08-07"
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"kind": "feature",
|
|
27
|
-
"id": "M",
|
|
28
|
-
"code": "L3.ac-bdd-invalid",
|
|
29
|
-
"reason": "Downstream of the M ac-bdd-error entry — same missing-Feature-declaration cause, reported twice by the validator.",
|
|
30
|
-
"since": "2026-08-07"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"kind": "feature",
|
|
34
|
-
"id": "F82",
|
|
35
|
-
"code": "L3.ac-bdd-error",
|
|
36
|
-
"reason": "CHECKER GAP, same class as M: wayfinder map with a deliberate no-AC prose disclaimer (plus a noted exception for two implementation-ready child tickets). Not a syntax error to fix.",
|
|
37
|
-
"since": "2026-08-07"
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"kind": "feature",
|
|
41
|
-
"id": "F82",
|
|
42
|
-
"code": "L3.ac-bdd-invalid",
|
|
43
|
-
"reason": "Downstream of the F82 ac-bdd-error entry — same cause, reported twice.",
|
|
44
|
-
"since": "2026-08-07"
|
|
45
|
-
},
|
|
46
18
|
{
|
|
47
19
|
"kind": "feature",
|
|
48
20
|
"id": "F821",
|
|
@@ -140,7 +140,7 @@ spur <noun> <verb> … --json
|
|
|
140
140
|
spur <noun> --help
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
**Long-tail:** Additional `/sp:dev-*` commands (handover, gitmsg, fixall, dogfood, reverse, arch,
|
|
143
|
+
**Long-tail:** Additional `/sp:dev-*` commands (handover, gitmsg, fixall, findconflict, dogfood, reverse, arch,
|
|
144
144
|
…) are indexed in the project plugin README (`plugins/sp/README.md` when present).
|
|
145
145
|
|
|
146
146
|
**Outside spur-cli:** Nouns not fully documented in `sp:spur-cli` (`agent`, `history`, `message`,
|
|
@@ -57,9 +57,11 @@ vars:
|
|
|
57
57
|
# `--vars '{"agent":"claude"}'`. Pinned (not left to the AiRunner's <default>
|
|
58
58
|
# selection) so a broken/misconfigured agent on the box can't silently capture the run.
|
|
59
59
|
agent: "omp"
|
|
60
|
-
# Implement-only executor override (R1, task 0454).
|
|
61
|
-
#
|
|
62
|
-
#
|
|
60
|
+
# Implement-only executor override (R1, task 0454). Resolves to this YAML literal
|
|
61
|
+
# unless overridden. The `--agent` flag (execution-batch.md §3.2) forwards into BOTH
|
|
62
|
+
# `agent` and `implementAgent`, so a pinned `--agent X` reaches implement too. To
|
|
63
|
+
# pin ONLY implement while other hops keep the default, pass
|
|
64
|
+
# `--vars '{"implementAgent":"omp-zai"}'`.
|
|
63
65
|
implementAgent: "omp"
|
|
64
66
|
# Step-level timeout for agentic hops (review / verify / test-fix) in ms.
|
|
65
67
|
# Soft quality-gate shells are unbounded by this var (host shell only).
|
|
@@ -90,6 +92,12 @@ vars:
|
|
|
90
92
|
# project can point it at its own formatter; invoked best-effort (a missing or
|
|
91
93
|
# failing formatter must never abort a run — the quality gate is the real gate).
|
|
92
94
|
formatCmd: "bun run format"
|
|
95
|
+
# Bounded `file:line` anchors extracted from the failing quality-gate log by the
|
|
96
|
+
# `test` / `test-recheck` hops (deduped, max 20, space-separated on one line).
|
|
97
|
+
# `test-fix` reads `.spur/run/<wbs>-test-gate.findings` into this var and names the
|
|
98
|
+
# anchors directly in the /sp:dev-fixall input, so the fix agent starts AT the failing
|
|
99
|
+
# file:line instead of re-deriving it from a fresh gate run (0482 R3).
|
|
100
|
+
gateFindings: ""
|
|
93
101
|
# Max R-items in ## Requirements before size precheck fails (R2, task 0454).
|
|
94
102
|
# Override with `--vars '{"maxImplementReqs":"12"}'`.
|
|
95
103
|
maxImplementReqs: "5"
|
|
@@ -147,7 +155,7 @@ states:
|
|
|
147
155
|
SIZE_FILE=".spur/run/$wbs-precheck-size.status" &&
|
|
148
156
|
mkdir -p .spur/run &&
|
|
149
157
|
bun plugins/sp/scripts/task-size-precheck.ts "$wbs"
|
|
150
|
-
--max-reqs "$maxImplementReqs" --max-plan-items "$maxImplementPlanItems" &&
|
|
158
|
+
--spur-bin "$spurBin" --max-reqs "$maxImplementReqs" --max-plan-items "$maxImplementPlanItems" &&
|
|
151
159
|
exit 0
|
|
152
160
|
|
|
153
161
|
- id: implement
|
|
@@ -214,10 +222,14 @@ states:
|
|
|
214
222
|
command: >-
|
|
215
223
|
mkdir -p .spur/run &&
|
|
216
224
|
STATUS_FILE=".spur/run/$wbs-test-gate.status" &&
|
|
225
|
+
LOG_FILE=".spur/run/$wbs-test-gate.log" &&
|
|
217
226
|
ATTEMPT_FILE=".spur/run/$wbs-test-fix-attempt" &&
|
|
218
227
|
echo 0 > "$ATTEMPT_FILE" &&
|
|
219
228
|
set +e &&
|
|
220
|
-
( sh -c "$qualityGateCmd" ); gate_rc=$?; set -e &&
|
|
229
|
+
( sh -c "$qualityGateCmd" ) > "$LOG_FILE" 2>&1; gate_rc=$?; set -e &&
|
|
230
|
+
cat "$LOG_FILE" &&
|
|
231
|
+
FINDINGS_FILE=".spur/run/$wbs-test-gate.findings" &&
|
|
232
|
+
set +e; grep -oE '[A-Za-z0-9_./-]+\.[A-Za-z]+:[0-9]+' "$LOG_FILE" | sort -u | head -20 | tr '\n' ' ' > "$FINDINGS_FILE"; set -e &&
|
|
221
233
|
if [ "$gate_rc" -eq 0 ]; then
|
|
222
234
|
printf 'PASS\n' > "$STATUS_FILE";
|
|
223
235
|
else
|
|
@@ -238,10 +250,19 @@ states:
|
|
|
238
250
|
ATTEMPT_FILE=".spur/run/$wbs-test-fix-attempt" &&
|
|
239
251
|
n=$(cat "$ATTEMPT_FILE" 2>/dev/null || echo 0) &&
|
|
240
252
|
printf '%s\n' "$((n + 1))" > "$ATTEMPT_FILE"
|
|
253
|
+
# R3 (0482): project the extracted gate anchors into a var so the dispatch input
|
|
254
|
+
# can NAME the failing file:line, not merely point at a log. A vars template cannot
|
|
255
|
+
# run a shell, so the read is an action, not an inline `$(cat ...)` substitution.
|
|
256
|
+
- kind: file.read.into-var
|
|
257
|
+
options:
|
|
258
|
+
path: .spur/run/${vars.wbs}-test-gate.findings
|
|
259
|
+
var: gateFindings
|
|
241
260
|
- kind: agent.run
|
|
242
261
|
options:
|
|
243
262
|
agent: ${vars.agent}
|
|
244
|
-
|
|
263
|
+
# R3 (0482): `--findings` names the failing anchors inline; `--gate-log` remains
|
|
264
|
+
# the full-context escape hatch when the digest is not enough.
|
|
265
|
+
input: /sp:dev-fixall "${vars.qualityGateCmd}" --gate-log .spur/run/${vars.wbs}-test-gate.log --findings "${vars.gateFindings}"
|
|
245
266
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
246
267
|
|
|
247
268
|
- id: test-recheck
|
|
@@ -256,8 +277,12 @@ states:
|
|
|
256
277
|
command: >-
|
|
257
278
|
mkdir -p .spur/run &&
|
|
258
279
|
STATUS_FILE=".spur/run/$wbs-test-gate.status" &&
|
|
280
|
+
LOG_FILE=".spur/run/$wbs-test-gate.log" &&
|
|
259
281
|
set +e &&
|
|
260
|
-
( sh -c "$qualityGateCmd" ); gate_rc=$?; set -e &&
|
|
282
|
+
( sh -c "$qualityGateCmd" ) > "$LOG_FILE" 2>&1; gate_rc=$?; set -e &&
|
|
283
|
+
cat "$LOG_FILE" &&
|
|
284
|
+
FINDINGS_FILE=".spur/run/$wbs-test-gate.findings" &&
|
|
285
|
+
set +e; grep -oE '[A-Za-z0-9_./-]+\.[A-Za-z]+:[0-9]+' "$LOG_FILE" | sort -u | head -20 | tr '\n' ' ' > "$FINDINGS_FILE"; set -e &&
|
|
261
286
|
if [ "$gate_rc" -eq 0 ]; then
|
|
262
287
|
printf 'PASS\n' > "$STATUS_FILE";
|
|
263
288
|
else
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.38",
|
|
4
4
|
"description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spur",
|
package/spur.js
CHANGED
|
@@ -60540,7 +60540,7 @@ var init_schema4 = __esm(() => {
|
|
|
60540
60540
|
{
|
|
60541
60541
|
schema_version: STAGE_REGISTRY_SCHEMA_VERSION,
|
|
60542
60542
|
id: "test",
|
|
60543
|
-
aliases: ["dev-unit"],
|
|
60543
|
+
aliases: ["dev-unit", "dev-fixall"],
|
|
60544
60544
|
description: "dev-unit: generate/extend tests to coverage target",
|
|
60545
60545
|
artifacts: [{ kind: "test-file", direction: "output", required: true }],
|
|
60546
60546
|
reasoning_skill: "sp:code-testing",
|
|
@@ -60572,7 +60572,10 @@ var init_schema4 = __esm(() => {
|
|
|
60572
60572
|
retry: { max_attempts: 2, terminal_stop: "escalate" },
|
|
60573
60573
|
model_policy: {
|
|
60574
60574
|
min_tier: "capable-1",
|
|
60575
|
-
fallback: [
|
|
60575
|
+
fallback: [
|
|
60576
|
+
{ tier: "capable-2", trigger: "resource-exhaustion" },
|
|
60577
|
+
{ tier: "capable-2", trigger: "gate-fail" }
|
|
60578
|
+
]
|
|
60576
60579
|
},
|
|
60577
60580
|
context_layers: [],
|
|
60578
60581
|
observability: [],
|
|
@@ -60635,7 +60638,10 @@ var init_schema4 = __esm(() => {
|
|
|
60635
60638
|
retry: { max_attempts: 3, terminal_stop: "block" },
|
|
60636
60639
|
model_policy: {
|
|
60637
60640
|
min_tier: "capable-1",
|
|
60638
|
-
fallback: [
|
|
60641
|
+
fallback: [
|
|
60642
|
+
{ tier: "capable-2", trigger: "resource-exhaustion" },
|
|
60643
|
+
{ tier: "capable-2", trigger: "gate-fail" }
|
|
60644
|
+
]
|
|
60639
60645
|
},
|
|
60640
60646
|
context_layers: [],
|
|
60641
60647
|
observability: [],
|
|
@@ -61762,7 +61768,7 @@ function normalizeFeatureStatus(raw) {
|
|
|
61762
61768
|
}
|
|
61763
61769
|
return resolved;
|
|
61764
61770
|
}
|
|
61765
|
-
var TASK_STATUSES, FEATURE_STATUSES, TASK_STATUS_ICONS, FEATURE_STATUS_ICONS, PRIORITIES, PROFILES, TASK_TYPES, TASK_VARIANTS, DEFAULT_TASK_VARIANT = "standard", FEATURE_ID_PATTERN, TASK_STATUS_ALIASES, FEATURE_STATUS_ALIASES, isoDateString, featureId, wbsString, taskFrontmatterSchema, featureFrontmatterSchema, TASK_TEMPLATES, taskBatchItemSchema, taskBatchSchema;
|
|
61771
|
+
var TASK_STATUSES, FEATURE_STATUSES, TASK_STATUS_ICONS, FEATURE_STATUS_ICONS, PRIORITIES, PROFILES, TASK_TYPES, TASK_VARIANTS, DEFAULT_TASK_VARIANT = "standard", FEATURE_ID_PATTERN, WAYFINDER_MAP_TAG = "wayfinder-map", TASK_STATUS_ALIASES, FEATURE_STATUS_ALIASES, isoDateString, featureId, wbsString, taskFrontmatterSchema, featureFrontmatterSchema, TASK_TEMPLATES, taskBatchItemSchema, taskBatchSchema;
|
|
61766
61772
|
var init_schema6 = __esm(() => {
|
|
61767
61773
|
init_zod();
|
|
61768
61774
|
TASK_STATUSES = ["backlog", "todo", "wip", "testing", "blocked", "done", "cancelled"];
|
|
@@ -62359,6 +62365,7 @@ __export(exports_src, {
|
|
|
62359
62365
|
acquireCreateLock: () => acquireCreateLock,
|
|
62360
62366
|
WorkspaceDao: () => WorkspaceDao,
|
|
62361
62367
|
WorkflowStateDao: () => WorkflowStateDao,
|
|
62368
|
+
WAYFINDER_MAP_TAG: () => WAYFINDER_MAP_TAG,
|
|
62362
62369
|
VOLATILE_LAYER_NAMES: () => VOLATILE_LAYER_NAMES,
|
|
62363
62370
|
UNKNOWN_MODEL_PRICING: () => UNKNOWN_MODEL_PRICING,
|
|
62364
62371
|
UNIVERSAL_SECTIONS: () => UNIVERSAL_SECTIONS,
|
|
@@ -62602,7 +62609,6 @@ class AgentService {
|
|
|
62602
62609
|
let currentModel = resolved.model;
|
|
62603
62610
|
let currentSource = resolved.source;
|
|
62604
62611
|
let currentStage = resolved.stage;
|
|
62605
|
-
const maxEscalations = currentStage?.policy.fallback.length ?? 0;
|
|
62606
62612
|
const attemptedExecutors = new Set(currentStage ? [currentStage.executorName] : []);
|
|
62607
62613
|
if (!jsonOutput && TIER2_AGENTS.has(currentAgent)) {
|
|
62608
62614
|
this.ctx.output.error(`Warning: ${currentAgent} is a Tier-2 agent (TUI/gateway only)`);
|
|
@@ -62722,12 +62728,16 @@ class AgentService {
|
|
|
62722
62728
|
if (result.exitCode === 0)
|
|
62723
62729
|
break;
|
|
62724
62730
|
const escalationSignal = classifyObjectiveFailure(result);
|
|
62725
|
-
if (escalationSignal === undefined || currentStage === undefined
|
|
62731
|
+
if (escalationSignal === undefined || currentStage === undefined) {
|
|
62732
|
+
break;
|
|
62733
|
+
}
|
|
62734
|
+
const matchingFallbacks = currentStage.policy.fallback.filter((fallback) => fallback.trigger === escalationSignal);
|
|
62735
|
+
if (matchingFallbacks.length === 0 || escalationSignal !== "resource-exhaustion" && attempt >= matchingFallbacks.length) {
|
|
62726
62736
|
break;
|
|
62727
62737
|
}
|
|
62728
62738
|
runFlags.signal = escalationSignal;
|
|
62729
62739
|
runFlags["from-executor"] = currentStage.executorName;
|
|
62730
|
-
const nextResolved = await this.resolveAgent(prompt, runFlags, doctorRunner);
|
|
62740
|
+
const nextResolved = await this.resolveAgent(prompt, runFlags, doctorRunner, attemptedExecutors);
|
|
62731
62741
|
if (!nextResolved.ok || nextResolved.stage === undefined || attemptedExecutors.has(nextResolved.stage.executorName)) {
|
|
62732
62742
|
if (!jsonOutput) {
|
|
62733
62743
|
this.ctx.output.error(`Escalation chain exhausted after ${attempt + 1} attempt(s); executors tried: ${[...attemptedExecutors].join(", ")}`);
|
|
@@ -62774,26 +62784,58 @@ class AgentService {
|
|
|
62774
62784
|
...observer !== undefined ? { observer } : {}
|
|
62775
62785
|
};
|
|
62776
62786
|
}
|
|
62777
|
-
async resolveAgent(prompt, flags, doctorRunner) {
|
|
62787
|
+
async resolveAgent(prompt, flags, doctorRunner, exclude) {
|
|
62778
62788
|
const raw = stringFlag(flags, "agent", "auto");
|
|
62779
62789
|
if (raw === "auto")
|
|
62780
|
-
return this.resolveAgentAuto(prompt, flags, doctorRunner);
|
|
62790
|
+
return this.resolveAgentAuto(prompt, flags, doctorRunner, exclude);
|
|
62781
62791
|
if (raw === "inline")
|
|
62782
|
-
return this.resolveAgentAuto(prompt, flags, doctorRunner);
|
|
62783
|
-
return this.
|
|
62792
|
+
return this.resolveAgentAuto(prompt, flags, doctorRunner, exclude);
|
|
62793
|
+
return this.resolvePinned(raw, prompt, flags, doctorRunner, exclude);
|
|
62794
|
+
}
|
|
62795
|
+
async resolvePinned(selector, prompt, flags, doctorRunner, exclude) {
|
|
62796
|
+
if (stringFlag(flags, "signal", "") !== "") {
|
|
62797
|
+
const stageRecord2 = this.resolveCanonicalStage(prompt, flags);
|
|
62798
|
+
if (stageRecord2 !== undefined) {
|
|
62799
|
+
const stageRes = await this.resolveStageModelPolicy(stageRecord2, flags, doctorRunner, exclude);
|
|
62800
|
+
if (stageRes !== undefined)
|
|
62801
|
+
return stageRes;
|
|
62802
|
+
}
|
|
62803
|
+
}
|
|
62804
|
+
const base2 = await this.resolveExecutorSelector(selector, doctorRunner, "explicit");
|
|
62805
|
+
if (!base2.ok)
|
|
62806
|
+
return base2;
|
|
62807
|
+
const stageRecord = this.resolveCanonicalStage(prompt, flags);
|
|
62808
|
+
if (stageRecord !== undefined) {
|
|
62809
|
+
const executors = this.ctx.agentConfig?.executors;
|
|
62810
|
+
const pinned = executors?.find((e) => e.name === selector);
|
|
62811
|
+
const tier = pinned !== undefined ? getExecutorTier(pinned) : stageRecord.model_policy.min_tier;
|
|
62812
|
+
return {
|
|
62813
|
+
...base2,
|
|
62814
|
+
stage: {
|
|
62815
|
+
stageId: stageRecord.id,
|
|
62816
|
+
policy: stageRecord.model_policy,
|
|
62817
|
+
executorName: selector,
|
|
62818
|
+
executorTier: tier
|
|
62819
|
+
}
|
|
62820
|
+
};
|
|
62821
|
+
}
|
|
62822
|
+
return base2;
|
|
62784
62823
|
}
|
|
62785
|
-
|
|
62786
|
-
const config4 = this.ctx.agentConfig;
|
|
62824
|
+
resolveCanonicalStage(prompt, flags) {
|
|
62787
62825
|
const phase = extractPhase(prompt);
|
|
62788
62826
|
const stageFlag = stringFlag(flags, "stage", "");
|
|
62789
62827
|
const targetStageId = stageFlag !== "" ? stageFlag : phase !== undefined ? phase : undefined;
|
|
62790
|
-
if (targetStageId
|
|
62791
|
-
|
|
62792
|
-
|
|
62793
|
-
|
|
62794
|
-
|
|
62795
|
-
|
|
62796
|
-
|
|
62828
|
+
if (targetStageId === undefined)
|
|
62829
|
+
return;
|
|
62830
|
+
return getCanonicalStage(targetStageId);
|
|
62831
|
+
}
|
|
62832
|
+
async resolveAgentAuto(prompt, flags, doctorRunner, exclude) {
|
|
62833
|
+
const config4 = this.ctx.agentConfig;
|
|
62834
|
+
const stageRecord = this.resolveCanonicalStage(prompt, flags);
|
|
62835
|
+
if (stageRecord !== undefined) {
|
|
62836
|
+
const stageRes = await this.resolveStageModelPolicy(stageRecord, flags, doctorRunner, exclude);
|
|
62837
|
+
if (stageRes !== undefined) {
|
|
62838
|
+
return stageRes;
|
|
62797
62839
|
}
|
|
62798
62840
|
}
|
|
62799
62841
|
if (config4?.default !== undefined) {
|
|
@@ -62803,7 +62845,7 @@ class AgentService {
|
|
|
62803
62845
|
}
|
|
62804
62846
|
return this.resolveAgentPriority(doctorRunner);
|
|
62805
62847
|
}
|
|
62806
|
-
async resolveStageModelPolicy(stageRecord, flags, doctorRunner) {
|
|
62848
|
+
async resolveStageModelPolicy(stageRecord, flags, doctorRunner, exclude) {
|
|
62807
62849
|
const executors = this.ctx.agentConfig?.executors;
|
|
62808
62850
|
if (!executors || executors.length === 0) {
|
|
62809
62851
|
return;
|
|
@@ -62812,6 +62854,44 @@ class AgentService {
|
|
|
62812
62854
|
const signalRaw = stringFlag(flags, "signal", "");
|
|
62813
62855
|
const signal = signalRaw.length > 0 ? signalRaw : undefined;
|
|
62814
62856
|
const fromExecutor = stringFlag(flags, "from-executor", "") || undefined;
|
|
62857
|
+
if (signal === "resource-exhaustion" && fromExecutor !== undefined) {
|
|
62858
|
+
const failed = executors.find((e) => e.name === fromExecutor);
|
|
62859
|
+
if (failed !== undefined) {
|
|
62860
|
+
const failedTier = getExecutorTier(failed);
|
|
62861
|
+
const failedCanonical = resolveAgentName(failed.agent);
|
|
62862
|
+
const exhaustedAgents2 = new Set(executors.filter((executor) => executor.name === fromExecutor || (exclude?.has(executor.name) ?? false)).map((executor) => resolveAgentName(executor.agent)).filter((agent) => agent !== undefined));
|
|
62863
|
+
const sideways = executors.filter((e) => {
|
|
62864
|
+
const canonical = resolveAgentName(e.agent);
|
|
62865
|
+
return e.name !== fromExecutor && getExecutorTier(e) === failedTier && canonical !== failedCanonical && (canonical === undefined || !exhaustedAgents2.has(canonical)) && !(exclude?.has(e.name) ?? false);
|
|
62866
|
+
});
|
|
62867
|
+
for (const executor of sideways) {
|
|
62868
|
+
const canonical = resolveAgentName(executor.agent);
|
|
62869
|
+
if (canonical === undefined) {
|
|
62870
|
+
return {
|
|
62871
|
+
ok: false,
|
|
62872
|
+
exitCode: 2,
|
|
62873
|
+
message: `Executor '${executor.name}' for stage '${stageRecord.id}' maps to unknown agent '${executor.agent}'`
|
|
62874
|
+
};
|
|
62875
|
+
}
|
|
62876
|
+
const usable = await this.checkUsable(canonical, doctorRunner);
|
|
62877
|
+
if (usable.ok) {
|
|
62878
|
+
this.ctx.output.error(`Failover: ${fromExecutor} (tier ${failedTier}) exhausted; using same-tier ${executor.name} on a different binary`);
|
|
62879
|
+
return {
|
|
62880
|
+
ok: true,
|
|
62881
|
+
agent: canonical,
|
|
62882
|
+
model: executor.model,
|
|
62883
|
+
source: "stage",
|
|
62884
|
+
stage: {
|
|
62885
|
+
stageId: stageRecord.id,
|
|
62886
|
+
policy,
|
|
62887
|
+
executorName: executor.name,
|
|
62888
|
+
executorTier: getExecutorTier(executor)
|
|
62889
|
+
}
|
|
62890
|
+
};
|
|
62891
|
+
}
|
|
62892
|
+
}
|
|
62893
|
+
}
|
|
62894
|
+
}
|
|
62815
62895
|
let targetTier = policy.min_tier;
|
|
62816
62896
|
if (signal !== undefined) {
|
|
62817
62897
|
const currentExec = executors.find((e) => e.name === fromExecutor);
|
|
@@ -62824,7 +62904,11 @@ class AgentService {
|
|
|
62824
62904
|
}
|
|
62825
62905
|
}
|
|
62826
62906
|
}
|
|
62827
|
-
const
|
|
62907
|
+
const exhaustedAgents = signal === "resource-exhaustion" ? new Set(executors.filter((executor) => exclude?.has(executor.name) ?? false).map((executor) => resolveAgentName(executor.agent)).filter((agent) => agent !== undefined)) : undefined;
|
|
62908
|
+
const eligible = executors.filter((e) => {
|
|
62909
|
+
const canonical = resolveAgentName(e.agent);
|
|
62910
|
+
return isTierEligible(getExecutorTier(e), targetTier) && !(exclude?.has(e.name) ?? false) && (canonical === undefined || !(exhaustedAgents?.has(canonical) ?? false));
|
|
62911
|
+
});
|
|
62828
62912
|
if (eligible.length === 0) {
|
|
62829
62913
|
return;
|
|
62830
62914
|
}
|
|
@@ -63098,7 +63182,7 @@ function classifyObjectiveFailure(result) {
|
|
|
63098
63182
|
if (result.exitCode === 0)
|
|
63099
63183
|
return;
|
|
63100
63184
|
const text4 = `${result.stderr} ${result.stdout}`.toLowerCase();
|
|
63101
|
-
if (/\b(rate[\
|
|
63185
|
+
if (/\b(rate[\s_-]?limit(?:_exceeded|_error)?|429|529|too many requests|quota|usage[\s_-]?limit|limit[\s_-]?(reached|will[\s_-]?reset|resets?)|out of tokens?|insufficient[\s_-]?(credits?|balance|quota|funds)|token[\s_-]?(limit|budget)|context[\s_-]?(length|window)|maximum context|overloaded)\b/.test(text4)) {
|
|
63102
63186
|
return "resource-exhaustion";
|
|
63103
63187
|
}
|
|
63104
63188
|
return;
|
|
@@ -64174,7 +64258,7 @@ var init_feature_check = __esm(() => {
|
|
|
64174
64258
|
const status = fm.status ?? "backlog";
|
|
64175
64259
|
const entry = this.resolveMatrixEntry(isGroupFeature(fm), status);
|
|
64176
64260
|
this.runL2(doc2, entry, findings);
|
|
64177
|
-
this.runL3(doc2, findings);
|
|
64261
|
+
this.runL3(doc2, findings, fm);
|
|
64178
64262
|
if (options?.featuresDir) {
|
|
64179
64263
|
await this.checkOneActiveGoal(fm, featureId2, options.featuresDir, findings, options.asStatus);
|
|
64180
64264
|
await this.checkChildrenLimit(featureId2, options.featuresDir, findings);
|
|
@@ -64186,55 +64270,59 @@ var init_feature_check = __esm(() => {
|
|
|
64186
64270
|
await this.runL4(doc2, featureId2, status, taskScanDirs, dogfoodDir, runDir, findings);
|
|
64187
64271
|
return { id: featureId2, ...this.summarizeWithStatus(status, findings, strict, options?.severityOverrides) };
|
|
64188
64272
|
}
|
|
64189
|
-
runL3(doc2, findings) {
|
|
64273
|
+
runL3(doc2, findings, fm) {
|
|
64190
64274
|
const rawAc = doc2.getSection("Acceptance Criteria");
|
|
64191
64275
|
if (rawAc !== null && rawAc.trim().length > 0) {
|
|
64192
|
-
const
|
|
64193
|
-
const
|
|
64194
|
-
|
|
64195
|
-
|
|
64196
|
-
const
|
|
64197
|
-
|
|
64276
|
+
const tags = Array.isArray(fm.tags) ? fm.tags : [];
|
|
64277
|
+
const isWayfinderMap = tags.includes(WAYFINDER_MAP_TAG);
|
|
64278
|
+
if (!isWayfinderMap) {
|
|
64279
|
+
const acBody = stripAcFence(rawAc);
|
|
64280
|
+
const checklist = parseChecklist(acBody);
|
|
64281
|
+
const looksGherkin = /^\s*(Feature:|Scenario:|Scenario Outline:)/m.test(acBody);
|
|
64282
|
+
if (checklist.length > 0 && !looksGherkin) {
|
|
64283
|
+
const emptyItems = checklist.filter((c3) => c3.text.length === 0);
|
|
64284
|
+
for (const item of emptyItems) {
|
|
64285
|
+
findings.push({
|
|
64286
|
+
layer: "L3",
|
|
64287
|
+
code: FINDING_CODES.L3_AC_CHECKLIST_TEXT,
|
|
64288
|
+
severity: "warning",
|
|
64289
|
+
section: "Acceptance Criteria",
|
|
64290
|
+
line: item.line,
|
|
64291
|
+
message: "Checklist item has no text"
|
|
64292
|
+
});
|
|
64293
|
+
}
|
|
64294
|
+
return;
|
|
64295
|
+
}
|
|
64296
|
+
const bddResult = validateAcceptanceCriteria(acBody);
|
|
64297
|
+
for (const err of bddResult.errors) {
|
|
64298
|
+
findings.push({
|
|
64299
|
+
layer: "L3",
|
|
64300
|
+
code: FINDING_CODES.L3_AC_BDD_ERROR,
|
|
64301
|
+
severity: "error",
|
|
64302
|
+
section: "Acceptance Criteria",
|
|
64303
|
+
line: err.line,
|
|
64304
|
+
message: `BDD: ${err.message}`
|
|
64305
|
+
});
|
|
64306
|
+
}
|
|
64307
|
+
for (const warn of bddResult.warnings) {
|
|
64198
64308
|
findings.push({
|
|
64199
64309
|
layer: "L3",
|
|
64200
|
-
code: FINDING_CODES.
|
|
64310
|
+
code: FINDING_CODES.L3_AC_BDD_WARNING,
|
|
64201
64311
|
severity: "warning",
|
|
64202
64312
|
section: "Acceptance Criteria",
|
|
64203
|
-
line:
|
|
64204
|
-
message:
|
|
64313
|
+
line: warn.line,
|
|
64314
|
+
message: `BDD: ${warn.message}`
|
|
64315
|
+
});
|
|
64316
|
+
}
|
|
64317
|
+
if (!bddResult.valid) {
|
|
64318
|
+
findings.push({
|
|
64319
|
+
layer: "L3",
|
|
64320
|
+
code: FINDING_CODES.L3_AC_BDD_INVALID,
|
|
64321
|
+
severity: "error",
|
|
64322
|
+
section: "Acceptance Criteria",
|
|
64323
|
+
message: "Acceptance Criteria validation failed; fix BDD syntax errors"
|
|
64205
64324
|
});
|
|
64206
64325
|
}
|
|
64207
|
-
return;
|
|
64208
|
-
}
|
|
64209
|
-
const bddResult = validateAcceptanceCriteria(acBody);
|
|
64210
|
-
for (const err of bddResult.errors) {
|
|
64211
|
-
findings.push({
|
|
64212
|
-
layer: "L3",
|
|
64213
|
-
code: FINDING_CODES.L3_AC_BDD_ERROR,
|
|
64214
|
-
severity: "error",
|
|
64215
|
-
section: "Acceptance Criteria",
|
|
64216
|
-
line: err.line,
|
|
64217
|
-
message: `BDD: ${err.message}`
|
|
64218
|
-
});
|
|
64219
|
-
}
|
|
64220
|
-
for (const warn of bddResult.warnings) {
|
|
64221
|
-
findings.push({
|
|
64222
|
-
layer: "L3",
|
|
64223
|
-
code: FINDING_CODES.L3_AC_BDD_WARNING,
|
|
64224
|
-
severity: "warning",
|
|
64225
|
-
section: "Acceptance Criteria",
|
|
64226
|
-
line: warn.line,
|
|
64227
|
-
message: `BDD: ${warn.message}`
|
|
64228
|
-
});
|
|
64229
|
-
}
|
|
64230
|
-
if (!bddResult.valid) {
|
|
64231
|
-
findings.push({
|
|
64232
|
-
layer: "L3",
|
|
64233
|
-
code: FINDING_CODES.L3_AC_BDD_INVALID,
|
|
64234
|
-
severity: "error",
|
|
64235
|
-
section: "Acceptance Criteria",
|
|
64236
|
-
message: "Acceptance Criteria validation failed; fix BDD syntax errors"
|
|
64237
|
-
});
|
|
64238
64326
|
}
|
|
64239
64327
|
}
|
|
64240
64328
|
const scopeBody = doc2.getSection("Scope");
|
|
@@ -64659,6 +64747,10 @@ class FeatureService {
|
|
|
64659
64747
|
throw new Error(`Field "${key}" is not settable via update \u2014 status changes go through the lifecycle ` + `transition (spur feature update ${id} <status>); id and timestamps are machine-owned.`);
|
|
64660
64748
|
}
|
|
64661
64749
|
const ref = await this.refFor(id);
|
|
64750
|
+
if (key === "tags") {
|
|
64751
|
+
const tags = value.split(",").map((t) => t.trim()).filter(Boolean);
|
|
64752
|
+
return this.ctx.writeService.updateFrontmatterArray(ref, key, tags);
|
|
64753
|
+
}
|
|
64662
64754
|
return this.ctx.writeService.updateFrontmatter(ref, key, value);
|
|
64663
64755
|
}
|
|
64664
64756
|
async transition(id, toStatus, actor) {
|
|
@@ -68585,9 +68677,14 @@ var init_task_check = __esm(() => {
|
|
|
68585
68677
|
if (taskAc.trim().length === 0)
|
|
68586
68678
|
return;
|
|
68587
68679
|
let featureAc;
|
|
68680
|
+
let featureTags = [];
|
|
68588
68681
|
try {
|
|
68589
68682
|
const raw = await this.fs.readFile(featurePath);
|
|
68590
|
-
|
|
68683
|
+
const featureDoc = MarkdownDocument.parse(raw, "feature");
|
|
68684
|
+
featureTags = Array.isArray(featureDoc.frontmatterData?.tags) ? featureDoc.frontmatterData.tags.filter((t) => typeof t === "string") : [];
|
|
68685
|
+
if (featureTags.includes(WAYFINDER_MAP_TAG))
|
|
68686
|
+
return;
|
|
68687
|
+
featureAc = stripAcFence(featureDoc.getSection("Acceptance Criteria") ?? "");
|
|
68591
68688
|
} catch {
|
|
68592
68689
|
return;
|
|
68593
68690
|
}
|
|
@@ -71304,7 +71401,7 @@ class AgentRunActionRunner {
|
|
|
71304
71401
|
};
|
|
71305
71402
|
}
|
|
71306
71403
|
if (!ok) {
|
|
71307
|
-
await writePartialWorkArtifact(context4, agentLabel, model, traced, cwd);
|
|
71404
|
+
await writePartialWorkArtifact(context4, agentLabel, model, traced, cwd, sessionDir);
|
|
71308
71405
|
}
|
|
71309
71406
|
const partialWorkHint = `partial work (if any) preserved at .spur/run/${context4.runId}-${stepLabel}-partial.md; resume from that tree per the timed-out-implement runbook (plugins/sp/skills/spur-dev/references/execution-workflow.md)`;
|
|
71310
71407
|
const error51 = ok ? undefined : traced.signal !== undefined ? timeoutMs !== undefined ? `agent.run '${stepLabel}' (${agentLabel}) terminated by signal ${traced.signal} (configured timeout: ${timeoutMs}ms; timeout or cancellation); ${partialWorkHint}` : `agent.run '${stepLabel}' (${agentLabel}) was cancelled by signal ${traced.signal}; ${partialWorkHint}` : traced.message !== undefined ? `agent.run '${stepLabel}' (${agentLabel}) dispatch failed: ${traced.message}` : `agent.run '${stepLabel}' (${agentLabel}) exited with code ${exitCode}; ${partialWorkHint}`;
|
|
@@ -71332,7 +71429,7 @@ class AgentRunActionRunner {
|
|
|
71332
71429
|
}
|
|
71333
71430
|
return {
|
|
71334
71431
|
ok,
|
|
71335
|
-
data: buildResultData(exitCode, agentLabel, capture, answer, invocation),
|
|
71432
|
+
data: buildResultData(exitCode, agentLabel, capture, answer, invocation, ok ? undefined : traced.stdout, ok ? undefined : traced.stderr ?? undefined, this.agentConfig.secretValues),
|
|
71336
71433
|
error: error51,
|
|
71337
71434
|
setVars: ok ? {
|
|
71338
71435
|
__agentSession: resumeRetried ? "no-resume" : "open",
|
|
@@ -71373,12 +71470,19 @@ async function discoverSessionId(sessionDir) {
|
|
|
71373
71470
|
return;
|
|
71374
71471
|
}
|
|
71375
71472
|
}
|
|
71376
|
-
function buildResultData(exitCode, agentLabel, capture, answer, invocation) {
|
|
71473
|
+
function buildResultData(exitCode, agentLabel, capture, answer, invocation, stdout, stderr, secretValues = []) {
|
|
71377
71474
|
const data = { exitCode, agent: agentLabel };
|
|
71378
|
-
if (capture)
|
|
71379
|
-
data.answer = answer;
|
|
71475
|
+
if (capture) {
|
|
71476
|
+
data.answer = exitCode === 0 ? answer : tail(redactAndBound(answer, secretValues, Number.MAX_SAFE_INTEGER), 4096);
|
|
71477
|
+
}
|
|
71380
71478
|
if (invocation !== undefined)
|
|
71381
71479
|
data.invocation = invocation;
|
|
71480
|
+
if (stdout !== undefined && stdout.length > 0) {
|
|
71481
|
+
data.stdoutTail = tail(redactAndBound(stdout, secretValues, Number.MAX_SAFE_INTEGER), 4096);
|
|
71482
|
+
}
|
|
71483
|
+
if (stderr !== undefined && stderr.length > 0) {
|
|
71484
|
+
data.stderrTail = tail(redactAndBound(stderr, secretValues, Number.MAX_SAFE_INTEGER), 4096);
|
|
71485
|
+
}
|
|
71382
71486
|
return data;
|
|
71383
71487
|
}
|
|
71384
71488
|
function asOptionalString(value) {
|
|
@@ -71438,7 +71542,7 @@ function extractCompletedRequirementsHeuristic(taskMarkdown) {
|
|
|
71438
71542
|
}
|
|
71439
71543
|
return found.size > 0 ? [...found] : ["unknown \u2014 Solution empty and Plan checkboxes open"];
|
|
71440
71544
|
}
|
|
71441
|
-
async function writePartialWorkArtifact(context4, agentLabel, model, traced, cwd) {
|
|
71545
|
+
async function writePartialWorkArtifact(context4, agentLabel, model, traced, cwd, sessionDir) {
|
|
71442
71546
|
try {
|
|
71443
71547
|
const signal = traced.signal;
|
|
71444
71548
|
const exitReason = signal !== undefined ? `killed by signal ${signal} (likely timeout or cancellation)` : traced.message !== undefined ? `dispatch error: ${traced.message}` : `exited with code ${traced.exitCode}`;
|
|
@@ -71448,6 +71552,16 @@ async function writePartialWorkArtifact(context4, agentLabel, model, traced, cwd
|
|
|
71448
71552
|
const headerLine = model !== undefined ? `${agentLabel} (model: ${model})` : agentLabel;
|
|
71449
71553
|
const inv = traced.invocation;
|
|
71450
71554
|
const argvLine = inv ? `${inv.command} ${inv.argv.join(" ")}` : "(invocation not captured)";
|
|
71555
|
+
const latchedSessionPath = join17(cwd, ".spur", "run", `${context4.runId}-agent-session.json`);
|
|
71556
|
+
const resumeContext = [
|
|
71557
|
+
"",
|
|
71558
|
+
"## resume context",
|
|
71559
|
+
"",
|
|
71560
|
+
`- session dir: ${sessionDir ?? "(none captured)"}`,
|
|
71561
|
+
`- latched session file: ${latchedSessionPath}`,
|
|
71562
|
+
""
|
|
71563
|
+
].join(`
|
|
71564
|
+
`);
|
|
71451
71565
|
let completedSection = "";
|
|
71452
71566
|
const wbs = String(context4.vars.wbs ?? "");
|
|
71453
71567
|
if (wbs) {
|
|
@@ -71508,6 +71622,7 @@ async function writePartialWorkArtifact(context4, agentLabel, model, traced, cwd
|
|
|
71508
71622
|
"```",
|
|
71509
71623
|
stderrTail || "(empty)",
|
|
71510
71624
|
"```",
|
|
71625
|
+
resumeContext,
|
|
71511
71626
|
completedSection
|
|
71512
71627
|
].join(`
|
|
71513
71628
|
`);
|
|
@@ -71551,12 +71666,16 @@ async function gitHasNonCorpusChanges(cwd, excludeGlobs = ["docs/tasks3/*", "doc
|
|
|
71551
71666
|
function tail(text4, maxChars) {
|
|
71552
71667
|
if (text4.length <= maxChars)
|
|
71553
71668
|
return text4;
|
|
71554
|
-
|
|
71555
|
-
|
|
71669
|
+
const marker = `... (truncated) ...
|
|
71670
|
+
`;
|
|
71671
|
+
if (maxChars <= marker.length)
|
|
71672
|
+
return marker.slice(0, maxChars);
|
|
71673
|
+
return `${marker}${text4.slice(text4.length - (maxChars - marker.length))}`;
|
|
71556
71674
|
}
|
|
71557
71675
|
var PARTIAL_ARTIFACT_TAIL_CHARS = 4000, AGENT_RUN_PROGRESS_INTERVAL_MS = 30000, KIND = "agent.run";
|
|
71558
71676
|
var init_agent_run = __esm(() => {
|
|
71559
71677
|
init_dist5();
|
|
71678
|
+
init_agent_execution();
|
|
71560
71679
|
init_task_locator();
|
|
71561
71680
|
init_steering();
|
|
71562
71681
|
});
|
|
@@ -72406,8 +72525,9 @@ class WorkflowAppService {
|
|
|
72406
72525
|
});
|
|
72407
72526
|
const runId = opts.runId ?? crypto.randomUUID();
|
|
72408
72527
|
const isDry = opts.dryRun === true;
|
|
72528
|
+
const warnings = [];
|
|
72409
72529
|
const runVars = {
|
|
72410
|
-
...await resolveDefaultAgentVar(this.ctx.cwd, opts.vars),
|
|
72530
|
+
...await resolveDefaultAgentVar(this.ctx.cwd, opts.vars, (m) => warnings.push(m)),
|
|
72411
72531
|
...opts.vars ?? {},
|
|
72412
72532
|
__runId: runId
|
|
72413
72533
|
};
|
|
@@ -72430,7 +72550,16 @@ class WorkflowAppService {
|
|
|
72430
72550
|
}
|
|
72431
72551
|
await this.stampFailureReason(runId, result);
|
|
72432
72552
|
await this.maybeLinkPipelineRun(file2, runId, opts);
|
|
72433
|
-
|
|
72553
|
+
const runResult = result;
|
|
72554
|
+
if (warnings.length > 0) {
|
|
72555
|
+
for (const warning of warnings) {
|
|
72556
|
+
try {
|
|
72557
|
+
this.ctx.warn?.(warning);
|
|
72558
|
+
} catch {}
|
|
72559
|
+
}
|
|
72560
|
+
runResult.warnings = warnings;
|
|
72561
|
+
}
|
|
72562
|
+
return runResult;
|
|
72434
72563
|
}
|
|
72435
72564
|
async maybeLinkPipelineRun(file2, runId, opts) {
|
|
72436
72565
|
const wbs = opts.vars?.wbs;
|
|
@@ -72689,10 +72818,13 @@ class WorkflowAppService {
|
|
|
72689
72818
|
const processExec = this.ctx.processExecutor?.();
|
|
72690
72819
|
const host = createDefaultWorkflowEngineHost(processExec !== undefined ? { processExecutor: processExec } : {});
|
|
72691
72820
|
const bus = this.ctx.observabilityBus?.();
|
|
72692
|
-
let agentSlice = {
|
|
72821
|
+
let agentSlice = {
|
|
72822
|
+
...this.ctx.secretValues !== undefined ? { secretValues: this.ctx.secretValues } : {}
|
|
72823
|
+
};
|
|
72693
72824
|
try {
|
|
72694
72825
|
const agent = (await loadSpurConfig(this.ctx.cwd)).agent;
|
|
72695
72826
|
agentSlice = {
|
|
72827
|
+
...agentSlice,
|
|
72696
72828
|
...agent?.default !== undefined ? { default: agent.default } : {},
|
|
72697
72829
|
...agent?.sessionAffinity !== undefined ? { sessionAffinity: agent.sessionAffinity } : {}
|
|
72698
72830
|
};
|
|
@@ -72779,15 +72911,28 @@ async function fileExists(path9) {
|
|
|
72779
72911
|
const fs3 = createNodeFileSystem3();
|
|
72780
72912
|
return await fs3.exists(path9);
|
|
72781
72913
|
}
|
|
72782
|
-
async function resolveDefaultAgentVar(cwd, callerVars) {
|
|
72783
|
-
|
|
72784
|
-
return {};
|
|
72914
|
+
async function resolveDefaultAgentVar(cwd, callerVars, warn) {
|
|
72915
|
+
let config4;
|
|
72785
72916
|
try {
|
|
72786
|
-
|
|
72787
|
-
return typeof configured === "string" && configured.length > 0 ? { agent: configured } : {};
|
|
72917
|
+
config4 = await loadSpurConfig(cwd);
|
|
72788
72918
|
} catch {
|
|
72789
72919
|
return {};
|
|
72790
72920
|
}
|
|
72921
|
+
const configured = config4.agent?.default;
|
|
72922
|
+
if (typeof configured !== "string" || configured.length === 0) {
|
|
72923
|
+
return {};
|
|
72924
|
+
}
|
|
72925
|
+
const valid = config4.agent?.executors?.some((e) => e.name === configured) === true || resolveAgentName(configured) !== undefined;
|
|
72926
|
+
if (!valid) {
|
|
72927
|
+
warn(`agent.default "${configured}" does not name a configured executor or agent binary; leaving the pipeline's literal agent in force`);
|
|
72928
|
+
return {};
|
|
72929
|
+
}
|
|
72930
|
+
const result = {};
|
|
72931
|
+
if (callerVars?.agent === undefined)
|
|
72932
|
+
result.agent = configured;
|
|
72933
|
+
if (callerVars?.implementAgent === undefined)
|
|
72934
|
+
result.implementAgent = configured;
|
|
72935
|
+
return result;
|
|
72791
72936
|
}
|
|
72792
72937
|
async function resolveWorkflowLogRetentionDays(cwd) {
|
|
72793
72938
|
try {
|
|
@@ -72894,6 +73039,7 @@ var TASK_PIPELINE_WORKFLOW = "task-pipeline", EMBEDDED_SCHEMA_PREFIX = "\x00embe
|
|
|
72894
73039
|
var init_workflow_service = __esm(() => {
|
|
72895
73040
|
init_loader();
|
|
72896
73041
|
init_src2();
|
|
73042
|
+
init_dist9();
|
|
72897
73043
|
init_dist7();
|
|
72898
73044
|
init_dist5();
|
|
72899
73045
|
init_builtins2();
|
|
@@ -81317,7 +81463,7 @@ init_dist6();
|
|
|
81317
81463
|
var CLI_CONFIG = {
|
|
81318
81464
|
binaryName: "spur",
|
|
81319
81465
|
binaryLabel: "spur",
|
|
81320
|
-
binaryVersion: "0.3.
|
|
81466
|
+
binaryVersion: "0.3.38",
|
|
81321
81467
|
configDir: ".spur",
|
|
81322
81468
|
configFile: ".spur/config.yaml",
|
|
81323
81469
|
databaseFile: ".spur/spur.db"
|
|
@@ -91491,6 +91637,8 @@ function createServerContext(appRt, options) {
|
|
|
91491
91637
|
workflowService() {
|
|
91492
91638
|
workflowSvc ??= new WorkflowAppService({
|
|
91493
91639
|
cwd,
|
|
91640
|
+
secretValues: configuredSecretValues(options.env ?? process.env),
|
|
91641
|
+
warn: (message) => appRt.logger.warn(message),
|
|
91494
91642
|
getDb: this.getDb.bind(this),
|
|
91495
91643
|
agentService: this.agentService.bind(this),
|
|
91496
91644
|
ruleService: this.ruleService.bind(this),
|
|
@@ -92722,7 +92870,16 @@ ${result.content}`);
|
|
|
92722
92870
|
context4.setExitCode(1);
|
|
92723
92871
|
}
|
|
92724
92872
|
});
|
|
92725
|
-
task.command("update").summary("Update a task status or replace a section.").argument("<wbs>", "Task WBS number").argument("[status]", "New status (for lifecycle transition)").
|
|
92873
|
+
task.command("update").summary("Update a task status or replace a section.").argument("<wbs>", "Task WBS number").argument("[status]", "New status (for lifecycle transition)").addHelpText("after", [
|
|
92874
|
+
"Lifecycle: `task update <wbs> <status>` moves a task through",
|
|
92875
|
+
"backlog \u2192 todo \u2192 wip \u2192 testing \u2192 done, running the lifecycle guards on",
|
|
92876
|
+
"`wip \u2192 testing` (`spur task check`) and `testing \u2192 done` (`--strict-core`).",
|
|
92877
|
+
"A GuardDeniedError on `testing \u2192 done` means no pipeline run is recorded for the",
|
|
92878
|
+
"task: run `/sp:dev-verify <wbs> --next` to PASS it, or record the audited bypass with",
|
|
92879
|
+
'`SPUR_PROVENANCE_OVERRIDE=1 spur task update <wbs> done --force-done --reason "\u2026"`.',
|
|
92880
|
+
"See the gate checklist (spur-dev/references/gate-checklists.md)."
|
|
92881
|
+
].join(`
|
|
92882
|
+
`)).option("--section <name>", "Section name to replace").option("--from-file <path>", "File to read section body from (requires --section)").option("--feature <id>", "Set the feature_id frontmatter field (traceability edge)").option("--priority <p>", "Set the priority frontmatter field (P0\u2013P3)").option("--ac-numbering <mode>", "Set the ac_numbering frontmatter field (task-local) \u2014 opts the task into the Requirements\u2194AC coverage check").option("--no-lifecycle", "Suppress lifecycle workflow run creation (use during pipeline runs to avoid orphaned lifecycle runs)").option("--force-done", "Allow transitioning to `done` even when the verify verdict is not PASS; records an override (task 0292)").option("--reason <text>", "Rationale for a forced-done override (paired with --force-done; persisted as done_reason)").option("--verdict-dir <path>", "Directory holding <wbs>-verdict.json artifacts (default: .spur/run)").option("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, status, options) => {
|
|
92726
92883
|
const svc = await makeService2(context4, options.folder, options.lifecycle === false);
|
|
92727
92884
|
try {
|
|
92728
92885
|
if (options.section !== undefined) {
|
|
@@ -93721,6 +93878,24 @@ async function spawnAsyncWorkflowWorker(spurBin, cmd) {
|
|
|
93721
93878
|
rejectOnError: false
|
|
93722
93879
|
});
|
|
93723
93880
|
}
|
|
93881
|
+
function asyncRegisterTimeoutMs() {
|
|
93882
|
+
const raw2 = Number(process.env.SPUR_ASYNC_REGISTER_TIMEOUT_MS);
|
|
93883
|
+
return Number.isFinite(raw2) && raw2 > 0 ? raw2 : 5000;
|
|
93884
|
+
}
|
|
93885
|
+
async function waitForRunRegistration(service, runId, timeoutMs = asyncRegisterTimeoutMs(), pollMs = 250) {
|
|
93886
|
+
const deadline = Date.now() + timeoutMs;
|
|
93887
|
+
for (;; ) {
|
|
93888
|
+
try {
|
|
93889
|
+
await service.trace(runId);
|
|
93890
|
+
return true;
|
|
93891
|
+
} catch {
|
|
93892
|
+
if (Date.now() >= deadline) {
|
|
93893
|
+
return false;
|
|
93894
|
+
}
|
|
93895
|
+
await sleep3(pollMs);
|
|
93896
|
+
}
|
|
93897
|
+
}
|
|
93898
|
+
}
|
|
93724
93899
|
function parseVars(raw2) {
|
|
93725
93900
|
if (raw2 === undefined) {
|
|
93726
93901
|
return;
|
|
@@ -93759,6 +93934,8 @@ async function resolveWorkflowPaths(cwd) {
|
|
|
93759
93934
|
function registerWorkflowCommand(program2, context4) {
|
|
93760
93935
|
const makeSvc = (json3, bus) => new WorkflowAppService({
|
|
93761
93936
|
cwd: context4.cwd,
|
|
93937
|
+
secretValues: configuredSecretValues(context4.env),
|
|
93938
|
+
warn: (message) => context4.output.error(`Warning: ${message}`),
|
|
93762
93939
|
getDb: () => context4.getDb(),
|
|
93763
93940
|
agentService: () => context4.agentService(),
|
|
93764
93941
|
ruleService: () => context4.ruleService(),
|
|
@@ -93843,6 +94020,17 @@ ${result.errors.map((m) => ` - ${m}`).join(`
|
|
|
93843
94020
|
context4.setExitCode(result2.status === "done" ? 0 : 1);
|
|
93844
94021
|
return;
|
|
93845
94022
|
}
|
|
94023
|
+
if (!await waitForRunRegistration(makeSvc(options.json), runId2)) {
|
|
94024
|
+
const reason = "async worker failed to start or register the run";
|
|
94025
|
+
const hint = "run the workflow synchronously (omit --async) to see the failure";
|
|
94026
|
+
if (json3) {
|
|
94027
|
+
context4.output.write(toJson2({ status: "failed", reason, hint }));
|
|
94028
|
+
} else if (!silent) {
|
|
94029
|
+
context4.output.write(`async spawn failed: ${reason} \u2014 ${hint}.`);
|
|
94030
|
+
}
|
|
94031
|
+
context4.setExitCode(1);
|
|
94032
|
+
return;
|
|
94033
|
+
}
|
|
93846
94034
|
const asyncResult = { runId: runId2, status: "started", workflowName: file2 };
|
|
93847
94035
|
if (json3)
|
|
93848
94036
|
context4.output.write(toJson2(asyncResult));
|