@gobing-ai/spur 0.4.20 → 0.4.22

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.
Files changed (75) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/workflows/task-pipeline.yaml +28 -10
  3. package/package.json +11 -11
  4. package/plugins/sp/plugin.json +1 -1
  5. package/plugins/sp/skills/conflict-finding/SKILL.md +2 -0
  6. package/plugins/sp/skills/fleet-join/SKILL.md +70 -4
  7. package/plugins/sp/skills/issue-finding/SKILL.md +2 -0
  8. package/plugins/sp/skills/reverse-engineering/SKILL.md +2 -0
  9. package/plugins/sp/skills/spur-cli/references/agent.md +9 -1
  10. package/plugins/sp/skills/spur-cli/references/self.md +37 -2
  11. package/plugins/sp/skills/wayfinder/SKILL.md +2 -0
  12. package/spur.js +53344 -39432
  13. package/web/_astro/{BoardApp.BDo7Cplz.js → BoardApp.CNBpKuB6.js} +155 -157
  14. package/web/_astro/{BoardApp.uR1DxOv6.js → BoardApp.DVWLxHcV.js} +1 -1
  15. package/web/_astro/{TaskDetail.BiGHeRcp.js → TaskDetail.BY6jO_3P.js} +1 -1
  16. package/web/_astro/arc.CLFbvaU4.js +1 -0
  17. package/web/_astro/{architectureDiagram-3BPJPVTR.C2BEgSVb.js → architectureDiagram-3BPJPVTR.YYrMKtrm.js} +1 -1
  18. package/web/_astro/{blockDiagram-GPEHLZMM.D8G9DLH3.js → blockDiagram-GPEHLZMM.D-Xzu32A.js} +6 -6
  19. package/web/_astro/{board-facade-react-router-dom.Dy5pjzOw.js → board-facade-react-router-dom.CHNMUrya.js} +1 -1
  20. package/web/_astro/{board-facade-react-router.DjjqU27_.js → board-facade-react-router.Dob6MDgf.js} +1 -1
  21. package/web/_astro/{c4Diagram-AAUBKEIU.dU7HR9k5.js → c4Diagram-AAUBKEIU.Cv7EV8QD.js} +1 -1
  22. package/web/_astro/channel.DzHZuqj0.js +1 -0
  23. package/web/_astro/{chunk-2J33WTMH.5BIk-HVp.js → chunk-2J33WTMH.CkAKN-fF.js} +1 -1
  24. package/web/_astro/{chunk-4BX2VUAB.BVIKX24s.js → chunk-4BX2VUAB.D-xu2Tgi.js} +1 -1
  25. package/web/_astro/{chunk-55IACEB6.Bz2tdkGM.js → chunk-55IACEB6.yNQibVGY.js} +1 -1
  26. package/web/_astro/{chunk-727SXJPM.R0d_FlTN.js → chunk-727SXJPM.DlVIFHHZ.js} +4 -4
  27. package/web/_astro/{chunk-AQP2D5EJ.C2kK8Ok-.js → chunk-AQP2D5EJ.C-LGFaeS.js} +1 -1
  28. package/web/_astro/{chunk-FMBD7UC4.COAncjFn.js → chunk-FMBD7UC4.Df2n26zT.js} +1 -1
  29. package/web/_astro/{chunk-ND2GUHAM.CeNg5VSN.js → chunk-ND2GUHAM.CV_k83Gj.js} +1 -1
  30. package/web/_astro/{chunk-QZHKN3VN.DRGDDzrj.js → chunk-QZHKN3VN.BQQDO3h3.js} +1 -1
  31. package/web/_astro/chunk-YNUBSHFH.B6ndyWdA.js +7 -0
  32. package/web/_astro/{classDiagram-4FO5ZUOK.DmM59Lgb.js → classDiagram-4FO5ZUOK.DkSMdA84.js} +1 -1
  33. package/web/_astro/{classDiagram-v2-Q7XG4LA2.DmM59Lgb.js → classDiagram-v2-Q7XG4LA2.DkSMdA84.js} +1 -1
  34. package/web/_astro/{cose-bilkent-S5V4N54A.BjKuSb1i.js → cose-bilkent-S5V4N54A.CCN7GwAZ.js} +1 -1
  35. package/web/_astro/{cynefin-OW5HDTMX.DQevIJEc.js → cynefin-OW5HDTMX.CqQCttkM.js} +1 -1
  36. package/web/_astro/cytoscape.esm.BDkkwuE4.js +321 -0
  37. package/web/_astro/{dagre-BM42HDAG.CqOK1C5k.js → dagre-BM42HDAG.CCn6iftc.js} +2 -2
  38. package/web/_astro/diagram-2AECGRRQ.DImUVZK-.js +43 -0
  39. package/web/_astro/diagram-5GNKFQAL.BYNywZwV.js +10 -0
  40. package/web/_astro/{diagram-KO2AKTUF.BDC9f2pC.js → diagram-KO2AKTUF.LJZ9DjsJ.js} +3 -3
  41. package/web/_astro/{diagram-LMA3HP47.D-Mrwnmt.js → diagram-LMA3HP47.CJ3YpVED.js} +1 -1
  42. package/web/_astro/{diagram-OG6HWLK6.XXGX1BQ_.js → diagram-OG6HWLK6.CnVtq5_j.js} +1 -1
  43. package/web/_astro/{erDiagram-TEJ5UH35.DahWkk7M.js → erDiagram-TEJ5UH35.SdmuzZwT.js} +5 -5
  44. package/web/_astro/{flowDiagram-I6XJVG4X.DaG5HfPs.js → flowDiagram-I6XJVG4X.C9V-B-nO.js} +4 -4
  45. package/web/_astro/{ganttDiagram-6RSMTGT7.DZe_CQWm.js → ganttDiagram-6RSMTGT7.DT5v7SL5.js} +1 -1
  46. package/web/_astro/{gitGraphDiagram-PVQCEYII.C4zSw3kS.js → gitGraphDiagram-PVQCEYII.B3qURYBL.js} +1 -1
  47. package/web/_astro/index.C8LjH5ri.css +1 -0
  48. package/web/_astro/{infoDiagram-5YYISTIA.CmaEULgT.js → infoDiagram-5YYISTIA.CHO0xT8l.js} +1 -1
  49. package/web/_astro/{ishikawaDiagram-YF4QCWOH.DKKhZJ98.js → ishikawaDiagram-YF4QCWOH.buALAOGh.js} +5 -5
  50. package/web/_astro/{journeyDiagram-JHISSGLW.B6d7TuYQ.js → journeyDiagram-JHISSGLW.DvKuIdbX.js} +1 -1
  51. package/web/_astro/{kanban-definition-UN3LZRKU.xiG_jH03.js → kanban-definition-UN3LZRKU.Dvoi9jdN.js} +7 -7
  52. package/web/_astro/{linear.DsbUmqlh.js → linear.EsEfz8Ex.js} +1 -1
  53. package/web/_astro/mermaid.core.Vk3ryVC7.js +303 -0
  54. package/web/_astro/{mindmap-definition-RKZ34NQL.Cm2UvqJQ.js → mindmap-definition-RKZ34NQL.DG1pkymq.js} +2 -2
  55. package/web/_astro/{pieDiagram-4H26LBE5.COyvnb_Q.js → pieDiagram-4H26LBE5.DAUmIdd6.js} +3 -3
  56. package/web/_astro/{quadrantDiagram-W4KKPZXB.BdaJr8EI.js → quadrantDiagram-W4KKPZXB.Du3QUozl.js} +1 -1
  57. package/web/_astro/{requirementDiagram-4Y6WPE33.aS-qeofA.js → requirementDiagram-4Y6WPE33.neRi5GrQ.js} +3 -3
  58. package/web/_astro/{sankeyDiagram-5OEKKPKP.BxaOWF5N.js → sankeyDiagram-5OEKKPKP.CMUtxA70.js} +4 -4
  59. package/web/_astro/{sequenceDiagram-3UESZ5HK.CfZPPWA1.js → sequenceDiagram-3UESZ5HK.CfYFLmYH.js} +4 -4
  60. package/web/_astro/{stateDiagram-AJRCARHV.D6VXy0-M.js → stateDiagram-AJRCARHV.Bn4dmAad.js} +1 -1
  61. package/web/_astro/{stateDiagram-v2-BHNVJYJU.DatbDxXn.js → stateDiagram-v2-BHNVJYJU.BMNezvp-.js} +1 -1
  62. package/web/_astro/{timeline-definition-PNZ67QCA.BxQ7-VRY.js → timeline-definition-PNZ67QCA.ByaDwsBX.js} +3 -3
  63. package/web/_astro/{vennDiagram-CIIHVFJN.BJny6MAy.js → vennDiagram-CIIHVFJN.CUYU1Mc9.js} +5 -5
  64. package/web/_astro/{wardleyDiagram-YWT4CUSO.gzTiYN-4.js → wardleyDiagram-YWT4CUSO.DpZZeJr2.js} +3 -3
  65. package/web/_astro/{xychartDiagram-2RQKCTM6.DTR_zwDh.js → xychartDiagram-2RQKCTM6.CWwI33U0.js} +1 -1
  66. package/web/board-runtime.json +6 -2
  67. package/web/index.html +2 -2
  68. package/web/_astro/arc.BLw5ykEN.js +0 -1
  69. package/web/_astro/channel.DSbvfOEf.js +0 -1
  70. package/web/_astro/chunk-YNUBSHFH.CxeAygfi.js +0 -7
  71. package/web/_astro/cytoscape.esm.BB4DxJjf.js +0 -321
  72. package/web/_astro/diagram-2AECGRRQ.B7i8Xbcc.js +0 -43
  73. package/web/_astro/diagram-5GNKFQAL.DXGFk2rU.js +0 -10
  74. package/web/_astro/index.DVDW9-eD.css +0 -1
  75. package/web/_astro/mermaid.core.CihlV6Wy.js +0 -301
@@ -7,7 +7,7 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "sp",
10
- "version": "0.4.20",
10
+ "version": "0.4.22",
11
11
  "source": "./plugins/sp"
12
12
  }
13
13
  ]
@@ -660,19 +660,27 @@ states:
660
660
  # feature credit) is owned by `plugins/sp/agents/super-reviewer.md` Output Format,
661
661
  # which this pointer names.
662
662
  input: >-
663
- /sp:dev-review --tasks ${vars.wbs} --auto --focus ${vars.reviewFocus} Answer contract (full: super-reviewer Output
664
- Format). Two artifacts: the `## Review` section (deriveReviewConfidence) and your captured answer
665
- (`spur task verdict` input). The answer has one `Confidence: HIGH|MEDIUM|LOW` line, EVIDENCE-derived
666
- (deriveVerifyConfidence), never the section's disposition level. End with one `Verdict: PASS|PARTIAL|FAIL` line after `| Req | Status | Evidence |` (one row per requirement; MET | PARTIAL | UNMET) and `| AC | Status |
667
- Evidence Type | Evidence |` (one row per AC; MET | PARTIAL | UNMET | N/A). Evidence types (`+` ok): test
668
- | command | static-ref | manual-review | llm-judge | n/a. Evidence cells cite a repo-root-relative `path:line`
669
- (never `/tmp`: out-of-repo blocks done) or a named check. AC rows
670
- key on the task's identities or a declared `AC-N` alias; a linked scenario covers its task and a task-local criterion
671
- needs no feature credit. Open P1-P3 blocks completion.
663
+ /sp:dev-review --tasks ${vars.wbs} --auto --focus ${vars.reviewFocus}
664
+ Answer contract: super-reviewer Output Format. Emit the machine-linted artifact, not a prose summary.
665
+ `## Review`: deriveReviewConfidence. Captured answer (`spur task verdict`) has one `Confidence: HIGH|MEDIUM|LOW`
666
+ line (deriveVerifyConfidence), never the section's disposition level.
667
+ End with one `Verdict: PASS|PARTIAL|FAIL` line (plain, final) after
668
+ `| Req | Status | Evidence |` (one row per requirement; MET | PARTIAL | UNMET) and
669
+ `| AC | Status | Evidence Type | Evidence |` (one row per AC; MET | PARTIAL | UNMET | N/A).
670
+ Evidence types (`+` ok): test | command | static-ref | manual-review | llm-judge | n/a.
671
+ Cite repo-root-relative `path:line` (never `/tmp`) or named checks. AC rows use the task's identities or
672
+ a declared `AC-N` alias; a linked scenario covers its task; a task-local criterion needs no feature credit.
673
+ Open P1-P3 blocks completion.
674
+ Also write it to `.spur/run/${vars.__runId}-review-cert.md`.
672
675
  # The captured review answer is the run's certification input (and the
673
676
  # `task verdict` input below): expectFile fails the step when no answer landed.
674
677
  answerFile: .spur/run/${vars.__runId}-review-answer.txt
675
678
  expectFile: .spur/run/${vars.__runId}-review-answer.txt
679
+ # The reviewer WRITES this one itself (see (c2)); declared so the composition lint can
680
+ # see the run path the prompt names (1233 R2).
681
+ inputs:
682
+ - path: .spur/run/${vars.__runId}-review-cert.md
683
+ kind: artifact
676
684
  # Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
677
685
  role: reviewer
678
686
  # B7 R3/R6 (0894): declared fresh policy (reviewer default) — the action
@@ -683,6 +691,16 @@ states:
683
691
  priority: ${vars.taskPriority}
684
692
  compareExecutorWith: implement
685
693
  timeoutMs: ${vars.stepTimeoutMs}
694
+ # (c2) The reviewer is asked to WRITE its canonical artifact to `-review-cert.md`, because
695
+ # the captured answer above is the agent's stdout: a reviewer that writes the artifact and
696
+ # then narrates its summary has the artifact overwritten by the narration, and the verdict
697
+ # lint fails work that is actually complete (four such runs in the G74 batch, 2026-10-24).
698
+ # The agent-written cert wins; when it is absent or empty the captured answer is used, so
699
+ # this is a strict widening of accepted inputs, never a new failure mode.
700
+ - kind: shell
701
+ options:
702
+ command: >-
703
+ A=".spur/run/$__runId-review-answer.txt"; C=".spur/run/$__runId-review-cert.md"; [ -s "$C" ] || cp "$A" "$C" 2>/dev/null; exit 0
686
704
  # (d) `spur task verdict` owns the answer lint and the verdict derivation (task 1003
687
705
  # R2): one complete requirement row per declared requirement, AC identities resolved,
688
706
  # status/evidence vocabularies accepted, hedged or uncited evidence rejected. It
@@ -694,7 +712,7 @@ states:
694
712
  options:
695
713
  id: review-verdict
696
714
  executable: "${vars.spurBin}"
697
- args: ["task", "verdict", "${vars.wbs}", "--from-answer", ".spur/run/${vars.__runId}-review-answer.txt"]
715
+ args: ["task", "verdict", "${vars.wbs}", "--from-answer", ".spur/run/${vars.__runId}-review-cert.md"]
698
716
  resultFile: .spur/run/${vars.__runId}-review-verdict.status
699
717
  softFail: true
700
718
  # (e) one fold, one token: PASS requires the typed verdict PASS, the artifact's own
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.4.20",
3
+ "version": "0.4.22",
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",
@@ -57,19 +57,19 @@
57
57
  "prepublishOnly": "cd ../.. && bun run apps/cli/src/index.ts builder bump-ver --check spur --no-logo"
58
58
  },
59
59
  "devDependencies": {
60
- "@commander-js/extra-typings": "^14.0.0",
61
- "@gobing-ai/ts-db": "^0.5.33",
62
- "@gobing-ai/ts-ai-runner": "^0.5.33",
63
- "@gobing-ai/ts-dual-workflow-engine": "^0.5.33",
64
- "@gobing-ai/ts-infra": "^0.5.33",
65
- "@gobing-ai/ts-llm-jsonl-importer": "^0.5.33",
66
- "@gobing-ai/ts-rule-engine": "^0.5.33",
67
- "@gobing-ai/ts-runtime": "^0.5.33",
68
- "@gobing-ai/ts-utils": "^0.5.33",
60
+ "@commander-js/extra-typings": "14.0.0",
61
+ "@gobing-ai/ts-db": "0.7.1",
62
+ "@gobing-ai/ts-ai-runner": "0.7.1",
63
+ "@gobing-ai/ts-dual-workflow-engine": "0.7.1",
64
+ "@gobing-ai/ts-infra": "0.7.1",
65
+ "@gobing-ai/ts-llm-jsonl-importer": "0.7.1",
66
+ "@gobing-ai/ts-rule-engine": "0.7.1",
67
+ "@gobing-ai/ts-runtime": "0.7.1",
68
+ "@gobing-ai/ts-utils": "0.7.1",
69
69
  "@types/bun": "1.4.3",
70
70
  "@types/figlet": "^1.7.0",
71
71
  "@types/node-notifier": "8.0.5",
72
- "commander": "^14.0.3",
72
+ "commander": "14.0.3",
73
73
  "figlet": "^1.11.0",
74
74
  "zod": "4.4.3"
75
75
  },
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "sp",
3
- "version": "0.4.20",
3
+ "version": "0.4.22",
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
  }
@@ -234,6 +234,8 @@ findings, unresolved, coverage, cost, remediation, errors`.
234
234
  | `Bash` | `git`, `rg`, `spur task/feature ... --json`, `spur ... check`, `sp:doc-evolve` audit |
235
235
  | `Write` | Only for temp files / owner-routed repair payloads after confirmation (with `--resolve`) |
236
236
 
237
+ **Search routing** (hybrid search, ADR-147): concept, cross-file, or "how does X work" questions → semantic first — the zg MCP tool `zvec_grep_search` when the host exposes it, else `spur self search "<query>" --mode fuzzy --json`. Literal, symbol, regex, or exhaustive occurrence lists → `rg`. If zg is absent or the search response carries a `skipped` reason → fall back to `rg`.
238
+
237
239
  ## Platform Notes
238
240
 
239
241
  ### Claude Code
@@ -26,7 +26,44 @@ Pick the role whose work this session can actually do. `--session-id` defaults t
26
26
  `CLAUDE_CODE_SESSION_ID`, which is what the Stop hook matches against — without it the hook cannot
27
27
  find this session, though the `wait --inbox` loop still works.
28
28
 
29
- ## Pull loop
29
+ **Renewal is automatic (G75 R1, task 1279).** `join` records the *agent* process that launched this
30
+ session — never the short-lived CLI pid — and starts one detached renewer that heartbeats the lease
31
+ every third of the TTL. Long work with no `wait --inbox` in between no longer lets the lease expire.
32
+ The record says which happened: `identity: 'verified'` with a `pid`/`processStartedAt` means renewal
33
+ is running; `identity: 'unverified'` with `pid: null` means no agent ancestor could be proved, so
34
+ nothing renews and the session must keep looping `wait --inbox` to stay joined. A rejoin from the
35
+ same session reuses the running renewer instead of starting a second one.
36
+
37
+ ## Binding a declared member (opt-in, fenced)
38
+
39
+ ```bash
40
+ spur agent join --role coder --member <declared-member-id> --json
41
+ ```
42
+
43
+ `--member` binds this session to one **declared** member instead of joining as a plain guest: the
44
+ record id becomes the member id, so `wait --inbox <member-id>` addresses the declared member's inbox,
45
+ and a supervised spawn or fleet dispatch sees the member busy through its own occupant row. It exits 2
46
+ with a named reason for an unknown/disabled member, a role mismatch, an executor that cannot attest
47
+ the stage capabilities, a live occupant, or a concurrent binder that lost the race. Guests without
48
+ `--member` are unchanged — and still excluded from role selection.
49
+
50
+ ### Attached lifecycle reports carry the binding pin (task 1282)
51
+
52
+ A bound occupant's lifecycle reports must present the host binding pin or they are refused (exit 2):
53
+
54
+ ```bash
55
+ spur agent report --state working --seq <ns> --pin <ownerEpoch>:<occupancyGeneration> --json
56
+ ```
57
+
58
+ The first pin-matched report completes the handshake (the host's strategy then treats this member as
59
+ stage-visible); later reports never move it. A pin mismatch means the occupant generation moved —
60
+ re-read `spur agent list --specs --json` instead of retrying. Plain guests keep reporting without a pin.
61
+
62
+ ## Pull loop (the default path — hooks are acceleration only)
63
+
64
+ The durable inbox loop below is what makes a guest work on **every** host, hooks installed or not.
65
+ Never substitute it with repeated model turns or terminal polling while the session is busy: each
66
+ `wait --inbox` call is one bounded pull, and the session does the work between pulls.
30
67
 
31
68
  `spur agent wait --inbox <id> --timeout 540000 --json` returns **0** with up to five claimed messages
32
69
  in `{ id, pending, messages }`, settling their delivery after the output handoff. It returns **1**
@@ -39,7 +76,13 @@ the work is done.
39
76
  2. Read the returned messages, including their IDs. `spur message show <id> --json` can reread one;
40
77
  `spur message inbox --agent <id> --json` is a non-consuming historical view.
41
78
  3. Do the work the message asks for.
42
- 4. `spur message reply <msgId> "<result>"` → reply to each one.
79
+ 4. `spur message reply <msgId> "<result>"` → reply to each one. A bound occupant answering a **stage
80
+ request** (a `fleet:task:<requestId>:<attempt>` keyed message) MUST make the reply body the JSON
81
+ correlation payload — `{ "requestId": …, "attempt": …, "ownerEpoch": …, "occupancyGeneration": …,
82
+ "artifact": "<path or summary>" }` — with the values from the request and its own binding. The
83
+ host certifies the turn on that payload alone; a plain-text reply never completes a stage and the
84
+ request is retried to someone else. Do not answer stage requests for a binding you no longer hold:
85
+ a stale epoch/generation in the payload is fenced off, not promoted.
43
86
  5. Re-arm: go back to step 1 while the operator wants this session in the fleet.
44
87
 
45
88
  ## Leave
@@ -49,8 +92,29 @@ spur agent leave --json # the guest joined by this session
49
92
  spur agent leave my-session --json # an explicit id
50
93
  ```
51
94
 
52
- Leaving releases the lease immediately. If the session dies without leaving, the lease expires and
53
- the reconciler pass retires the guest — pending work is never lost, it returns to `queued`.
95
+ Leaving releases the lease immediately. A **bound** occupant's leave drains first: it stops new
96
+ assignment, waits up to the drain grace (default 30s, `--drain-timeout <ms>` to bound it) for the
97
+ in-flight turn to settle, then retires the record. A turn that does not settle in time is never
98
+ failed by the leave — it stays outcome-unknown and resumable; nothing is killed. If the session dies
99
+ without leaving, the lease expires and the reconciler pass retires the guest — pending work is never
100
+ lost, it returns to `queued`.
101
+
102
+ ## Host capabilities (probed, not assumed)
103
+
104
+ `bun run fleet-e2e --host-probe` writes `docs/reports/fleet-host-capabilities.json`: one row per
105
+ host×mode with the installed binary path/version, the wake channel and the exact `--help` line that
106
+ proves it. Anything unproven is `pull-only` with a reason.
107
+
108
+ | Host | Interactive wake | Headless |
109
+ | --- | --- | --- |
110
+ | Codex | `app-server` (a reachable App Server connection) | `pull-only` |
111
+ | Claude Code | `hook` (the Stop callback below) | `pull-only` |
112
+ | Pi | `rpc` | `pull-only` |
113
+
114
+ These values are **installed-host evidence**, so re-read the report instead of trusting this table
115
+ after an upgrade. A `pull-only` row is not a defect: the loop above is the portable contract, and the
116
+ hook/RPC/App Server channels only shorten the wait. `--real-hosts --agents <csv>` is the opt-in lane
117
+ that drives a real host end to end; the default lane never launches one.
54
118
 
55
119
  ## Stop-hook delivery (opportunistic)
56
120
 
@@ -66,3 +130,5 @@ so the pull loop above — not the hook — is what makes a guest work anywhere.
66
130
  - Guests are **not** supervised: do not expect restart, pid ownership, or `--drain` injection.
67
131
  - Never dispatch a stage to a guest id: `spur agent run --spec <guest-id>` is refused on purpose.
68
132
  - Never hold the write slot through a guest: the guest lease is the only heartbeat (R5).
133
+ - Renewal stops itself on agent-process death, a reused pid, a lost fence or `leave`. There is no
134
+ command to stop it and no daemon behind it: it lives no longer than the agent that joined.
@@ -367,6 +367,8 @@ With `--create-task`, include `"task": { "wbs": "…", "file": "…", "status":
367
367
  | `Bash` | `spur history` + `spur task` CLI |
368
368
  | `Write` | Temp files for section bodies |
369
369
 
370
+ **Search routing** (hybrid search, ADR-147): concept, cross-file, or "how does X work" questions → semantic first — the zg MCP tool `zvec_grep_search` when the host exposes it, else `spur self search "<query>" --mode fuzzy --json`. Literal, symbol, regex, or exhaustive occurrence lists → `rg`. If zg is absent or the search response carries a `skipped` reason → fall back to `rg`.
371
+
370
372
  ## Platform Notes
371
373
 
372
374
  - **Claude Code** — invoke via `/sp:dev-find-issue …` or `Skill(skill="sp:issue-finding",
@@ -141,6 +141,8 @@ Execute phases in order. Stop at the phase boundary required by the selected mod
141
141
  3. Exclude or de-prioritize generated, vendored, build output, dependency cache, lockfile-only, binary-heavy, and snapshot-heavy paths unless they are directly relevant.
142
142
  4. For monorepos, identify packages/apps and either ask for scope or analyze each package at the requested depth if feasible.
143
143
 
144
+ **Search routing** (hybrid search, ADR-147): concept, cross-file, or "how does X work" questions → semantic first — the zg MCP tool `zvec_grep_search` when the host exposes it, else `spur self search "<query>" --mode fuzzy --json`. Literal, symbol, regex, or exhaustive occurrence lists → `rg`. If zg is absent or the search response carries a `skipped` reason → fall back to `rg`.
145
+
144
146
  ### Phase 3: Classify
145
147
 
146
148
  Classify the codebase as one or more archetypes:
@@ -26,7 +26,7 @@ that before using `run` for fan-out dispatch.
26
26
  | `trace <runId>` | Print one execution record's lineage — the run, its dispatch parents, agent session ids and streams (1076 R4, ADR-132) | `--follow` `--timeout <ms>` `--json` |
27
27
  | `list` | List detected coding agents, or agent specs with `--specs` (live run status + member session merged from `spur self serve`) | `--specs` `--server <url>` `--json` |
28
28
  | `report` | Report a fleet member lifecycle state (`working`/`idle`/`blocked`) from a host hook (G73 R1/R2, task 1080) | `--state <state>` `--seq <ns>` `--spec <id>` `--json` |
29
- | `join` | Join the fleet as a guest occupant — a live session that pulls its own work (G73 R2, task 1081) | `--role <name>` `--id <id>` `--session-id <sid>` `--pid <n>` `--executor <name>` `--json` |
29
+ | `join` | Join the fleet as a guest occupant — a live session that pulls its own work (G73 R2, task 1081); `--member` binds a declared member instead (G75 R3, task 1281) | `--role <name>` `--id <id>` `--member <id>` `--session-id <sid>` `--pid <n>` `--executor <name>` `--json` |
30
30
  | `leave` | Leave the fleet: release the guest lease, retire the occupant, return claimed messages to `queued` | `[id]` `--session-id <sid>` `--json` |
31
31
  | `status` | Agent specs with live process status and member session (requires `spur self serve`) | `--server <url>` `--json` |
32
32
  | `doctor [agent]` | Check agent readiness | `--json` `--probe-health` `--force-refresh` |
@@ -228,11 +228,19 @@ returns its claimed messages to `queued` and removes the record.
228
228
  | `--session-id <sid>` | Host session id (defaults to `CLAUDE_CODE_SESSION_ID`) |
229
229
  | `--pid <n>` | Process id to record |
230
230
  | `--executor <name>` | Executor name to record (informational — a guest is never dispatched to) |
231
+ | `--member <id>` | Bind a **declared member** instead of joining as a guest (G75 R3, task 1281): resolves the enabled member, checks role/executor, requires a capability proof, and takes the fenced occupant of `guest:<member-id>`. The record id becomes the member id, so `wait --inbox <member-id>` addresses the declared member's inbox. Exits 2 with `unknown-member`, `disabled-member`, `role-mismatch`, `capability-unattested`, `live-occupant` or `collision`. Without it, `join` is unchanged |
231
232
  | `--json` | Machine-readable result |
232
233
 
233
234
  A guest carrying no executor attestation is never a stage target: `spur agent run --spec <guest-id>`
234
235
  is refused (R3), and `requiresCapabilities` stages therefore can never reach one.
235
236
 
237
+ `--member <id>` inverts that for exactly one declared id: the binding takes the member's occupant row
238
+ at generation `max+1` and the `guest:<member-id>` claim, so a supervised spawn or strategy dispatch
239
+ sees the member busy through the same `hasRunning` check it already uses — no dual writers. Every
240
+ renew re-checks both the claim epoch and that generation, so a taken-over member refuses the stale
241
+ binder's claim/accept. `memberBinding.stageEligible` is `false` when the binder's host has only the
242
+ `pull-only` wake channel (`bun run fleet-e2e --host-probe`), i.e. it may receive messages but no stage.
243
+
236
244
  ## `wait --inbox <id>` - the guest pull primitive (G73 R4)
237
245
 
238
246
  ```bash
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: spur-cli-self
3
- description: "spur-cli noun reference for `spur self`: self-management verbs — SDLC action runner (`exec`), scaffold (`init`), database maintenance (`maintain`), schema migrations (`migrate`), local web server (`serve`), and status overview (`status`). The five legacy verbs (`init`, `maintain`, `migrate`, `serve`, `status`) each mount the same command builder as their legacy top-level noun, which remains a hidden alias; `exec` has no top-level alias."
3
+ description: "spur-cli noun reference for `spur self`: self-management verbs — SDLC action runner (`exec`), scaffold (`init`), database maintenance (`maintain`), schema migrations (`migrate`), hybrid rg/zg search (`search`), local web server (`serve`), and status overview (`status`). The five legacy verbs (`init`, `maintain`, `migrate`, `serve`, `status`) each mount the same command builder as their legacy top-level noun, which remains a hidden alias; `exec` and `search` have no top-level alias."
4
4
  see_also:
5
5
  - spur-cli
6
6
  ---
@@ -14,7 +14,8 @@ Both paths share the same command builder: identical flags, output, and exit cod
14
14
  top-level forms are omitted from `spur --help`, leaving `self` as the visible surface.
15
15
 
16
16
  `exec` is the exception: it exists only as `spur self exec` — there is no `spur exec` top-level
17
- alias (`spur exec <action>` is an unknown command), so nothing depends on a legacy path.
17
+ alias (`spur exec <action>` is an unknown command), so nothing depends on a legacy path. `search`
18
+ is the second such verb: `spur self search` only.
18
19
 
19
20
  ## Verb map
20
21
 
@@ -24,6 +25,7 @@ alias (`spur exec <action>` is an unknown command), so nothing depends on a lega
24
25
  | `init` | Scaffold a new Spur project in the current directory | `--name <name>` `--force` `--minimal` `--adopt-global-config` `--json` |
25
26
  | `maintain` | Run database maintenance: PRAGMA optimize, WAL truncation, optional VACUUM | `--vacuum` `--json` |
26
27
  | `migrate` | Apply CLI-owned schema migrations (or `--check` the importer schema) | `--check` `--json` |
28
+ | `search` | Hybrid exact (`rg`) and semantic (`zg`) search over project docs and code | `--mode` `--scope` `--glob` `--limit` `--regex` `--status` `--json` |
27
29
  | `serve` | Start the Spur web server (local fallback) | `--port <n>` `--host <addr>` `--no-open` `--cwd <path>` `--json` |
28
30
  | `status [path]` | Show project and git status for a Spur project | `--json` |
29
31
 
@@ -63,6 +65,39 @@ first non-zero exit.
63
65
  `--json` without `--list` or `--dry-run` prints a final `{ action, steps: [{ name, exitCode, durationMs }], exitCode }`
64
66
  after the streaming output. Full surface: `docs/design/sdlc-actions.md` §3.3.
65
67
 
68
+ ## `self search` - hybrid exact and semantic search
69
+
70
+ ```bash
71
+ spur self search "how is a paused workflow resumed" # auto: exact + fuzzy where available
72
+ spur self search Bun.which --mode exact # ripgrep only
73
+ spur self search 'runCost' --mode fuzzy --limit 5 # zg semantic only
74
+ spur self search resum --mode exact --scope docs --glob '*.md'
75
+ spur self search --status # backend readiness, no query
76
+ spur self search Bun.which --mode exact --json # raw SearchResponse payload
77
+ ```
78
+
79
+ One verb over the hybrid search service (`docs/design/hybrid-search.md` §4, ADR-147). `--mode` is
80
+ `exact` | `fuzzy` | `auto` (default `auto`), `--scope` is `docs` | `code` | `all` (default `all`),
81
+ `--limit` is 1–100 (default 20), `--glob` adds a backend glob (repeatable; prefix `!` to negate),
82
+ and `--regex` switches the exact route from fixed string to pattern. `--status` prints the `rg`/`zg`
83
+ readiness payload and takes no query.
84
+
85
+ The groups are `exact` (ripgrep `--json` events) and `fuzzy` (zg `--hybrid`); a group is `null` when
86
+ that route did not run, and `skipped[]` names the degraded route with its reason
87
+ (`zg-not-installed`, `zg-index-missing`, `zg-timeout`, `zg-failed`, `zg-output-error`) plus an exact
88
+ hint string. Hints are printed, never executed — Spur installs and indexes nothing. Human output
89
+ prints `exact (n)` / `fuzzy (n)` headers with `path:start[-end] [matchedBy]` rows, the first three
90
+ snippet lines indented, then the skip lines; zero hits print `no matches`. `--json` emits the
91
+ `SearchResponse` verbatim (`--json-envelope` for the ADR-091 envelope).
92
+
93
+ | Exit | When |
94
+ | ---- | ---- |
95
+ | `0` | results, empty results, or a degraded fuzzy route |
96
+ | `1` | invalid request (empty query, bad mode/scope/limit), rg missing or rg failed |
97
+ | `2` | no query and no `--status` (usage) |
98
+
99
+ Raw zg/rg passthrough flags are deliberately absent: they would leak the adapter.
100
+
66
101
  ## `self init` - scaffold a Spur project
67
102
 
68
103
  ```bash
@@ -142,6 +142,8 @@ Invoked when a map already exists (operator provides the feature ID). A ticket i
142
142
  | **Prototype** | Cheap, rough, concrete artifact to react to — an outline, stub, or rough implementation via `sp:code-implementation`. | Linked prototype as a task artifact |
143
143
  | **Task** | Literal manual work — nothing to prototype or research. Moving data, provisioning access. | Checklist in task body; resolved when done |
144
144
 
145
+ **Search routing** (hybrid search, ADR-147): concept, cross-file, or "how does X work" questions → semantic first — the zg MCP tool `zvec_grep_search` when the host exposes it, else `spur self search "<query>" --mode fuzzy --json`. Literal, symbol, regex, or exhaustive occurrence lists → `rg`. If zg is absent or the search response carries a `skipped` reason → fall back to `rg`.
146
+
145
147
  An open question from the map is **not** resolved this way. It is settled with the operator in
146
148
  conversation (`AskUserQuestion` where available) and recorded straight into **## Decisions so far**
147
149
  — no task is claimed, and it does not consume the session's one ticket.