@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.
@@ -7,7 +7,7 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "sp",
10
- "version": "0.3.68",
10
+ "version": "0.3.69",
11
11
  "source": "./plugins/sp"
12
12
  }
13
13
  ]
@@ -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 --json 2>/dev/null ||\n $spurBin feature update \"$FID\" active 2>/dev/null || true;\n fi;\nfi; exit 0",
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 skipped — plugins/sp/scripts/task-size-precheck.ts not present in project.\" >&2 &&\n echo \"PASS\" > \"$SIZE_FILE\";\nfi && exit 0",
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: "omp"
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: "omp"
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
@@ -44,7 +44,7 @@ failureStates:
44
44
  - failed
45
45
  vars:
46
46
  featureId: ""
47
- agent: "omp"
47
+ agent: "auto"
48
48
  profile: "standard"
49
49
  spurBin: "spur"
50
50
  stepTimeoutMs: "1800000"
@@ -65,7 +65,7 @@ vars:
65
65
  spurBin: "spur"
66
66
  # R8 (0366): injected by WorkflowAppService.run(); stamps discovery artifact provenance.
67
67
  __runId: ""
68
- agent: "omp"
68
+ agent: "auto"
69
69
  planningAgent: ""
70
70
  stepTimeoutMs: "1800000"
71
71
 
@@ -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"}'`. Pinned (not left to the AiRunner's <default>
58
- # selection) so a broken/misconfigured agent on the box can't silently capture the run.
59
- agent: "omp"
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
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: "omp"
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 --json 2>/dev/null ||
185
- $spurBin feature update "$FID" active 2>/dev/null || true;
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 — evaluate R-item and Plan-item counts.
190
- # Writes PASS/FAIL to .spur/run/<wbs>-precheck-size.status. Always exit 0
191
- # (soft check, like doctor). The precheck→implement guard reads the file.
192
- # Temporary config-only bypass (0723): keep deterministic size limits but defer
193
- # the executor-tier policy to the dedicated task-pipeline upgrade. This avoids
194
- # the script's second `spur agent doctor` call while the precheck path is repaired.
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" --max-plan-items "$maxImplementPlanItems";
213
+ --spur-bin "$spurBin" --max-reqs "$maxImplementReqs"
214
+ --max-plan-items "$maxImplementPlanItems";
203
215
  else
204
- echo "task-size-precheck skippedplugins/sp/scripts/task-size-precheck.ts not present in project." >&2 &&
205
- echo "PASS" > "$SIZE_FILE";
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
 
@@ -27,7 +27,7 @@ vars:
27
27
  wbs: "0000"
28
28
  profile: "standard"
29
29
  spurBin: "spur"
30
- agent: "omp"
30
+ agent: "auto"
31
31
  stepTimeoutMs: "1800000"
32
32
  approval: "required"
33
33
  resolutionMode: "research"
@@ -49,7 +49,7 @@ vars:
49
49
  profile: "standard"
50
50
  merge: "false"
51
51
  spurBin: "spur"
52
- agent: "omp"
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.68",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sp",
3
- "version": "0.3.68",
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
- if [ "$1" = task ] && [ "$2" = show ]; then
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, '--executor', 'standard'],
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) plus the
4
- * size-vs-executor-capability gate (R3, task 0487).
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. With
8
- * `--executor`, also shells `spur agent doctor <exec> --json` and blocks a large
9
- * task routed to a sub-`capable-1` executor.
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 check, like doctor). The precheck→implement guard in
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. The capability tier therefore comes from the CLI rather
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>] [--executor <name>]
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>] [--executor <name>]',
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
- let maxReqs = Number(process.env.MAX_IMPLEMENT_REQS) || 5;
95
- let maxPlanItems = Number(process.env.MAX_IMPLEMENT_PLAN_ITEMS) || 8;
96
- let executor = '';
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]) || 5;
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]) || 8;
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, executor };
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, executor } = parseArgs(process.argv.slice(2));
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 (soft, like doctor)
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 (e.g. `agent: "omp"` in `task-pipeline.yaml`). This fires only when no
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, "history", "daily", "--json", "--json-envelope"],
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 === "" ? "" : `: ${result.stderr.length > 400 ? `\u2026${result.stderr.slice(-400)}` : 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
- let stdoutDetail = "";
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, executor) {
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, LARGE_TASK_THRESHOLDS, R_ITEM_RE, CHECKLIST_ITEM_RE;
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: 5,
82467
- maxPlanItems: 8
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.68",
98230
+ binaryVersion: "0.3.69",
98250
98231
  configDir: ".spur",
98251
98232
  configFile: ".spur/config.yaml",
98252
98233
  databaseFile: ".spur/spur.db"