@gobing-ai/spur 0.3.11 → 0.3.13

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 (55) hide show
  1. package/package.json +1 -1
  2. package/spur.js +181 -20
  3. package/web/_astro/{BoardApp.DDpBMmZh.js → BoardApp.CqBOrPzf.js} +101 -101
  4. package/web/_astro/BoardApp.XG2FoRlA.js +1 -0
  5. package/web/_astro/{TaskDetail.BuXwnD5s.js → TaskDetail.BmxycoeS.js} +1 -1
  6. package/web/_astro/{arc.ROFFfvlC.js → arc.-ZkWbWcN.js} +1 -1
  7. package/web/_astro/{architectureDiagram-3BPJPVTR.BF8jS1C7.js → architectureDiagram-3BPJPVTR.fJ8ETc6n.js} +1 -1
  8. package/web/_astro/{blockDiagram-GPEHLZMM.BAZd0UR-.js → blockDiagram-GPEHLZMM.CdhoveXr.js} +1 -1
  9. package/web/_astro/{c4Diagram-AAUBKEIU.BQiuu65q.js → c4Diagram-AAUBKEIU.CWv7Jy0p.js} +1 -1
  10. package/web/_astro/channel.BrBVc6tg.js +1 -0
  11. package/web/_astro/{chunk-2J33WTMH.CaZ7Cj2C.js → chunk-2J33WTMH.CvYWDwal.js} +1 -1
  12. package/web/_astro/{chunk-4BX2VUAB.Dl95s7jh.js → chunk-4BX2VUAB.BVp_y1oc.js} +1 -1
  13. package/web/_astro/{chunk-55IACEB6.BS6cwIjQ.js → chunk-55IACEB6.CQCviGQk.js} +1 -1
  14. package/web/_astro/{chunk-727SXJPM.BodG8be9.js → chunk-727SXJPM.B3E6Bisv.js} +1 -1
  15. package/web/_astro/{chunk-AQP2D5EJ.D7bElS24.js → chunk-AQP2D5EJ.BvWfYj9E.js} +1 -1
  16. package/web/_astro/{chunk-FMBD7UC4.B4QCK-dI.js → chunk-FMBD7UC4.DcLP2Ypw.js} +1 -1
  17. package/web/_astro/{chunk-ND2GUHAM.X1msYFei.js → chunk-ND2GUHAM.DvXZJEqI.js} +1 -1
  18. package/web/_astro/{chunk-QZHKN3VN.B2c4-Dz1.js → chunk-QZHKN3VN.D4rK2HTI.js} +1 -1
  19. package/web/_astro/{classDiagram-4FO5ZUOK.b8-sWDxa.js → classDiagram-4FO5ZUOK.DSCYtdVf.js} +1 -1
  20. package/web/_astro/{classDiagram-v2-Q7XG4LA2.b8-sWDxa.js → classDiagram-v2-Q7XG4LA2.DSCYtdVf.js} +1 -1
  21. package/web/_astro/{cose-bilkent-S5V4N54A.CT4xXx1C.js → cose-bilkent-S5V4N54A.Bf7HMV4Q.js} +1 -1
  22. package/web/_astro/{dagre-BM42HDAG.DAukBvJI.js → dagre-BM42HDAG.DsBoJTBN.js} +1 -1
  23. package/web/_astro/{diagram-2AECGRRQ.iAkt-am3.js → diagram-2AECGRRQ.BMVxdKV3.js} +1 -1
  24. package/web/_astro/{diagram-5GNKFQAL.BbKJ0fPo.js → diagram-5GNKFQAL.BllW5zst.js} +1 -1
  25. package/web/_astro/{diagram-KO2AKTUF.DmAKP2Me.js → diagram-KO2AKTUF.CNHoSD6q.js} +1 -1
  26. package/web/_astro/{diagram-LMA3HP47.CJzAaFxI.js → diagram-LMA3HP47.DZT6otFZ.js} +1 -1
  27. package/web/_astro/{diagram-OG6HWLK6.z2Yhlt_S.js → diagram-OG6HWLK6.EwFWHBRO.js} +1 -1
  28. package/web/_astro/{erDiagram-TEJ5UH35.C1inBaHt.js → erDiagram-TEJ5UH35.CaXPcR5W.js} +1 -1
  29. package/web/_astro/{flowDiagram-I6XJVG4X.D6y2SP1V.js → flowDiagram-I6XJVG4X.FEcfML44.js} +1 -1
  30. package/web/_astro/{ganttDiagram-6RSMTGT7.BWgsQaJO.js → ganttDiagram-6RSMTGT7.Bs7t3AQM.js} +1 -1
  31. package/web/_astro/{gitGraphDiagram-PVQCEYII.So8AZT3S.js → gitGraphDiagram-PVQCEYII.DmxLV9JC.js} +1 -1
  32. package/web/_astro/index.BOgTYz24.css +1 -0
  33. package/web/_astro/{infoDiagram-5YYISTIA.alTk5ecq.js → infoDiagram-5YYISTIA.E7_NrFUE.js} +1 -1
  34. package/web/_astro/{ishikawaDiagram-YF4QCWOH.X82FajaS.js → ishikawaDiagram-YF4QCWOH.Bs5Q4r-U.js} +1 -1
  35. package/web/_astro/{journeyDiagram-JHISSGLW.74uvU6ld.js → journeyDiagram-JHISSGLW.D3U7T1y1.js} +1 -1
  36. package/web/_astro/{kanban-definition-UN3LZRKU.D6BjF9UG.js → kanban-definition-UN3LZRKU.DfdWsO2E.js} +1 -1
  37. package/web/_astro/{linear.BpRIYzdb.js → linear.CAqUTUf3.js} +1 -1
  38. package/web/_astro/{mermaid.core.fdUDKzul.js → mermaid.core.ZlPUJ21K.js} +4 -4
  39. package/web/_astro/{mindmap-definition-RKZ34NQL.Df5SdMrH.js → mindmap-definition-RKZ34NQL.ClTJkcTG.js} +1 -1
  40. package/web/_astro/{pieDiagram-4H26LBE5.xNCa7QSz.js → pieDiagram-4H26LBE5.B9kJfBw_.js} +1 -1
  41. package/web/_astro/{quadrantDiagram-W4KKPZXB.C_3DSB9b.js → quadrantDiagram-W4KKPZXB.CtTJH5Fo.js} +1 -1
  42. package/web/_astro/{requirementDiagram-4Y6WPE33.C2MFScI8.js → requirementDiagram-4Y6WPE33.WVLq9rr2.js} +1 -1
  43. package/web/_astro/{sankeyDiagram-5OEKKPKP.DEy6YHUG.js → sankeyDiagram-5OEKKPKP.L_XUdyqe.js} +1 -1
  44. package/web/_astro/{sequenceDiagram-3UESZ5HK.CdQjeOkf.js → sequenceDiagram-3UESZ5HK.DCdtsEdj.js} +1 -1
  45. package/web/_astro/{stateDiagram-AJRCARHV.C-j_0D_L.js → stateDiagram-AJRCARHV.-Koi9kLh.js} +1 -1
  46. package/web/_astro/{stateDiagram-v2-BHNVJYJU.BMzYaYSQ.js → stateDiagram-v2-BHNVJYJU.DHXu_l8w.js} +1 -1
  47. package/web/_astro/{timeline-definition-PNZ67QCA.BaAa5dDC.js → timeline-definition-PNZ67QCA.Czotaq0j.js} +1 -1
  48. package/web/_astro/{vennDiagram-CIIHVFJN.GZL7T0cb.js → vennDiagram-CIIHVFJN.BfEI3zhc.js} +1 -1
  49. package/web/_astro/{wardley-L42UT6IY.STDdK5oO.js → wardley-L42UT6IY.C33mahwT.js} +1 -1
  50. package/web/_astro/{wardleyDiagram-YWT4CUSO.BJLlSFei.js → wardleyDiagram-YWT4CUSO.D8IzxB1w.js} +1 -1
  51. package/web/_astro/{xychartDiagram-2RQKCTM6.CRGBFqsD.js → xychartDiagram-2RQKCTM6.D-cfi9uT.js} +1 -1
  52. package/web/index.html +2 -2
  53. package/web/_astro/BoardApp.CXm9NttC.js +0 -1
  54. package/web/_astro/channel.e58tDmJn.js +0 -1
  55. package/web/_astro/index.BaoJPQzM.css +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
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",
package/spur.js CHANGED
@@ -52515,6 +52515,27 @@ class InboxRecentDao {
52515
52515
  throw error51;
52516
52516
  }
52517
52517
  }
52518
+ async countReplies(ids) {
52519
+ const result = new Map;
52520
+ if (ids.length === 0)
52521
+ return result;
52522
+ const placeholders = ids.map((_, i2) => `?${i2 + 1}`).join(", ");
52523
+ try {
52524
+ const rows = await this.db.queryAll(`SELECT in_reply_to, COUNT(*) AS n
52525
+ FROM inbox_messages
52526
+ WHERE in_reply_to IS NOT NULL AND in_reply_to IN (${placeholders})
52527
+ GROUP BY in_reply_to`, ...ids);
52528
+ for (const row of rows) {
52529
+ result.set(row.in_reply_to, row.n);
52530
+ }
52531
+ } catch (error51) {
52532
+ if (error51 instanceof Error && error51.message.includes("no such table: inbox_messages")) {
52533
+ return result;
52534
+ }
52535
+ throw error51;
52536
+ }
52537
+ return result;
52538
+ }
52518
52539
  }
52519
52540
 
52520
52541
  // ../../packages/domain/src/schema/phase-runs.ts
@@ -61170,16 +61191,28 @@ class SupervisorService {
61170
61191
  status: "running",
61171
61192
  startedAt: new Date().toISOString(),
61172
61193
  ringBuffer: frames,
61173
- teamId
61194
+ teamId,
61195
+ agentType: spec.type
61174
61196
  };
61175
61197
  this.processes.set(agentId, { handle, entry });
61176
61198
  this.restartAttempts.delete(agentId);
61177
61199
  this.pipeStream(handle.stdout, "stdout", agentId, frames);
61178
61200
  this.pipeStream(handle.stderr, "stderr", agentId, frames);
61179
- this.emit("process.spawned", { agentId, pid });
61201
+ this.emit("process.spawned", {
61202
+ agentId,
61203
+ pid,
61204
+ teamId,
61205
+ agentType: spec.type
61206
+ });
61180
61207
  handle.exited.then(async (code) => {
61181
61208
  entry.exitCode = code;
61182
- this.emit("process.exited", { agentId, pid, exitCode: code });
61209
+ this.emit("process.exited", {
61210
+ agentId,
61211
+ pid,
61212
+ exitCode: code,
61213
+ teamId: entry.teamId ?? null,
61214
+ ...entry.agentType ? { agentType: entry.agentType } : {}
61215
+ });
61183
61216
  if (code === 0 || entry.status === "stopped") {
61184
61217
  entry.status = "exited";
61185
61218
  this.scheduleCleanup(agentId, entry);
@@ -61226,7 +61259,12 @@ class SupervisorService {
61226
61259
  this.restartTimers.delete(agentId);
61227
61260
  }
61228
61261
  this.restartAttempts.delete(agentId);
61229
- this.emit("process.stopped", { agentId, pid: proc.entry.pid });
61262
+ this.emit("process.stopped", {
61263
+ agentId,
61264
+ pid: proc.entry.pid,
61265
+ teamId: proc.entry.teamId ?? null,
61266
+ ...proc.entry.agentType ? { agentType: proc.entry.agentType } : {}
61267
+ });
61230
61268
  }
61231
61269
  async startAutostart(ids) {
61232
61270
  const results = [];
@@ -61369,9 +61407,11 @@ function safeStringify(event2) {
61369
61407
  }
61370
61408
  function extractSystemEventActor(event2) {
61371
61409
  if (event2 && typeof event2 === "object") {
61372
- const candidate = event2.actor;
61373
- if (typeof candidate === "string")
61374
- return candidate;
61410
+ const obj = event2;
61411
+ if (typeof obj.actor === "string" && obj.actor.length > 0)
61412
+ return obj.actor;
61413
+ if (typeof obj.agentId === "string" && obj.agentId.length > 0)
61414
+ return obj.agentId;
61375
61415
  }
61376
61416
  return null;
61377
61417
  }
@@ -61442,6 +61482,10 @@ function hasPopulatedPriorityTable(body) {
61442
61482
  }
61443
61483
  return false;
61444
61484
  }
61485
+ function extractReviewSectionBody(markdown) {
61486
+ const match = markdown.match(/^### Review[ \t]*\n([\s\S]*?)(?=^### |Z)/m);
61487
+ return match ? match[1] ?? "" : null;
61488
+ }
61445
61489
  function isReviewScaffold(body) {
61446
61490
  if (isPlaceholderBody(body))
61447
61491
  return true;
@@ -63016,16 +63060,50 @@ class TeamService {
63016
63060
  async listRecent(limit = 50) {
63017
63061
  const dao2 = await this.inboxRecentDao();
63018
63062
  const rows = await dao2.listRecent(limit);
63063
+ if (rows.length === 0) {
63064
+ return { messages: [], count: 0 };
63065
+ }
63066
+ const [teams, specs] = await Promise.all([this.listTeams(), this.listAgentSpecs()]);
63067
+ const identityById = new Map;
63068
+ for (const team of teams) {
63069
+ for (const spec of team.specs) {
63070
+ identityById.set(spec.id, {
63071
+ agentId: spec.id,
63072
+ teamId: team.teamId,
63073
+ teamName: team.name,
63074
+ memberLabel: spec.name,
63075
+ agentType: spec.type
63076
+ });
63077
+ }
63078
+ }
63079
+ for (const spec of specs) {
63080
+ if (!identityById.has(spec.id)) {
63081
+ identityById.set(spec.id, {
63082
+ agentId: spec.id,
63083
+ memberLabel: spec.name,
63084
+ agentType: spec.type
63085
+ });
63086
+ }
63087
+ }
63088
+ const replyCounts = await dao2.countReplies(rows.map((r) => r.id));
63089
+ const resolveEndpoint = (agentId) => identityById.get(agentId) ?? { agentId };
63019
63090
  return {
63020
- messages: rows.map((row) => ({
63021
- id: row.id,
63022
- fromId: row.from_id,
63023
- toId: row.to_id,
63024
- body: row.body,
63025
- status: row.status,
63026
- createdAt: new Date(row.created_at).toISOString(),
63027
- inReplyTo: row.in_reply_to
63028
- })),
63091
+ messages: rows.map((row) => {
63092
+ const replyCount = replyCounts.get(row.id) ?? 0;
63093
+ return {
63094
+ id: row.id,
63095
+ fromId: row.from_id,
63096
+ toId: row.to_id,
63097
+ body: row.body,
63098
+ status: row.status,
63099
+ createdAt: new Date(row.created_at).toISOString(),
63100
+ inReplyTo: row.in_reply_to,
63101
+ ...row.from_id !== null ? { from: resolveEndpoint(row.from_id) } : {},
63102
+ to: resolveEndpoint(row.to_id),
63103
+ hasReply: replyCount > 0,
63104
+ replyCount
63105
+ };
63106
+ }),
63029
63107
  count: rows.length
63030
63108
  };
63031
63109
  }
@@ -64805,6 +64883,8 @@ var init_workflow_service = __esm(() => {
64805
64883
  });
64806
64884
 
64807
64885
  // ../../packages/app/src/workflow/lifecycle-adapter.ts
64886
+ import { readFile as readFile3 } from "fs/promises";
64887
+
64808
64888
  class LifecycleAdapter {
64809
64889
  opts;
64810
64890
  workflowCache;
@@ -64840,6 +64920,15 @@ class LifecycleAdapter {
64840
64920
  };
64841
64921
  }
64842
64922
  }
64923
+ const reviewDenial = await this.checkReviewReadyForDone(ref);
64924
+ if (reviewDenial !== null) {
64925
+ return {
64926
+ allowed: false,
64927
+ from: currentStatus,
64928
+ to,
64929
+ report: reviewDenial
64930
+ };
64931
+ }
64843
64932
  }
64844
64933
  const existing = await svc.findRunByKey(profile.workflowName, externalKey);
64845
64934
  const runId = existing?.id ?? createId("run");
@@ -64890,6 +64979,27 @@ class LifecycleAdapter {
64890
64979
  vars: { ...workflow.vars, [this.opts.profile.varKey]: id, spurBin: this.opts.spurBin }
64891
64980
  };
64892
64981
  }
64982
+ async checkReviewReadyForDone(ref) {
64983
+ const markdown = await this.loadTaskMarkdown(ref);
64984
+ if (markdown === null) {
64985
+ return null;
64986
+ }
64987
+ const reviewBody = extractReviewSectionBody(markdown);
64988
+ if (reviewBody === null || !hasPopulatedPriorityTable(reviewBody)) {
64989
+ return `Review L3 gate failed for task ${ref.id}: ### Review must contain a populated ` + `P1\u2013P4 priority findings table before testing\u2192done (strict-core). ` + `Author the table via /sp:dev-review, then re-run the done transition.`;
64990
+ }
64991
+ return null;
64992
+ }
64993
+ async loadTaskMarkdown(ref) {
64994
+ if (this.opts.readTaskMarkdown) {
64995
+ return this.opts.readTaskMarkdown(ref);
64996
+ }
64997
+ try {
64998
+ return await readFile3(ref.filePath, "utf8");
64999
+ } catch {
65000
+ return null;
65001
+ }
65002
+ }
64893
65003
  formatDenial(detail, guardReport) {
64894
65004
  if (guardReport === undefined || guardReport === null)
64895
65005
  return detail;
@@ -64901,6 +65011,7 @@ var TASK_LIFECYCLE_PROFILE, FEATURE_LIFECYCLE_PROFILE;
64901
65011
  var init_lifecycle_adapter = __esm(() => {
64902
65012
  init_src2();
64903
65013
  init_dist8();
65014
+ init_task_check();
64904
65015
  TASK_LIFECYCLE_PROFILE = {
64905
65016
  linkKind: "lifecycle",
64906
65017
  workflowName: "task-lifecycle",
@@ -64965,7 +65076,9 @@ __export(exports_src2, {
64965
65076
  normalizeSystemEventPayload: () => normalizeSystemEventPayload,
64966
65077
  hitlConfirmDefault: () => hitlConfirmDefault,
64967
65078
  hitlAutoApproveEnabled: () => hitlAutoApproveEnabled,
65079
+ hasPopulatedPriorityTable: () => hasPopulatedPriorityTable,
64968
65080
  extractSystemEventActor: () => extractSystemEventActor,
65081
+ extractReviewSectionBody: () => extractReviewSectionBody,
64969
65082
  deriveVerdict: () => deriveVerdict,
64970
65083
  createPsProcessInspector: () => createPsProcessInspector,
64971
65084
  clampToolUseLimit: () => clampToolUseLimit,
@@ -73009,7 +73122,7 @@ import { join as join15, resolve as resolve5 } from "path";
73009
73122
  var CLI_CONFIG = {
73010
73123
  binaryName: "spur",
73011
73124
  binaryLabel: "spur",
73012
- binaryVersion: "0.3.11",
73125
+ binaryVersion: "0.3.13",
73013
73126
  configDir: ".spur",
73014
73127
  configFile: ".spur/config.yaml",
73015
73128
  databaseFile: ".spur/spur.db"
@@ -79036,9 +79149,11 @@ init_event_names();
79036
79149
  // ../server/src/modules/events/index.ts
79037
79150
  function extractSystemEventActor2(event2) {
79038
79151
  if (event2 && typeof event2 === "object") {
79039
- const candidate = event2.actor;
79040
- if (typeof candidate === "string")
79041
- return candidate;
79152
+ const obj = event2;
79153
+ if (typeof obj.actor === "string" && obj.actor.length > 0)
79154
+ return obj.actor;
79155
+ if (typeof obj.agentId === "string" && obj.agentId.length > 0)
79156
+ return obj.agentId;
79042
79157
  }
79043
79158
  return null;
79044
79159
  }
@@ -79650,6 +79765,7 @@ var featureModule = {
79650
79765
  };
79651
79766
 
79652
79767
  // ../server/src/modules/health/index.ts
79768
+ import { basename as basename3 } from "path";
79653
79769
  var startedAt = Date.now();
79654
79770
  var healthModule = {
79655
79771
  name: "health",
@@ -79674,6 +79790,9 @@ var healthModule = {
79674
79790
  }
79675
79791
  return c3.json({ status: "error", db: "unreachable" }, 503);
79676
79792
  });
79793
+ app.get("/api/project", (c3) => {
79794
+ return c3.json({ name: ctx ? basename3(ctx.cwd) : null });
79795
+ });
79677
79796
  }
79678
79797
  };
79679
79798
 
@@ -80174,11 +80293,13 @@ var teamModule = {
80174
80293
  name: team.name,
80175
80294
  members: team.specs.map((spec) => {
80176
80295
  const proc = processes.find((p2) => p2.agentId === spec.id);
80296
+ const configModel = spec.config?.model;
80177
80297
  return {
80178
80298
  id: spec.id,
80179
80299
  type: spec.type,
80180
80300
  status: proc?.status ?? "unknown",
80181
80301
  autoStart: spec.autoStart === true,
80302
+ ...typeof configModel === "string" && configModel.length > 0 ? { model: configModel } : {},
80182
80303
  ...proc?.pid !== undefined ? { pid: proc.pid } : {}
80183
80304
  };
80184
80305
  })
@@ -83810,6 +83931,46 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
83810
83931
  context4.setExitCode(1);
83811
83932
  }
83812
83933
  });
83934
+ task.command("run-link").summary("Record a pipeline provenance link for a task (used by --next auto chains to satisfy testing\u2192done guard).").argument("<wbs>", "Task WBS number").option("--source <source>", "Link source identifier (e.g. next-auto)", "chain").option("--run-id <id>", "Explicit run_id (auto-generated when omitted)").option("--json", "Output machine-readable JSON").action(async (wbs, options) => {
83935
+ try {
83936
+ const db2 = await context4.getDb();
83937
+ const dao2 = new TaskRunLinkDao(db2);
83938
+ const existing = await dao2.listByWbs(wbs, 20);
83939
+ if (existing.some((row) => row.kind === "pipeline")) {
83940
+ const entry = existing.find((row) => row.kind === "pipeline");
83941
+ if (!entry) {
83942
+ context4.output.error(`Pipeline link lookup inconsistent for ${wbs}`);
83943
+ context4.setExitCode(1);
83944
+ return;
83945
+ }
83946
+ const result2 = {
83947
+ id: entry.id,
83948
+ wbs: entry.wbs,
83949
+ runId: entry.run_id,
83950
+ kind: entry.kind,
83951
+ existed: true
83952
+ };
83953
+ if (options.json) {
83954
+ context4.output.write(toJson2(result2));
83955
+ } else {
83956
+ context4.output.write(`Pipeline run-link already exists for ${wbs} (${entry.id}). Skipped.`);
83957
+ }
83958
+ return;
83959
+ }
83960
+ const id = createId("trl");
83961
+ const runId = options.runId ?? `chain:${options.source}:${wbs}:${Date.now()}`;
83962
+ await dao2.insert({ id, wbs, run_id: runId, kind: "pipeline", created_at: new Date().toISOString() });
83963
+ const result = { id, wbs, runId, kind: "pipeline" };
83964
+ if (options.json) {
83965
+ context4.output.write(toJson2(result));
83966
+ } else {
83967
+ context4.output.write(`Recorded pipeline run-link ${id} for task ${wbs} (source: ${options.source})`);
83968
+ }
83969
+ } catch (err) {
83970
+ context4.output.error(String(err));
83971
+ context4.setExitCode(1);
83972
+ }
83973
+ });
83813
83974
  }
83814
83975
  async function makeService2(context4, folderOverride, noLifecycle = false) {
83815
83976
  const foldersConfig = (await resolvePlanningFolders(context4.fs)).foldersConfig;