@gobing-ai/spur 0.3.28 → 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)"
@@ -63,6 +63,9 @@ All product development work goes through the harness by default.
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`,
@@ -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
@@ -182,7 +182,8 @@ states:
182
182
  Record pipeline results into the task file via `spur task record` —
183
183
  Testing/Review from the verdict, Solution backfilled from git diff as a
184
184
  safety net, optional transition to testing. Post-record step conditionally syncs
185
- 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
186
187
  to the run report if absent (task 0328 / ADR-0322).
187
188
  onEnter:
188
189
  - kind: shell
@@ -190,7 +191,7 @@ states:
190
191
  command: "${vars.spurBin} task record ${vars.wbs} --solution-from-diff --transition testing"
191
192
  - kind: shell
192
193
  options:
193
- 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'
194
195
 
195
196
  - id: done
196
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.28",
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",
@@ -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",