@gobing-ai/spur 0.3.27 → 0.3.29
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/rules/strict/runtime-boundaries.yaml +1 -0
- package/config/rules/typescript/no-syscall-emulation-in-boundary-mock.yaml +57 -0
- package/config/templates/AGENTS.md +9 -6
- package/config/workflows/feature-lifecycle.yaml +9 -7
- package/config/workflows/task-pipeline.yaml +12 -3
- package/config/workflows/wrapup-pipeline.yaml +5 -4
- package/package.json +9 -9
- package/schemas/spur-config.schema.json +1 -1
- package/spur.js +1326 -517
|
@@ -60,6 +60,7 @@ rules:
|
|
|
60
60
|
- "packages/app/src/services/token-ledger-service.ts" # FD byte-window log tailing
|
|
61
61
|
- "packages/app/src/services/token-ledger-watcher.ts" # node:fs watch() live watcher
|
|
62
62
|
- "packages/app/src/services/project-registry.ts" # atomic projects.json persistence
|
|
63
|
+
- "packages/app/src/observability/run-output-sink.ts" # sync FD append for mid-run tail-able artifact (task 0414 R2); observe() is sync from the lifecycle relay
|
|
63
64
|
- "apps/web/src/modules/discover.ts" # Vite/Astro module scanner fallback under bun test
|
|
64
65
|
|
|
65
66
|
# Synchronous bootstrap & path resolution:
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
$schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
|
|
2
|
+
# Forbid mocking the syscall under test where the mock emulates it (task 0415 R4).
|
|
3
|
+
#
|
|
4
|
+
# WHY: `sp:test-driven-development` SKILL.md:164 already states the rule — "Mock
|
|
5
|
+
# what crosses a process/IO boundary; never mock the code under test." This rule
|
|
6
|
+
# enforces the narrow, mechanically detectable case: a test that mocks a
|
|
7
|
+
# subprocess/syscall boundary (Bun.spawnSync / node:child_process) AND whose mock
|
|
8
|
+
# handler emulates the syscall output (stat / mtime / %m) that the code under test
|
|
9
|
+
# consumes. The 0411 defect is the canonical fixture: the verdict-mtime tests
|
|
10
|
+
# mocked `Bun.spawnSync` INCLUDING the `stat -f %m` call, so the BSD-only stat
|
|
11
|
+
# syntax silently returned nothing on Linux while 57 tests stayed green — the mock
|
|
12
|
+
# replaced the very portability the tests were supposed to verify.
|
|
13
|
+
#
|
|
14
|
+
# SCOPE / discriminator: mocking the subprocess boundary to control *subprocess
|
|
15
|
+
# responses* (e.g. feature-sync-bounded.test.ts intercepting `show`/`list`/`sync`
|
|
16
|
+
# commands) is legitimate and NOT flagged — the boundary mock returns canned
|
|
17
|
+
# responses, it does not re-implement the syscall the code under test calls. The
|
|
18
|
+
# rule fires only when the mock handler itself references `stat`/`%m`/`birthtime`
|
|
19
|
+
# (emulating the syscall) within a bounded window after the mock installation.
|
|
20
|
+
#
|
|
21
|
+
# FIX: use real files + real statSync/utimesSync (the 0411 remediation), or mock a
|
|
22
|
+
# higher-level seam the code under test does NOT depend on for the behavior under
|
|
23
|
+
# test. If a legit syscall-emulating mock is unavoidable, waive with a stated
|
|
24
|
+
# reason in the test.
|
|
25
|
+
include:
|
|
26
|
+
- "apps/**/tests/**/*.test.ts"
|
|
27
|
+
- "apps/**/tests/**/*.test.tsx"
|
|
28
|
+
- "packages/**/tests/**/*.test.ts"
|
|
29
|
+
- "packages/**/tests/**/*.test.tsx"
|
|
30
|
+
- "plugins/**/tests/**/*.test.ts"
|
|
31
|
+
- "plugins/**/tests/**/*.test.tsx"
|
|
32
|
+
exclude:
|
|
33
|
+
- "**/node_modules/**"
|
|
34
|
+
- "**/dist/**"
|
|
35
|
+
|
|
36
|
+
rules:
|
|
37
|
+
- id: no-syscall-emulation-in-boundary-mock
|
|
38
|
+
description: >
|
|
39
|
+
A test that mocks a subprocess/syscall boundary (Bun.spawnSync assignment,
|
|
40
|
+
spyOn(Bun,'spawnSync'), or mock.module('node:child_process')) MUST NOT have
|
|
41
|
+
the mock handler emulate the syscall output the code under test consumes
|
|
42
|
+
(stat / `-f %m` / birthtime / mtime-derived fingerprints). That mocks the
|
|
43
|
+
code under test, not the boundary: platform-specific syscall syntax (e.g.
|
|
44
|
+
BSD-only `stat -f %m`) silently no-ops on Linux while the test stays green —
|
|
45
|
+
the 0411 verdict-mtime defect. Prefer real files + real statSync/utimesSync
|
|
46
|
+
(the 0411 remediation) or mock a higher-level seam. Control of *subprocess
|
|
47
|
+
responses* (show/list/sync commands) is fine — only syscall emulation is
|
|
48
|
+
forbidden. sp:test-driven-development SKILL.md:164.
|
|
49
|
+
severity: warning
|
|
50
|
+
evaluator:
|
|
51
|
+
type: rg
|
|
52
|
+
config:
|
|
53
|
+
# Correlation in one bounded window: a boundary-mock installation followed
|
|
54
|
+
# (within ~700 chars) by stat/mtime syscall emulation. The window keeps
|
|
55
|
+
# legit subprocess-response mocks (which never mention stat/%m) quiet.
|
|
56
|
+
multiline: true
|
|
57
|
+
pattern: "(?:Bun\\.spawnSync\\s*=|spyOn\\(\\s*Bun\\s*,\\s*['\"]spawnSync['\"]\\s*\\)|mock\\.module\\(\\s*['\"]node:child_process['\"]\\s*\\))[\\s\\S]{0,700}?(?:cmd\\.includes\\(\\s*['\"]stat|stat\\s+-[fc]|%m|birthtime)"
|
|
@@ -33,10 +33,10 @@ All product development work goes through the harness by default.
|
|
|
33
33
|
### Harness tool routing
|
|
34
34
|
|
|
35
35
|
| Need | Route to | Avoid |
|
|
36
|
-
|
|
36
|
+
| ------ | ---------- | -------- |
|
|
37
37
|
| Plan a feature (intake → AC → tasks) | `/sp:dev-plan`, `/sp:dev-idea` | Freeform feature files without gates |
|
|
38
|
-
| Drive one task end-to-end | `/sp:dev-run <wbs>` or **`sp:super-
|
|
39
|
-
| Batch or parallel task runs | `/sp:dev-runall`, `/sp:dev-parallel` → **`sp:super-
|
|
38
|
+
| Drive one task end-to-end | `/sp:dev-run <wbs>` or **`sp:super-planner`** | Implement with no task / no pipeline |
|
|
39
|
+
| Batch or parallel task runs | `/sp:dev-runall`, `/sp:dev-parallel` → **`sp:super-planner`** | Unordered multi-task thrash |
|
|
40
40
|
| Batch-refine tasks under a feature | `/sp:dev-refineall --feature <id> --auto` | Hand-looping `/sp:dev-refine` per WBS |
|
|
41
41
|
| Multi-step corpus CLI (tasks/features/rules/workflows) | **`sp:expert-spur`** | Raw Write/Edit on corpus files |
|
|
42
42
|
| Look up `spur` verbs / flags / `--json` | Skill **`sp:spur-cli`** | Inventing flags from memory |
|
|
@@ -59,10 +59,13 @@ All product development work goes through the harness by default.
|
|
|
59
59
|
2. **Gates before done** — `spur task check` / `spur feature check` / `spur rule run`; pipeline done
|
|
60
60
|
needs a real verify **PASS**.
|
|
61
61
|
3. **`--json` for machines** — parse CLI with `--json`.
|
|
62
|
-
4. **Route, don’t invent** — verbs → `sp:spur-cli`; lifecycle → `/sp:dev-*` / `sp:super-
|
|
62
|
+
4. **Route, don’t invent** — verbs → `sp:spur-cli`; lifecycle → `/sp:dev-*` / `sp:super-planner`;
|
|
63
63
|
multi-noun corpus → `sp:expert-spur`; review → `sp:super-reviewer`; docs process → `sp:doc-evolve`.
|
|
64
64
|
5. **Keep tool ownership explicit** — project lifecycle/corpus/gates → Spur; plugin installation and
|
|
65
65
|
capability lifecycle → Superskill. Do not hand-maintain per-platform adapters Superskill generates.
|
|
66
|
+
6. **Run dev skills inline by default** — direct model-bearing `/sp:dev-*` commands execute in the
|
|
67
|
+
current coding-agent session. `--subprocess` or a named dispatch-surface trigger uses
|
|
68
|
+
`spur agent run`; direct `spur agent run` and workflow `agent.run` remain subprocess surfaces.
|
|
66
69
|
|
|
67
70
|
**Platform fallback:** Platforms without slash commands and/or subagents still use the harness.
|
|
68
71
|
Install the plugin through Superskill for the target platform, then use skills `sp:spur-dev`,
|
|
@@ -84,7 +87,7 @@ authority first, then derived docs, then this file.
|
|
|
84
87
|
### Doc map
|
|
85
88
|
|
|
86
89
|
| Doc | Owns | Authority | When |
|
|
87
|
-
|
|
90
|
+
| ------ | ------ | ----------- | ------ |
|
|
88
91
|
| `docs/00_ADR.md` | **WHY** | Authoritative (content) | Structural change; dated entry before diverging |
|
|
89
92
|
| `docs/01_PRD.md` | **WHAT** | Authoritative on scope | New feature/command |
|
|
90
93
|
| `docs/02_ROADMAP.md` | **WHEN** | Derived | Phase placement |
|
|
@@ -134,7 +137,7 @@ _(Fill from package manifests / README — or during `sp:doc-evolve` customize.)
|
|
|
134
137
|
## Spur CLI surface
|
|
135
138
|
|
|
136
139
|
**Not the verb catalog.** For `task` / `feature` / `rule` / `workflow` flags, exit codes, and
|
|
137
|
-
`--json` shapes → skill **`sp:spur-cli`**. Lifecycle → `/sp:dev-*` / **`sp:super-
|
|
140
|
+
`--json` shapes → skill **`sp:spur-cli`**. Lifecycle → `/sp:dev-*` / **`sp:super-planner`**.
|
|
138
141
|
Multi-noun corpus campaigns → **`sp:expert-spur`**.
|
|
139
142
|
|
|
140
143
|
```bash
|
|
@@ -5,11 +5,13 @@ description: >
|
|
|
5
5
|
Feature lifecycle FSM (design §2.3, §5.1, DD-13). States are the canonical
|
|
6
6
|
FeatureStatus vocabulary; transitions encode the §2.3 graph including the
|
|
7
7
|
`verifying` status (DD-13). Guards invoke `${vars.spurBin} feature check`
|
|
8
|
-
at the active→verifying and verifying→done placements
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
at the active→verifying and verifying→done placements, passing the edge's
|
|
9
|
+
target via `--as <status>` (0418) so the one-active-goal rule evaluates the
|
|
10
|
+
post-transition state and never denies the exit it would relieve.
|
|
11
|
+
`cancelled` is terminal. Guard commands reference the check verb (0057) —
|
|
12
|
+
structural validation passes today, behavioral wiring activates as the verb
|
|
13
|
+
ships. Unconditional transitions use the `always` guard (externally-driven
|
|
14
|
+
via `requestTransition`, not auto-advance).
|
|
13
15
|
initialState: backlog
|
|
14
16
|
terminalStates:
|
|
15
17
|
- cancelled
|
|
@@ -47,7 +49,7 @@ transitions:
|
|
|
47
49
|
guard:
|
|
48
50
|
kind: shell
|
|
49
51
|
options:
|
|
50
|
-
command: '${vars.spurBin} feature check ${vars.featureId}'
|
|
52
|
+
command: '${vars.spurBin} feature check ${vars.featureId} --as verifying'
|
|
51
53
|
- from: verifying
|
|
52
54
|
to: done
|
|
53
55
|
description: >
|
|
@@ -55,7 +57,7 @@ transitions:
|
|
|
55
57
|
guard:
|
|
56
58
|
kind: shell
|
|
57
59
|
options:
|
|
58
|
-
command: '${vars.spurBin} feature check ${vars.featureId} --strict'
|
|
60
|
+
command: '${vars.spurBin} feature check ${vars.featureId} --strict --as done'
|
|
59
61
|
|
|
60
62
|
# Rework: verifying → active (mandatory History entry)
|
|
61
63
|
- from: verifying
|
|
@@ -49,7 +49,15 @@ vars:
|
|
|
49
49
|
# Step-level timeout for agent.run steps in milliseconds. A timed-out agent
|
|
50
50
|
# subprocess is killed (not abandoned) and the step fails → pipeline routes to
|
|
51
51
|
# `failed`. Override per run with `--vars '{"stepTimeoutMs":120000}'`.
|
|
52
|
-
|
|
52
|
+
# Raised 600s → 1800s (task 0398 R4). The H6 dogfood batch lost 3 of its 4
|
|
53
|
+
# timeouts on `test` steps hitting exactly the 600s wall (0391, 0395 test;
|
|
54
|
+
# `.spur/run/*-test-partial.md`), the same failure signature that justified
|
|
55
|
+
# implementTimeoutMs below. Agentic test/review/verify steps read the diff,
|
|
56
|
+
# run the suite, and write a section — that does not fit in 10 min on this
|
|
57
|
+
# corpus. Same honesty rule as implementTimeoutMs: 30 min is headroom, not a
|
|
58
|
+
# licence to run unbounded. If a step still hits this wall, STOP and record it
|
|
59
|
+
# rather than raising again without operator sign-off.
|
|
60
|
+
stepTimeoutMs: "1800000"
|
|
53
61
|
# Timeout for the `implement` step specifically (R2a). Implementation is the
|
|
54
62
|
# heaviest agent.run step (full read/write/test-probe loop) and has timed out
|
|
55
63
|
# at the 600s default in five consecutive dogfood runs (bugs 742/744/746/748),
|
|
@@ -174,7 +182,8 @@ states:
|
|
|
174
182
|
Record pipeline results into the task file via `spur task record` —
|
|
175
183
|
Testing/Review from the verdict, Solution backfilled from git diff as a
|
|
176
184
|
safety net, optional transition to testing. Post-record step conditionally syncs
|
|
177
|
-
feature status
|
|
185
|
+
feature status via the bounded `feature-sync-bounded` wrapper (task 0411
|
|
186
|
+
retry-suppression) if `feature_id` is present, or appends an orphan link proposal
|
|
178
187
|
to the run report if absent (task 0328 / ADR-0322).
|
|
179
188
|
onEnter:
|
|
180
189
|
- kind: shell
|
|
@@ -182,7 +191,7 @@ states:
|
|
|
182
191
|
command: "${vars.spurBin} task record ${vars.wbs} --solution-from-diff --transition testing"
|
|
183
192
|
- kind: shell
|
|
184
193
|
options:
|
|
185
|
-
command: 'FID=$(${vars.spurBin} task show ${vars.wbs} --json 2>/dev/null | jq -r ".feature_id // .frontmatter.feature_id // empty"); if [ -n "$FID" ]; then
|
|
194
|
+
command: 'FID=$(${vars.spurBin} task show ${vars.wbs} --json 2>/dev/null | jq -r ".feature_id // .frontmatter.feature_id // empty"); if [ -n "$FID" ]; then bun plugins/sp/scripts/feature-sync-bounded.ts "$FID" --spur-bin "${vars.spurBin}" --json; else echo "Orphan task ${vars.wbs} — no feature_id linked; proposal: consider linking to a parent feature." >> .spur/run/${vars.wbs}-report.txt; fi'
|
|
186
195
|
|
|
187
196
|
- id: done
|
|
188
197
|
description: >
|
|
@@ -117,13 +117,14 @@ states:
|
|
|
117
117
|
|
|
118
118
|
- id: feature-transition
|
|
119
119
|
description: >
|
|
120
|
-
If vars.feature is set, derive and sync feature status with linked task states via
|
|
121
|
-
`
|
|
122
|
-
Replaces prior unconditional `spur feature advance` with conservative status sync
|
|
120
|
+
If vars.feature is set, derive and sync feature status with linked task states via the
|
|
121
|
+
bounded `feature-sync-bounded` wrapper (task 0328 / ADR-0322, task 0411 retry-suppression).
|
|
122
|
+
Replaces prior unconditional `spur feature advance` with conservative status sync that
|
|
123
|
+
suppresses identical blocked retries until relevant inputs change.
|
|
123
124
|
onEnter:
|
|
124
125
|
- kind: shell
|
|
125
126
|
options:
|
|
126
|
-
command: '${vars.
|
|
127
|
+
command: 'bun plugins/sp/scripts/feature-sync-bounded.ts ${vars.feature} --spur-bin "${vars.spurBin}" --json'
|
|
127
128
|
|
|
128
129
|
- id: branch-cleanup
|
|
129
130
|
description: >
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.29",
|
|
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",
|
|
@@ -50,14 +50,14 @@
|
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@commander-js/extra-typings": "^14.0.0",
|
|
53
|
-
"@gobing-ai/ts-db": "^0.4.
|
|
54
|
-
"@gobing-ai/ts-ai-runner": "^0.4.
|
|
55
|
-
"@gobing-ai/ts-dual-workflow-engine": "^0.4.
|
|
56
|
-
"@gobing-ai/ts-infra": "^0.4.
|
|
57
|
-
"@gobing-ai/ts-llm-jsonl-importer": "^0.4.
|
|
58
|
-
"@gobing-ai/ts-rule-engine": "^0.4.
|
|
59
|
-
"@gobing-ai/ts-runtime": "^0.4.
|
|
60
|
-
"@gobing-ai/ts-utils": "^0.4.
|
|
53
|
+
"@gobing-ai/ts-db": "^0.4.15",
|
|
54
|
+
"@gobing-ai/ts-ai-runner": "^0.4.15",
|
|
55
|
+
"@gobing-ai/ts-dual-workflow-engine": "^0.4.15",
|
|
56
|
+
"@gobing-ai/ts-infra": "^0.4.15",
|
|
57
|
+
"@gobing-ai/ts-llm-jsonl-importer": "^0.4.15",
|
|
58
|
+
"@gobing-ai/ts-rule-engine": "^0.4.15",
|
|
59
|
+
"@gobing-ai/ts-runtime": "^0.4.15",
|
|
60
|
+
"@gobing-ai/ts-utils": "^0.4.15",
|
|
61
61
|
"@types/bun": "1.3.14",
|
|
62
62
|
"@types/figlet": "^1.7.0",
|
|
63
63
|
"@types/node-notifier": "8.0.5",
|
|
@@ -239,7 +239,7 @@
|
|
|
239
239
|
},
|
|
240
240
|
"tasks": {
|
|
241
241
|
"type": "object",
|
|
242
|
-
"description": "Task-folder registration (design §9). Absorbs the legacy docs/.tasks/config.json folders +
|
|
242
|
+
"description": "Task-folder registration (design §9). Absorbs the legacy docs/.tasks/config.json folders + baseCounter concepts. Mirrors @gobing-ai/spur-config tasksConfigSchema (Zod is SSOT).",
|
|
243
243
|
"properties": {
|
|
244
244
|
"folders": {
|
|
245
245
|
"type": "object",
|