@gobing-ai/spur 0.4.19 → 0.4.20

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 (59) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.example.yaml +4 -0
  3. package/package.json +9 -9
  4. package/plugins/sp/plugin.json +1 -1
  5. package/plugins/sp/scripts/hooks/fleet-guest-stop.ts +32 -12
  6. package/plugins/sp/skills/fleet-join/SKILL.md +7 -4
  7. package/plugins/sp/skills/spur-cli/references/agent.md +6 -4
  8. package/schemas/spur-config.schema.json +5 -0
  9. package/spur.js +1810 -1684
  10. package/web/_astro/{BoardApp.DY8fwPfw.js → BoardApp.BDo7Cplz.js} +141 -141
  11. package/web/_astro/{BoardApp.CNF3sCnm.js → BoardApp.uR1DxOv6.js} +1 -1
  12. package/web/_astro/{TaskDetail.8z3DgYux.js → TaskDetail.BiGHeRcp.js} +1 -1
  13. package/web/_astro/{arc.CFEYbpFT.js → arc.BLw5ykEN.js} +1 -1
  14. package/web/_astro/{architectureDiagram-3BPJPVTR.3eDvG_X8.js → architectureDiagram-3BPJPVTR.C2BEgSVb.js} +1 -1
  15. package/web/_astro/{blockDiagram-GPEHLZMM.CMbLzMpk.js → blockDiagram-GPEHLZMM.D8G9DLH3.js} +1 -1
  16. package/web/_astro/{c4Diagram-AAUBKEIU.2lZW8nZ-.js → c4Diagram-AAUBKEIU.dU7HR9k5.js} +1 -1
  17. package/web/_astro/channel.DSbvfOEf.js +1 -0
  18. package/web/_astro/{chunk-2J33WTMH.BtM1eI3D.js → chunk-2J33WTMH.5BIk-HVp.js} +1 -1
  19. package/web/_astro/{chunk-4BX2VUAB.CKeckzAf.js → chunk-4BX2VUAB.BVIKX24s.js} +1 -1
  20. package/web/_astro/{chunk-55IACEB6.BBmHet8U.js → chunk-55IACEB6.Bz2tdkGM.js} +1 -1
  21. package/web/_astro/{chunk-727SXJPM.Bi8v7M_x.js → chunk-727SXJPM.R0d_FlTN.js} +1 -1
  22. package/web/_astro/{chunk-AQP2D5EJ.DOcBJyb6.js → chunk-AQP2D5EJ.C2kK8Ok-.js} +1 -1
  23. package/web/_astro/{chunk-FMBD7UC4.C-EdUBD7.js → chunk-FMBD7UC4.COAncjFn.js} +1 -1
  24. package/web/_astro/{chunk-ND2GUHAM.IqTyWQhk.js → chunk-ND2GUHAM.CeNg5VSN.js} +1 -1
  25. package/web/_astro/{chunk-QZHKN3VN.a2DShvIL.js → chunk-QZHKN3VN.DRGDDzrj.js} +1 -1
  26. package/web/_astro/{classDiagram-4FO5ZUOK.CQBnvIjk.js → classDiagram-4FO5ZUOK.DmM59Lgb.js} +1 -1
  27. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CQBnvIjk.js → classDiagram-v2-Q7XG4LA2.DmM59Lgb.js} +1 -1
  28. package/web/_astro/{cose-bilkent-S5V4N54A.BW9Ryz7c.js → cose-bilkent-S5V4N54A.BjKuSb1i.js} +1 -1
  29. package/web/_astro/{cynefin-OW5HDTMX.BPhLeSep.js → cynefin-OW5HDTMX.DQevIJEc.js} +1 -1
  30. package/web/_astro/{dagre-BM42HDAG._oD9MsDB.js → dagre-BM42HDAG.CqOK1C5k.js} +1 -1
  31. package/web/_astro/{diagram-2AECGRRQ.iciSOlxY.js → diagram-2AECGRRQ.B7i8Xbcc.js} +1 -1
  32. package/web/_astro/{diagram-5GNKFQAL.ZzqeC45Q.js → diagram-5GNKFQAL.DXGFk2rU.js} +1 -1
  33. package/web/_astro/{diagram-KO2AKTUF.CMqWGOYB.js → diagram-KO2AKTUF.BDC9f2pC.js} +1 -1
  34. package/web/_astro/{diagram-LMA3HP47.B5MPN99W.js → diagram-LMA3HP47.D-Mrwnmt.js} +1 -1
  35. package/web/_astro/{diagram-OG6HWLK6.Zgi-ANAF.js → diagram-OG6HWLK6.XXGX1BQ_.js} +1 -1
  36. package/web/_astro/{erDiagram-TEJ5UH35.Bd5aeIyR.js → erDiagram-TEJ5UH35.DahWkk7M.js} +1 -1
  37. package/web/_astro/{flowDiagram-I6XJVG4X.B8LBo_PH.js → flowDiagram-I6XJVG4X.DaG5HfPs.js} +1 -1
  38. package/web/_astro/{ganttDiagram-6RSMTGT7.Di2HLHk4.js → ganttDiagram-6RSMTGT7.DZe_CQWm.js} +1 -1
  39. package/web/_astro/{gitGraphDiagram-PVQCEYII.BqDuU6fc.js → gitGraphDiagram-PVQCEYII.C4zSw3kS.js} +1 -1
  40. package/web/_astro/{infoDiagram-5YYISTIA.C_hw98uF.js → infoDiagram-5YYISTIA.CmaEULgT.js} +1 -1
  41. package/web/_astro/{ishikawaDiagram-YF4QCWOH.uK7JK9Kk.js → ishikawaDiagram-YF4QCWOH.DKKhZJ98.js} +1 -1
  42. package/web/_astro/{journeyDiagram-JHISSGLW.D03MNAmn.js → journeyDiagram-JHISSGLW.B6d7TuYQ.js} +1 -1
  43. package/web/_astro/{kanban-definition-UN3LZRKU.DuiDtKhq.js → kanban-definition-UN3LZRKU.xiG_jH03.js} +1 -1
  44. package/web/_astro/{linear.Der6_z5m.js → linear.DsbUmqlh.js} +1 -1
  45. package/web/_astro/{mermaid.core.CaxkEgwh.js → mermaid.core.CihlV6Wy.js} +4 -4
  46. package/web/_astro/{mindmap-definition-RKZ34NQL.DecT082u.js → mindmap-definition-RKZ34NQL.Cm2UvqJQ.js} +1 -1
  47. package/web/_astro/{pieDiagram-4H26LBE5.DuAEfZXh.js → pieDiagram-4H26LBE5.COyvnb_Q.js} +1 -1
  48. package/web/_astro/{quadrantDiagram-W4KKPZXB.CWXczU1k.js → quadrantDiagram-W4KKPZXB.BdaJr8EI.js} +1 -1
  49. package/web/_astro/{requirementDiagram-4Y6WPE33.BsAHUsR6.js → requirementDiagram-4Y6WPE33.aS-qeofA.js} +1 -1
  50. package/web/_astro/{sankeyDiagram-5OEKKPKP.ghBqvXN8.js → sankeyDiagram-5OEKKPKP.BxaOWF5N.js} +1 -1
  51. package/web/_astro/{sequenceDiagram-3UESZ5HK.BKbwb7ad.js → sequenceDiagram-3UESZ5HK.CfZPPWA1.js} +1 -1
  52. package/web/_astro/{stateDiagram-AJRCARHV.D5geOKAc.js → stateDiagram-AJRCARHV.D6VXy0-M.js} +1 -1
  53. package/web/_astro/{stateDiagram-v2-BHNVJYJU.Dc4Sjs4R.js → stateDiagram-v2-BHNVJYJU.DatbDxXn.js} +1 -1
  54. package/web/_astro/{timeline-definition-PNZ67QCA.ve2dW2tR.js → timeline-definition-PNZ67QCA.BxQ7-VRY.js} +1 -1
  55. package/web/_astro/{vennDiagram-CIIHVFJN.BdJumy3f.js → vennDiagram-CIIHVFJN.BJny6MAy.js} +1 -1
  56. package/web/_astro/{wardleyDiagram-YWT4CUSO.X8HgtN8V.js → wardleyDiagram-YWT4CUSO.gzTiYN-4.js} +1 -1
  57. package/web/_astro/{xychartDiagram-2RQKCTM6.C30VVwQ6.js → xychartDiagram-2RQKCTM6.DTR_zwDh.js} +1 -1
  58. package/web/index.html +1 -1
  59. package/web/_astro/channel.CdQ8ZO1m.js +0 -1
@@ -7,7 +7,7 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "sp",
10
- "version": "0.4.19",
10
+ "version": "0.4.20",
11
11
  "source": "./plugins/sp"
12
12
  }
13
13
  ]
@@ -58,6 +58,10 @@ bootstrap:
58
58
  command: >-
59
59
  spur --no-logo history import --source all &&
60
60
  spur --no-logo workflow run config/workflows/history-anatomy.yaml --quiet
61
+ # Without this the chain inherits the 10-minute scheduler default and dies mid-validate.
62
+ # 1h45m: enrich (45-52 min on a slow executor) + validate, still below the server
63
+ # queue's 2h visibility timeout.
64
+ timeoutMs: 6300000
61
65
 
62
66
  # ── Spur app section (validated by spurConfigSchema in
63
67
  # @gobing-ai/spur-config) ──
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.4.19",
3
+ "version": "0.4.20",
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",
@@ -58,14 +58,14 @@
58
58
  },
59
59
  "devDependencies": {
60
60
  "@commander-js/extra-typings": "^14.0.0",
61
- "@gobing-ai/ts-db": "^0.5.32",
62
- "@gobing-ai/ts-ai-runner": "^0.5.32",
63
- "@gobing-ai/ts-dual-workflow-engine": "^0.5.32",
64
- "@gobing-ai/ts-infra": "^0.5.32",
65
- "@gobing-ai/ts-llm-jsonl-importer": "^0.5.32",
66
- "@gobing-ai/ts-rule-engine": "^0.5.32",
67
- "@gobing-ai/ts-runtime": "^0.5.32",
68
- "@gobing-ai/ts-utils": "^0.5.32",
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",
69
69
  "@types/bun": "1.4.3",
70
70
  "@types/figlet": "^1.7.0",
71
71
  "@types/node-notifier": "8.0.5",
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "sp",
3
- "version": "0.4.19",
3
+ "version": "0.4.20",
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
  }
@@ -37,7 +37,7 @@ export const GUEST_STOP_PROBE_MS = 2000;
37
37
  /** How many pending messages the delivered reason names. */
38
38
  export const GUEST_STOP_MAX_MESSAGES = 5;
39
39
 
40
- /** Characters of each message body the reason carries. */
40
+ /** Preview length for legacy queued snapshots; an owned handoff carries the full body. */
41
41
  export const GUEST_STOP_BODY_CHARS = 140;
42
42
 
43
43
  /** Hard per-invocation timeout for a `spur` child the hook shells out to. */
@@ -108,20 +108,41 @@ export function stopDecisionFor(
108
108
  } catch {
109
109
  return null;
110
110
  }
111
- const list = Array.isArray(rows) ? rows : undefined;
111
+ const payload = rows !== null && typeof rows === 'object' && 'data' in rows ? rows.data : rows;
112
+ const list = Array.isArray(payload)
113
+ ? payload
114
+ : payload !== null && typeof payload === 'object' && 'messages' in payload && Array.isArray(payload.messages)
115
+ ? payload.messages
116
+ : undefined;
112
117
  if (list === undefined) return null;
118
+ const handedOff = payload !== null && typeof payload === 'object' && 'pending' in payload;
113
119
  const pending = list
114
- .map((row) => row as { from_id?: unknown; fromId?: unknown; body?: unknown; status?: unknown })
115
- .filter((row) => row.body !== undefined && (row.status === undefined || row.status === 'queued'))
116
- .slice(0, maxMessages);
120
+ .filter((row) => row !== null && typeof row === 'object')
121
+ .map((row) => row as { id?: unknown; from_id?: unknown; fromId?: unknown; body?: unknown; status?: unknown })
122
+ .filter(
123
+ (row) =>
124
+ typeof row.body === 'string' &&
125
+ (row.status === undefined ||
126
+ (handedOff
127
+ ? ['queued', 'claimed', 'delivered'].includes(String(row.status))
128
+ : row.status === 'queued')),
129
+ )
130
+ .slice(0, handedOff ? undefined : maxMessages);
117
131
  if (pending.length === 0) return null;
118
132
  const lines = pending.map((row) => {
119
133
  const from =
120
134
  typeof row.from_id === 'string' ? row.from_id : typeof row.fromId === 'string' ? row.fromId : 'fleet';
121
- const body = String(row.body ?? '')
122
- .replace(/\s+/g, ' ')
123
- .trim();
124
- return `- ${from}: ${body.length > GUEST_STOP_BODY_CHARS ? `${body.slice(0, GUEST_STOP_BODY_CHARS)}…` : body}`;
135
+ const body = handedOff
136
+ ? String(row.body ?? '')
137
+ : String(row.body ?? '')
138
+ .replace(/\s+/g, ' ')
139
+ .trim();
140
+ const id = typeof row.id === 'string' ? ` [${row.id}]` : '';
141
+ const preview =
142
+ !handedOff && body.length > GUEST_STOP_BODY_CHARS
143
+ ? `${body.slice(0, GUEST_STOP_BODY_CHARS)}… (read the full message with spur message show ${String(row.id ?? '<msgId>')} --json)`
144
+ : body;
145
+ return `- ${from}${id}: ${preview}`;
125
146
  });
126
147
  const reason = `Pending fleet messages for ${guestId}:\n${lines.join('\n')}\n\nHandle them, then \`spur message reply <msgId> …\` and continue.`;
127
148
  return JSON.stringify({ decision: 'block', reason });
@@ -155,11 +176,10 @@ export function runFleetGuestStop(payload: GuestStopPayload, deps: GuestStopDeps
155
176
  guest.id,
156
177
  '--timeout',
157
178
  String(deps.probeMs ?? GUEST_STOP_PROBE_MS),
179
+ '--json',
158
180
  ]);
159
181
  if (probe.status !== 0) return null;
160
- const inbox = cli(['message', 'inbox', '--agent', guest.id, '--json']);
161
- if (inbox.status !== 0) return null;
162
- return stopDecisionFor(guest.id, inbox.stdout, deps.maxMessages);
182
+ return stopDecisionFor(guest.id, probe.stdout, deps.maxMessages);
163
183
  } catch {
164
184
  return null;
165
185
  }
@@ -28,13 +28,16 @@ find this session, though the `wait --inbox` loop still works.
28
28
 
29
29
  ## Pull loop
30
30
 
31
- `spur agent wait --inbox <id> --timeout 540000` returns **0** the moment queued work exists and
32
- **1** on timeout. Each call heartbeats the lease, so a looping session stays joined. The Bash tool's
31
+ `spur agent wait --inbox <id> --timeout 540000 --json` returns **0** with up to five claimed messages
32
+ in `{ id, pending, messages }`, settling their delivery after the output handoff. It returns **1**
33
+ on timeout. An output failure releases the exact claims for retry; inbox listing itself never
34
+ consumes messages. Each call heartbeats the lease, so a looping session stays joined. The Bash tool's
33
35
  own ~10-minute limit is why the timeout stays under it: one bounded call per turn, re-armed after
34
36
  the work is done.
35
37
 
36
- 1. `spur agent wait --inbox <id> --timeout 540000 --json` → exit 0 means work is waiting.
37
- 2. `spur message inbox --agent <id> --json` → read the messages.
38
+ 1. `spur agent wait --inbox <id> --timeout 540000 --json` → exit 0 hands this session `messages`.
39
+ 2. Read the returned messages, including their IDs. `spur message show <id> --json` can reread one;
40
+ `spur message inbox --agent <id> --json` is a non-consuming historical view.
38
41
  3. Do the work the message asks for.
39
42
  4. `spur message reply <msgId> "<result>"` → reply to each one.
40
43
  5. Re-arm: go back to step 1 while the operator wants this session in the fleet.
@@ -239,10 +239,12 @@ is refused (R3), and `requiresCapabilities` stages therefore can never reach one
239
239
  spur agent wait --inbox reviewer-g1 --timeout 540000 --json
240
240
  ```
241
241
 
242
- Returns `0` as soon as queued inbox work exists for the guest and `1` on timeout or when the id is
243
- not a joined guest — never a silent hang. Every call heartbeats the guest's lease, so a session that
244
- keeps looping stays joined; the timeout stays under the Bash tool's ~10-minute limit. The
245
- `fleet-join` skill loops it (wait → `spur message inbox --agent <id>` → work → `spur message reply`),
242
+ Returns `0` after handing off up to five queued messages, with `{ id, pending, messages }` in JSON;
243
+ `pending` is the handed-off batch count. Exact claims settle as delivered after output succeeds and
244
+ release for retry if output fails; further queued work stays for the next pull. Returns `1` on timeout
245
+ or when the id is not a joined guest. Every call heartbeats the guest's lease, so a session that keeps
246
+ looping stays joined; the timeout stays under the Bash tool's ~10-minute limit. The
247
+ `fleet-join` skill loops it (wait → work from returned messages → `spur message reply`),
246
248
  and the `fleet-guest-stop` plugin hook delivers the same messages at a turn boundary on hosts that
247
249
  support a blocking Stop hook.
248
250
 
@@ -120,6 +120,11 @@
120
120
  "type": "string",
121
121
  "minLength": 1,
122
122
  "description": "Five-field cron expression (minute hour day-of-month month day-of-week) evaluated in local time. Mutually exclusive with `intervalMinutes`."
123
+ },
124
+ "timeoutMs": {
125
+ "type": ["integer", "null"],
126
+ "minimum": 1,
127
+ "description": "Total child budget for this job in milliseconds. At the deadline the runtime SIGTERMs the owned process group, then SIGKILLs after the scheduler grace. `null` is explicit unlimited; absent falls back to the global scheduler default (10 min). Keep a model-driven chain below the server queue's 2 h visibility timeout so it fails its own attempt instead of being re-delivered."
123
128
  }
124
129
  },
125
130
  "oneOf": [