@gobing-ai/spur 0.3.68 → 0.3.69
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/workflow-composition-baseline.json +2 -2
- package/config/workflows/basic.yaml +1 -1
- package/config/workflows/docs-pipeline.yaml +1 -1
- package/config/workflows/feature-dev.yaml +1 -1
- package/config/workflows/idea-pipeline.yaml +1 -1
- package/config/workflows/task-pipeline.yaml +30 -17
- package/config/workflows/wayfinder-resolution.yaml +1 -1
- package/config/workflows/wrapup-pipeline.yaml +1 -1
- package/package.json +1 -1
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/surface-drift-inventory.ts +2 -15
- package/plugins/sp/scripts/task-size-precheck.ts +21 -79
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +2 -1
- package/spur.js +14 -33
|
@@ -320,14 +320,14 @@
|
|
|
320
320
|
},
|
|
321
321
|
"precheck:onEnter:2": {
|
|
322
322
|
"kind": "shell",
|
|
323
|
-
"invocation": "if [ \"$profile\" = auto ]; then\n FID=$($spurBin task show $wbs --json 2>/dev/null |\n jq -r '.feature_id // .frontmatter.feature_id // empty' 2>/dev/null);\n if [ -n \"$FID\" ]; then\n $spurBin feature sync \"$FID\" --force
|
|
323
|
+
"invocation": "if [ \"$profile\" = auto ]; then\n FID=$($spurBin task show $wbs --json 2>/dev/null |\n jq -r '.feature_id // .frontmatter.feature_id // empty' 2>/dev/null);\n if [ -n \"$FID\" ]; then\n if ! $spurBin feature sync \"$FID\" --force 2>/dev/null; then\n if ! $spurBin feature update \"$FID\" active 2>/dev/null; then\n echo \"precheck: FAIL - feature reactivation $FID failed;\" >&2;\n echo \"precheck: feature sync + feature update both errored\" >&2;\n exit 1;\n fi;\n fi;\n fi;\nfi; exit 0",
|
|
324
324
|
"stateEffect": "write",
|
|
325
325
|
"evidenceEffect": "none",
|
|
326
326
|
"disposition": "GLUE"
|
|
327
327
|
},
|
|
328
328
|
"precheck:onEnter:3": {
|
|
329
329
|
"kind": "shell",
|
|
330
|
-
"invocation": "SIZE_FILE=\".spur/run/$wbs-precheck-size.status\" && mkdir -p .spur/run && if [ -f plugins/sp/scripts/task-size-precheck.ts ]; then bun plugins/sp/scripts/task-size-precheck.ts \"$wbs\" --spur-bin \"$spurBin\" --max-reqs \"$maxImplementReqs\" --max-plan-items \"$maxImplementPlanItems\"; else\n echo \"task-size-precheck
|
|
330
|
+
"invocation": "SIZE_FILE=\".spur/run/$wbs-precheck-size.status\" && mkdir -p .spur/run && if [ -f plugins/sp/scripts/task-size-precheck.ts ]; then bun plugins/sp/scripts/task-size-precheck.ts \"$wbs\" --spur-bin \"$spurBin\" --max-reqs \"$maxImplementReqs\" --max-plan-items \"$maxImplementPlanItems\"; else\n echo \"task-size-precheck failed closed — checker script\" >&2 &&\n echo \"plugins/sp/scripts/task-size-precheck.ts absent.\" >&2 &&\n echo \"FAIL\" > \"$SIZE_FILE\";\nfi && exit 0",
|
|
331
331
|
"stateEffect": "read",
|
|
332
332
|
"evidenceEffect": "write",
|
|
333
333
|
"disposition": "EXT"
|
|
@@ -24,7 +24,7 @@ failureStates:
|
|
|
24
24
|
vars:
|
|
25
25
|
# `task` is reserved by the engine runtime — use taskLabel for the free-form label.
|
|
26
26
|
taskLabel: "task"
|
|
27
|
-
agent: "
|
|
27
|
+
agent: "auto"
|
|
28
28
|
stepTimeoutMs: "1800000"
|
|
29
29
|
# Override per project: `--vars '{"qualityGateCmd":"bun run lint && bun run test"}'`
|
|
30
30
|
qualityGateCmd: "bun run check"
|
|
@@ -30,7 +30,7 @@ vars:
|
|
|
30
30
|
wbs: "0000"
|
|
31
31
|
profile: "standard"
|
|
32
32
|
spurBin: "spur"
|
|
33
|
-
agent: "
|
|
33
|
+
agent: "auto"
|
|
34
34
|
stepTimeoutMs: "1800000"
|
|
35
35
|
# Proof-state bracket (task 0704, mirroring task-pipeline 0612/0703). `proofDigest` is the
|
|
36
36
|
# canonical capture at verify entry; `proofDigestNow` is the live re-capture compared against
|
|
@@ -54,15 +54,16 @@ vars:
|
|
|
54
54
|
# runs and `workflow validate` resolve the template. (See AGENTS.md / ADR-026.)
|
|
55
55
|
spurBin: "spur"
|
|
56
56
|
# Agent the pipeline's agent.run steps invoke. Override per run with
|
|
57
|
-
# `--vars '{"agent":"claude"}'`.
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
#
|
|
57
|
+
# `--vars '{"agent":"claude"}'`. `auto` is the reserved config-resolving selector:
|
|
58
|
+
# `agent.default` role -> tier -> cheapest USABLE executor. A named literal here would
|
|
59
|
+
# pin a box-specific binary into tracked SSOT and escape that usability ladder.
|
|
60
|
+
agent: "auto"
|
|
61
|
+
# Implement-only executor override (R1, task 0454). Resolves like `agent` unless
|
|
62
|
+
# overridden. The `--agent` flag (execution-batch.md §3.2) forwards into BOTH
|
|
62
63
|
# `agent` and `implementAgent`, so a pinned `--agent X` reaches implement too. To
|
|
63
64
|
# pin ONLY implement while other hops keep the default, pass
|
|
64
65
|
# `--vars '{"implementAgent":"omp-zai"}'`.
|
|
65
|
-
implementAgent: "
|
|
66
|
+
implementAgent: "auto"
|
|
66
67
|
# Step-level timeout for agentic hops (review / verify / test-fix) in ms.
|
|
67
68
|
# Soft quality-gate shells are unbounded by this var (host shell only).
|
|
68
69
|
# Raised 600s → 1800s (task 0398 R4 / H6 dogfood). Override:
|
|
@@ -174,6 +175,10 @@ states:
|
|
|
174
175
|
# R1 (0453): auto-profile precheck reopens a done feature before task check.
|
|
175
176
|
# Under profile=auto, resolve feature_id, sync (preferred) or update to active.
|
|
176
177
|
# Under non-auto, leave R4 message to guide the operator.
|
|
178
|
+
# R3 (0723): a real reactivation failure is surfaced, not swallowed —
|
|
179
|
+
# the default 'fail' onEnter policy halts the sequence and routes the
|
|
180
|
+
# run to `failed` before implementation. Verbs stay single-shot:
|
|
181
|
+
# one sync, then one update fallback, never retried in a loop.
|
|
177
182
|
- kind: shell
|
|
178
183
|
options:
|
|
179
184
|
command: >-
|
|
@@ -181,17 +186,23 @@ states:
|
|
|
181
186
|
FID=$($spurBin task show $wbs --json 2>/dev/null |
|
|
182
187
|
jq -r '.feature_id // .frontmatter.feature_id // empty' 2>/dev/null);
|
|
183
188
|
if [ -n "$FID" ]; then
|
|
184
|
-
$spurBin feature sync "$FID" --force
|
|
185
|
-
$spurBin feature update "$FID" active 2>/dev/null
|
|
189
|
+
if ! $spurBin feature sync "$FID" --force 2>/dev/null; then
|
|
190
|
+
if ! $spurBin feature update "$FID" active 2>/dev/null; then
|
|
191
|
+
echo "precheck: FAIL - feature reactivation $FID failed;" >&2;
|
|
192
|
+
echo "precheck: feature sync + feature update both errored" >&2;
|
|
193
|
+
exit 1;
|
|
194
|
+
fi;
|
|
195
|
+
fi;
|
|
186
196
|
fi;
|
|
187
197
|
fi;
|
|
188
198
|
exit 0
|
|
189
|
-
# R2 (0454): task size precheck —
|
|
190
|
-
#
|
|
191
|
-
#
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
# the
|
|
199
|
+
# R2 (0454, 0723): task size precheck — deterministic count-only
|
|
200
|
+
# evaluation of R-item and Plan-item counts. No executor-tier policy:
|
|
201
|
+
# dispatch-time requiresCapabilities at `agent.run` is the
|
|
202
|
+
# authoritative capability check. Writes PASS/FAIL to
|
|
203
|
+
# .spur/run/<wbs>-precheck-size.status. Always exit 0 (soft action);
|
|
204
|
+
# the precheck→implement guard reads the file, so a missing checker
|
|
205
|
+
# fails closed (writes FAIL, never PASS).
|
|
195
206
|
- kind: shell
|
|
196
207
|
options:
|
|
197
208
|
command: >-
|
|
@@ -199,10 +210,12 @@ states:
|
|
|
199
210
|
mkdir -p .spur/run &&
|
|
200
211
|
if [ -f plugins/sp/scripts/task-size-precheck.ts ]; then
|
|
201
212
|
bun plugins/sp/scripts/task-size-precheck.ts "$wbs"
|
|
202
|
-
--spur-bin "$spurBin" --max-reqs "$maxImplementReqs"
|
|
213
|
+
--spur-bin "$spurBin" --max-reqs "$maxImplementReqs"
|
|
214
|
+
--max-plan-items "$maxImplementPlanItems";
|
|
203
215
|
else
|
|
204
|
-
echo "task-size-precheck
|
|
205
|
-
echo "
|
|
216
|
+
echo "task-size-precheck failed closed — checker script" >&2 &&
|
|
217
|
+
echo "plugins/sp/scripts/task-size-precheck.ts absent." >&2 &&
|
|
218
|
+
echo "FAIL" > "$SIZE_FILE";
|
|
206
219
|
fi &&
|
|
207
220
|
exit 0
|
|
208
221
|
|
|
@@ -49,7 +49,7 @@ vars:
|
|
|
49
49
|
profile: "standard"
|
|
50
50
|
merge: "false"
|
|
51
51
|
spurBin: "spur"
|
|
52
|
-
agent: "
|
|
52
|
+
agent: "auto"
|
|
53
53
|
stepTimeoutMs: "1800000"
|
|
54
54
|
# Corpus-aware quality gate for the feature-transition hop (R1, task 0625).
|
|
55
55
|
# The per-task gate (`spur-check`) deliberately excludes corpus-check — the only
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.69",
|
|
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/plugins/sp/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.69",
|
|
4
4
|
"description": "Spur — a local-first harness engineering toolkit that wraps mainstream coding agents with constraint checking, workflow orchestration, and history analytics.",
|
|
5
5
|
"extensions": {
|
|
6
6
|
"pi": ["./hooks/pi/guard-extension.ts"]
|
|
@@ -473,11 +473,7 @@ export function executeScripts(root: string = PLUGIN_ROOT): void {
|
|
|
473
473
|
writeFileSync(
|
|
474
474
|
fake,
|
|
475
475
|
`#!/bin/sh
|
|
476
|
-
|
|
477
|
-
printf '%s' '{"content":"### Requirements\\n- [ ] R1. x\\n### Plan\\n- [ ] p1"}'
|
|
478
|
-
else
|
|
479
|
-
printf '%s' '{"agents":[{"capabilityTier":"standard"}]}'
|
|
480
|
-
fi
|
|
476
|
+
printf '%s' '{"content":"### Requirements\\n- [ ] R1. x\\n### Plan\\n- [ ] p1"}'
|
|
481
477
|
`,
|
|
482
478
|
);
|
|
483
479
|
chmodSync(fake, 0o755);
|
|
@@ -485,7 +481,7 @@ fi
|
|
|
485
481
|
try {
|
|
486
482
|
execFileSync(
|
|
487
483
|
process.execPath,
|
|
488
|
-
[join(root, 'scripts', 'task-size-precheck.ts'), '0487', '--spur-bin', fake
|
|
484
|
+
[join(root, 'scripts', 'task-size-precheck.ts'), '0487', '--spur-bin', fake],
|
|
489
485
|
{ cwd: dir, encoding: 'utf8', timeout: 30_000, stdio: 'pipe' },
|
|
490
486
|
);
|
|
491
487
|
const content = readFileSync(statusPath, 'utf8');
|
|
@@ -687,15 +683,6 @@ export function probeJsonShapes(run: CliRunner = runCli): void {
|
|
|
687
683
|
{ file: 'plugins/sp/scripts/surface-drift-inventory.ts', line: 1 },
|
|
688
684
|
);
|
|
689
685
|
}
|
|
690
|
-
const doctor = jsonEnvelopeShapes['spur agent doctor omp'];
|
|
691
|
-
const capOk = (doctor?.keys ?? []).some((k) => k.endsWith('.capabilityTier'));
|
|
692
|
-
record(
|
|
693
|
-
'agent doctor <name> --json -> agents[0].capabilityTier (asserted by task-size-precheck.ts:130)',
|
|
694
|
-
'json-exec(field-presence)',
|
|
695
|
-
capOk ? 'ok' : 'mismatch',
|
|
696
|
-
capOk ? 'field present in live envelope' : 'field ABSENT from live envelope',
|
|
697
|
-
{ file: 'plugins/sp/scripts/task-size-precheck.ts', line: 130 },
|
|
698
|
-
);
|
|
699
686
|
// Curated prose flag-claims: assertions phrased as prose ("no explicit `--flag`") that the
|
|
700
687
|
// generic backtick-span extractor cannot scope to a command. Extend this list when a prose
|
|
701
688
|
// claim is found; each entry is verified against the live help capture.
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* task-size-precheck — pipeline size precheck guard (R2, task 0454
|
|
4
|
-
*
|
|
3
|
+
* task-size-precheck — pipeline size precheck guard (R2, task 0454; count-only
|
|
4
|
+
* since task 0723).
|
|
5
5
|
*
|
|
6
6
|
* Shells `spur task show <wbs> --json`, evaluates R-item count and Plan
|
|
7
|
-
* checklist count against limits, writes PASS/FAIL to status file.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* checklist count against limits, writes PASS/FAIL to status file. No executor
|
|
8
|
+
* or doctor involvement: executor liveness/routing/capabilities are attested
|
|
9
|
+
* fail-closed at the `agent.run` dispatch boundary, not predicted here.
|
|
10
10
|
*
|
|
11
|
-
* Always exits 0 (soft
|
|
12
|
-
* task-pipeline.yaml reads the status file
|
|
11
|
+
* Always exits 0 (soft action). The precheck→implement guard in
|
|
12
|
+
* task-pipeline.yaml reads the status file; a missing or failing checker writes
|
|
13
|
+
* FAIL, so readiness fails closed.
|
|
13
14
|
*
|
|
14
15
|
* Ships with the plugin to arbitrary projects, so it stays node-builtin-only —
|
|
15
|
-
* no workspace imports.
|
|
16
|
-
* than from `getExecutorTier` directly; `spur agent doctor --json` exposes it as
|
|
17
|
-
* `capabilityTier` precisely so the inference regex is not duplicated here.
|
|
16
|
+
* no workspace imports.
|
|
18
17
|
*
|
|
19
18
|
* Usage:
|
|
20
19
|
* bun plugins/sp/scripts/task-size-precheck.ts <wbs> [--spur-bin <path>]
|
|
21
|
-
* [--max-reqs <n>] [--max-plan-items <n>]
|
|
20
|
+
* [--max-reqs <n>] [--max-plan-items <n>]
|
|
22
21
|
*
|
|
23
22
|
* Env: SPUR_BIN, MAX_IMPLEMENT_REQS, MAX_IMPLEMENT_PLAN_ITEMS
|
|
24
23
|
*/
|
|
@@ -27,7 +26,6 @@ import { execFileSync } from 'node:child_process';
|
|
|
27
26
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
28
27
|
import { join } from 'node:path';
|
|
29
28
|
import { fileURLToPath } from 'node:url';
|
|
30
|
-
import { STAGE_FLOOR_TIER, TIER_ORDER } from './stage-registry-adapter';
|
|
31
29
|
|
|
32
30
|
// ─── Regex (sync with packages/app/src/services/task-size-precheck.ts) ───────
|
|
33
31
|
|
|
@@ -37,32 +35,11 @@ const R_ITEM_RE = /^\s*-\s*\[[ xX]\]\s*(\*\*)?R\d+\./m;
|
|
|
37
35
|
/** Matches checklist items under the Plan section. */
|
|
38
36
|
const CHECKLIST_ITEM_RE = /^\s*-\s*\[[ xX]\]/m;
|
|
39
37
|
|
|
40
|
-
/**
|
|
41
|
-
* Large-task thresholds for the capability gate (R3, task 0487) — the DEFAULT
|
|
42
|
-
* caps, not the overridable `--max-*` limits. Raising the caps says "I accept a
|
|
43
|
-
* big task"; it does not make a flash-tier model able to finish one inside
|
|
44
|
-
* `implementTimeoutMs`.
|
|
45
|
-
*/
|
|
46
|
-
const LARGE_TASK_REQS = 5;
|
|
47
|
-
const LARGE_TASK_PLAN_ITEMS = 8;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Capability tiers strong enough for a large task (R3, task 0487). The floor is
|
|
51
|
-
* the `review` stage's Layer-1 tier — `reviewer` per `references/roles.md`,
|
|
52
|
-
* read via the stage-registry adapter (0538 R4: no tier literal here; roles.md
|
|
53
|
-
* is the pointer). Tiers at or above the floor pass. An unreachable roles.md
|
|
54
|
-
* degrades to the pre-reconcile band — fail-closed for a safety gate.
|
|
55
|
-
*/
|
|
56
|
-
const CAPABLE_TIERS: ReadonlySet<string> = (() => {
|
|
57
|
-
const floor = STAGE_FLOOR_TIER.get('review') ?? 'capable-1';
|
|
58
|
-
return new Set(TIER_ORDER.slice(Math.max(0, TIER_ORDER.indexOf(floor))));
|
|
59
|
-
})();
|
|
60
|
-
|
|
61
38
|
// ─── CLI ─────────────────────────────────────────────────────────────────────
|
|
62
39
|
|
|
63
40
|
function usage(): never {
|
|
64
41
|
console.error(
|
|
65
|
-
'Usage: bun plugins/sp/scripts/task-size-precheck.ts <wbs> [--spur-bin <path>] [--max-reqs <n>] [--max-plan-items <n>]
|
|
42
|
+
'Usage: bun plugins/sp/scripts/task-size-precheck.ts <wbs> [--spur-bin <path>] [--max-reqs <n>] [--max-plan-items <n>]',
|
|
66
43
|
);
|
|
67
44
|
process.exit(1);
|
|
68
45
|
}
|
|
@@ -87,13 +64,14 @@ function parseArgs(argv: string[]): {
|
|
|
87
64
|
spurBin: string;
|
|
88
65
|
maxReqs: number;
|
|
89
66
|
maxPlanItems: number;
|
|
90
|
-
executor: string;
|
|
91
67
|
} {
|
|
92
68
|
let spurBin = defaultSpurBin();
|
|
93
69
|
let wbs = '';
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
70
|
+
// Doubled deterministic ceiling (0723 operator decision): 10 R-items / 16
|
|
71
|
+
// Plan items — keep in sync with DEFAULT_TASK_SIZE_LIMITS in
|
|
72
|
+
// packages/app/src/services/task-size-precheck.ts (asserted by test).
|
|
73
|
+
let maxReqs = Number(process.env.MAX_IMPLEMENT_REQS) || 10;
|
|
74
|
+
let maxPlanItems = Number(process.env.MAX_IMPLEMENT_PLAN_ITEMS) || 16;
|
|
97
75
|
|
|
98
76
|
let i = 0;
|
|
99
77
|
while (i < argv.length) {
|
|
@@ -102,13 +80,10 @@ function parseArgs(argv: string[]): {
|
|
|
102
80
|
spurBin = argv[i + 1] ?? defaultSpurBin();
|
|
103
81
|
i += 2;
|
|
104
82
|
} else if (arg === '--max-reqs') {
|
|
105
|
-
maxReqs = Number(argv[i + 1]) ||
|
|
83
|
+
maxReqs = Number(argv[i + 1]) || 10;
|
|
106
84
|
i += 2;
|
|
107
85
|
} else if (arg === '--max-plan-items') {
|
|
108
|
-
maxPlanItems = Number(argv[i + 1]) ||
|
|
109
|
-
i += 2;
|
|
110
|
-
} else if (arg === '--executor') {
|
|
111
|
-
executor = argv[i + 1] ?? '';
|
|
86
|
+
maxPlanItems = Number(argv[i + 1]) || 16;
|
|
112
87
|
i += 2;
|
|
113
88
|
} else if (!arg.startsWith('--')) {
|
|
114
89
|
wbs = arg;
|
|
@@ -119,7 +94,7 @@ function parseArgs(argv: string[]): {
|
|
|
119
94
|
}
|
|
120
95
|
|
|
121
96
|
if (!wbs) usage();
|
|
122
|
-
return { wbs, spurBin, maxReqs, maxPlanItems
|
|
97
|
+
return { wbs, spurBin, maxReqs, maxPlanItems };
|
|
123
98
|
}
|
|
124
99
|
|
|
125
100
|
/**
|
|
@@ -135,29 +110,8 @@ function runSpur(spurBin: string, args: string[]): string {
|
|
|
135
110
|
});
|
|
136
111
|
}
|
|
137
112
|
|
|
138
|
-
/**
|
|
139
|
-
* Capability tier of `executor` per `spur agent doctor <exec> --json`.
|
|
140
|
-
* Unknown executor, unreadable doctor output, or an undeclared-and-uninferrable
|
|
141
|
-
* tier all read as `standard` — conservative: a false block is one flag away,
|
|
142
|
-
* a false pass costs a 30-minute timed-out implement.
|
|
143
|
-
*/
|
|
144
|
-
function resolveCapabilityTier(spurBin: string, executor: string): { tier: string; resolvedName: string } {
|
|
145
|
-
try {
|
|
146
|
-
const out = runSpur(spurBin, ['agent', 'doctor', executor, '--json']);
|
|
147
|
-
const row = JSON.parse(out)?.agents?.[0];
|
|
148
|
-
const tier = row?.capabilityTier;
|
|
149
|
-
// R1 (0622 F2/F4 residue): `doctor <role>` resolves the role to its cheapest
|
|
150
|
-
// eligible executor (`coder` → `omp`); surface the resolved executor name in
|
|
151
|
-
// the block message, not the role the caller passed in.
|
|
152
|
-
const resolvedName = typeof row?.agent === 'string' && row.agent.length > 0 ? row.agent : executor;
|
|
153
|
-
return { tier: typeof tier === 'string' && tier ? tier : 'standard', resolvedName };
|
|
154
|
-
} catch {
|
|
155
|
-
return { tier: 'standard', resolvedName: executor };
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
113
|
function main(): void {
|
|
160
|
-
const { wbs, spurBin, maxReqs, maxPlanItems
|
|
114
|
+
const { wbs, spurBin, maxReqs, maxPlanItems } = parseArgs(process.argv.slice(2));
|
|
161
115
|
|
|
162
116
|
// Fetch task content via spur
|
|
163
117
|
let taskContent: string;
|
|
@@ -166,7 +120,7 @@ function main(): void {
|
|
|
166
120
|
const task = JSON.parse(result);
|
|
167
121
|
taskContent = task.content ?? task.body ?? '';
|
|
168
122
|
} catch {
|
|
169
|
-
// If spur fails, write FAIL and exit 0
|
|
123
|
+
// If spur fails, write FAIL and exit 0 — the status file carries the verdict
|
|
170
124
|
const statusDir = join(process.cwd(), '.spur', 'run');
|
|
171
125
|
if (!existsSync(statusDir)) mkdirSync(statusDir, { recursive: true });
|
|
172
126
|
writeFileSync(join(statusDir, `${wbs}-precheck-size.status`), 'FAIL\n');
|
|
@@ -187,18 +141,6 @@ function main(): void {
|
|
|
187
141
|
const planItemCount = planBody.match(new RegExp(CHECKLIST_ITEM_RE.source, 'gm'))?.length ?? 0;
|
|
188
142
|
|
|
189
143
|
const reasons: string[] = [];
|
|
190
|
-
// R3 (0487): a large task on a sub-capable executor blocks even when the caller
|
|
191
|
-
// raised the caps — the caps are an acceptance of size, not a capability grant.
|
|
192
|
-
if (executor && (reqCount > LARGE_TASK_REQS || planItemCount > LARGE_TASK_PLAN_ITEMS)) {
|
|
193
|
-
const { tier, resolvedName } = resolveCapabilityTier(spurBin, executor);
|
|
194
|
-
if (!CAPABLE_TIERS.has(tier)) {
|
|
195
|
-
reasons.push(
|
|
196
|
-
`Task size (${reqCount} R-items / ${planItemCount} Plan items) requires a capable executor, ` +
|
|
197
|
-
`but ${resolvedName} is tier ${tier}. ` +
|
|
198
|
-
`Pass \`--agent <capable>\` or \`--vars '{"implementAgent":"<capable>"}'\`, or split the task.`,
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
144
|
if (reqCount > maxReqs) {
|
|
203
145
|
reasons.push(
|
|
204
146
|
`Task has ${reqCount} R-items (max ${maxReqs}). ` +
|
|
@@ -139,7 +139,8 @@ resolved in this order; first match wins:
|
|
|
139
139
|
2. **`agent.default`** from `.spur/config.yaml` (project layer, then `~/.config/spur/config.yaml`) —
|
|
140
140
|
`spur workflow run` injects it as the `agent` var when `vars.agent` was not set by the caller.
|
|
141
141
|
3. **YAML literal `agent:` in the pipeline file** — the last-resort fallback declared in the
|
|
142
|
-
workflow YAML
|
|
142
|
+
workflow YAML. Every shipped pipeline declares `agent: "auto"`, so this rung resolves through
|
|
143
|
+
the role/tier ladder instead of pinning an executor name; it fires only when no
|
|
143
144
|
`agent.default` is configured anywhere.
|
|
144
145
|
|
|
145
146
|
`--agent auto` tier-resolves an executor (stage `model_policy` → `agent.default` → tier priority)
|
package/spur.js
CHANGED
|
@@ -78919,6 +78919,12 @@ function safeJsonParse(raw) {
|
|
|
78919
78919
|
return null;
|
|
78920
78920
|
}
|
|
78921
78921
|
}
|
|
78922
|
+
function outputTail(text4) {
|
|
78923
|
+
const trimmed = text4.trim();
|
|
78924
|
+
if (trimmed === "")
|
|
78925
|
+
return "";
|
|
78926
|
+
return `: ${trimmed.length > 400 ? `\u2026${trimmed.slice(-400)}` : trimmed}`;
|
|
78927
|
+
}
|
|
78922
78928
|
function validateHistoryRefreshPayload(raw) {
|
|
78923
78929
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
78924
78930
|
throw new Error("history refresh payload must be a JSON object");
|
|
@@ -79005,7 +79011,7 @@ async function handleHistoryRefreshJob(deps, job) {
|
|
|
79005
79011
|
throw new Error(split.error);
|
|
79006
79012
|
const result = await deps.executor.run({
|
|
79007
79013
|
command: split.command,
|
|
79008
|
-
args: [...split.leadingArgs, "
|
|
79014
|
+
args: [...split.leadingArgs, "--no-logo", "history", "daily"],
|
|
79009
79015
|
cwd: deps.cwd,
|
|
79010
79016
|
env: {
|
|
79011
79017
|
[HISTORY_REFRESH_CONTEXT_ENV]: JSON.stringify(payload),
|
|
@@ -79013,30 +79019,13 @@ async function handleHistoryRefreshJob(deps, job) {
|
|
|
79013
79019
|
},
|
|
79014
79020
|
maxOutput: HISTORY_REFRESH_MAX_OUTPUT
|
|
79015
79021
|
});
|
|
79016
|
-
const stderrDetail = result.stderr
|
|
79022
|
+
const stderrDetail = outputTail(result.stderr);
|
|
79017
79023
|
if (result.exitCode === null) {
|
|
79018
79024
|
const signalDetail = result.signal === undefined ? "" : ` (${result.signal})`;
|
|
79019
79025
|
throw new Error(`history refresh child terminated before a normal exit${signalDetail}${stderrDetail}`);
|
|
79020
79026
|
}
|
|
79021
79027
|
if (result.exitCode !== 0) {
|
|
79022
|
-
|
|
79023
|
-
try {
|
|
79024
|
-
const failure = JSON.parse(result.stdout);
|
|
79025
|
-
const message = failure.ok === false ? failure.error?.message : undefined;
|
|
79026
|
-
if (typeof message === "string" && message !== "") {
|
|
79027
|
-
stdoutDetail = `: ${message.length > 400 ? `\u2026${message.slice(-400)}` : message}`;
|
|
79028
|
-
}
|
|
79029
|
-
} catch {}
|
|
79030
|
-
throw new Error(`history daily exited ${result.exitCode}${stdoutDetail || stderrDetail}`);
|
|
79031
|
-
}
|
|
79032
|
-
let parsed;
|
|
79033
|
-
try {
|
|
79034
|
-
parsed = JSON.parse(result.stdout);
|
|
79035
|
-
} catch (e) {
|
|
79036
|
-
throw new Error(`history daily emitted invalid JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
79037
|
-
}
|
|
79038
|
-
if (parsed.ok !== true || typeof parsed.data !== "object" || parsed.data === null) {
|
|
79039
|
-
throw new Error("history daily emitted an unexpected JSON shape (expected {ok:true,data})");
|
|
79028
|
+
throw new Error(`history daily exited ${result.exitCode}${outputTail(result.stdout) || stderrDetail}`);
|
|
79040
79029
|
}
|
|
79041
79030
|
}
|
|
79042
79031
|
var HISTORY_REFRESH_JOB = "history.refresh", HISTORY_REFRESH_CONTEXT_ENV = "SPUR_HISTORY_REFRESH_CONTEXT", HISTORY_REFRESH_MAX_OUTPUT = 1e6;
|
|
@@ -82421,9 +82410,6 @@ var init_task_scaffold = __esm(() => {
|
|
|
82421
82410
|
});
|
|
82422
82411
|
|
|
82423
82412
|
// ../../packages/app/src/services/task-size-precheck.ts
|
|
82424
|
-
function isBelowCapable(tier) {
|
|
82425
|
-
return TIER_RANK[tier ?? "standard"] < TIER_RANK["capable-1"];
|
|
82426
|
-
}
|
|
82427
82413
|
function countRItems(content) {
|
|
82428
82414
|
const matches = content.match(new RegExp(R_ITEM_RE.source, "gm"));
|
|
82429
82415
|
return matches?.length ?? 0;
|
|
@@ -82439,13 +82425,10 @@ function countPlanItems(content) {
|
|
|
82439
82425
|
const matches = planBody.match(new RegExp(CHECKLIST_ITEM_RE.source, "gm"));
|
|
82440
82426
|
return matches?.length ?? 0;
|
|
82441
82427
|
}
|
|
82442
|
-
function evaluateTaskSize(content, limits = DEFAULT_TASK_SIZE_LIMITS
|
|
82428
|
+
function evaluateTaskSize(content, limits = DEFAULT_TASK_SIZE_LIMITS) {
|
|
82443
82429
|
const reqCount = countRItems(content);
|
|
82444
82430
|
const planItemCount = countPlanItems(content);
|
|
82445
82431
|
const reasons = [];
|
|
82446
|
-
if (executor !== undefined && isBelowCapable(executor.tier) && (reqCount > LARGE_TASK_THRESHOLDS.maxReqs || planItemCount > LARGE_TASK_THRESHOLDS.maxPlanItems)) {
|
|
82447
|
-
reasons.push(`Task size (${reqCount} R-items / ${planItemCount} Plan items) requires a capable executor, ` + `but ${executor.name} is tier ${executor.tier ?? "standard"}. ` + `Pass \`--agent <capable>\` or \`--vars '{"implementAgent":"<capable>"}'\`, or split the task.`);
|
|
82448
|
-
}
|
|
82449
82432
|
if (reqCount > limits.maxReqs) {
|
|
82450
82433
|
reasons.push(`Task has ${reqCount} R-items (max ${limits.maxReqs}). ` + `Consider decomposing into smaller tasks or raise maxImplementReqs via --vars.`);
|
|
82451
82434
|
}
|
|
@@ -82459,14 +82442,12 @@ function evaluateTaskSize(content, limits = DEFAULT_TASK_SIZE_LIMITS, executor)
|
|
|
82459
82442
|
reasons
|
|
82460
82443
|
};
|
|
82461
82444
|
}
|
|
82462
|
-
var DEFAULT_TASK_SIZE_LIMITS,
|
|
82445
|
+
var DEFAULT_TASK_SIZE_LIMITS, R_ITEM_RE, CHECKLIST_ITEM_RE;
|
|
82463
82446
|
var init_task_size_precheck = __esm(() => {
|
|
82464
|
-
init_src2();
|
|
82465
82447
|
DEFAULT_TASK_SIZE_LIMITS = {
|
|
82466
|
-
maxReqs:
|
|
82467
|
-
maxPlanItems:
|
|
82448
|
+
maxReqs: 10,
|
|
82449
|
+
maxPlanItems: 16
|
|
82468
82450
|
};
|
|
82469
|
-
LARGE_TASK_THRESHOLDS = DEFAULT_TASK_SIZE_LIMITS;
|
|
82470
82451
|
R_ITEM_RE = /^\s*-\s*\[[ xX]\]\s*(\*\*)?R\d+\./m;
|
|
82471
82452
|
CHECKLIST_ITEM_RE = /^\s*-\s*\[[ xX]\]/m;
|
|
82472
82453
|
});
|
|
@@ -98246,7 +98227,7 @@ import { createRequire } from "module";
|
|
|
98246
98227
|
var CLI_CONFIG = {
|
|
98247
98228
|
binaryName: "spur",
|
|
98248
98229
|
binaryLabel: "spur",
|
|
98249
|
-
binaryVersion: "0.3.
|
|
98230
|
+
binaryVersion: "0.3.69",
|
|
98250
98231
|
configDir: ".spur",
|
|
98251
98232
|
configFile: ".spur/config.yaml",
|
|
98252
98233
|
databaseFile: ".spur/spur.db"
|