@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.
@@ -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-coder`** | Implement with no task / no pipeline |
39
- | Batch or parallel task runs | `/sp:dev-runall`, `/sp:dev-parallel` → **`sp:super-coder`** | Unordered multi-task thrash |
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-coder`;
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-coder`**.
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. `cancelled` is terminal.
9
- Guard commands reference the check verb (0057) structural validation passes
10
- today, behavioral wiring activates as the verb ships. Unconditional transitions
11
- use the `always` guard (externally-driven via `requestTransition`, not
12
- auto-advance).
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
- stepTimeoutMs: "600000"
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 if `feature_id` is present, or appends an orphan link proposal
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 ${vars.spurBin} feature sync "$FID" --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'
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
- `spur feature sync` (task 0328 / ADR-0322).
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.spurBin} feature sync ${vars.feature} --json'
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.27",
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.14",
54
- "@gobing-ai/ts-ai-runner": "^0.4.14",
55
- "@gobing-ai/ts-dual-workflow-engine": "^0.4.14",
56
- "@gobing-ai/ts-infra": "^0.4.14",
57
- "@gobing-ai/ts-llm-jsonl-importer": "^0.4.14",
58
- "@gobing-ai/ts-rule-engine": "^0.4.14",
59
- "@gobing-ai/ts-runtime": "^0.4.14",
60
- "@gobing-ai/ts-utils": "^0.4.14",
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 + base_counter concepts. Mirrors @gobing-ai/spur-config tasksConfigSchema (Zod is SSOT).",
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",