@gobing-ai/spur 0.4.9 → 0.4.11

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 (89) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/rules/README.md +1 -1
  3. package/config/rules/boundary/env-var-hygiene.yaml +0 -1
  4. package/config/rules/boundary/sp-script-placement.yaml +3 -3
  5. package/config/rules/docs/sourced-api-claims.yaml +23 -96
  6. package/config/workflows/feature-verification.yaml +1 -9
  7. package/config/workflows/pr-review.yaml +72 -224
  8. package/config/workflows/task-pipeline.yaml +2 -2
  9. package/package.json +1 -1
  10. package/plugins/sp/agents/expert-spur.md +0 -1
  11. package/plugins/sp/commands/dev-changelog.md +1 -1
  12. package/plugins/sp/commands/dev-pr-review.md +27 -17
  13. package/plugins/sp/plugin.json +1 -1
  14. package/plugins/sp/skills/brainstorm/SKILL.md +12 -11
  15. package/plugins/sp/skills/brainstorm/agents/openai.yaml +1 -1
  16. package/plugins/sp/skills/brainstorm/examples/ideation-example.md +0 -4
  17. package/plugins/sp/skills/brainstorm/metadata.openclaw +1 -1
  18. package/plugins/sp/skills/brainstorm/references/workflows.md +4 -14
  19. package/plugins/sp/skills/code-verification/references/secu-review.md +7 -4
  20. package/plugins/sp/skills/history-anatomy/references/operations.md +3 -4
  21. package/plugins/sp/skills/history-anatomy/references/report-contract.md +2 -3
  22. package/plugins/sp/skills/issue-finding/SKILL.md +1 -1
  23. package/plugins/sp/skills/parallel-execution/SKILL.md +1 -1
  24. package/plugins/sp/skills/parallel-execution/references/result-synthesis.md +9 -10
  25. package/plugins/sp/skills/pr-reviewing/SKILL.md +157 -210
  26. package/plugins/sp/skills/reverse-engineering/SKILL.md +4 -6
  27. package/plugins/sp/skills/session-review/SKILL.md +10 -26
  28. package/plugins/sp/skills/source-driven-development/SKILL.md +35 -31
  29. package/plugins/sp/skills/spur-cli/SKILL.md +1 -1
  30. package/plugins/sp/skills/spur-cli/references/agent.md +5 -2
  31. package/plugins/sp/skills/spur-cli/references/builder.md +86 -13
  32. package/plugins/sp/skills/spur-cli/references/features.md +1 -1
  33. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +1 -1
  34. package/plugins/sp/skills/spur-dev/references/dev-operations.md +5 -6
  35. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +1 -1
  36. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +3 -2
  37. package/spur.js +1280 -417
  38. package/web/_astro/{BoardApp.CaNvQBD_.js → BoardApp.B5rTVNd_.js} +1 -1
  39. package/web/_astro/{BoardApp.C5jnkkKP.js → BoardApp.BMprmPRc.js} +141 -141
  40. package/web/_astro/{TaskDetail.DGKndhI8.js → TaskDetail.Ddsur8qd.js} +1 -1
  41. package/web/_astro/{arc.DHrwdg3G.js → arc.DLqVVBxP.js} +1 -1
  42. package/web/_astro/{architectureDiagram-3BPJPVTR.pvKpK1s5.js → architectureDiagram-3BPJPVTR.C-Pk3uAi.js} +1 -1
  43. package/web/_astro/{blockDiagram-GPEHLZMM.BQmRcZsS.js → blockDiagram-GPEHLZMM.BRO1i_Nm.js} +1 -1
  44. package/web/_astro/{c4Diagram-AAUBKEIU.CCLjHeH1.js → c4Diagram-AAUBKEIU.BU_cWu6e.js} +1 -1
  45. package/web/_astro/channel.Bs3Illhy.js +1 -0
  46. package/web/_astro/{chunk-2J33WTMH.DL6Yuoma.js → chunk-2J33WTMH.D2VXiQ2t.js} +1 -1
  47. package/web/_astro/{chunk-4BX2VUAB.CzlyEI7l.js → chunk-4BX2VUAB.BcHT772J.js} +1 -1
  48. package/web/_astro/{chunk-55IACEB6.B60EWxp1.js → chunk-55IACEB6.DhDtICmo.js} +1 -1
  49. package/web/_astro/{chunk-727SXJPM.c7v_fRLo.js → chunk-727SXJPM.BoKHp7gV.js} +1 -1
  50. package/web/_astro/{chunk-AQP2D5EJ.8BxLWdDc.js → chunk-AQP2D5EJ.Chu4JRpE.js} +1 -1
  51. package/web/_astro/{chunk-FMBD7UC4.DmKuxGFT.js → chunk-FMBD7UC4.wHdwKPlq.js} +1 -1
  52. package/web/_astro/{chunk-ND2GUHAM.1Zwd4mqV.js → chunk-ND2GUHAM.ffN-M1JB.js} +1 -1
  53. package/web/_astro/{chunk-QZHKN3VN.BvYLu-A1.js → chunk-QZHKN3VN.FRubwg4S.js} +1 -1
  54. package/web/_astro/{classDiagram-4FO5ZUOK.CEqRt0WU.js → classDiagram-4FO5ZUOK.c22tQsc_.js} +1 -1
  55. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CEqRt0WU.js → classDiagram-v2-Q7XG4LA2.c22tQsc_.js} +1 -1
  56. package/web/_astro/{cose-bilkent-S5V4N54A.C7JiUdj2.js → cose-bilkent-S5V4N54A.B9c6fiSj.js} +1 -1
  57. package/web/_astro/{cynefin-OW5HDTMX.B6mZ-1be.js → cynefin-OW5HDTMX.DrZsrUHl.js} +1 -1
  58. package/web/_astro/{dagre-BM42HDAG.BuryfDy6.js → dagre-BM42HDAG.DG1U4b3y.js} +1 -1
  59. package/web/_astro/{diagram-2AECGRRQ.dao39YQM.js → diagram-2AECGRRQ.CH15b-K3.js} +1 -1
  60. package/web/_astro/{diagram-5GNKFQAL.lnGZe4At.js → diagram-5GNKFQAL.BH1j7twR.js} +1 -1
  61. package/web/_astro/{diagram-KO2AKTUF.uWfhzEfT.js → diagram-KO2AKTUF.q2zCkbAj.js} +1 -1
  62. package/web/_astro/{diagram-LMA3HP47.ppq8-198.js → diagram-LMA3HP47.BLaaZnHV.js} +1 -1
  63. package/web/_astro/{diagram-OG6HWLK6.CKSCqDt5.js → diagram-OG6HWLK6.BcpRdQwI.js} +1 -1
  64. package/web/_astro/{erDiagram-TEJ5UH35.sFSf2yBN.js → erDiagram-TEJ5UH35.D7KBlqfM.js} +1 -1
  65. package/web/_astro/{flowDiagram-I6XJVG4X.BJQjKLbz.js → flowDiagram-I6XJVG4X.D_gmuRqg.js} +1 -1
  66. package/web/_astro/{ganttDiagram-6RSMTGT7.DAHKNj2N.js → ganttDiagram-6RSMTGT7.DzgWYniD.js} +1 -1
  67. package/web/_astro/{gitGraphDiagram-PVQCEYII.BlmzcW_O.js → gitGraphDiagram-PVQCEYII.576i3hm3.js} +1 -1
  68. package/web/_astro/index.DiC1a6lh.css +1 -0
  69. package/web/_astro/{infoDiagram-5YYISTIA.DGhjaqsi.js → infoDiagram-5YYISTIA.DMCclBiz.js} +1 -1
  70. package/web/_astro/{ishikawaDiagram-YF4QCWOH.gKIpgt6F.js → ishikawaDiagram-YF4QCWOH.9Kyca4SH.js} +1 -1
  71. package/web/_astro/{journeyDiagram-JHISSGLW.DG3tmP0R.js → journeyDiagram-JHISSGLW.chbdG-8q.js} +1 -1
  72. package/web/_astro/{kanban-definition-UN3LZRKU.CgHDSxkR.js → kanban-definition-UN3LZRKU.Bds54-e0.js} +1 -1
  73. package/web/_astro/{linear.BuGEpcuM.js → linear.D5ND9GN_.js} +1 -1
  74. package/web/_astro/{mermaid.core.DBVqkWBp.js → mermaid.core.D9N_PGbj.js} +4 -4
  75. package/web/_astro/{mindmap-definition-RKZ34NQL.D-kOcji9.js → mindmap-definition-RKZ34NQL.t0xop5vp.js} +1 -1
  76. package/web/_astro/{pieDiagram-4H26LBE5.BvGPwtZt.js → pieDiagram-4H26LBE5.DqgKh6hM.js} +1 -1
  77. package/web/_astro/{quadrantDiagram-W4KKPZXB.BKb_9FV6.js → quadrantDiagram-W4KKPZXB.DeuHVi2t.js} +1 -1
  78. package/web/_astro/{requirementDiagram-4Y6WPE33.Cx0fvD8v.js → requirementDiagram-4Y6WPE33.BdYFSG4W.js} +1 -1
  79. package/web/_astro/{sankeyDiagram-5OEKKPKP.fatULi0W.js → sankeyDiagram-5OEKKPKP.k1LPPETJ.js} +1 -1
  80. package/web/_astro/{sequenceDiagram-3UESZ5HK.ChHyKqco.js → sequenceDiagram-3UESZ5HK.DdyfpzoG.js} +1 -1
  81. package/web/_astro/{stateDiagram-AJRCARHV.BdUpKWyj.js → stateDiagram-AJRCARHV.DSEI8il0.js} +1 -1
  82. package/web/_astro/{stateDiagram-v2-BHNVJYJU.fmJxqIm5.js → stateDiagram-v2-BHNVJYJU.CoMnQRKo.js} +1 -1
  83. package/web/_astro/{timeline-definition-PNZ67QCA.BayDn5-V.js → timeline-definition-PNZ67QCA.DM1AHNm-.js} +1 -1
  84. package/web/_astro/{vennDiagram-CIIHVFJN.Bm2Aggwa.js → vennDiagram-CIIHVFJN.C-cJmVtl.js} +1 -1
  85. package/web/_astro/{wardleyDiagram-YWT4CUSO.DI75O-uG.js → wardleyDiagram-YWT4CUSO.D0xMzKPb.js} +1 -1
  86. package/web/_astro/{xychartDiagram-2RQKCTM6.DL90E7Q7.js → xychartDiagram-2RQKCTM6.BH5xJcEq.js} +1 -1
  87. package/web/index.html +2 -2
  88. package/web/_astro/channel.C7wG9RxA.js +0 -1
  89. package/web/_astro/index.BCY1JJHd.css +0 -1
package/spur.js CHANGED
@@ -14967,7 +14967,6 @@ var init_finding_codes = __esm(() => {
14967
14967
  "L4.feature-receipt-run",
14968
14968
  "L4.feature-receipt-stale",
14969
14969
  "L4.feature-receipt-contract",
14970
- "L4.feature-receipt-confidence",
14971
14970
  "precheck-size",
14972
14971
  "precheck-evidence",
14973
14972
  "inventory-coverage",
@@ -15040,7 +15039,6 @@ var init_finding_codes = __esm(() => {
15040
15039
  L4_FEATURE_RECEIPT_RUN: "L4.feature-receipt-run",
15041
15040
  L4_FEATURE_RECEIPT_STALE: "L4.feature-receipt-stale",
15042
15041
  L4_FEATURE_RECEIPT_CONTRACT: "L4.feature-receipt-contract",
15043
- L4_FEATURE_RECEIPT_CONFIDENCE: "L4.feature-receipt-confidence",
15044
15042
  PRECHECK_SIZE: "precheck-size",
15045
15043
  PRECHECK_EVIDENCE: "precheck-evidence",
15046
15044
  INVENTORY_COVERAGE: "inventory-coverage",
@@ -15065,6 +15063,7 @@ var init_projects = __esm(() => {
15065
15063
  projectEntrySchema = object2({
15066
15064
  name: string2().min(1),
15067
15065
  path: string2().min(1),
15066
+ host: string2().optional(),
15068
15067
  port: number2().int().nonnegative().default(0)
15069
15068
  });
15070
15069
  projectsFileSchema = object2({
@@ -85638,6 +85637,12 @@ function normalizeProjectPath(pathInput) {
85638
85637
  function setPortProbeForTests(probe) {
85639
85638
  testPortProbe = probe;
85640
85639
  }
85640
+ function isLocalHost(host) {
85641
+ if (!host)
85642
+ return true;
85643
+ const trimmed = host.trim().toLowerCase();
85644
+ return trimmed === "localhost" || trimmed === "127.0.0.1" || trimmed === "::1" || trimmed === "";
85645
+ }
85641
85646
  function classifyPortBindError(err) {
85642
85647
  const code = err && typeof err === "object" && "code" in err ? String(err.code) : "";
85643
85648
  if (code === "EADDRINUSE" || code === "EADDRNOTAVAIL") {
@@ -85648,31 +85653,34 @@ function classifyPortBindError(err) {
85648
85653
  async function isPortLiveOnHost(port, host, timeoutMs) {
85649
85654
  return new Promise((resolve) => {
85650
85655
  const socket = connect({ port, host, timeout: timeoutMs });
85651
- socket.on("connect", () => {
85652
- socket.destroy();
85653
- resolve(true);
85654
- });
85655
- socket.on("timeout", () => {
85656
- socket.destroy();
85657
- resolve(false);
85658
- });
85659
- socket.on("error", () => {
85656
+ socket.unref();
85657
+ let settled = false;
85658
+ const cleanup = (res) => {
85659
+ if (settled)
85660
+ return;
85661
+ settled = true;
85660
85662
  socket.destroy();
85661
- resolve(false);
85662
- });
85663
+ resolve(res);
85664
+ };
85665
+ socket.on("connect", () => cleanup(true));
85666
+ socket.on("timeout", () => cleanup(false));
85667
+ socket.on("error", () => cleanup(false));
85663
85668
  });
85664
85669
  }
85665
- async function isPortLive(port, timeoutMs = 200) {
85670
+ async function isPortLive(port, timeoutMs = 200, host = "localhost") {
85666
85671
  if (testPortProbe) {
85667
- return await testPortProbe(port) === "in-use";
85672
+ return await testPortProbe(port, host) === "in-use";
85668
85673
  }
85669
- if (port <= 0)
85674
+ if (port <= 0 || port > 65535)
85670
85675
  return false;
85671
- if (await isPortLiveOnHost(port, "127.0.0.1", timeoutMs))
85672
- return true;
85673
- if (await isPortLiveOnHost(port, "::1", timeoutMs))
85674
- return true;
85675
- return false;
85676
+ if (isLocalHost(host)) {
85677
+ if (await isPortLiveOnHost(port, "127.0.0.1", timeoutMs))
85678
+ return true;
85679
+ if (await isPortLiveOnHost(port, "::1", timeoutMs))
85680
+ return true;
85681
+ return false;
85682
+ }
85683
+ return isPortLiveOnHost(port, host, timeoutMs);
85676
85684
  }
85677
85685
  async function probePort(port) {
85678
85686
  if (testPortProbe) {
@@ -85943,10 +85951,12 @@ class ProjectRegistry {
85943
85951
  const data = this.readRaw();
85944
85952
  let changed = false;
85945
85953
  for (const project of data.projects) {
85946
- const normalized = normalizeProjectPath(project.path);
85947
- if (normalized !== project.path) {
85948
- project.path = normalized;
85949
- changed = true;
85954
+ if (isLocalHost(project.host)) {
85955
+ const normalized = normalizeProjectPath(project.path);
85956
+ if (normalized !== project.path) {
85957
+ project.path = normalized;
85958
+ changed = true;
85959
+ }
85950
85960
  }
85951
85961
  }
85952
85962
  if (changed) {
@@ -85957,13 +85967,17 @@ class ProjectRegistry {
85957
85967
  async list() {
85958
85968
  await this.healTildePaths();
85959
85969
  await this.refreshProjects();
85960
- await this.healStale();
85961
85970
  return this.readRaw().projects;
85962
85971
  }
85963
85972
  async getByPath(pathInput) {
85964
85973
  const normalized = normalizeProjectPath(pathInput);
85965
85974
  const projects = await this.list();
85966
- return projects.find((p) => normalizeProjectPath(p.path) === normalized);
85975
+ return projects.find((p) => {
85976
+ if (isLocalHost(p.host)) {
85977
+ return normalizeProjectPath(p.path) === normalized;
85978
+ }
85979
+ return p.path === pathInput || p.path === normalized;
85980
+ });
85967
85981
  }
85968
85982
  async getByName(nameInput) {
85969
85983
  const trimmed = nameInput.trim();
@@ -85973,11 +85987,20 @@ class ProjectRegistry {
85973
85987
  async upsert(entry) {
85974
85988
  return this.withLock(async () => {
85975
85989
  const currentData = this.readRaw();
85976
- const normalizedPath = normalizeProjectPath(entry.path);
85977
- const index = currentData.projects.findIndex((p) => normalizeProjectPath(p.path) === normalizedPath);
85990
+ const host = entry.host && entry.host.trim() !== "" ? entry.host.trim() : "localhost";
85991
+ const isLocal = isLocalHost(host);
85992
+ const normalizedPath = isLocal ? normalizeProjectPath(entry.path) : entry.path;
85993
+ const index = currentData.projects.findIndex((p) => {
85994
+ const pHost = p.host && p.host.trim() !== "" ? p.host.trim() : "localhost";
85995
+ if (isLocal) {
85996
+ return isLocalHost(pHost) && normalizeProjectPath(p.path) === normalizedPath;
85997
+ }
85998
+ return pHost.toLowerCase() === host.toLowerCase() && p.path === normalizedPath;
85999
+ });
85978
86000
  const newEntry = {
85979
86001
  name: entry.name,
85980
86002
  path: normalizedPath,
86003
+ host,
85981
86004
  port: entry.port ?? 0
85982
86005
  };
85983
86006
  if (index >= 0 && currentData.projects[index]) {
@@ -85985,13 +86008,20 @@ class ProjectRegistry {
85985
86008
  currentData.projects[index] = {
85986
86009
  ...existing,
85987
86010
  name: entry.name,
86011
+ host: entry.host ?? existing.host ?? "localhost",
85988
86012
  port: entry.port ?? existing.port
85989
86013
  };
85990
86014
  } else {
85991
86015
  currentData.projects.push(newEntry);
85992
86016
  }
85993
86017
  this.writeRaw(currentData);
85994
- const res = currentData.projects.find((p) => normalizeProjectPath(p.path) === normalizedPath);
86018
+ const res = currentData.projects.find((p) => {
86019
+ const pHost = p.host && p.host.trim() !== "" ? p.host.trim() : "localhost";
86020
+ if (isLocal) {
86021
+ return isLocalHost(pHost) && normalizeProjectPath(p.path) === normalizedPath;
86022
+ }
86023
+ return pHost.toLowerCase() === host.toLowerCase() && p.path === normalizedPath;
86024
+ });
85995
86025
  if (!res) {
85996
86026
  throw new Error(`Failed to upsert project: ${normalizedPath}`);
85997
86027
  }
@@ -86004,9 +86034,10 @@ class ProjectRegistry {
86004
86034
  const normalized = normalizeProjectPath(nameOrPath);
86005
86035
  const initialLen = currentData.projects.length;
86006
86036
  currentData.projects = currentData.projects.filter((p) => {
86007
- const pNorm = normalizeProjectPath(p.path);
86037
+ const pNorm = isLocalHost(p.host) ? normalizeProjectPath(p.path) : p.path;
86038
+ const pathMatch = pNorm === normalized || p.path === nameOrPath;
86008
86039
  const nameMatch = p.name.toLowerCase() === nameOrPath.trim().toLowerCase();
86009
- return pNorm !== normalized && !nameMatch;
86040
+ return !pathMatch && !nameMatch;
86010
86041
  });
86011
86042
  if (currentData.projects.length !== initialLen) {
86012
86043
  this.writeRaw(currentData);
@@ -86019,7 +86050,10 @@ class ProjectRegistry {
86019
86050
  return this.withLock(async () => {
86020
86051
  const currentData = this.readRaw();
86021
86052
  const normalized = normalizeProjectPath(pathInput);
86022
- const project = currentData.projects.find((p) => normalizeProjectPath(p.path) === normalized);
86053
+ const project = currentData.projects.find((p) => {
86054
+ const pNorm = isLocalHost(p.host) ? normalizeProjectPath(p.path) : p.path;
86055
+ return pNorm === normalized || p.path === pathInput;
86056
+ });
86023
86057
  if (project) {
86024
86058
  project.port = port;
86025
86059
  this.writeRaw(currentData);
@@ -86057,10 +86091,13 @@ class ProjectRegistry {
86057
86091
  return this.withLock(async () => {
86058
86092
  const data = this.readRaw();
86059
86093
  let changed = false;
86094
+ const resetProjects = [];
86060
86095
  for (const project of data.projects) {
86061
86096
  if (project.port > 0) {
86062
- const live = await isPortLive(project.port);
86097
+ const host = project.host ?? "localhost";
86098
+ const live = await isPortLive(project.port, 200, host);
86063
86099
  if (!live) {
86100
+ resetProjects.push({ ...project });
86064
86101
  project.port = 0;
86065
86102
  changed = true;
86066
86103
  }
@@ -86069,6 +86106,7 @@ class ProjectRegistry {
86069
86106
  if (changed) {
86070
86107
  this.writeRaw(data);
86071
86108
  }
86109
+ return resetProjects;
86072
86110
  });
86073
86111
  }
86074
86112
  async refreshProjects(options) {
@@ -86078,9 +86116,15 @@ class ProjectRegistry {
86078
86116
  await Promise.resolve().then(() => init_project_stop());
86079
86117
  const removed = await this.withLock(async () => {
86080
86118
  const data = this.readRaw();
86081
- const purged = data.projects.filter((p) => !existsSync8(normalizeProjectPath(p.path)));
86119
+ const purged = data.projects.filter((p) => {
86120
+ if (isLocalHost(p.host)) {
86121
+ return !existsSync8(normalizeProjectPath(p.path));
86122
+ }
86123
+ return false;
86124
+ });
86082
86125
  if (purged.length > 0) {
86083
- data.projects = data.projects.filter((p) => existsSync8(normalizeProjectPath(p.path)));
86126
+ const purgedPaths = new Set(purged.map((p) => `${p.host ?? "localhost"}:${normalizeProjectPath(p.path)}`));
86127
+ data.projects = data.projects.filter((p) => !purgedPaths.has(`${p.host ?? "localhost"}:${normalizeProjectPath(p.path)}`));
86084
86128
  this.writeRaw(data);
86085
86129
  }
86086
86130
  return purged;
@@ -86088,7 +86132,7 @@ class ProjectRegistry {
86088
86132
  const terminated = [];
86089
86133
  if (terminate) {
86090
86134
  for (const project of removed) {
86091
- if (project.port > 0) {
86135
+ if (project.port > 0 && isLocalHost(project.host)) {
86092
86136
  const stop = await stopRegisteredProject(project, {
86093
86137
  processExecutor: executor,
86094
86138
  killTimeoutMs,
@@ -86101,11 +86145,13 @@ class ProjectRegistry {
86101
86145
  }
86102
86146
  }
86103
86147
  }
86148
+ const resetPorts = await this.healStale();
86104
86149
  return {
86105
86150
  removed,
86106
86151
  terminated,
86107
86152
  purgedProjects: removed,
86108
- terminatedProcesses: terminated
86153
+ terminatedProcesses: terminated,
86154
+ resetPorts
86109
86155
  };
86110
86156
  }
86111
86157
  }
@@ -89363,22 +89409,11 @@ async function completeFeatureVerificationReceipt(fs, runDir, running, options)
89363
89409
  if (running.status !== "RUNNING") {
89364
89410
  throw new Error(`receipt for run ${running.runId} is already ${running.status}`);
89365
89411
  }
89366
- if (options.confidence !== undefined) {
89367
- if (!FEATURE_RECEIPT_CONFIDENCE_LEVELS.includes(options.confidence)) {
89368
- throw new Error(`confidence must be HIGH, MEDIUM or LOW (got ${JSON.stringify(options.confidence)})`);
89369
- }
89370
- if (options.confidenceBasis === undefined || options.confidenceBasis.length === 0) {
89371
- throw new Error(`confidence ${options.confidence} requires a non-empty confidenceBasis`);
89372
- }
89373
- }
89374
89412
  const receipt = {
89375
89413
  ...running,
89376
89414
  status: options.status,
89377
89415
  inputDigest: options.inputDigest ?? running.inputDigest,
89378
- completedAt: options.completedAt ?? new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
89379
- ...options.confidence === undefined ? {} : { confidence: options.confidence },
89380
- ...options.confidenceBasis === undefined ? {} : { confidenceBasis: [...options.confidenceBasis] },
89381
- ...options.confidenceAcknowledged === true ? { confidenceAcknowledged: true } : {}
89416
+ completedAt: options.completedAt ?? new Date().toISOString().replace(/\.\d{3}Z$/, "Z")
89382
89417
  };
89383
89418
  const paths = featureReceiptPaths(runDir, receipt.featureId, receipt.runId);
89384
89419
  await fs.ensureDir(runDir);
@@ -89406,23 +89441,6 @@ function parseFeatureVerificationReceipt(raw) {
89406
89441
  if (typeof r.featureId !== "string" || typeof r.runId !== "string" || typeof r.workdir !== "string" || typeof v?.name !== "string" || typeof v?.sourcePath !== "string" || typeof v?.layer !== "string" || typeof v?.definitionDigest !== "string" || typeof r.verificationCmd !== "string" || typeof r.inputDigest !== "string" || r.status !== "RUNNING" && r.status !== "PASS" && r.status !== "FAIL" || typeof r.startedAt !== "string" || r.completedAt !== null && typeof r.completedAt !== "string") {
89407
89442
  throw new Error("required fields missing or mistyped");
89408
89443
  }
89409
- if (r.confidence !== undefined) {
89410
- if (!FEATURE_RECEIPT_CONFIDENCE_LEVELS.includes(r.confidence)) {
89411
- throw new Error(`confidence must be HIGH, MEDIUM or LOW (got ${JSON.stringify(r.confidence)})`);
89412
- }
89413
- if (!Array.isArray(r.confidenceBasis) || r.confidenceBasis.length === 0) {
89414
- throw new Error("confidence requires a non-empty confidenceBasis");
89415
- }
89416
- if (!r.confidenceBasis.every((entry) => typeof entry === "string" && entry.length > 0)) {
89417
- throw new Error("confidenceBasis entries must be non-empty strings");
89418
- }
89419
- }
89420
- if (r.confidenceBasis !== undefined && !Array.isArray(r.confidenceBasis)) {
89421
- throw new Error("confidenceBasis must be an array");
89422
- }
89423
- if (r.confidenceAcknowledged !== undefined && typeof r.confidenceAcknowledged !== "boolean") {
89424
- throw new Error("confidenceAcknowledged must be a boolean");
89425
- }
89426
89444
  return parsed;
89427
89445
  }
89428
89446
  async function readReceiptCopy(fs, path, label) {
@@ -89560,15 +89578,6 @@ async function validateFeatureVerificationReceipt(fs, options) {
89560
89578
  detail: `receipt digest ${latest.inputDigest} \u2260 current inputs ${options.currentDigest} \u2014 tree, spec, learnings or check contract changed after the pass`
89561
89579
  };
89562
89580
  }
89563
- const confidence = latest.confidence;
89564
- const certified = confidence === "HIGH" || confidence === "MEDIUM";
89565
- if (!certified && latest.confidenceAcknowledged !== true) {
89566
- return {
89567
- ok: false,
89568
- reason: "unacknowledged-low-confidence",
89569
- detail: confidence === undefined ? "receipt records a PASS with no confidence level (written before the field existed) \u2014 re-run the feature-scoped verification pass, which derives HIGH or MEDIUM from its own evidence" : `receipt records a ${confidence} confidence${(latest.confidenceBasis ?? []).length > 0 ? ` (basis: ${(latest.confidenceBasis ?? []).join("; ")})` : ""} \u2014 re-run the pass where the level can be HIGH or MEDIUM, or re-run it with the acknowledgement (ackLowConfidence) when LOW is the honest level`
89570
- };
89571
- }
89572
89581
  return { ok: true, receipt: latest };
89573
89582
  }
89574
89583
  async function captureFeatureReceiptDigest(repoRoot, featureContent, learningsContent) {
@@ -89577,12 +89586,11 @@ async function captureFeatureReceiptDigest(repoRoot, featureContent, learningsCo
89577
89586
  function isProofCaptureError(err) {
89578
89587
  return err instanceof ProofCaptureError;
89579
89588
  }
89580
- var FEATURE_RECEIPT_SCHEMA_VERSION = 1, DEFAULT_FEATURE_VERIFICATION_CMD = "bun run spur-check-feature", FEATURE_RECEIPT_CONFIDENCE_LEVELS, RECEIPT_ID_PATTERN;
89589
+ var FEATURE_RECEIPT_SCHEMA_VERSION = 1, DEFAULT_FEATURE_VERIFICATION_CMD = "bun run spur-check-feature", RECEIPT_ID_PATTERN;
89581
89590
  var init_feature_verification_receipt = __esm(() => {
89582
89591
  init_durable_plane_ignore();
89583
89592
  init_run_path();
89584
89593
  init_proof_input_fingerprint();
89585
- FEATURE_RECEIPT_CONFIDENCE_LEVELS = ["HIGH", "MEDIUM", "LOW"];
89586
89594
  RECEIPT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
89587
89595
  });
89588
89596
 
@@ -92018,7 +92026,6 @@ var init_planning_check_base = __esm(() => {
92018
92026
  FINDING_CODES.L4_FEATURE_RECEIPT_RUN,
92019
92027
  FINDING_CODES.L4_FEATURE_RECEIPT_STALE,
92020
92028
  FINDING_CODES.L4_FEATURE_RECEIPT_CONTRACT,
92021
- FINDING_CODES.L4_FEATURE_RECEIPT_CONFIDENCE,
92022
92029
  FINDING_CODES.L3_REVIEW_TESTING_CONTRADICTION,
92023
92030
  FINDING_CODES.L4_ROLLUP_SUBTASKS_OPEN,
92024
92031
  FINDING_CODES.L4_ROLLUP_MISSING_ROSTER,
@@ -93604,8 +93611,7 @@ var init_feature_check = __esm(() => {
93604
93611
  failed: FINDING_CODES.L4_FEATURE_RECEIPT_FAILED,
93605
93612
  run: FINDING_CODES.L4_FEATURE_RECEIPT_RUN,
93606
93613
  stale: FINDING_CODES.L4_FEATURE_RECEIPT_STALE,
93607
- "contract-mismatch": FINDING_CODES.L4_FEATURE_RECEIPT_CONTRACT,
93608
- "unacknowledged-low-confidence": FINDING_CODES.L4_FEATURE_RECEIPT_CONFIDENCE
93614
+ "contract-mismatch": FINDING_CODES.L4_FEATURE_RECEIPT_CONTRACT
93609
93615
  };
93610
93616
  findings.push({
93611
93617
  layer: "L4",
@@ -101835,39 +101841,6 @@ var init_proof_fingerprint = __esm(() => {
101835
101841
  ACCEPTED_OPTION_KEYS = ["expect", "featureFile", "taskFile", "var"];
101836
101842
  });
101837
101843
 
101838
- // ../../packages/app/src/workflow/actions/response-validate.ts
101839
- class ResponseValidateActionRunner {
101840
- kind = KIND14;
101841
- engine;
101842
- constructor(engine) {
101843
- this.engine = engine;
101844
- }
101845
- async execute(options, _context) {
101846
- const text = asString5(options.text);
101847
- if (text === undefined) {
101848
- return {
101849
- ok: false,
101850
- error: "response.validate: text is required (use a template like {{ steps.generate.answer }})"
101851
- };
101852
- }
101853
- const result = this.engine.validate(text);
101854
- return {
101855
- ok: result.ok,
101856
- data: {
101857
- reason: result.reason,
101858
- ...result.issues !== undefined ? { issues: result.issues } : {}
101859
- },
101860
- error: result.ok ? undefined : result.reason
101861
- };
101862
- }
101863
- }
101864
- function asString5(value) {
101865
- if (value === undefined || value === null)
101866
- return;
101867
- return String(value);
101868
- }
101869
- var KIND14 = "response.validate";
101870
-
101871
101844
  // ../../packages/app/src/workflow/utils.ts
101872
101845
  var identity5 = (s) => s, NO_COLOR;
101873
101846
  var init_utils3 = __esm(() => {
@@ -101883,15 +101856,15 @@ var init_utils3 = __esm(() => {
101883
101856
 
101884
101857
  // ../../packages/app/src/workflow/actions/rule-check.ts
101885
101858
  class RuleCheckActionRunner {
101886
- kind = KIND15;
101859
+ kind = KIND14;
101887
101860
  ruleService;
101888
101861
  constructor(ruleService) {
101889
101862
  this.ruleService = ruleService;
101890
101863
  }
101891
101864
  async execute(options, _context) {
101892
- const preset = asString6(options.preset) ?? "recommended-pre-check";
101893
- const rule = asString6(options.rule);
101894
- const failOn = asString6(options.failOn) ?? "error";
101865
+ const preset = asString5(options.preset) ?? "recommended-pre-check";
101866
+ const rule = asString5(options.rule);
101867
+ const failOn = asString5(options.failOn) ?? "error";
101895
101868
  const result = await this.ruleService.evaluate({
101896
101869
  preset,
101897
101870
  failOn,
@@ -101913,12 +101886,12 @@ class RuleCheckActionRunner {
101913
101886
  };
101914
101887
  }
101915
101888
  }
101916
- function asString6(value) {
101889
+ function asString5(value) {
101917
101890
  if (value === undefined || value === null)
101918
101891
  return;
101919
101892
  return String(value);
101920
101893
  }
101921
- var KIND15 = "rule.check";
101894
+ var KIND14 = "rule.check";
101922
101895
  var init_rule_check = __esm(() => {
101923
101896
  init_utils3();
101924
101897
  });
@@ -101986,7 +101959,7 @@ class RunArtifactActionRunner {
101986
101959
  fileSystem;
101987
101960
  artifactDao;
101988
101961
  processExecutor;
101989
- kind = KIND16;
101962
+ kind = KIND15;
101990
101963
  constructor(getDb, fileSystem = createNodeFileSystem2(), artifactDao, processExecutor) {
101991
101964
  this.getDb = getDb;
101992
101965
  this.fileSystem = fileSystem;
@@ -102012,7 +101985,7 @@ class RunArtifactActionRunner {
102012
101985
  if (proofBinding !== undefined && proofBinding !== "current") {
102013
101986
  return {
102014
101987
  ok: false,
102015
- error: `${KIND16}: unsupported proofBinding "${String(proofBinding)}" - only "current" is defined (ADR-071)`
101988
+ error: `${KIND15}: unsupported proofBinding "${String(proofBinding)}" - only "current" is defined (ADR-071)`
102016
101989
  };
102017
101990
  }
102018
101991
  const workdir = context.workdir ?? process.cwd();
@@ -102046,7 +102019,7 @@ class RunArtifactActionRunner {
102046
102019
  try {
102047
102020
  registeredPath = await persistDurableArtifact(normalized, workdir, context.runId, this.fileSystem, !requireExisting);
102048
102021
  } catch (error) {
102049
- return { ok: false, error: `${KIND16}: durable persist failed: ${error.message}` };
102022
+ return { ok: false, error: `${KIND15}: durable persist failed: ${error.message}` };
102050
102023
  }
102051
102024
  }
102052
102025
  let dao = this.artifactDao;
@@ -102077,24 +102050,24 @@ class RunArtifactActionRunner {
102077
102050
  if (artifactKind !== "verify-verdict") {
102078
102051
  return {
102079
102052
  ok: false,
102080
- error: `${KIND16}: proofBinding "current" is only supported for the verify-verdict artifact kind (got "${artifactKind}")`
102053
+ error: `${KIND15}: proofBinding "current" is only supported for the verify-verdict artifact kind (got "${artifactKind}")`
102081
102054
  };
102082
102055
  }
102083
102056
  if (!this.getDb) {
102084
102057
  return {
102085
102058
  ok: false,
102086
- error: `${KIND16}: proofBinding "current" requires the composed database context (RunDao run identity) \u2014 refusing an unverifiable binding`
102059
+ error: `${KIND15}: proofBinding "current" requires the composed database context (RunDao run identity) \u2014 refusing an unverifiable binding`
102087
102060
  };
102088
102061
  }
102089
102062
  if (typeof options.taskFile !== "string" || options.taskFile.trim() === "") {
102090
102063
  return {
102091
102064
  ok: false,
102092
- error: `${KIND16}: proofBinding "current" requires a non-empty "taskFile" option \u2014 the fresh digest must cover the canonical task spec`
102065
+ error: `${KIND15}: proofBinding "current" requires a non-empty "taskFile" option \u2014 the fresh digest must cover the canonical task spec`
102093
102066
  };
102094
102067
  }
102095
102068
  const inputs = await readProofInputContents(this.fileSystem, workdir, options);
102096
102069
  if (!inputs.ok) {
102097
- return { ok: false, error: `${KIND16}: ${inputs.error}` };
102070
+ return { ok: false, error: `${KIND15}: ${inputs.error}` };
102098
102071
  }
102099
102072
  let digest;
102100
102073
  try {
@@ -102106,19 +102079,19 @@ class RunArtifactActionRunner {
102106
102079
  fileSystem: this.fileSystem
102107
102080
  });
102108
102081
  } catch (error) {
102109
- return { ok: false, error: `${KIND16}: fresh proof capture failed: ${error.message}` };
102082
+ return { ok: false, error: `${KIND15}: fresh proof capture failed: ${error.message}` };
102110
102083
  }
102111
102084
  const varDigest = context.vars.proofDigestNow ?? context.vars.proofDigest;
102112
102085
  if (typeof varDigest !== "string" || !PROOF_DIGEST_RE.test(varDigest)) {
102113
102086
  return {
102114
102087
  ok: false,
102115
- error: `${KIND16}: proofBinding "current" does not hold - the run has no current proof input ` + `digest (vars.proofDigestNow/proofDigest missing or malformed), so the artifact cannot be ` + `bound to a proof that was never captured; capture one with proof.fingerprint first`
102088
+ error: `${KIND15}: proofBinding "current" does not hold - the run has no current proof input ` + `digest (vars.proofDigestNow/proofDigest missing or malformed), so the artifact cannot be ` + `bound to a proof that was never captured; capture one with proof.fingerprint first`
102116
102089
  };
102117
102090
  }
102118
102091
  if (varDigest !== digest) {
102119
102092
  return {
102120
102093
  ok: false,
102121
- error: `${KIND16}: the run's declared proof digest (${varDigest}) does not match the freshly ` + `captured digest (${digest}) \u2014 a stale or forged var cannot bless artifact content (0785 R3)`
102094
+ error: `${KIND15}: the run's declared proof digest (${varDigest}) does not match the freshly ` + `captured digest (${digest}) \u2014 a stale or forged var cannot bless artifact content (0785 R3)`
102122
102095
  };
102123
102096
  }
102124
102097
  let normalized;
@@ -102148,7 +102121,7 @@ class RunArtifactActionRunner {
102148
102121
  try {
102149
102122
  raw = await this.fileSystem.readFile(normalized);
102150
102123
  } catch (error) {
102151
- return { ok: false, error: `${KIND16}: verdict artifact unreadable: ${error.message}` };
102124
+ return { ok: false, error: `${KIND15}: verdict artifact unreadable: ${error.message}` };
102152
102125
  }
102153
102126
  const taskWbs = String(context.vars.wbs ?? "");
102154
102127
  const outcome = parseVerifyVerdict(raw, taskWbs);
@@ -102156,33 +102129,33 @@ class RunArtifactActionRunner {
102156
102129
  const detail = outcome.kind === "invalid" && outcome.reason !== undefined ? `: ${outcome.reason}` : "";
102157
102130
  return {
102158
102131
  ok: false,
102159
- error: `${KIND16}: verdict artifact is ${outcome.kind}${detail} \u2014 refusing to bind (0785 R3)`
102132
+ error: `${KIND15}: verdict artifact is ${outcome.kind}${detail} \u2014 refusing to bind (0785 R3)`
102160
102133
  };
102161
102134
  }
102162
102135
  const verdict = outcome.verdict;
102163
102136
  if (verdict.wbs !== taskWbs) {
102164
102137
  return {
102165
102138
  ok: false,
102166
- error: `${KIND16}: verdict artifact names wbs "${verdict.wbs}" but the run carries task "${taskWbs}"`
102139
+ error: `${KIND15}: verdict artifact names wbs "${verdict.wbs}" but the run carries task "${taskWbs}"`
102167
102140
  };
102168
102141
  }
102169
102142
  if (verdict.verdict !== "PASS") {
102170
102143
  return {
102171
102144
  ok: false,
102172
- error: `${KIND16}: verdict aggregate is ${verdict.verdict}, not PASS \u2014 refusing to bind`
102145
+ error: `${KIND15}: verdict aggregate is ${verdict.verdict}, not PASS \u2014 refusing to bind`
102173
102146
  };
102174
102147
  }
102175
102148
  let root;
102176
102149
  try {
102177
102150
  root = JSON.parse(raw);
102178
102151
  } catch {
102179
- return { ok: false, error: `${KIND16}: verdict artifact is malformed JSON` };
102152
+ return { ok: false, error: `${KIND15}: verdict artifact is malformed JSON` };
102180
102153
  }
102181
102154
  const proof = root.proof;
102182
102155
  if (proof === null || typeof proof !== "object" || Array.isArray(proof)) {
102183
102156
  return {
102184
102157
  ok: false,
102185
- error: `${KIND16}: verdict artifact carries no raw proof block \u2014 binding refused (0785 R3)`
102158
+ error: `${KIND15}: verdict artifact carries no raw proof block \u2014 binding refused (0785 R3)`
102186
102159
  };
102187
102160
  }
102188
102161
  const p = proof;
@@ -102196,13 +102169,13 @@ class RunArtifactActionRunner {
102196
102169
  if (p.digest !== digest) {
102197
102170
  return {
102198
102171
  ok: false,
102199
- error: `${KIND16}: proof.digest ${String(p.digest)} does not equal the freshly captured digest (${digest})`
102172
+ error: `${KIND15}: proof.digest ${String(p.digest)} does not equal the freshly captured digest (${digest})`
102200
102173
  };
102201
102174
  }
102202
102175
  if (p.runId !== context.runId) {
102203
102176
  return {
102204
102177
  ok: false,
102205
- error: `${KIND16}: proof.runId ${String(p.runId)} does not match the certifying run ${context.runId}`
102178
+ error: `${KIND15}: proof.runId ${String(p.runId)} does not match the certifying run ${context.runId}`
102206
102179
  };
102207
102180
  }
102208
102181
  const db = await this.getDb();
@@ -102210,21 +102183,21 @@ class RunArtifactActionRunner {
102210
102183
  if (runRow === undefined) {
102211
102184
  return {
102212
102185
  ok: false,
102213
- error: `${KIND16}: run ${context.runId} has no authoritative row \u2014 refusing binding (0785 R3)`
102186
+ error: `${KIND15}: run ${context.runId} has no authoritative row \u2014 refusing binding (0785 R3)`
102214
102187
  };
102215
102188
  }
102216
102189
  let metadata = {};
102217
102190
  try {
102218
102191
  metadata = JSON.parse(runRow.metadata_json || "{}");
102219
102192
  } catch {
102220
- return { ok: false, error: `${KIND16}: run ${context.runId} metadata_json is malformed \u2014 refusing binding` };
102193
+ return { ok: false, error: `${KIND15}: run ${context.runId} metadata_json is malformed \u2014 refusing binding` };
102221
102194
  }
102222
102195
  const resumeDigest = metadata.resumeDefinitionDigest;
102223
102196
  const expectedDefinition = typeof resumeDigest === "string" && resumeDigest !== "" ? resumeDigest : metadata.definitionDigest;
102224
102197
  if (typeof expectedDefinition !== "string" || p.definitionDigest !== expectedDefinition) {
102225
102198
  return {
102226
102199
  ok: false,
102227
- error: `${KIND16}: proof.definitionDigest ${String(p.definitionDigest)} does not match the run's ` + `${typeof resumeDigest === "string" && resumeDigest !== "" ? "resume " : ""}definition digest \u2014 ` + `a stale-definition artifact cannot certify this run (0785 R3)`
102200
+ error: `${KIND15}: proof.definitionDigest ${String(p.definitionDigest)} does not match the run's ` + `${typeof resumeDigest === "string" && resumeDigest !== "" ? "resume " : ""}definition digest \u2014 ` + `a stale-definition artifact cannot certify this run (0785 R3)`
102228
102201
  };
102229
102202
  }
102230
102203
  const qualityGate = stageOf("qualityGate");
@@ -102233,7 +102206,7 @@ class RunArtifactActionRunner {
102233
102206
  if (qualityGate === null || review === null || verification === null) {
102234
102207
  return {
102235
102208
  ok: false,
102236
- error: `${KIND16}: proof.stages is missing qualityGate/review/verification evidence \u2014 binding refused`
102209
+ error: `${KIND15}: proof.stages is missing qualityGate/review/verification evidence \u2014 binding refused`
102237
102210
  };
102238
102211
  }
102239
102212
  for (const [name, stage] of [
@@ -102244,26 +102217,26 @@ class RunArtifactActionRunner {
102244
102217
  if (stage.digest !== digest) {
102245
102218
  return {
102246
102219
  ok: false,
102247
- error: `${KIND16}: proof.stages.${name}.digest does not equal the freshly captured digest`
102220
+ error: `${KIND15}: proof.stages.${name}.digest does not equal the freshly captured digest`
102248
102221
  };
102249
102222
  }
102250
102223
  }
102251
102224
  if (qualityGate.status !== "PASS") {
102252
102225
  return {
102253
102226
  ok: false,
102254
- error: `${KIND16}: proof.stages.qualityGate.status is ${String(qualityGate.status)}, not PASS`
102227
+ error: `${KIND15}: proof.stages.qualityGate.status is ${String(qualityGate.status)}, not PASS`
102255
102228
  };
102256
102229
  }
102257
102230
  if (verification.status !== "PASS") {
102258
102231
  return {
102259
102232
  ok: false,
102260
- error: `${KIND16}: proof.stages.verification.status is ${String(verification.status)}, not PASS`
102233
+ error: `${KIND15}: proof.stages.verification.status is ${String(verification.status)}, not PASS`
102261
102234
  };
102262
102235
  }
102263
102236
  if (review.status !== "completed") {
102264
102237
  return {
102265
102238
  ok: false,
102266
- error: `${KIND16}: proof.stages.review.status is "${String(review.status)}" \u2014 a skipped or stale review is not completed evidence (0785 R4)`
102239
+ error: `${KIND15}: proof.stages.review.status is "${String(review.status)}" \u2014 a skipped or stale review is not completed evidence (0785 R4)`
102267
102240
  };
102268
102241
  }
102269
102242
  const markerPath = join41(resolve15(workdir), ".spur", "run", `${context.runId}-review-proof.digest`);
@@ -102276,7 +102249,7 @@ class RunArtifactActionRunner {
102276
102249
  if (marker === null || marker.trim() !== digest) {
102277
102250
  return {
102278
102251
  ok: false,
102279
- error: `${KIND16}: review-proof marker ${context.runId}-review-proof.digest is missing or does not name ` + `the current digest \u2014 review completion is not evidenced for this run (0785 R4)`
102252
+ error: `${KIND15}: review-proof marker ${context.runId}-review-proof.digest is missing or does not name ` + `the current digest \u2014 review completion is not evidenced for this run (0785 R4)`
102280
102253
  };
102281
102254
  }
102282
102255
  const dao = this.artifactDao ?? new ArtifactDao(db);
@@ -102287,7 +102260,7 @@ class RunArtifactActionRunner {
102287
102260
  registeredPath = await persistDurableArtifact(normalized, workdir, context.runId, this.fileSystem);
102288
102261
  }
102289
102262
  } catch (error) {
102290
- return { ok: false, error: `${KIND16}: durable persist failed: ${error.message}` };
102263
+ return { ok: false, error: `${KIND15}: durable persist failed: ${error.message}` };
102291
102264
  }
102292
102265
  const record = await dao.record({
102293
102266
  path: registeredPath,
@@ -102308,7 +102281,7 @@ class RunArtifactActionRunner {
102308
102281
  };
102309
102282
  }
102310
102283
  }
102311
- var copyFile2, link, mkdir2, readFile5, unlink2, writeFile3, KIND16 = "run.artifact", PROOF_DIGEST_RE;
102284
+ var copyFile2, link, mkdir2, readFile5, unlink2, writeFile3, KIND15 = "run.artifact", PROOF_DIGEST_RE;
102312
102285
  var init_run_artifact = __esm(() => {
102313
102286
  init_src2();
102314
102287
  init_dist5();
@@ -102322,7 +102295,7 @@ var init_run_artifact = __esm(() => {
102322
102295
 
102323
102296
  // ../../packages/app/src/workflow/guards/contract-violation.ts
102324
102297
  class ContractViolationGuardRunner {
102325
- kind = KIND17;
102298
+ kind = KIND16;
102326
102299
  constructor() {}
102327
102300
  async evaluate(_options, context) {
102328
102301
  const data = context.lastActionResult?.data;
@@ -102339,7 +102312,7 @@ class ContractViolationGuardRunner {
102339
102312
  };
102340
102313
  }
102341
102314
  }
102342
- var KIND17 = "contract-violation";
102315
+ var KIND16 = "contract-violation";
102343
102316
 
102344
102317
  // ../../packages/app/src/workflow/builtins.ts
102345
102318
  function registerSpurBuiltins(host, options) {
@@ -102370,9 +102343,6 @@ function registerSpurBuiltins(host, options) {
102370
102343
  if (options.httpRequester) {
102371
102344
  host.registerAction(new HttpRequestActionRunner(options.httpRequester, options.hostAllowlist ?? new Set), "builtin");
102372
102345
  }
102373
- if (options.responseValidateEngine) {
102374
- host.registerAction(new ResponseValidateActionRunner(options.responseValidateEngine), "builtin");
102375
- }
102376
102346
  }
102377
102347
  var init_builtins2 = __esm(() => {
102378
102348
  init_dist5();
@@ -108278,11 +108248,15 @@ function classifyProviderUsage(usage) {
108278
108248
  const hasWindow = RATE_WINDOW_NAMES.some((n) => usage[n] !== null && usage[n] !== undefined);
108279
108249
  return hasWindow ? { status: "headroom", exhaustedWindow: undefined } : { status: "no-usage", exhaustedWindow: undefined };
108280
108250
  }
108251
+ function agentProviderSlug(agent) {
108252
+ const lower = (agent ?? "").toLowerCase();
108253
+ return PROVIDER_ALIASES[lower] ?? lower;
108254
+ }
108281
108255
  function mapProvidersToExecutors(providers, executors) {
108282
108256
  const mapping = new Map;
108283
108257
  for (const provider of providers) {
108284
108258
  const lower = provider.toLowerCase();
108285
- const names = executors.filter((e) => (e.agent ?? "").toLowerCase() === lower || typeof e.model === "string" && e.model.split("/")[0]?.toLowerCase() === lower).map((e) => e.name);
108259
+ const names = executors.filter((e) => agentProviderSlug(e.agent) === lower || typeof e.model === "string" && e.model.split("/")[0]?.toLowerCase() === lower).map((e) => e.name);
108286
108260
  if (names.length > 0)
108287
108261
  mapping.set(provider, names);
108288
108262
  }
@@ -108534,7 +108508,7 @@ async function finalizeOutcomes(entries, context) {
108534
108508
  }
108535
108509
  }
108536
108510
  }
108537
- var codexbarWindowSchema, codexbarEntrySchema, RATE_WINDOW_NAMES, AgentUsageProducerError;
108511
+ var codexbarWindowSchema, codexbarEntrySchema, RATE_WINDOW_NAMES, AgentUsageProducerError, PROVIDER_ALIASES;
108538
108512
  var init_agent_usage_producer = __esm(() => {
108539
108513
  init_src();
108540
108514
  init_agent_quota_events();
@@ -108566,6 +108540,9 @@ var init_agent_usage_producer = __esm(() => {
108566
108540
  this.name = "AgentUsageProducerError";
108567
108541
  }
108568
108542
  };
108543
+ PROVIDER_ALIASES = {
108544
+ "antigravity-cli": "antigravity"
108545
+ };
108569
108546
  });
108570
108547
 
108571
108548
  // ../../packages/app/src/services/agent-usage-source.ts
@@ -119962,36 +119939,6 @@ async function createFeatureLookup(workdir, fileSystem = createNodeFileSystem2()
119962
119939
  return { path, content: await fileSystem.readFile(path) };
119963
119940
  };
119964
119941
  }
119965
- function deriveFeatureConfidence(input) {
119966
- const override = input.override === undefined || input.override === "" ? undefined : input.override;
119967
- const measured = [];
119968
- if (input.status === "PASS")
119969
- measured.push("verification command exited 0");
119970
- else
119971
- measured.push("verification command did not exit 0");
119972
- if (input.afterDigest !== undefined) {
119973
- measured.push(input.afterDigest === input.inputDigest ? "proof-input digest unchanged across the pass" : "proof-input digest changed across the pass");
119974
- }
119975
- if (input.status !== "PASS")
119976
- return { confidence: "LOW", basis: measured };
119977
- const derived = input.effectiveCmd === input.configuredCmd ? "HIGH" : "MEDIUM";
119978
- if (input.effectiveCmd !== input.configuredCmd) {
119979
- measured.push(`caller command override: ${input.effectiveCmd}`);
119980
- measured.push(`the selected definition declares: ${input.configuredCmd}`);
119981
- }
119982
- if (override === undefined)
119983
- return { confidence: derived, basis: measured };
119984
- return { confidence: override, basis: [`operator-declared ${override}`, ...measured] };
119985
- }
119986
- function assertConfidenceOverride(raw) {
119987
- if (raw === undefined || raw === "")
119988
- return;
119989
- const upper = raw.toUpperCase();
119990
- if (!FEATURE_RECEIPT_CONFIDENCE_LEVELS.includes(upper)) {
119991
- throw new Error(`confidence must be HIGH, MEDIUM or LOW (got ${JSON.stringify(raw)})`);
119992
- }
119993
- return upper;
119994
- }
119995
119942
  async function defaultRegisterArtifact(workdir, artifact) {
119996
119943
  const db = await openInlineRunProjectDb(workdir);
119997
119944
  try {
@@ -120027,7 +119974,6 @@ async function runFeatureVerification(input) {
120027
119974
  const vars = selected.workflow.vars;
120028
119975
  const configuredCmd = typeof vars?.verificationCmd === "string" && vars.verificationCmd.length > 0 ? vars.verificationCmd : "bun run spur-check-feature";
120029
119976
  const effectiveCmd = input.cmdOverride !== undefined && input.cmdOverride !== "" ? input.cmdOverride : configuredCmd;
120030
- const confidenceOverride = assertConfidenceOverride(input.confidenceOverride);
120031
119977
  const spec = await lookup(input.featureId);
120032
119978
  if (spec === null) {
120033
119979
  throw new Error(`feature ${input.featureId} not found in the configured features directory`);
@@ -120053,25 +119999,13 @@ async function runFeatureVerification(input) {
120053
119999
  const base = { featureId: input.featureId, runId: input.runId, effectiveCmd, logPath, receiptPath: runScoped };
120054
120000
  const launchSplit = split(effectiveCmd, "feature-verification verificationCmd");
120055
120001
  if ("error" in launchSplit) {
120056
- const { confidence, basis } = deriveFeatureConfidence({
120057
- status: "FAIL",
120058
- configuredCmd,
120059
- effectiveCmd,
120060
- inputDigest: beforeDigest,
120061
- override: confidenceOverride
120062
- });
120063
120002
  await receipts.complete(fileSystem, runDir, receipt, {
120064
120003
  status: "FAIL",
120065
- inputDigest: beforeDigest,
120066
- confidence,
120067
- confidenceBasis: basis,
120068
- confidenceAcknowledged: input.ackLowConfidence === true
120004
+ inputDigest: beforeDigest
120069
120005
  });
120070
120006
  return {
120071
120007
  ...base,
120072
120008
  status: "FAIL",
120073
- confidence,
120074
- confidenceBasis: basis,
120075
120009
  message: `verification FAIL (${launchSplit.error})`
120076
120010
  };
120077
120011
  }
@@ -120085,27 +120019,14 @@ async function runFeatureVerification(input) {
120085
120019
  const exit = result.status ?? 1;
120086
120020
  const afterDigest = await receipts.captureDigest(input.workdir, spec.content, learningsContent);
120087
120021
  const status = exit === 0 && afterDigest === beforeDigest ? "PASS" : "FAIL";
120088
- const { confidence, basis } = deriveFeatureConfidence({
120089
- status,
120090
- configuredCmd,
120091
- effectiveCmd,
120092
- inputDigest: beforeDigest,
120093
- afterDigest,
120094
- override: confidenceOverride
120095
- });
120096
120022
  await receipts.complete(fileSystem, runDir, receipt, {
120097
120023
  status,
120098
- inputDigest: afterDigest,
120099
- confidence,
120100
- confidenceBasis: basis,
120101
- confidenceAcknowledged: input.ackLowConfidence === true
120024
+ inputDigest: afterDigest
120102
120025
  });
120103
120026
  return {
120104
120027
  ...base,
120105
120028
  status,
120106
- confidence,
120107
- confidenceBasis: basis,
120108
- message: `verification ${status} (run ${input.runId}, confidence ${confidence}, log ${logPath})`
120029
+ message: `verification ${status} (run ${input.runId}, log ${logPath})`
120109
120030
  };
120110
120031
  } finally {
120111
120032
  closeLog(logFd);
@@ -120950,7 +120871,6 @@ __export(exports_src, {
120950
120871
  FEATURE_ACTION_NAMES: () => FEATURE_ACTION_NAMES,
120951
120872
  FEATURE_LIFECYCLE_PROFILE: () => FEATURE_LIFECYCLE_PROFILE,
120952
120873
  FEATURE_LIFECYCLE_WORKFLOW: () => FEATURE_LIFECYCLE_WORKFLOW,
120953
- FEATURE_RECEIPT_CONFIDENCE_LEVELS: () => FEATURE_RECEIPT_CONFIDENCE_LEVELS,
120954
120874
  FEATURE_RECEIPT_SCHEMA_VERSION: () => FEATURE_RECEIPT_SCHEMA_VERSION,
120955
120875
  FINDING_CODES: () => FINDING_CODES,
120956
120876
  FLEET_AUTO_TAG: () => FLEET_AUTO_TAG,
@@ -121014,7 +120934,6 @@ __export(exports_src, {
121014
120934
  RUN_STORE_LIST_MAX_LIMIT: () => RUN_STORE_LIST_MAX_LIMIT,
121015
120935
  RUN_STORE_WBS_DEFAULT_LIMIT: () => RUN_STORE_WBS_DEFAULT_LIMIT,
121016
120936
  RUN_STORE_WBS_MAX_LIMIT: () => RUN_STORE_WBS_MAX_LIMIT,
121017
- ResponseValidateActionRunner: () => ResponseValidateActionRunner,
121018
120937
  RetroCorrelator: () => RetroCorrelator,
121019
120938
  RuleCheckActionRunner: () => RuleCheckActionRunner,
121020
120939
  RuleService: () => RuleService,
@@ -121142,7 +121061,6 @@ __export(exports_src, {
121142
121061
  decodeRunListCursor: () => decodeRunListCursor,
121143
121062
  decorateWorkflowEvent: () => decorateWorkflowEvent,
121144
121063
  deduceCommandCategory: () => deduceCommandCategory,
121145
- deriveFeatureConfidence: () => deriveFeatureConfidence,
121146
121064
  deriveVerdict: () => deriveVerdict,
121147
121065
  discoverEnabledClaudePlugins: () => discoverEnabledClaudePlugins,
121148
121066
  disposeDecisionServices: () => disposeDecisionServices,
@@ -121205,6 +121123,7 @@ __export(exports_src, {
121205
121123
  isInlineRunActionStatus: () => isInlineRunActionStatus,
121206
121124
  isInlineRunCloseStatus: () => isInlineRunCloseStatus,
121207
121125
  isInsideRoot: () => isInsideRoot,
121126
+ isLocalHost: () => isLocalHost,
121208
121127
  isPortAvailable: () => isPortAvailable,
121209
121128
  isPortLive: () => isPortLive,
121210
121129
  isProofCaptureError: () => isProofCaptureError,
@@ -128491,7 +128410,7 @@ import {
128491
128410
  statSync as statSync17,
128492
128411
  writeFileSync as writeFileSync17
128493
128412
  } from "fs";
128494
- import { join as join80 } from "path";
128413
+ import { join as join81 } from "path";
128495
128414
  function parseScalar(raw) {
128496
128415
  const t = raw.trim();
128497
128416
  if (t.startsWith('"') && t.endsWith('"'))
@@ -128700,10 +128619,6 @@ function checkReportStructure(reportMarkdown) {
128700
128619
  if (!/(^|[\s`])P[123]([\s`.]|$)/.test(block) && !block.includes("symbolic-severity")) {
128701
128620
  problems.push("finding-invalid-severity");
128702
128621
  }
128703
- const confValue = block.match(/`confidence`\s*[:=]\s*`?([^`\n]*?)`?\s*(?:\n|$)/)?.[1]?.trim();
128704
- if (confValue !== undefined && confValue !== "" && !FINDING_CONFIDENCES.includes(confValue.toLowerCase())) {
128705
- problems.push(`finding-invalid-confidence:${confValue}`);
128706
- }
128707
128622
  const catValue = block.match(/`category`\s*[:=]\s*`?([^`\n]+?)`?\s*(?:\n|$)/)?.[1];
128708
128623
  if (catValue && !FINDING_CATEGORIES.includes(catValue.trim())) {
128709
128624
  problems.push(`finding-invalid-category:${catValue.trim()}`);
@@ -128737,7 +128652,7 @@ function logicDigest(path) {
128737
128652
  try {
128738
128653
  const h = createHash17("sha256");
128739
128654
  if (statSync17(path).isDirectory()) {
128740
- const walk = (dir) => readdirSync13(dir, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? walk(join80(dir, e.name)) : /\.(md|ya?ml)$/.test(e.name) ? [join80(dir, e.name)] : []).sort();
128655
+ const walk = (dir) => readdirSync13(dir, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? walk(join81(dir, e.name)) : /\.(md|ya?ml)$/.test(e.name) ? [join81(dir, e.name)] : []).sort();
128741
128656
  for (const f of walk(path)) {
128742
128657
  h.update(f.slice(path.length));
128743
128658
  h.update(readFileSync25(f));
@@ -129042,7 +128957,7 @@ function diffPorcelain(before, now, expects) {
129042
128957
  const beforePaths = porcelainPaths(before);
129043
128958
  return [...porcelainPaths(now)].filter((p) => !beforePaths.has(p) && !expects.has(p)).sort();
129044
128959
  }
129045
- var REPORT_SECTIONS, FINDING_FIELDS, DISPOSITIONS, FINDING_CATEGORIES, FINDING_CONFIDENCES, NOT_AVAILABLE = "not available", YAML_KEYS;
128960
+ var REPORT_SECTIONS, FINDING_FIELDS, DISPOSITIONS, FINDING_CATEGORIES, NOT_AVAILABLE = "not available", YAML_KEYS;
129046
128961
  var init_history_anatomy = __esm(() => {
129047
128962
  init_artifact_digest();
129048
128963
  REPORT_SECTIONS = [
@@ -129066,7 +128981,6 @@ var init_history_anatomy = __esm(() => {
129066
128981
  "trend",
129067
128982
  "observation",
129068
128983
  "inference",
129069
- "confidence",
129070
128984
  "contradictions",
129071
128985
  "evidenceAnchor",
129072
128986
  "severity",
@@ -129083,7 +128997,6 @@ var init_history_anatomy = __esm(() => {
129083
128997
  "telemetry",
129084
128998
  "positive"
129085
128999
  ];
129086
- FINDING_CONFIDENCES = ["high", "medium", "low"];
129087
129000
  YAML_KEYS = [
129088
129001
  "windowState",
129089
129002
  "generatedAt",
@@ -129108,7 +129021,7 @@ var init_history_anatomy = __esm(() => {
129108
129021
  // ../../packages/app/src/services/history-anatomy-cli.ts
129109
129022
  import { existsSync as existsSync29, mkdirSync as mkdirSync21, readFileSync as readFileSync26, writeFileSync as writeFileSync18 } from "fs";
129110
129023
  import { homedir as homedir13 } from "os";
129111
- import { join as join81 } from "path";
129024
+ import { join as join82 } from "path";
129112
129025
  function parseFlags(args) {
129113
129026
  const out = {};
129114
129027
  for (let i = 0;i < args.length; i++) {
@@ -129130,9 +129043,9 @@ function resolveSkillDir(deps = {}) {
129130
129043
  const cwd = deps.cwd ?? process.cwd();
129131
129044
  const home = deps.home ?? homedir13();
129132
129045
  const candidates = [
129133
- join81(cwd, "plugins", "sp", "skills", "history-anatomy"),
129134
- join81(home, ".agents", "skills", "sp-history-anatomy"),
129135
- join81(home, ".claude", "skills", "sp-history-anatomy")
129046
+ join82(cwd, "plugins", "sp", "skills", "history-anatomy"),
129047
+ join82(home, ".agents", "skills", "sp-history-anatomy"),
129048
+ join82(home, ".claude", "skills", "sp-history-anatomy")
129136
129049
  ];
129137
129050
  return candidates.find((dir) => existsSync29(dir)) ?? candidates[0];
129138
129051
  }
@@ -129363,7 +129276,7 @@ var init_history_anatomy_cli = __esm(() => {
129363
129276
 
129364
129277
  // ../../packages/app/src/services/daily-summary.ts
129365
129278
  import { existsSync as existsSync30, mkdirSync as mkdirSync22, readlinkSync as readlinkSync2, writeFileSync as writeFileSync19 } from "fs";
129366
- import { dirname as dirname40, isAbsolute as isAbsolute14, join as join82, resolve as resolve30 } from "path";
129279
+ import { dirname as dirname40, isAbsolute as isAbsolute14, join as join83, resolve as resolve30 } from "path";
129367
129280
  function parseArgs2(argv) {
129368
129281
  const args = argv;
129369
129282
  const options = {
@@ -129750,8 +129663,8 @@ function ensureDir(path) {
129750
129663
  }
129751
129664
  function writeSummary(markdown, options) {
129752
129665
  const filename = `summary_${options.date.replace(/-/g, "")}.md`;
129753
- const outputPath = options.outputPath || join82(DAILY_DIR, filename);
129754
- ensureDir(join82(outputPath, ".."));
129666
+ const outputPath = options.outputPath || join83(DAILY_DIR, filename);
129667
+ ensureDir(join83(outputPath, ".."));
129755
129668
  writeFileSync19(outputPath, markdown, "utf-8");
129756
129669
  return outputPath;
129757
129670
  }
@@ -129900,7 +129813,7 @@ var init_daily_summary = __esm(() => {
129900
129813
  // ../../packages/app/src/workflow/transcript.ts
129901
129814
  import { existsSync as existsSync31, readdirSync as readdirSync14 } from "fs";
129902
129815
  import { homedir as homedir14 } from "os";
129903
- import { join as join83 } from "path";
129816
+ import { join as join84 } from "path";
129904
129817
  function sniffTranscriptFormat(rows) {
129905
129818
  for (const row of rows) {
129906
129819
  if (row.type === "message" && PI_ROLES.has(row.message?.role ?? ""))
@@ -129929,7 +129842,7 @@ function parseTranscriptRows(lines) {
129929
129842
  }
129930
129843
  return { rows, skippedLines, format: sniffTranscriptFormat(rows.map(([r]) => r)) };
129931
129844
  }
129932
- function resolveTranscript(env, projectsRoot = join83(homedir14(), ".claude", "projects"), override) {
129845
+ function resolveTranscript(env, projectsRoot = join84(homedir14(), ".claude", "projects"), override) {
129933
129846
  if (override)
129934
129847
  return existsSync31(override) ? { ok: true, path: override } : { ok: false, reason: "no transcript" };
129935
129848
  const id = env.CLAUDE_CODE_SESSION_ID;
@@ -129948,7 +129861,7 @@ function resolveTranscript(env, projectsRoot = join83(homedir14(), ".claude", "p
129948
129861
  if (!existsSync31(projectsRoot))
129949
129862
  return { ok: false, reason: `no transcript root ${projectsRoot}` };
129950
129863
  for (const dir of readdirSync14(projectsRoot)) {
129951
- const path = join83(projectsRoot, dir, `${id}.jsonl`);
129864
+ const path = join84(projectsRoot, dir, `${id}.jsonl`);
129952
129865
  if (existsSync31(path))
129953
129866
  return { ok: true, path };
129954
129867
  }
@@ -130143,7 +130056,7 @@ var require_src9 = __commonJS(function(exports, module) {
130143
130056
 
130144
130057
  // ../../packages/app/src/workflow/run-summary-render.ts
130145
130058
  import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as readFileSync29, writeFileSync as writeFileSync21 } from "fs";
130146
- import { join as join98 } from "path";
130059
+ import { join as join99 } from "path";
130147
130060
  function renderSummaryMarkdown(summary) {
130148
130061
  const line = (r, bold = false) => {
130149
130062
  const stage = bold ? `**${r.stage}**` : r.stage;
@@ -130229,9 +130142,9 @@ function renderRollupMarkdown(rows) {
130229
130142
  }
130230
130143
  function writeCloseSummary(run, opts = {}) {
130231
130144
  const cwd = opts.cwd ?? process.cwd();
130232
- const runDir = join98(cwd, ".spur", "run");
130233
- const rel = join98(".spur", "run", `${run.runId}-summary.md`);
130234
- const abs = join98(cwd, rel);
130145
+ const runDir = join99(cwd, ".spur", "run");
130146
+ const rel = join99(".spur", "run", `${run.runId}-summary.md`);
130147
+ const abs = join99(cwd, rel);
130235
130148
  const na = (reason) => {
130236
130149
  mkdirSync24(runDir, { recursive: true });
130237
130150
  writeFileSync21(abs, `Execution summary: n/a (${reason})
@@ -130256,14 +130169,14 @@ function writeCloseSummary(run, opts = {}) {
130256
130169
  mkdirSync24(runDir, { recursive: true });
130257
130170
  writeFileSync21(abs, `${renderSummaryMarkdown(summary)}
130258
130171
  `);
130259
- writeFileSync21(join98(runDir, `${run.runId}-summary.json`), JSON.stringify({ ...summary, runId: run.runId, startedAt: run.startedAt, completedAt: run.completedAt }, null, 2));
130172
+ writeFileSync21(join99(runDir, `${run.runId}-summary.json`), JSON.stringify({ ...summary, runId: run.runId, startedAt: run.startedAt, completedAt: run.completedAt }, null, 2));
130260
130173
  return rel;
130261
130174
  } catch (error) {
130262
130175
  return na(error instanceof Error ? error.message : String(error));
130263
130176
  }
130264
130177
  }
130265
130178
  function gateFromReceipt(cwd, runId) {
130266
- const path = join98(cwd, ".spur", "run", `${runId}-check-receipt.json`);
130179
+ const path = join99(cwd, ".spur", "run", `${runId}-check-receipt.json`);
130267
130180
  if (!existsSync32(path))
130268
130181
  return { ms: null, reason: "no matching check receipt" };
130269
130182
  try {
@@ -138207,8 +138120,18 @@ init_src();
138207
138120
  init_dist5();
138208
138121
  import { homedir as homedir12 } from "os";
138209
138122
  import { join as join67 } from "path";
138210
- var CODEXBAR_ARGV = ["codexbar", "usage", "--format", "json", "--provider", "all"];
138211
- var DEFAULT_CAPTURE_TIMEOUT_MS = 180000;
138123
+ var CODEXBAR_ARGV = ["codexbar", "usage", "--format", "json"];
138124
+ var DEFAULT_CAPTURE_TIMEOUT_MS = 300000;
138125
+ var CAPTURE_TIMEOUT_OPTION = "agentUsageCaptureTimeoutMs";
138126
+ var MAX_TIMER_MS = 2147483647;
138127
+ function resolveCaptureTimeoutMs(spurConfig) {
138128
+ const raw = getAppOptions(spurConfig, CAPTURE_TIMEOUT_OPTION, undefined);
138129
+ if (raw === undefined)
138130
+ return DEFAULT_CAPTURE_TIMEOUT_MS;
138131
+ if (typeof raw === "number" && Number.isInteger(raw) && raw > 0 && raw <= MAX_TIMER_MS)
138132
+ return raw;
138133
+ throw new UsageSourceError(`bootstrap.options.${CAPTURE_TIMEOUT_OPTION} must be a positive integer number of milliseconds (at most ${MAX_TIMER_MS}); received ${JSON.stringify(raw)}`);
138134
+ }
138212
138135
 
138213
138136
  class CodexbarUsageSource {
138214
138137
  command;
@@ -138238,7 +138161,7 @@ class CodexbarUsageSource {
138238
138161
  }
138239
138162
  const outcome = result.outcome ?? "exit";
138240
138163
  if (outcome === "timeout" || outcome === "cancelled") {
138241
- throw new UsageSourceError(`\`${this.command.join(" ")}\` capture exceeded the ${this.timeoutMs} ms deadline and was terminated \u2014 ` + "result is unusable (fail-closed); retry or investigate the codexbar hang");
138164
+ throw new UsageSourceError(`\`${this.command.join(" ")}\` capture exceeded the ${this.timeoutMs} ms deadline and was terminated \u2014 ` + `result is unusable (fail-closed); retry, raise bootstrap.options.${CAPTURE_TIMEOUT_OPTION}, or investigate the codexbar hang`);
138242
138165
  }
138243
138166
  if (outcome === "signal" || outcome === "error") {
138244
138167
  throw new UsageSourceError(`\`${this.command.join(" ")}\` capture ended abnormally (${outcome}) \u2014 capture is unusable (fail-closed)`);
@@ -142424,13 +142347,13 @@ async function runAgentUsage(context, flags, deps) {
142424
142347
  }
142425
142348
  return 2;
142426
142349
  }
142427
- const options = {
142428
- source: deps?.source ?? _testUsageDeps?.source ?? new CodexbarUsageSource,
142429
- snapshotPath: deps?.snapshotPath ?? _testUsageDeps?.snapshotPath ?? defaultAgentUsageSnapshotPath(),
142430
- dryRun: flags.dryRun === true
142431
- };
142432
142350
  let result;
142433
142351
  try {
142352
+ const options = {
142353
+ source: deps?.source ?? _testUsageDeps?.source ?? new CodexbarUsageSource(undefined, resolveCaptureTimeoutMs(context.spurConfig)),
142354
+ snapshotPath: deps?.snapshotPath ?? _testUsageDeps?.snapshotPath ?? defaultAgentUsageSnapshotPath(),
142355
+ dryRun: flags.dryRun === true
142356
+ };
142434
142357
  result = await runAgentUsageProducer({
142435
142358
  getDb: () => context.getDb(),
142436
142359
  projectRoot: context.cwd,
@@ -142725,12 +142648,33 @@ function registerAgentCommand(program, context) {
142725
142648
  init_src();
142726
142649
  init_dist5();
142727
142650
  import { existsSync as existsSync26, readdirSync as readdirSync12, readFileSync as readFileSync23 } from "fs";
142728
- import { join as join78 } from "path";
142651
+ import { join as join79 } from "path";
142652
+
142653
+ // ../../scripts/commands/bundle-plugins.ts
142654
+ import { cp, rm as rm4 } from "fs/promises";
142655
+ import { join as join77 } from "path";
142656
+ import { fileURLToPath as fileURLToPath6 } from "url";
142657
+ var REPO_ROOT = fileURLToPath6(new URL("../../", import.meta.url));
142658
+ var PLUGIN_SOURCE = join77(REPO_ROOT, "plugins");
142659
+ var PLUGIN_TARGET = join77(REPO_ROOT, "apps/cli/plugins");
142660
+ var MARKETPLACE_SOURCE = join77(REPO_ROOT, ".claude-plugin");
142661
+ var MARKETPLACE_TARGET = join77(REPO_ROOT, "apps/cli/.claude-plugin");
142662
+ var EXCLUDE = /(^|\/)(\.DS_Store|tests|evals)($|\/)|\.test\.ts$/;
142663
+ async function bundlePlugins(pluginTarget = PLUGIN_TARGET, marketplaceTarget = MARKETPLACE_TARGET, pluginSource = PLUGIN_SOURCE, marketplaceSource = MARKETPLACE_SOURCE) {
142664
+ await rm4(pluginTarget, { recursive: true, force: true });
142665
+ await rm4(marketplaceTarget, { recursive: true, force: true });
142666
+ await cp(pluginSource, pluginTarget, {
142667
+ recursive: true,
142668
+ filter: (src) => !EXCLUDE.test(src.slice(pluginSource.length))
142669
+ });
142670
+ await cp(marketplaceSource, marketplaceTarget, { recursive: true });
142671
+ return { pluginTarget, marketplaceTarget };
142672
+ }
142729
142673
 
142730
142674
  // src/version-carriers.ts
142731
142675
  init_zod();
142732
142676
  import { existsSync as existsSync25 } from "fs";
142733
- import { join as join77 } from "path";
142677
+ import { join as join78 } from "path";
142734
142678
  var defaultLiteralProbe = { file: "src/config.ts", identifier: "binaryVersion" };
142735
142679
  var registry3 = new Map;
142736
142680
  function registerCarrierType(type, def) {
@@ -142789,7 +142733,7 @@ registerCarrierType("plugin-manifest", {
142789
142733
  schema: PluginManifestCarrierSchema,
142790
142734
  async syncRepoWide(carrier, { repoRoot }, version, staged, output) {
142791
142735
  for (const rel of carrier.paths) {
142792
- const absPath = join77(repoRoot, rel);
142736
+ const absPath = join78(repoRoot, rel);
142793
142737
  const manifest = await readJson(absPath);
142794
142738
  if (manifest === null) {
142795
142739
  output.write(` \u26A0 ${rel}: not found or malformed \u2014 skipping`);
@@ -142849,7 +142793,7 @@ async function run2(repoRoot, cmd, timeoutMs) {
142849
142793
  };
142850
142794
  }
142851
142795
  async function verifyLockfile(ctx, output) {
142852
- const lockPath = join78(ctx.repoRoot, "bun.lock");
142796
+ const lockPath = join79(ctx.repoRoot, "bun.lock");
142853
142797
  if (!existsSync26(lockPath) || Bun.file(lockPath).size === 0)
142854
142798
  return;
142855
142799
  const result = await run2(ctx.repoRoot, ["bun", "install", "--frozen-lockfile"]);
@@ -142859,7 +142803,7 @@ async function verifyLockfile(ctx, output) {
142859
142803
  output.write(" \u21B3 bun.lock: verified (bun install --frozen-lockfile)");
142860
142804
  }
142861
142805
  async function assertPluginInstallable(ctx, output) {
142862
- const smokePath = join78(ctx.repoRoot, "scripts", "commands", "plugin-install-smoke.ts");
142806
+ const smokePath = join79(ctx.repoRoot, "scripts", "commands", "plugin-install-smoke.ts");
142863
142807
  if (!existsSync26(smokePath)) {
142864
142808
  output.write(" \u21B3 plugin-install-smoke: gate script not present in this repo \u2014 skipped");
142865
142809
  return;
@@ -142901,7 +142845,7 @@ async function readJson2(path) {
142901
142845
  function rootWorkspaceGlobs(repoRoot) {
142902
142846
  let root;
142903
142847
  try {
142904
- root = JSON.parse(readFileSync23(join78(repoRoot, "package.json"), "utf8"));
142848
+ root = JSON.parse(readFileSync23(join79(repoRoot, "package.json"), "utf8"));
142905
142849
  } catch {
142906
142850
  return [];
142907
142851
  }
@@ -142918,11 +142862,11 @@ function workspaceDirs(repoRoot) {
142918
142862
  if (!glob.endsWith("/*"))
142919
142863
  continue;
142920
142864
  const parent = glob.slice(0, -2);
142921
- const parentAbs = join78(repoRoot, parent);
142865
+ const parentAbs = join79(repoRoot, parent);
142922
142866
  if (!existsSync26(parentAbs))
142923
142867
  continue;
142924
142868
  for (const entry of readdirSync12(parentAbs, { withFileTypes: true })) {
142925
- if (entry.isDirectory() && existsSync26(join78(parentAbs, entry.name, "package.json"))) {
142869
+ if (entry.isDirectory() && existsSync26(join79(parentAbs, entry.name, "package.json"))) {
142926
142870
  dirs.push(`${parent}/${entry.name}`);
142927
142871
  }
142928
142872
  }
@@ -142930,14 +142874,14 @@ function workspaceDirs(repoRoot) {
142930
142874
  return dirs;
142931
142875
  }
142932
142876
  async function releaseContext(repoRoot, bumpVerConfig = resolveBuilderBumpVerConfig(null)) {
142933
- const rootManifest = await readJson2(join78(repoRoot, "package.json"));
142877
+ const rootManifest = await readJson2(join79(repoRoot, "package.json"));
142934
142878
  if (rootManifest === null) {
142935
142879
  throw new Error(`no package.json found at ${repoRoot} \u2014 builder operates on a package workspace root.`);
142936
142880
  }
142937
142881
  const rootName = typeof rootManifest.name === "string" ? rootManifest.name : "root";
142938
142882
  const workspaces = [];
142939
142883
  for (const dir of workspaceDirs(repoRoot)) {
142940
- const manifest = await readJson2(join78(repoRoot, dir, "package.json"));
142884
+ const manifest = await readJson2(join79(repoRoot, dir, "package.json"));
142941
142885
  if (manifest === null)
142942
142886
  continue;
142943
142887
  workspaces.push({
@@ -142952,8 +142896,8 @@ async function releaseContext(repoRoot, bumpVerConfig = resolveBuilderBumpVerCon
142952
142896
  const packages = new Map;
142953
142897
  for (const ws of workspaces) {
142954
142898
  const probe = findLiteralProbe(versionCarriers, ws.dir) ?? defaultLiteralProbe;
142955
- const literalProbe = join78(ws.dir, probe.file);
142956
- const versionSourceFile = existsSync26(join78(repoRoot, literalProbe)) ? literalProbe : undefined;
142899
+ const literalProbe = join79(ws.dir, probe.file);
142900
+ const versionSourceFile = existsSync26(join79(repoRoot, literalProbe)) ? literalProbe : undefined;
142957
142901
  packages.set(shortName(ws.name), {
142958
142902
  packageDir: ws.dir,
142959
142903
  packageName: ws.name,
@@ -142991,7 +142935,7 @@ async function updateWorkspacePins(ctx, pkgName, oldVersion, newVersion, output)
142991
142935
  const changed = [];
142992
142936
  for (const dir of [".", ...workspaceDirs(ctx.repoRoot)]) {
142993
142937
  const relPath = dir === "." ? "package.json" : `${dir}/package.json`;
142994
- const manifest = await readJson2(join78(ctx.repoRoot, relPath));
142938
+ const manifest = await readJson2(join79(ctx.repoRoot, relPath));
142995
142939
  if (manifest === null)
142996
142940
  continue;
142997
142941
  let dirty = false;
@@ -143008,7 +142952,7 @@ async function updateWorkspacePins(ctx, pkgName, oldVersion, newVersion, output)
143008
142952
  }
143009
142953
  }
143010
142954
  if (dirty) {
143011
- await Bun.write(join78(ctx.repoRoot, relPath), `${JSON.stringify(manifest, null, 4)}
142955
+ await Bun.write(join79(ctx.repoRoot, relPath), `${JSON.stringify(manifest, null, 4)}
143012
142956
  `);
143013
142957
  output.write(` \u21B3 ${relPath}: ${pkgName} workspace pin ${oldVersion} \u2192 ${newVersion}`);
143014
142958
  changed.push(relPath);
@@ -143016,7 +142960,7 @@ async function updateWorkspacePins(ctx, pkgName, oldVersion, newVersion, output)
143016
142960
  }
143017
142961
  if (changed.length === 0)
143018
142962
  return changed;
143019
- const lockPath = join78(ctx.repoRoot, "bun.lock");
142963
+ const lockPath = join79(ctx.repoRoot, "bun.lock");
143020
142964
  if (existsSync26(lockPath)) {
143021
142965
  const lock = await Bun.file(lockPath).text();
143022
142966
  const staleSpec = `"${pkgName}": "workspace:${oldVersion}"`;
@@ -143028,7 +142972,7 @@ async function updateWorkspacePins(ctx, pkgName, oldVersion, newVersion, output)
143028
142972
  return changed;
143029
142973
  }
143030
142974
  async function updateVersionSourceFile(ctx, filePath, previous, next, output, identifier = "binaryVersion") {
143031
- const absPath = join78(ctx.repoRoot, filePath);
142975
+ const absPath = join79(ctx.repoRoot, filePath);
143032
142976
  const content = await Bun.file(absPath).text();
143033
142977
  const pattern = new RegExp(`(${identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:\\s*['"])\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-.]+)?(?:\\+[0-9A-Za-z-.]+)?(['"])`);
143034
142978
  const updated = content.replace(pattern, `$1${next}$2`);
@@ -143046,7 +142990,7 @@ async function syncExtraCarriers(ctx, version, staged, output) {
143046
142990
  }
143047
142991
  }
143048
142992
  async function syncMarketplaceAndPlugins(ctx, version, staged, output) {
143049
- const marketplacePath = join78(ctx.repoRoot, ".claude-plugin", "marketplace.json");
142993
+ const marketplacePath = join79(ctx.repoRoot, ".claude-plugin", "marketplace.json");
143050
142994
  const marketplaceFile = Bun.file(marketplacePath);
143051
142995
  if (!await marketplaceFile.exists())
143052
142996
  return;
@@ -143071,7 +143015,7 @@ async function syncMarketplaceAndPlugins(ctx, version, staged, output) {
143071
143015
  output.write(`Bumped marketplace plugins to ${version}`);
143072
143016
  }
143073
143017
  for (const entry of plugins) {
143074
- const pluginJsonPath = join78(ctx.repoRoot, entry.source, "plugin.json");
143018
+ const pluginJsonPath = join79(ctx.repoRoot, entry.source, "plugin.json");
143075
143019
  const pluginJson = await readJson2(pluginJsonPath);
143076
143020
  if (pluginJson === null) {
143077
143021
  output.write(` \u26A0 plugin.json not found at ${entry.source}/plugin.json \u2014 skipping`);
@@ -143085,6 +143029,14 @@ async function syncMarketplaceAndPlugins(ctx, version, staged, output) {
143085
143029
  output.write(`Bumped ${entry.source}/plugin.json to ${version}`);
143086
143030
  }
143087
143031
  }
143032
+ for (const config of ctx.packages.values()) {
143033
+ const pkgDir = join79(ctx.repoRoot, config.packageDir);
143034
+ const stagedMarketplace = join79(pkgDir, ".claude-plugin", "marketplace.json");
143035
+ if (!existsSync26(stagedMarketplace))
143036
+ continue;
143037
+ await bundlePlugins(join79(pkgDir, "plugins"), join79(pkgDir, ".claude-plugin"), join79(ctx.repoRoot, "plugins"), join79(ctx.repoRoot, ".claude-plugin"));
143038
+ output.write(`Refreshed staged plugin copy in ${config.packageDir}`);
143039
+ }
143088
143040
  }
143089
143041
  async function assertCleanTreeOnBranch(ctx) {
143090
143042
  const RUNTIME_NOISE = /^\?\? \.spur\/(?:spur\.db|spur\.db-shm|spur\.db-wal|logs\/)/;
@@ -143125,7 +143077,7 @@ async function bumpVersion(ctx, config, version, options, output) {
143125
143077
  throw new Error(`${config.packageName}@${version} is already published on npm. Use a new version.`);
143126
143078
  }
143127
143079
  await assertPluginInstallable(ctx, output);
143128
- const manifestPath = join78(ctx.repoRoot, config.packageDir, "package.json");
143080
+ const manifestPath = join79(ctx.repoRoot, config.packageDir, "package.json");
143129
143081
  const manifest = await readJson2(manifestPath);
143130
143082
  if (manifest === null)
143131
143083
  throw new Error(`missing or malformed manifest at ${config.packageDir}/package.json`);
@@ -143144,7 +143096,7 @@ async function bumpVersion(ctx, config, version, options, output) {
143144
143096
  await syncMarketplaceAndPlugins(ctx, version, staged, output);
143145
143097
  await syncExtraCarriers(ctx, version, staged, output);
143146
143098
  await verifyLockfile(ctx, output);
143147
- const lockPath = join78(ctx.repoRoot, "bun.lock");
143099
+ const lockPath = join79(ctx.repoRoot, "bun.lock");
143148
143100
  if (existsSync26(lockPath) && Bun.file(lockPath).size > 0)
143149
143101
  staged.push("bun.lock");
143150
143102
  await git2(ctx.repoRoot, ["add", ...staged]);
@@ -143201,7 +143153,7 @@ async function bumpAll(ctx, version, options, output) {
143201
143153
  }
143202
143154
  const staged = [];
143203
143155
  for (const config of configs) {
143204
- const manifestPath = join78(ctx.repoRoot, config.packageDir, "package.json");
143156
+ const manifestPath = join79(ctx.repoRoot, config.packageDir, "package.json");
143205
143157
  const manifest = await readJson2(manifestPath);
143206
143158
  if (manifest === null) {
143207
143159
  throw new Error(`missing or malformed manifest at ${config.packageDir}/package.json`);
@@ -143222,7 +143174,7 @@ async function bumpAll(ctx, version, options, output) {
143222
143174
  await syncMarketplaceAndPlugins(ctx, version, staged, output);
143223
143175
  await syncExtraCarriers(ctx, version, staged, output);
143224
143176
  await verifyLockfile(ctx, output);
143225
- const lockPath = join78(ctx.repoRoot, "bun.lock");
143177
+ const lockPath = join79(ctx.repoRoot, "bun.lock");
143226
143178
  if (existsSync26(lockPath) && Bun.file(lockPath).size > 0)
143227
143179
  staged.push("bun.lock");
143228
143180
  await git2(ctx.repoRoot, ["add", ...staged]);
@@ -143378,9 +143330,9 @@ async function checkVersionDrift(target, repoRoot = process.cwd(), output = cons
143378
143330
  if (config === undefined) {
143379
143331
  throw releaseUsage(target === undefined ? "bump-ver --check needs a package id (no aggregate package is configured)" : `unknown package "${target}". Package IDs: ${[...ctx.packages.keys()].join(", ")}`);
143380
143332
  }
143381
- const manifest = await readJson2(join78(repoRoot, config.packageDir, "package.json"));
143333
+ const manifest = await readJson2(join79(repoRoot, config.packageDir, "package.json"));
143382
143334
  const pkgVersion = typeof manifest?.version === "string" ? manifest.version : "";
143383
- const marketplacePath = join78(repoRoot, ".claude-plugin", "marketplace.json");
143335
+ const marketplacePath = join79(repoRoot, ".claude-plugin", "marketplace.json");
143384
143336
  const marketplaceFile = Bun.file(marketplacePath);
143385
143337
  if (!await marketplaceFile.exists()) {
143386
143338
  output.write("No .claude-plugin/marketplace.json \u2014 nothing to check.");
@@ -143393,8 +143345,8 @@ async function checkVersionDrift(target, repoRoot = process.cwd(), output = cons
143393
143345
  for (const entry of parsed.plugins ?? []) {
143394
143346
  if (typeof entry.source !== "string")
143395
143347
  continue;
143396
- const label = join78(entry.source, "plugin.json").replace(/^\.\//, "");
143397
- const pluginJson = await readJson2(join78(repoRoot, entry.source, "plugin.json"));
143348
+ const label = join79(entry.source, "plugin.json").replace(/^\.\//, "");
143349
+ const pluginJson = await readJson2(join79(repoRoot, entry.source, "plugin.json"));
143398
143350
  plugins.push({ label, version: typeof pluginJson?.version === "string" ? pluginJson.version : "" });
143399
143351
  }
143400
143352
  } catch {}
@@ -143462,8 +143414,8 @@ async function publishPackage(target, opts = {}) {
143462
143414
  const repoRoot = opts.repoRoot ?? process.cwd();
143463
143415
  const output = opts.output ?? consoleOutput;
143464
143416
  const runner = opts.runner ?? defaultPublishRunner;
143465
- const dir = (target.startsWith("/") ? target : join78(repoRoot, target)).replace(/\/+$/, "");
143466
- const manifestPath = join78(dir, "package.json");
143417
+ const dir = (target.startsWith("/") ? target : join79(repoRoot, target)).replace(/\/+$/, "");
143418
+ const manifestPath = join79(dir, "package.json");
143467
143419
  const manifestFile = Bun.file(manifestPath);
143468
143420
  if (!await manifestFile.exists())
143469
143421
  throw new Error(`no package.json at ${dir}`);
@@ -143471,7 +143423,7 @@ async function publishPackage(target, opts = {}) {
143471
143423
  const manifest = JSON.parse(original);
143472
143424
  const workspaces = new Map;
143473
143425
  for (const wsDir of workspaceDirs(repoRoot)) {
143474
- const pkg = await readJson2(join78(repoRoot, wsDir, "package.json"));
143426
+ const pkg = await readJson2(join79(repoRoot, wsDir, "package.json"));
143475
143427
  if (pkg !== null && typeof pkg.name === "string") {
143476
143428
  workspaces.set(pkg.name, {
143477
143429
  ...typeof pkg.version === "string" ? { version: pkg.version } : {},
@@ -143479,7 +143431,7 @@ async function publishPackage(target, opts = {}) {
143479
143431
  });
143480
143432
  }
143481
143433
  }
143482
- const root = await readJson2(join78(repoRoot, "package.json"));
143434
+ const root = await readJson2(join79(repoRoot, "package.json"));
143483
143435
  const catalog = root?.workspaces?.catalog ?? {};
143484
143436
  const changed = resolvePublishRanges(manifest, workspaces, catalog);
143485
143437
  if (changed > 0) {
@@ -143514,7 +143466,7 @@ async function publishPackage(target, opts = {}) {
143514
143466
  init_src();
143515
143467
  import { spawnSync as spawnSync7 } from "child_process";
143516
143468
  import { existsSync as existsSync27, readFileSync as readFileSync24, writeFileSync as writeFileSync16 } from "fs";
143517
- import { join as join79, resolve as resolve28 } from "path";
143469
+ import { join as join80, resolve as resolve28 } from "path";
143518
143470
  function createSpawnRunner(context = {}) {
143519
143471
  return (cmd) => {
143520
143472
  const proc = spawnSync7(cmd[0] ?? "", [...cmd.slice(1)], {
@@ -143657,7 +143609,7 @@ function extractSeverity2(body) {
143657
143609
  }
143658
143610
  var DEFAULT_FOCUS = "for correctness and regressions, security boundaries, data-loss risks, concurrency or race conditions, " + "API/backward compatibility, migration safety, error handling, and missing high-value tests. " + "Prioritize actionable issues over style or nits.";
143659
143611
  function hasCodeReviewRules(repoRoot) {
143660
- const agentsPath = join79(repoRoot, "AGENTS.md");
143612
+ const agentsPath = join80(repoRoot, "AGENTS.md");
143661
143613
  if (!existsSync27(agentsPath))
143662
143614
  return false;
143663
143615
  return /^## Code Review Rules\s*$/m.test(readFileSync24(agentsPath, "utf8"));
@@ -144215,10 +144167,333 @@ function runSpine(argv, rt) {
144215
144167
  return 2;
144216
144168
  }
144217
144169
  }
144170
+ function obj(value) {
144171
+ return typeof value === "object" && value !== null ? value : {};
144172
+ }
144173
+ function text4(value) {
144174
+ return typeof value === "string" ? value : "";
144175
+ }
144176
+ function list(value) {
144177
+ return Array.isArray(value) ? value : [];
144178
+ }
144179
+ function word(payload, fallback = "FAIL") {
144180
+ const verdict = payload?.verdict;
144181
+ return typeof verdict === "string" && verdict !== "" ? verdict : fallback;
144182
+ }
144183
+ function findingsArg(value) {
144184
+ return list(value);
144185
+ }
144186
+ function runVerbOp(argv, rt, sink) {
144187
+ const out = [];
144188
+ const previous = io;
144189
+ io = { out: (line) => out.push(line), err: (line) => sink.err(line) };
144190
+ try {
144191
+ const code = runSpine(argv, rt);
144192
+ let payload = null;
144193
+ for (let i = out.length - 1;i >= 0; i--) {
144194
+ const line = (out[i] ?? "").trim();
144195
+ if (!line.startsWith("{"))
144196
+ continue;
144197
+ try {
144198
+ payload = JSON.parse(line);
144199
+ } catch {
144200
+ payload = null;
144201
+ }
144202
+ break;
144203
+ }
144204
+ return { code, payload };
144205
+ } finally {
144206
+ io = previous;
144207
+ }
144208
+ }
144209
+ function validPollBudget(opts, sink) {
144210
+ if (opts.timeout !== undefined && !(Number.isFinite(Number(opts.timeout)) && Number(opts.timeout) >= 0)) {
144211
+ sink.err("error: --timeout must be a non-negative number");
144212
+ return false;
144213
+ }
144214
+ if (opts.interval !== undefined && !(Number.isFinite(Number(opts.interval)) && Number(opts.interval) > 0)) {
144215
+ sink.err("error: --interval must be a positive number");
144216
+ return false;
144217
+ }
144218
+ return true;
144219
+ }
144220
+ function writeVerbStatus(rt, opts, verdict) {
144221
+ if (opts.statusFile !== undefined)
144222
+ rt.writeStatus(opts.statusFile, `${verdict}
144223
+ `);
144224
+ }
144225
+ function currentHead(rt) {
144226
+ const res = run3(rt, ["git", "rev-parse", "HEAD"]);
144227
+ const head = res.stdout.trim();
144228
+ return res.code === 0 && head !== "" ? head : undefined;
144229
+ }
144230
+ function composeStatus(rt, opts, sink) {
144231
+ const flags = ["--json"];
144232
+ if (opts.since !== undefined)
144233
+ flags.push("--since", opts.since);
144234
+ const head = opts.head ?? currentHead(rt);
144235
+ if (head !== undefined)
144236
+ flags.push("--head", head);
144237
+ const status = runVerbOp(["status", ...flags], rt, sink);
144238
+ if (status.code !== 0)
144239
+ return { code: status.code, payload: { ...obj(status.payload), review: null } };
144240
+ if (status.payload?.pr === null || status.payload?.pr === undefined) {
144241
+ return { code: 0, payload: { ...obj(status.payload), review: null } };
144242
+ }
144243
+ const collect = runVerbOp(["collect", ...flags], rt, sink);
144244
+ return { code: collect.code, payload: { ...obj(status.payload), review: collect.payload } };
144245
+ }
144246
+ function composeCheck(rt, opts, sink) {
144247
+ const flags = ["--json"];
144248
+ if (opts.statusFile !== undefined)
144249
+ flags.push("--status-file", opts.statusFile);
144250
+ if (opts.base !== undefined)
144251
+ flags.push("--base", opts.base);
144252
+ const preflight = opts.scanOnly === true ? null : runVerbOp(["preflight", ...flags], rt, sink);
144253
+ if (preflight !== null && preflight.code !== 0) {
144254
+ return {
144255
+ code: preflight.code,
144256
+ payload: { ok: false, verdict: "FAIL", preflight: preflight.payload, hygiene: null }
144257
+ };
144258
+ }
144259
+ const hygiene = runVerbOp(["hygiene", ...flags], rt, sink);
144260
+ const opWord = word(hygiene.payload, "PASS");
144261
+ const verdict = hygiene.code === 0 ? opWord : opWord === "BLOCK" ? "BLOCK" : "FAIL";
144262
+ return {
144263
+ code: hygiene.code,
144264
+ payload: { ok: hygiene.code === 0, verdict, preflight: preflight?.payload ?? null, hygiene: hygiene.payload }
144265
+ };
144266
+ }
144267
+ function composeOpen(rt, opts, sink) {
144268
+ const flags = ["--json"];
144269
+ if (opts.statusFile !== undefined)
144270
+ flags.push("--status-file", opts.statusFile);
144271
+ const push = runVerbOp(["push", ...flags], rt, sink);
144272
+ if (push.code !== 0) {
144273
+ return { code: push.code, payload: { ok: false, verdict: "FAIL", push: push.payload, pr: null } };
144274
+ }
144275
+ const ensureFlags = [...flags];
144276
+ if (opts.base !== undefined)
144277
+ ensureFlags.push("--base", opts.base);
144278
+ const ensure = runVerbOp(["ensure", ...ensureFlags], rt, sink);
144279
+ const verdict = ensure.code === 0 ? ensure.payload?.created === true ? "CREATED" : "FOUND" : "FAIL";
144280
+ return {
144281
+ code: ensure.code,
144282
+ payload: { ok: ensure.code === 0, verdict, push: push.payload, pr: ensure.payload?.pr ?? null }
144283
+ };
144284
+ }
144285
+ function composeReview(rt, opts, sink) {
144286
+ const statusFlags = opts.statusFile !== undefined ? ["--status-file", opts.statusFile] : [];
144287
+ if (!validPollBudget(opts, sink)) {
144288
+ return { code: 1, payload: { ok: false, verdict: "FAIL", request: null, findings: [] } };
144289
+ }
144290
+ const requestFlags = ["--json", ...statusFlags];
144291
+ if (opts.force === true)
144292
+ requestFlags.push("--force");
144293
+ if (opts.focus !== undefined)
144294
+ requestFlags.push("--focus", opts.focus);
144295
+ const request = runVerbOp(["request", ...requestFlags], rt, sink);
144296
+ if (request.code !== 0) {
144297
+ return { code: request.code, payload: { ok: false, verdict: "FAIL", request: request.payload, findings: [] } };
144298
+ }
144299
+ const record = obj(request.payload);
144300
+ const correlation = [];
144301
+ if (text4(record.requestedAt) !== "")
144302
+ correlation.push("--since", text4(record.requestedAt));
144303
+ if (text4(record.head) !== "")
144304
+ correlation.push("--head", text4(record.head));
144305
+ const collected = () => {
144306
+ const collect = runVerbOp(["collect", ...correlation, "--json", ...statusFlags], rt, sink);
144307
+ return {
144308
+ code: collect.code,
144309
+ payload: {
144310
+ ok: collect.code === 0,
144311
+ verdict: collect.code === 0 ? word(collect.payload) : "FAIL",
144312
+ request: request.payload,
144313
+ findings: collect.code === 0 ? list(collect.payload?.findings) : []
144314
+ }
144315
+ };
144316
+ };
144317
+ const pending = (code) => {
144318
+ writeVerbStatus(rt, opts, "PENDING");
144319
+ return { code, payload: { ok: true, verdict: "PENDING", request: request.payload, findings: [] } };
144320
+ };
144321
+ if (record.alreadyReviewed === true)
144322
+ return collected();
144323
+ if (record.pending === true || record.requested === true && opts.noWait === true)
144324
+ return pending(0);
144325
+ const waitFlags = [...correlation, "--json", ...statusFlags];
144326
+ if (opts.timeout !== undefined)
144327
+ waitFlags.push("--timeout", opts.timeout);
144328
+ if (opts.interval !== undefined)
144329
+ waitFlags.push("--interval", opts.interval);
144330
+ const waited = runVerbOp(["wait", ...waitFlags], rt, sink);
144331
+ if (waited.code === 3)
144332
+ return pending(3);
144333
+ if (waited.code !== 0) {
144334
+ return { code: waited.code, payload: { ok: false, verdict: "FAIL", request: request.payload, findings: [] } };
144335
+ }
144336
+ return collected();
144337
+ }
144338
+ function renderPrVerb(verb, payload) {
144339
+ if (verb === "status") {
144340
+ const pr = payload.pr === null || payload.pr === undefined ? null : obj(payload.pr);
144341
+ const review = payload.review === null || payload.review === undefined ? null : obj(payload.review);
144342
+ const lines = [
144343
+ `Repository: ${text4(payload.repo) || "unknown"}`,
144344
+ `PR: ${pr === null ? "none" : `#${String(pr.number ?? "")} ${text4(pr.url)}`}`,
144345
+ `Branch: ${text4(payload.branch)}`,
144346
+ `HEAD: ${text4(payload.head)}`,
144347
+ `Base: ${text4(payload.base)}`,
144348
+ `Local: ${text4(payload.local)}`,
144349
+ `CI: ${text4(payload.ci)}`,
144350
+ `Codex: ${text4(payload.codex)}`
144351
+ ];
144352
+ if (review === null)
144353
+ return [...lines, "Review: no PR \u2014 nothing collected"];
144354
+ lines.push(`Review: ${text4(review.verdict) || "unknown"}`);
144355
+ const findings = findingsArg(review.findings);
144356
+ if (findings.length > 0)
144357
+ lines.push("", renderFindings(findings));
144358
+ return lines;
144359
+ }
144360
+ if (verb === "check") {
144361
+ const preflight = payload.preflight === null || payload.preflight === undefined ? null : obj(payload.preflight);
144362
+ const hygiene = payload.hygiene === null || payload.hygiene === undefined ? null : obj(payload.hygiene);
144363
+ const lines = [
144364
+ preflight === null ? "Preflight: skipped (--scan-only)" : preflight.ok === true ? `Preflight: ${text4(preflight.nameWithOwner)} ${text4(preflight.branch)} @ ${text4(preflight.shortHead)}` : `Preflight: FAIL \u2014 ${text4(preflight.error) || "probe failed"}`
144365
+ ];
144366
+ if (hygiene === null)
144367
+ return lines;
144368
+ lines.push(`Hygiene: ${text4(hygiene.verdict) || "FAIL"}`);
144369
+ for (const blocker of list(hygiene.blockers))
144370
+ lines.push(` BLOCK ${String(blocker)}`);
144371
+ for (const warning of list(hygiene.warnings))
144372
+ lines.push(` WARN ${String(warning)}`);
144373
+ return lines;
144374
+ }
144375
+ if (verb === "open") {
144376
+ const push = obj(payload.push);
144377
+ const pr = payload.pr === null || payload.pr === undefined ? null : obj(payload.pr);
144378
+ return [
144379
+ `Push: ${push.pushed === true ? "PUSHED" : "UP_TO_DATE"} ${text4(push.head).slice(0, 7)}`,
144380
+ `PR: ${pr === null ? "none" : `#${String(pr.number ?? "")} ${text4(pr.url)} (${text4(payload.verdict)})`}`
144381
+ ];
144382
+ }
144383
+ const request = obj(payload.request);
144384
+ const outcome = request.requested === true ? "REQUESTED" : request.alreadyReviewed === true ? "ALREADY_REVIEWED" : request.pending === true ? "ALREADY_REQUESTED" : "FAIL";
144385
+ const lines = [
144386
+ `Request: ${outcome} PR #${String(request.pr ?? "?")} ${text4(request.url)}`,
144387
+ `HEAD: ${text4(request.head).slice(0, 7)}`,
144388
+ `Review: ${text4(payload.verdict) || "unknown"}`
144389
+ ];
144390
+ const findings = findingsArg(payload.findings);
144391
+ if (findings.length > 0)
144392
+ lines.push("", renderFindings(findings));
144393
+ return lines;
144394
+ }
144395
+ var PR_VERB_COMPOSERS = { status: composeStatus, check: composeCheck, open: composeOpen, review: composeReview };
144396
+ function runPrVerb(verb, opts, sink, context = {}) {
144397
+ const rt = createRuntime(context);
144398
+ const result = PR_VERB_COMPOSERS[verb](rt, opts, sink);
144399
+ if (opts.json === true)
144400
+ sink.out(JSON.stringify(result.payload));
144401
+ else
144402
+ for (const line of renderPrVerb(verb, result.payload))
144403
+ sink.out(line);
144404
+ return result.code;
144405
+ }
144218
144406
 
144219
144407
  // src/commands/builder/pr.ts
144220
- function registerBuilderPrCommand(builder, context) {
144221
- builder.command("pr").summary("GitHub Codex PR-review loop (preflight | push | ensure | hygiene | request | wait | collect | status)").description(PR_REVIEW_HELP).allowUnknownOption(true).allowExcessArguments(true).argument("[argv...]", "subcommand and flags (see description)").action((argv) => {
144408
+ var PR_VERB_FOR_OP = {
144409
+ preflight: "pr-check",
144410
+ hygiene: "pr-check",
144411
+ push: "pr-open",
144412
+ ensure: "pr-open",
144413
+ "ensure-pr": "pr-open",
144414
+ request: "pr-review",
144415
+ wait: "pr-review",
144416
+ collect: "pr-status",
144417
+ status: "pr-status"
144418
+ };
144419
+ function prDeprecationLine(op) {
144420
+ const replacement = op === undefined ? undefined : PR_VERB_FOR_OP[op];
144421
+ return replacement !== undefined ? `spur builder pr ${op} is deprecated; use spur builder ${replacement}` : "spur builder pr is deprecated; use spur builder pr-status | pr-check | pr-open | pr-review";
144422
+ }
144423
+ function registerBuilderPrCommands(builder, context) {
144424
+ const io = () => ({
144425
+ out: (line) => context.output.write(line),
144426
+ err: (line) => context.output.error(line)
144427
+ });
144428
+ const ctx = () => ({ cwd: context.cwd, env: context.env });
144429
+ const run = (verb, opts) => {
144430
+ context.setExitCode(runPrVerb(verb, opts, io(), ctx()));
144431
+ };
144432
+ builder.command("pr-status").summary("read-only PR/CI/Codex status with the current-HEAD review as `review`").description([
144433
+ "Read-only composition of the status and collect operations:",
144434
+ "repository, branch, PR/base, local changes, CI and Codex state, plus",
144435
+ "`review` \u2014 the current-HEAD Codex record when the branch has a PR,",
144436
+ "else null. Writes no verdict: a status probe is not a gate.",
144437
+ "Exit codes: 0 ok \xB7 2 probe failure."
144438
+ ].join(`
144439
+ `)).option("--since <iso>", "only count review output submitted at or after this time").option("--head <sha>", "pin the reviewed commit (default: the current local HEAD)").option(...SHARED_OPTIONS.json).option("--status-file <path>", "accepted for symmetry; a read-only status writes no verdict").action((options) => {
144440
+ run("status", {
144441
+ ...options.since !== undefined ? { since: options.since } : {},
144442
+ ...options.head !== undefined ? { head: options.head } : {},
144443
+ ...options.json === true ? { json: true } : {},
144444
+ ...options.statusFile !== undefined ? { statusFile: options.statusFile } : {}
144445
+ });
144446
+ });
144447
+ builder.command("pr-check").summary("PR submission gate: preflight then hygiene (PASS | WARN | BLOCK | FAIL)").description([
144448
+ "Preflight (git/gh/repo checks, clean tree, not the base branch) then",
144449
+ "the hygiene scan of base...HEAD for secrets/.env/conflict markers",
144450
+ "(BLOCK) and debug residue (WARN). A preflight FAIL stops before",
144451
+ "hygiene. `--scan-only` runs hygiene alone, for a local review on a",
144452
+ "dirty tree. Exit codes: 0 ok \xB7 2 BLOCK/preflight FAIL/probe failure."
144453
+ ].join(`
144454
+ `)).option("--base <branch>", "base branch for the base...HEAD diff (default: the PR base, else the repo default)").option("--scan-only", "hygiene only; skip preflight (a dirty tree is expected)").option(...SHARED_OPTIONS.json).option("--status-file <path>", "write the one-word verdict (PASS | WARN | BLOCK | FAIL)").action((options) => {
144455
+ run("check", {
144456
+ ...options.base !== undefined ? { base: options.base } : {},
144457
+ ...options.scanOnly === true ? { scanOnly: true } : {},
144458
+ ...options.json === true ? { json: true } : {},
144459
+ ...options.statusFile !== undefined ? { statusFile: options.statusFile } : {}
144460
+ });
144461
+ });
144462
+ builder.command("pr-open").summary("publish the branch and reuse or create its pull request (FOUND | CREATED | FAIL)").description([
144463
+ "Push the branch (normal push only \u2014 never force; sets upstream when",
144464
+ "missing), then reuse the branch PR or create one with",
144465
+ "`gh pr create --fill`. A rejected push stops before any PR work.",
144466
+ "Exit codes: 0 ok \xB7 2 push rejected or ensure failed."
144467
+ ].join(`
144468
+ `)).option("--base <branch>", "base for a newly created PR (default: the repo default branch)").option(...SHARED_OPTIONS.json).option("--status-file <path>", "write the one-word verdict (FOUND | CREATED | FAIL)").action((options) => {
144469
+ run("open", {
144470
+ ...options.base !== undefined ? { base: options.base } : {},
144471
+ ...options.json === true ? { json: true } : {},
144472
+ ...options.statusFile !== undefined ? { statusFile: options.statusFile } : {}
144473
+ });
144474
+ });
144475
+ builder.command("pr-review").summary("request the GitHub Codex review and collect its result (CLEAN | FINDINGS | PENDING | FAIL)").description([
144476
+ "Post the `@codex review` request (per-HEAD dedupe unless --force),",
144477
+ "poll for the result, then collect it. An already-reviewed HEAD is",
144478
+ "collected without a new request; an in-flight request or --no-wait",
144479
+ "returns PENDING. Exit codes: 0 ok \xB7 2 hard failure \xB7 3 wait timeout",
144480
+ "(verdict PENDING \u2014 pending, never failure)."
144481
+ ].join(`
144482
+ `)).option("--force", "request a fresh review even when the current HEAD is already reviewed").option("--focus <text>", "extra review focus appended to the request").option("--no-wait", "post the request and return PENDING without polling").option("--timeout <s>", "wait budget in seconds (service default 600)").option("--interval <s>", "poll interval in seconds (service default 30)").option(...SHARED_OPTIONS.json).option("--status-file <path>", "write the one-word verdict (CLEAN | FINDINGS | PENDING | FAIL)").action((options) => {
144483
+ run("review", {
144484
+ ...options.force === true ? { force: true } : {},
144485
+ ...options.focus !== undefined ? { focus: options.focus } : {},
144486
+ ...options.wait === false ? { noWait: true } : {},
144487
+ ...options.timeout !== undefined ? { timeout: options.timeout } : {},
144488
+ ...options.interval !== undefined ? { interval: options.interval } : {},
144489
+ ...options.json === true ? { json: true } : {},
144490
+ ...options.statusFile !== undefined ? { statusFile: options.statusFile } : {}
144491
+ });
144492
+ });
144493
+ }
144494
+ function registerBuilderPrAliasCommand(builder, context) {
144495
+ builder.command("pr", { hidden: true }).summary("deprecated alias for the flat pr-<verb> commands").description(PR_REVIEW_HELP).allowUnknownOption(true).allowExcessArguments(true).argument("[argv...]", "subcommand and flags (see description)").action((argv) => {
144496
+ context.output.error(prDeprecationLine(argv[0]));
144222
144497
  context.setExitCode(main(argv, {
144223
144498
  out: (line) => context.output.write(line),
144224
144499
  err: (line) => context.output.error(line)
@@ -144226,9 +144501,574 @@ function registerBuilderPrCommand(builder, context) {
144226
144501
  });
144227
144502
  }
144228
144503
 
144504
+ // ../../packages/app/src/services/branch-topology/render.ts
144505
+ var INDENT = " ";
144506
+ var MERMAID_DEFAULT_BRANCH = "main";
144507
+ var TABLE_HEADERS = ["branch", "ahead", "behind", "contained", "last commit", "worktree", "PR"];
144508
+ function renderMarkdown2(topology, graph) {
144509
+ const lines = [
144510
+ `## Branch topology vs \`${markdownCell(topology.base)}\``,
144511
+ "",
144512
+ `Generated ${topology.generatedAt}.`
144513
+ ];
144514
+ for (const warning of topology.warnings)
144515
+ lines.push("", `> warning: ${warning}`);
144516
+ lines.push("", ...markdownTable(topology.branches), ...omittedFooter(topology, "_", "_"));
144517
+ lines.push("", "```mermaid", renderGitGraph(topology, graph), "```");
144518
+ return `${lines.join(`
144519
+ `)}
144520
+ `;
144521
+ }
144522
+ function renderText(topology) {
144523
+ const rows = [TABLE_HEADERS.map((header) => header), ...topology.branches.map(rowCells)];
144524
+ const widths = TABLE_HEADERS.map((_, column) => Math.max(...rows.map((row) => (row[column] ?? "").length)));
144525
+ const lines = [`base: ${topology.base}`, `generated: ${topology.generatedAt}`];
144526
+ for (const warning of topology.warnings)
144527
+ lines.push(`warning: ${warning}`);
144528
+ lines.push("", ...rows.map((row) => alignRow(row, widths)), ...omittedFooter(topology, "", ""));
144529
+ return `${lines.join(`
144530
+ `)}
144531
+ `;
144532
+ }
144533
+ function markdownTable(branches) {
144534
+ const lines = [`| ${TABLE_HEADERS.join(" | ")} |`, `| ${TABLE_HEADERS.map(() => "---").join(" | ")} |`];
144535
+ for (const row of branches) {
144536
+ lines.push(`| ${rowCells(row).map(markdownCell).join(" | ")} |`);
144537
+ }
144538
+ return lines;
144539
+ }
144540
+ function rowCells(row) {
144541
+ return [
144542
+ row.name,
144543
+ String(row.ahead),
144544
+ String(row.behind),
144545
+ row.contained ? "yes" : "no",
144546
+ row.lastCommitAt.slice(0, 10),
144547
+ row.worktree ?? "-",
144548
+ row.pr === null ? "-" : `#${row.pr.number} ${row.pr.state}${row.pr.reviewDecision === null ? "" : ` ${row.pr.reviewDecision}`}`
144549
+ ];
144550
+ }
144551
+ function alignRow(cells, widths) {
144552
+ return cells.map((cell, column) => {
144553
+ const width = widths[column] ?? cell.length;
144554
+ return column === 0 ? cell.padEnd(width) : cell.padStart(width);
144555
+ }).join(" ").trimEnd();
144556
+ }
144557
+ function omittedFooter(topology, open, close) {
144558
+ if (topology.omitted === 0)
144559
+ return [];
144560
+ const noun = topology.omitted === 1 ? "branch" : "branches";
144561
+ return ["", `${open}Omitted: ${topology.omitted} ${noun} beyond --limit.${close}`];
144562
+ }
144563
+ function markdownCell(value) {
144564
+ return value.replaceAll("|", "\\|").replaceAll("`", "'");
144565
+ }
144566
+ function renderGitGraph(topology, graph) {
144567
+ const lines = [];
144568
+ if (graph.theme !== undefined && graph.theme.length > 0) {
144569
+ lines.push(`%%{init: { 'theme': '${sanitizeId(graph.theme)}' } }%%`);
144570
+ }
144571
+ lines.push("gitGraph");
144572
+ const baseId = sanitizeId(topology.base);
144573
+ const usedRefs = new Set;
144574
+ const base = refToken(topology.base, usedRefs);
144575
+ const baseRow = baseRowOf(topology);
144576
+ if (baseId !== MERMAID_DEFAULT_BRANCH)
144577
+ lines.push(`${INDENT}branch ${base}`);
144578
+ const tagsBySha = collectTags(topology.branches);
144579
+ const usedIds = new Set;
144580
+ const usedTags = new Set;
144581
+ const window2 = (baseRow?.commits ?? []).slice(0, Math.max(graph.maxCommits, 0));
144582
+ for (const { sha } of deepForkPoints(topology, baseRow, window2.length, graph.hideContained)) {
144583
+ lines.push(`${INDENT}${commitStatement({ sha, subject: "", tags: tagsBySha.get(sha) ?? [] }, usedIds, usedTags, undefined)}`);
144584
+ }
144585
+ for (const [index, commit] of window2.slice().reverse().entries()) {
144586
+ const isTip = index === window2.length - 1;
144587
+ lines.push(`${INDENT}${commitStatement(commit, usedIds, usedTags, isTip ? "HIGHLIGHT" : commitType(commit))}`);
144588
+ }
144589
+ for (const row of topology.branches) {
144590
+ if (row === baseRow)
144591
+ continue;
144592
+ if (row.contained && graph.hideContained)
144593
+ continue;
144594
+ lines.push(`${INDENT}checkout ${base}`);
144595
+ const branch = refToken(row.name, usedRefs);
144596
+ lines.push(branch === MERMAID_DEFAULT_BRANCH ? `${INDENT}checkout ${branch}` : `${INDENT}branch ${branch}`);
144597
+ if (row.contained) {
144598
+ lines.push(`${INDENT}${commitStatement({ sha: row.tip, subject: "", tags: tagsBySha.get(row.tip) ?? [] }, usedIds, usedTags, undefined)}`);
144599
+ lines.push(`${INDENT}checkout ${base}`);
144600
+ lines.push(`${INDENT}merge ${branch}`);
144601
+ continue;
144602
+ }
144603
+ const shown = row.commits.slice(0, Math.max(Math.min(graph.maxCommits, row.ahead), 0)).reverse();
144604
+ const hidden = Math.max(row.ahead - shown.length, 0);
144605
+ if (hidden > 0) {
144606
+ lines.push(`${INDENT}commit id:"${uniqueId(`+${hidden} earlier`, usedIds)}"`);
144607
+ }
144608
+ for (const [index, commit] of shown.entries()) {
144609
+ const isTip = index === shown.length - 1;
144610
+ lines.push(`${INDENT}${commitStatement(commit, usedIds, usedTags, isTip ? "HIGHLIGHT" : commitType(commit))}`);
144611
+ }
144612
+ }
144613
+ return lines.join(`
144614
+ `);
144615
+ }
144616
+ function commitStatement(commit, usedIds, usedTags, type) {
144617
+ const sha = short(commit.sha);
144618
+ const id = uniqueId(sanitizeId(sha), usedIds);
144619
+ const tag = claimTag(commit.tags[0], usedTags);
144620
+ let line = `commit id:"${id}"`;
144621
+ if (tag !== undefined)
144622
+ line += ` tag:"${tag}"`;
144623
+ if (type !== undefined)
144624
+ line += ` type: ${type}`;
144625
+ return line;
144626
+ }
144627
+ function claimTag(name, usedTags) {
144628
+ if (name === undefined)
144629
+ return;
144630
+ const sanitized = sanitizeId(name);
144631
+ if (usedTags.has(sanitized))
144632
+ return;
144633
+ usedTags.add(sanitized);
144634
+ return sanitized;
144635
+ }
144636
+ function deepForkPoints(topology, baseRow, windowLength, hideContained) {
144637
+ const behindBySha = new Map;
144638
+ for (const row of topology.branches) {
144639
+ if (row === baseRow)
144640
+ continue;
144641
+ if (row.contained && hideContained)
144642
+ continue;
144643
+ if (row.forkPoint.length === 0 || row.forkPoint === row.tip)
144644
+ continue;
144645
+ if (row.behind < windowLength)
144646
+ continue;
144647
+ if (!behindBySha.has(row.forkPoint))
144648
+ behindBySha.set(row.forkPoint, row.behind);
144649
+ }
144650
+ return [...behindBySha].map(([sha, behind]) => ({ sha, behind })).sort((a, b) => b.behind - a.behind || (a.sha < b.sha ? -1 : 1));
144651
+ }
144652
+ function commitType(commit) {
144653
+ return commit.subject.startsWith('Revert "') ? "REVERSE" : undefined;
144654
+ }
144655
+ function collectTags(branches) {
144656
+ const tags = new Map;
144657
+ for (const row of branches) {
144658
+ for (const commit of row.commits)
144659
+ tags.set(commit.sha, commit.tags);
144660
+ }
144661
+ return tags;
144662
+ }
144663
+ function isBaseRow(base, row) {
144664
+ if (row.name === base)
144665
+ return true;
144666
+ if (row.kind !== "local")
144667
+ return false;
144668
+ return row.name === base.replace(/^[^/]+\//, "");
144669
+ }
144670
+ function short(sha) {
144671
+ return sha.slice(0, 7);
144672
+ }
144673
+ function refToken(value, usedRefs) {
144674
+ const id = uniqueId(sanitizeRef(value), usedRefs);
144675
+ return /^[A-Za-z_]/.test(id) ? id : `"${id}"`;
144676
+ }
144677
+ function sanitizeId(value) {
144678
+ return sanitizeRef(value).slice(0, 40);
144679
+ }
144680
+ function sanitizeRef(value) {
144681
+ return value.replace(/[^A-Za-z0-9._/-]/g, "-");
144682
+ }
144683
+ function baseRowOf(topology) {
144684
+ return topology.branches.find((row) => row.name === topology.base) ?? topology.branches.find((row) => isBaseRow(topology.base, row)) ?? null;
144685
+ }
144686
+ function uniqueId(id, usedIds) {
144687
+ let candidate = id;
144688
+ let suffix = 2;
144689
+ while (usedIds.has(candidate)) {
144690
+ candidate = `${id}-${suffix}`;
144691
+ suffix += 1;
144692
+ }
144693
+ usedIds.add(candidate);
144694
+ return candidate;
144695
+ }
144696
+
144697
+ // ../../packages/app/src/services/branch-topology/index.ts
144698
+ var GRAPH_COMMIT_WINDOW = 10;
144699
+ var FIELD_SEP = "\x00";
144700
+
144701
+ class BranchTopologyUsageError extends Error {
144702
+ }
144703
+
144704
+ class BranchTopologyGitError extends Error {
144705
+ }
144706
+ function collectBranchTopology(runner, opts) {
144707
+ const warnings = [];
144708
+ const base = resolveBase(runner, opts.base);
144709
+ const refs = listRefs(runner, opts.scope);
144710
+ const worktrees = readWorktrees(runner, warnings);
144711
+ const tagIndex = readTagIndex(runner, warnings);
144712
+ const rows = refs.map((ref) => buildRow(runner, base.sha, ref));
144713
+ const detached = detachedHead(runner, base.sha);
144714
+ if (detached !== null && opts.scope !== "remote")
144715
+ rows.push(detached);
144716
+ for (const row of rows) {
144717
+ row.worktree = row.kind === "detached" ? worktrees.bySha.get(row.tip) ?? null : worktrees.byBranch.get(row.name) ?? null;
144718
+ }
144719
+ if (opts.pr)
144720
+ attachPullRequests(rows, readOpenPullRequests(runner, warnings));
144721
+ const sorted = sortRows(rows);
144722
+ const branches = sorted.slice(0, Math.max(opts.limit, 0));
144723
+ const omitted = sorted.length - branches.length;
144724
+ for (const row of branches) {
144725
+ if (row.contained && !isBaseRow(base.name, row))
144726
+ continue;
144727
+ row.commits = readCommits(runner, row.tip, tagIndex, warnings);
144728
+ }
144729
+ return { base: base.name, generatedAt: new Date().toISOString(), branches, omitted, warnings };
144730
+ }
144731
+ function resolveBase(runner, explicit) {
144732
+ const trimmed = explicit?.trim();
144733
+ const requested = trimmed !== undefined && trimmed.length > 0 ? trimmed : undefined;
144734
+ let candidate = requested ?? "";
144735
+ if (candidate.length === 0) {
144736
+ const originHead = runner(["git", "symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
144737
+ const target = originHead.code === 0 ? originHead.stdout.trim() : "";
144738
+ candidate = target.length > 0 ? target : "main";
144739
+ }
144740
+ const resolved = runner(["git", "rev-parse", "--verify", "--quiet", `${candidate}^{commit}`]);
144741
+ if (resolved.code !== 0) {
144742
+ if ((resolved.error ?? "").length > 0) {
144743
+ throw new BranchTopologyGitError(`git is not available: ${resolved.error}`);
144744
+ }
144745
+ if (/not a git repository/i.test(resolved.stderr)) {
144746
+ throw new BranchTopologyGitError(`not a git repository: ${failureDetail(resolved)}`);
144747
+ }
144748
+ throw new BranchTopologyUsageError(requested === undefined ? `unknown base ref '${candidate}' (no --base given; tried origin/HEAD, then main)` : `unknown base ref '${candidate}'`);
144749
+ }
144750
+ return { name: candidate, sha: resolved.stdout.trim() };
144751
+ }
144752
+ function listRefs(runner, scope) {
144753
+ const namespaces = scope === "local" ? ["refs/heads"] : scope === "remote" ? ["refs/remotes"] : ["refs/heads", "refs/remotes"];
144754
+ const res = runner(["git", "for-each-ref", "--format=%(refname)%00%(objectname)", ...namespaces]);
144755
+ if (res.code !== 0)
144756
+ throw gitFailure(res, "git for-each-ref");
144757
+ const refs = [];
144758
+ for (const line of res.stdout.split(`
144759
+ `)) {
144760
+ if (line.length === 0)
144761
+ continue;
144762
+ const [refname, sha] = line.split(FIELD_SEP);
144763
+ if (refname === undefined || sha === undefined || sha.length === 0)
144764
+ continue;
144765
+ if (refname.endsWith("/HEAD"))
144766
+ continue;
144767
+ if (refname.startsWith("refs/heads/")) {
144768
+ refs.push({ name: refname.slice("refs/heads/".length), kind: "local", sha });
144769
+ } else if (refname.startsWith("refs/remotes/")) {
144770
+ refs.push({ name: refname.slice("refs/remotes/".length), kind: "remote", sha });
144771
+ }
144772
+ }
144773
+ if (scope !== "all")
144774
+ return refs;
144775
+ const localTips = new Map(refs.filter((ref) => ref.kind === "local").map((ref) => [ref.name, ref.sha]));
144776
+ return refs.filter((ref) => {
144777
+ if (ref.kind !== "remote")
144778
+ return true;
144779
+ const twin = ref.name.slice(ref.name.indexOf("/") + 1);
144780
+ return localTips.get(twin) !== ref.sha;
144781
+ });
144782
+ }
144783
+ function buildRow(runner, baseSha, ref) {
144784
+ const mergeBase = runner(["git", "merge-base", baseSha, ref.sha]);
144785
+ const forkPoint = mergeBase.code === 0 ? mergeBase.stdout.trim() : "";
144786
+ const counts = runner(["git", "rev-list", "--left-right", "--count", `${baseSha}...${ref.sha}`]);
144787
+ if (counts.code !== 0)
144788
+ throw gitFailure(counts, `git rev-list for ${ref.name}`);
144789
+ const [behindRaw, aheadRaw] = counts.stdout.trim().split(/\s+/);
144790
+ const behind = parseCount2(behindRaw, ref.name);
144791
+ const ahead = parseCount2(aheadRaw, ref.name);
144792
+ const log = runner(["git", "log", "-1", "--format=%H%x00%cI%x00%s", ref.sha]);
144793
+ if (log.code !== 0)
144794
+ throw gitFailure(log, `git log for ${ref.name}`);
144795
+ const [tip, lastCommitAt, ...subjectParts] = log.stdout.replace(/\n$/, "").split(FIELD_SEP);
144796
+ return {
144797
+ name: ref.name,
144798
+ kind: ref.kind,
144799
+ tip: tip ?? ref.sha,
144800
+ forkPoint,
144801
+ ahead,
144802
+ behind,
144803
+ contained: ahead === 0,
144804
+ lastCommitAt: lastCommitAt ?? "",
144805
+ subject: subjectParts.join(FIELD_SEP),
144806
+ worktree: null,
144807
+ pr: null,
144808
+ commits: []
144809
+ };
144810
+ }
144811
+ function parseCount2(raw, ref) {
144812
+ const parsed = Number.parseInt(raw ?? "", 10);
144813
+ if (!Number.isInteger(parsed)) {
144814
+ throw new BranchTopologyGitError(`git rev-list for ${ref} returned '${raw ?? ""}'`);
144815
+ }
144816
+ return parsed;
144817
+ }
144818
+ function detachedHead(runner, baseSha) {
144819
+ const symbolic = runner(["git", "symbolic-ref", "--short", "-q", "HEAD"]);
144820
+ if (symbolic.code === 0)
144821
+ return null;
144822
+ const head = runner(["git", "rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
144823
+ if (head.code !== 0)
144824
+ return null;
144825
+ const tip = head.stdout.trim();
144826
+ return buildRow(runner, baseSha, { name: shortSha(tip), kind: "detached", sha: tip });
144827
+ }
144828
+ function sortRows(rows) {
144829
+ const time = (row) => {
144830
+ const parsed = Date.parse(row.lastCommitAt);
144831
+ return Number.isFinite(parsed) ? parsed : 0;
144832
+ };
144833
+ return [...rows].sort((a, b) => {
144834
+ const byTime = time(b) - time(a);
144835
+ if (byTime !== 0)
144836
+ return byTime;
144837
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
144838
+ });
144839
+ }
144840
+ function readWorktrees(runner, warnings) {
144841
+ const index = { byBranch: new Map, bySha: new Map };
144842
+ const res = runner(["git", "worktree", "list", "--porcelain"]);
144843
+ if (res.code !== 0) {
144844
+ warnings.push(`git worktree list failed: ${failureDetail(res)}`);
144845
+ return index;
144846
+ }
144847
+ let path = "";
144848
+ let head = "";
144849
+ let detached = false;
144850
+ const flush = () => {
144851
+ if (detached && head.length > 0 && path.length > 0)
144852
+ index.bySha.set(head, path);
144853
+ path = "";
144854
+ head = "";
144855
+ detached = false;
144856
+ };
144857
+ for (const line of res.stdout.split(`
144858
+ `)) {
144859
+ if (line.length === 0) {
144860
+ flush();
144861
+ } else if (line.startsWith("worktree ")) {
144862
+ flush();
144863
+ path = line.slice("worktree ".length).trim();
144864
+ } else if (line.startsWith("HEAD ")) {
144865
+ head = line.slice("HEAD ".length).trim();
144866
+ } else if (line === "detached") {
144867
+ detached = true;
144868
+ } else if (line.startsWith("branch refs/heads/")) {
144869
+ index.byBranch.set(line.slice("branch refs/heads/".length).trim(), path);
144870
+ }
144871
+ }
144872
+ flush();
144873
+ return index;
144874
+ }
144875
+ function readTagIndex(runner, warnings) {
144876
+ const index = new Map;
144877
+ const res = runner(["git", "for-each-ref", "--format=%(objectname) %(*objectname) %(refname:short)", "refs/tags"]);
144878
+ if (res.code !== 0) {
144879
+ warnings.push(`git for-each-ref refs/tags failed: ${failureDetail(res)}`);
144880
+ return index;
144881
+ }
144882
+ for (const line of res.stdout.split(`
144883
+ `)) {
144884
+ if (line.length === 0)
144885
+ continue;
144886
+ const [object, peeled, ...nameParts] = line.split(" ");
144887
+ const name = nameParts.join(" ");
144888
+ if (object === undefined || name.length === 0)
144889
+ continue;
144890
+ const target = (peeled ?? "").length > 0 ? peeled : object;
144891
+ index.set(target, [...index.get(target) ?? [], name]);
144892
+ }
144893
+ return index;
144894
+ }
144895
+ function readCommits(runner, sha, tagIndex, warnings) {
144896
+ const res = runner(["git", "log", "-n", String(GRAPH_COMMIT_WINDOW), "--format=%H%x00%s", sha]);
144897
+ if (res.code !== 0) {
144898
+ warnings.push(`git log for ${shortSha(sha)} failed: ${failureDetail(res)}`);
144899
+ return [];
144900
+ }
144901
+ const commits = [];
144902
+ for (const line of res.stdout.split(`
144903
+ `)) {
144904
+ if (line.length === 0)
144905
+ continue;
144906
+ const [commitSha, ...subjectParts] = line.split(FIELD_SEP);
144907
+ if (commitSha === undefined || commitSha.length === 0)
144908
+ continue;
144909
+ commits.push({
144910
+ sha: commitSha,
144911
+ subject: subjectParts.join(FIELD_SEP),
144912
+ tags: tagIndex.get(commitSha) ?? []
144913
+ });
144914
+ }
144915
+ return commits;
144916
+ }
144917
+ function readOpenPullRequests(runner, warnings) {
144918
+ const index = new Map;
144919
+ const res = runner([
144920
+ "gh",
144921
+ "pr",
144922
+ "list",
144923
+ "--state",
144924
+ "open",
144925
+ "--json",
144926
+ "number,headRefName,state,reviewDecision",
144927
+ "--limit",
144928
+ "200"
144929
+ ]);
144930
+ if (res.code !== 0 || res.stdout.trim().length === 0) {
144931
+ warnings.push(`--pr: gh pr list failed: ${failureDetail(res)}`);
144932
+ return index;
144933
+ }
144934
+ let parsed;
144935
+ try {
144936
+ parsed = JSON.parse(res.stdout);
144937
+ } catch {
144938
+ warnings.push("--pr: gh pr list returned unparseable JSON");
144939
+ return index;
144940
+ }
144941
+ if (!Array.isArray(parsed)) {
144942
+ warnings.push("--pr: gh pr list returned unparseable JSON");
144943
+ return index;
144944
+ }
144945
+ for (const entry of parsed) {
144946
+ const { number, headRefName, state, reviewDecision } = entry;
144947
+ if (typeof number !== "number" || typeof headRefName !== "string")
144948
+ continue;
144949
+ const pr = {
144950
+ number,
144951
+ state: typeof state === "string" ? state : "",
144952
+ reviewDecision: typeof reviewDecision === "string" && reviewDecision.length > 0 ? reviewDecision : null
144953
+ };
144954
+ const existing = index.get(headRefName);
144955
+ if (existing === undefined || pr.number < existing.number)
144956
+ index.set(headRefName, pr);
144957
+ }
144958
+ return index;
144959
+ }
144960
+ function attachPullRequests(rows, index) {
144961
+ for (const row of rows) {
144962
+ if (row.kind === "detached")
144963
+ continue;
144964
+ const keys = row.kind === "remote" ? [row.name, row.name.replace(/^[^/]+\//, "")] : [row.name];
144965
+ row.pr = keys.map((key) => index.get(key)).find((pr) => pr !== undefined) ?? null;
144966
+ }
144967
+ }
144968
+ function shortSha(sha) {
144969
+ return sha.slice(0, 7);
144970
+ }
144971
+ function gitFailure(res, what) {
144972
+ return new BranchTopologyGitError(`${what} failed: ${failureDetail(res)}`);
144973
+ }
144974
+ function failureDetail(res) {
144975
+ const first = (res.stderr.trim() || res.stdout.trim()).split(`
144976
+ `)[0] ?? "";
144977
+ if (first.length > 0)
144978
+ return first;
144979
+ return (res.error ?? "").length > 0 ? res.error : `exit ${res.code}`;
144980
+ }
144981
+
144982
+ // src/commands/builder/show-branches.ts
144983
+ var FORMATS = ["markdown", "json", "text"];
144984
+ function registerBuilderShowBranchesCommand(builder, context) {
144985
+ const command = builder.command("show-branches").summary("read-only branch topology report against a base (markdown | json | text)").description([
144986
+ "Report every branch against a base ref, read-only: fork point, commits ahead/behind,",
144987
+ "contained flag, last commit, worktree binding, and (with --pr) open pull-request state.",
144988
+ "Base resolution: --base, else the origin/HEAD target, else main.",
144989
+ "markdown (default) prints the summary table and one condensed mermaid gitGraph;",
144990
+ "json prints the raw BranchTopology object; text prints the aligned table only.",
144991
+ "Exit codes: 0 ok \xB7 1 usage (bad --format/--limit/--max-commits, unknown base ref,",
144992
+ "--local with --remote) \xB7 2 git failure (not a repository, git missing)."
144993
+ ].join(`
144994
+ `)).option("--format <format>", "output format: markdown | json | text (default markdown)", "markdown").option("--json", "alias of --format json (prints the raw BranchTopology object)").option("--base <ref>", "base ref to measure every branch against (default: origin/HEAD, else main)").option("--local", "only local branches").option("--remote", "only remote branches").option("--pr", "enrich rows with open pull-request state (one gh call; degrades with a warning)").option("--limit <n>", "maximum branch rows kept, most recent last-commit first (default 30)", "30").option("--max-commits <n>", "maximum commit nodes per branch line in the graph (default 3)", "3").option("--hide-contained", "drop contained branches from the graph instead of drawing their join").option("--theme <name>", "mermaid theme name for the graph init directive").action((options) => {
144995
+ const format = resolveFormat(options.format, command.getOptionValueSource("format") === "cli", options.json === true, context);
144996
+ if (format === null)
144997
+ return;
144998
+ const limit = positiveInteger(options.limit, "--limit", context);
144999
+ if (limit === null)
145000
+ return;
145001
+ const maxCommits = positiveInteger(options.maxCommits, "--max-commits", context);
145002
+ if (maxCommits === null)
145003
+ return;
145004
+ if (options.local === true && options.remote === true) {
145005
+ usageError("--local and --remote are mutually exclusive", context);
145006
+ return;
145007
+ }
145008
+ try {
145009
+ const topology = collectBranchTopology(createSpawnRunner({ cwd: context.cwd, env: context.env }), {
145010
+ ...options.base !== undefined ? { base: options.base } : {},
145011
+ scope: options.local === true ? "local" : options.remote === true ? "remote" : "all",
145012
+ pr: options.pr === true,
145013
+ limit
145014
+ });
145015
+ if (format === "json") {
145016
+ context.output.write(toJson2(topology));
145017
+ } else if (format === "text") {
145018
+ context.output.write(renderText(topology));
145019
+ } else {
145020
+ context.output.write(renderMarkdown2(topology, {
145021
+ maxCommits,
145022
+ hideContained: options.hideContained === true,
145023
+ ...options.theme !== undefined ? { theme: options.theme } : {}
145024
+ }));
145025
+ }
145026
+ context.setExitCode(0);
145027
+ } catch (err) {
145028
+ if (err instanceof BranchTopologyUsageError) {
145029
+ usageError(err.message, context);
145030
+ return;
145031
+ }
145032
+ if (err instanceof BranchTopologyGitError) {
145033
+ context.output.error(err.message);
145034
+ context.setExitCode(2);
145035
+ return;
145036
+ }
145037
+ context.output.error(err instanceof Error ? err.message : String(err));
145038
+ context.setExitCode(2);
145039
+ }
145040
+ });
145041
+ }
145042
+ function resolveFormat(requested, explicitFormat, json, context) {
145043
+ if (!FORMATS.includes(requested)) {
145044
+ usageError(`unknown --format '${requested}': expected ${FORMATS.join(" | ")}`, context);
145045
+ return null;
145046
+ }
145047
+ if (!json)
145048
+ return requested;
145049
+ if (explicitFormat && requested !== "json") {
145050
+ usageError(`--json conflicts with --format ${requested}`, context);
145051
+ return null;
145052
+ }
145053
+ return "json";
145054
+ }
145055
+ function positiveInteger(value, flag, context) {
145056
+ const trimmed = value.trim();
145057
+ const parsed = Number.parseInt(trimmed, 10);
145058
+ if (!/^\d+$/.test(trimmed) || parsed < 1) {
145059
+ usageError(`${flag} expects a positive integer, got '${value}'`, context);
145060
+ return null;
145061
+ }
145062
+ return parsed;
145063
+ }
145064
+ function usageError(message, context) {
145065
+ context.output.error(message);
145066
+ context.setExitCode(1);
145067
+ }
145068
+
144229
145069
  // src/commands/builder.ts
144230
145070
  function registerBuilderCommand(program, context) {
144231
- const noun = program.command("builder").summary("release plumbing: version bumps, tags, and PR-review ops");
145071
+ const noun = program.command("builder").summary("release plumbing: version bumps, tags, branch topology, and PR-review ops");
144232
145072
  noun.command("bump-ver").summary("bump a package version, commit, tag, optionally push").description([
144233
145073
  "Bump one workspace package (or every released package with --all),",
144234
145074
  "rewrite workspace pins, commit, and tag. A bare version bumps all:",
@@ -144330,7 +145170,9 @@ function registerBuilderCommand(program, context) {
144330
145170
  context.setExitCode(1);
144331
145171
  }
144332
145172
  });
144333
- registerBuilderPrCommand(noun, context);
145173
+ registerBuilderShowBranchesCommand(noun, context);
145174
+ registerBuilderPrCommands(noun, context);
145175
+ registerBuilderPrAliasCommand(noun, context);
144334
145176
  }
144335
145177
 
144336
145178
  // src/commands/decision.ts
@@ -144717,9 +145559,9 @@ function registerFeatureVerifyCommand(feature, context) {
144717
145559
  "Runs the selected feature-verification definition\u2019s command once (or --cmd) and writes the",
144718
145560
  "run-scoped + feature-latest receipts and .spur/run/<runId>-feature-verification.status.",
144719
145561
  "Exit: 0 after a completed pass (PASS or FAIL); 1 when the pass could not run.",
144720
- "Flags fall back to the workflow env names: featureId, __runId, verificationCmd, confidence, ackLowConfidence."
145562
+ "Flags fall back to the workflow env names: featureId, __runId, verificationCmd."
144721
145563
  ].join(`
144722
- `)).argument("[id]", "Feature ID (env: featureId)").option("--run-id <id>", "workflow run id the receipt binds to (env: __runId)").option("--cmd <command>", "verification command override (env: verificationCmd)").option("--confidence <level>", "declared confidence HIGH | MEDIUM | LOW (env: confidence)").option("--ack-low-confidence", "acknowledge a LOW confidence level on the receipt (env: ackLowConfidence=true)").action(async (id, options) => {
145564
+ `)).argument("[id]", "Feature ID (env: featureId)").option("--run-id <id>", "workflow run id the receipt binds to (env: __runId)").option("--cmd <command>", "verification command override (env: verificationCmd)").action(async (id, options) => {
144723
145565
  await Promise.resolve().then(() => init_src3());
144724
145566
  const env = context.env;
144725
145567
  const featureId = id || env.featureId || "";
@@ -144728,9 +145570,7 @@ function registerFeatureVerifyCommand(feature, context) {
144728
145570
  workdir: context.cwd,
144729
145571
  featureId,
144730
145572
  runId: options.runId || env.__runId || "",
144731
- cmdOverride: options.cmd || env.verificationCmd || "",
144732
- confidenceOverride: options.confidence || env.confidence || "",
144733
- ackLowConfidence: options.ackLowConfidence === true || env.ackLowConfidence === "true"
145573
+ cmdOverride: options.cmd || env.verificationCmd || ""
144734
145574
  });
144735
145575
  context.output.write(`feature verify: ${featureId} ${result.message}`);
144736
145576
  } catch (err) {
@@ -145177,7 +146017,7 @@ import { createRequire as createRequire3 } from "module";
145177
146017
  var CLI_CONFIG = {
145178
146018
  binaryName: "spur",
145179
146019
  binaryLabel: "spur",
145180
- binaryVersion: "0.4.9",
146020
+ binaryVersion: "0.4.11",
145181
146021
  configDir: ".spur",
145182
146022
  configFile: ".spur/config.yaml",
145183
146023
  databaseFile: ".spur/spur.db"
@@ -145721,7 +146561,7 @@ init_loader2();
145721
146561
  init_src2();
145722
146562
  init_dist11();
145723
146563
  import { homedir as homedir15 } from "os";
145724
- import { join as join84, resolve as resolve31 } from "path";
146564
+ import { join as join85, resolve as resolve31 } from "path";
145725
146565
 
145726
146566
  // src/config/scaffold-manifest.ts
145727
146567
  var SCAFFOLD_MANIFEST = [
@@ -145750,8 +146590,8 @@ var SCAFFOLD_MANIFEST = [
145750
146590
  ];
145751
146591
 
145752
146592
  // src/commands/init.ts
145753
- var GLOBAL_CONFIG_DIR = join84(".config", "spur");
145754
- var GLOBAL_RULES_DIR2 = join84(GLOBAL_CONFIG_DIR, "rules");
146593
+ var GLOBAL_CONFIG_DIR = join85(".config", "spur");
146594
+ var GLOBAL_RULES_DIR2 = join85(GLOBAL_CONFIG_DIR, "rules");
145755
146595
  var GLOBAL_CONFIG_EXAMPLE = BUNDLED_GLOBAL_CONFIG;
145756
146596
  var GLOBAL_CONFIG_FILE2 = "config.yaml";
145757
146597
  var INDEXED_CONTEXT_MARKER = "## Indexed context";
@@ -145782,7 +146622,7 @@ If \`.spur/context/\` is absent, proceed normally. Never block work on its absen
145782
146622
  `;
145783
146623
  function globalRulesRoot(context) {
145784
146624
  const override = context.env.SPUR_GLOBAL_RULES_DIR;
145785
- return override !== undefined && override.length > 0 ? resolve31(context.cwd, override) : join84(homedir15(), GLOBAL_RULES_DIR2);
146625
+ return override !== undefined && override.length > 0 ? resolve31(context.cwd, override) : join85(homedir15(), GLOBAL_RULES_DIR2);
145786
146626
  }
145787
146627
  async function writeIfNew(context, path, content, force, result) {
145788
146628
  if (!force && await context.fs.exists(path)) {
@@ -145799,18 +146639,18 @@ async function seedGlobalRules(context) {
145799
146639
  const target = globalRulesRoot(context);
145800
146640
  let written = 0;
145801
146641
  for (const relPath of await listBundledRuleFiles()) {
145802
- const destination = join84(target, relPath);
146642
+ const destination = join85(target, relPath);
145803
146643
  if (await context.fs.exists(destination))
145804
146644
  continue;
145805
- await context.fs.ensureDir(join84(target, ...relPath.split("/").slice(0, -1)));
145806
- await context.fs.writeFile(destination, await context.fs.readFile(join84(source, relPath)));
146645
+ await context.fs.ensureDir(join85(target, ...relPath.split("/").slice(0, -1)));
146646
+ await context.fs.writeFile(destination, await context.fs.readFile(join85(source, relPath)));
145807
146647
  written += 1;
145808
146648
  }
145809
146649
  return written;
145810
146650
  }
145811
146651
  function resolveGlobalConfigDir(context) {
145812
146652
  const globalOverride = context.env.SPUR_GLOBAL_RULES_DIR;
145813
- return globalOverride !== undefined && globalOverride.length > 0 ? resolve31(context.cwd, globalOverride) : join84(homedir15(), GLOBAL_CONFIG_DIR);
146653
+ return globalOverride !== undefined && globalOverride.length > 0 ? resolve31(context.cwd, globalOverride) : join85(homedir15(), GLOBAL_CONFIG_DIR);
145814
146654
  }
145815
146655
  async function seedGlobalConfig(context) {
145816
146656
  const source = bundledConfigRoot();
@@ -145821,15 +146661,15 @@ async function seedGlobalConfig(context) {
145821
146661
  for (const relPath of listBundledConfigFiles()) {
145822
146662
  if (relPath === GLOBAL_CONFIG_EXAMPLE)
145823
146663
  continue;
145824
- const destination = join84(target, relPath);
146664
+ const destination = join85(target, relPath);
145825
146665
  if (await context.fs.exists(destination))
145826
146666
  continue;
145827
- await context.fs.ensureDir(join84(target, ...relPath.split("/").slice(0, -1)));
145828
- await context.fs.writeFile(destination, await context.fs.readFile(join84(source, relPath)));
146667
+ await context.fs.ensureDir(join85(target, ...relPath.split("/").slice(0, -1)));
146668
+ await context.fs.writeFile(destination, await context.fs.readFile(join85(source, relPath)));
145829
146669
  written += 1;
145830
146670
  }
145831
- const examplePath = join84(source, GLOBAL_CONFIG_EXAMPLE);
145832
- const globalConfigPath = join84(target, GLOBAL_CONFIG_FILE2);
146671
+ const examplePath = join85(source, GLOBAL_CONFIG_EXAMPLE);
146672
+ const globalConfigPath = join85(target, GLOBAL_CONFIG_FILE2);
145833
146673
  if (await context.fs.exists(examplePath) && !await context.fs.exists(globalConfigPath)) {
145834
146674
  await context.fs.ensureDir(target);
145835
146675
  await context.fs.writeFile(globalConfigPath, await context.fs.readFile(examplePath));
@@ -145844,10 +146684,10 @@ function registerInitCommand(program, context, options = {}) {
145844
146684
  const minimal = options.minimal === true;
145845
146685
  const adoptGlobal = options.adoptGlobalConfig === true;
145846
146686
  const projectName = options.name ?? "default";
145847
- const configPath = join84(context.cwd, CLI_CONFIG.configFile);
146687
+ const configPath = join85(context.cwd, CLI_CONFIG.configFile);
145848
146688
  const result = { created: [], skipped: [] };
145849
146689
  const globalConfigDir = resolveGlobalConfigDir(context);
145850
- const globalConfigPath = join84(globalConfigDir, GLOBAL_CONFIG_FILE2);
146690
+ const globalConfigPath = join85(globalConfigDir, GLOBAL_CONFIG_FILE2);
145851
146691
  const globalConfigExists = await context.fs.exists(globalConfigPath);
145852
146692
  let misplacedKeys = [];
145853
146693
  if (globalConfigExists) {
@@ -145860,20 +146700,20 @@ function registerInitCommand(program, context, options = {}) {
145860
146700
  const configRoot = bundledConfigRoot();
145861
146701
  if (configRoot !== null && !minimal) {
145862
146702
  for (const relPath of listBundledProjectSeedFiles()) {
145863
- const sourcePath = join84(configRoot, relPath);
146703
+ const sourcePath = join85(configRoot, relPath);
145864
146704
  if (!await context.fs.exists(sourcePath))
145865
146705
  continue;
145866
- const targetPath = join84(context.cwd, CLI_CONFIG.configDir, relPath);
145867
- await context.fs.ensureDir(join84(targetPath, ".."));
146706
+ const targetPath = join85(context.cwd, CLI_CONFIG.configDir, relPath);
146707
+ await context.fs.ensureDir(join85(targetPath, ".."));
145868
146708
  await writeIfNew(context, targetPath, await context.fs.readFile(sourcePath), false, result);
145869
146709
  }
145870
146710
  for (const entry of SCAFFOLD_MANIFEST) {
145871
- const sourcePath = join84(configRoot, entry.source);
146711
+ const sourcePath = join85(configRoot, entry.source);
145872
146712
  if (!await context.fs.exists(sourcePath))
145873
146713
  continue;
145874
- const baseDir = entry.root === true ? context.cwd : join84(context.cwd, CLI_CONFIG.configDir);
145875
- const targetPath = join84(baseDir, entry.target);
145876
- await context.fs.ensureDir(join84(targetPath, ".."));
146714
+ const baseDir = entry.root === true ? context.cwd : join85(context.cwd, CLI_CONFIG.configDir);
146715
+ const targetPath = join85(baseDir, entry.target);
146716
+ await context.fs.ensureDir(join85(targetPath, ".."));
145877
146717
  const entryForce = false;
145878
146718
  let body = await context.fs.readFile(sourcePath);
145879
146719
  if (entry.target === "AGENTS.md") {
@@ -145890,7 +146730,7 @@ function registerInitCommand(program, context, options = {}) {
145890
146730
  const backupPath = `${globalConfigPath}.bak-${new Date().toISOString().replace(/[:.]/g, "-")}`;
145891
146731
  await context.fs.writeFile(backupPath, await context.fs.readFile(globalConfigPath));
145892
146732
  const bundledRoot = bundledConfigRoot();
145893
- const examplePath = bundledRoot !== null ? join84(bundledRoot, GLOBAL_CONFIG_EXAMPLE) : null;
146733
+ const examplePath = bundledRoot !== null ? join85(bundledRoot, GLOBAL_CONFIG_EXAMPLE) : null;
145894
146734
  if (examplePath !== null && await context.fs.exists(examplePath)) {
145895
146735
  await context.fs.writeFile(globalConfigPath, await context.fs.readFile(examplePath));
145896
146736
  adoptedGlobal = true;
@@ -145963,13 +146803,13 @@ function registerInitCommand(program, context, options = {}) {
145963
146803
  ].join(`
145964
146804
  `)}
145965
146805
  `;
145966
- await context.fs.ensureDir(join84(context.cwd, CLI_CONFIG.configDir));
146806
+ await context.fs.ensureDir(join85(context.cwd, CLI_CONFIG.configDir));
145967
146807
  await context.fs.writeFile(configPath, configYaml);
145968
146808
  result.created.push(configPath);
145969
- const agentsDir = join84(context.cwd, CLI_CONFIG.configDir, "agents");
146809
+ const agentsDir = join85(context.cwd, CLI_CONFIG.configDir, "agents");
145970
146810
  await context.fs.ensureDir(agentsDir);
145971
- await writeIfNew(context, join84(agentsDir, ".gitkeep"), "", force, result);
145972
- const gitignorePath = join84(context.cwd, ".gitignore");
146811
+ await writeIfNew(context, join85(agentsDir, ".gitkeep"), "", force, result);
146812
+ const gitignorePath = join85(context.cwd, ".gitignore");
145973
146813
  const contextEntry = ".spur/context/";
145974
146814
  if (await context.fs.exists(gitignorePath)) {
145975
146815
  const existing = await context.fs.readFile(gitignorePath);
@@ -145989,20 +146829,20 @@ ${contextEntry}
145989
146829
  const configRoot = bundledConfigRoot();
145990
146830
  if (configRoot !== null) {
145991
146831
  for (const relPath of listBundledProjectSeedFiles()) {
145992
- const sourcePath = join84(configRoot, relPath);
146832
+ const sourcePath = join85(configRoot, relPath);
145993
146833
  if (!await context.fs.exists(sourcePath))
145994
146834
  continue;
145995
- const targetPath = join84(context.cwd, CLI_CONFIG.configDir, relPath);
145996
- await context.fs.ensureDir(join84(targetPath, ".."));
146835
+ const targetPath = join85(context.cwd, CLI_CONFIG.configDir, relPath);
146836
+ await context.fs.ensureDir(join85(targetPath, ".."));
145997
146837
  await writeIfNew(context, targetPath, await context.fs.readFile(sourcePath), force, result);
145998
146838
  }
145999
146839
  for (const entry of SCAFFOLD_MANIFEST) {
146000
- const sourcePath = join84(configRoot, entry.source);
146840
+ const sourcePath = join85(configRoot, entry.source);
146001
146841
  if (!await context.fs.exists(sourcePath))
146002
146842
  continue;
146003
- const baseDir = entry.root === true ? context.cwd : join84(context.cwd, CLI_CONFIG.configDir);
146004
- const targetPath = join84(baseDir, entry.target);
146005
- await context.fs.ensureDir(join84(targetPath, ".."));
146843
+ const baseDir = entry.root === true ? context.cwd : join85(context.cwd, CLI_CONFIG.configDir);
146844
+ const targetPath = join85(baseDir, entry.target);
146845
+ await context.fs.ensureDir(join85(targetPath, ".."));
146006
146846
  const entryForce = entry.preserve === true ? false : force;
146007
146847
  let body = await context.fs.readFile(sourcePath);
146008
146848
  if (entry.target === "AGENTS.md") {
@@ -146015,7 +146855,7 @@ ${contextEntry}
146015
146855
  }
146016
146856
  }
146017
146857
  }
146018
- const agentsMdPath = join84(context.cwd, "AGENTS.md");
146858
+ const agentsMdPath = join85(context.cwd, "AGENTS.md");
146019
146859
  if (await context.fs.exists(agentsMdPath)) {
146020
146860
  const existing = await context.fs.readFile(agentsMdPath);
146021
146861
  if (!existing.includes(INDEXED_CONTEXT_MARKER)) {
@@ -146056,11 +146896,11 @@ ${contextEntry}
146056
146896
  // src/commands/maintain.ts
146057
146897
  init_src();
146058
146898
  init_src2();
146059
- import { join as join85 } from "path";
146899
+ import { join as join86 } from "path";
146060
146900
  function registerMaintainCommand(program, context, options = {}) {
146061
146901
  program.command("maintain", { hidden: options.hidden === true }).summary("run database maintenance (optimize, WAL checkpoint truncation, optional VACUUM)").description("Run database maintenance: PRAGMA optimize, WAL checkpoint truncation; optionally VACUUM compaction.").option("--vacuum", "Run VACUUM defragmentation and page compaction", false).option(...SHARED_OPTIONS.jsonSupported).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options) => {
146062
146902
  const db = await context.getDb();
146063
- const dbPath = context.env.DATABASE_URL ?? join85(context.cwd, DEFAULT_DATABASE_URL);
146903
+ const dbPath = context.env.DATABASE_URL ?? join86(context.cwd, DEFAULT_DATABASE_URL);
146064
146904
  const result = await maintainDatabase(db, {
146065
146905
  vacuum: options.vacuum === true,
146066
146906
  dbPath: dbPath.startsWith(":memory:") ? undefined : dbPath
@@ -146484,14 +147324,14 @@ async function waitForPendingDrain(agentService, coordination, pin, timeoutMs, s
146484
147324
 
146485
147325
  // src/commands/migrate.ts
146486
147326
  init_src2();
146487
- import { join as join87, relative as relative18 } from "path";
147327
+ import { join as join88, relative as relative18 } from "path";
146488
147328
 
146489
147329
  // src/context.ts
146490
147330
  init_src3();
146491
147331
  init_src();
146492
147332
  init_src2();
146493
147333
  init_dist5();
146494
- import { dirname as dirname41, join as join86, resolve as resolve32 } from "path";
147334
+ import { dirname as dirname41, join as join87, resolve as resolve32 } from "path";
146495
147335
  import { isatty as isatty4 } from "tty";
146496
147336
 
146497
147337
  // ../../node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
@@ -147314,7 +148154,7 @@ ${l}
147314
148154
  }).prompt();
147315
148155
  };
147316
148156
  var i2 = `${styleText2("gray", S_BAR)} `;
147317
- var text4 = (t) => new n3({
148157
+ var text5 = (t) => new n3({
147318
148158
  validate: t.validate,
147319
148159
  placeholder: t.placeholder,
147320
148160
  defaultValue: t.defaultValue,
@@ -147384,7 +148224,7 @@ class ClackHitlResponder {
147384
148224
  return { value: result };
147385
148225
  }
147386
148226
  case "input": {
147387
- const result = await text4({
148227
+ const result = await text5({
147388
148228
  message: request.prompt
147389
148229
  });
147390
148230
  if (isCancel(result)) {
@@ -147476,7 +148316,7 @@ function createCliContext(options) {
147476
148316
  }
147477
148317
  function noopSetExitCode(_code) {}
147478
148318
  function resolveDatabaseUrl(cwd, env) {
147479
- return env.DATABASE_URL === undefined ? join86(cwd, DEFAULT_DATABASE_URL) : buildConfigFromEnv(env).database.url;
148319
+ return env.DATABASE_URL === undefined ? join87(cwd, DEFAULT_DATABASE_URL) : buildConfigFromEnv(env).database.url;
147480
148320
  }
147481
148321
  async function createMigratedDbAdapter(cwd = process.cwd(), env = getEnvVars(), dbUrl) {
147482
148322
  const url = dbUrl ?? resolveDatabaseUrl(cwd, env);
@@ -147539,7 +148379,7 @@ function registerMigrateCommand(program, context, options = {}) {
147539
148379
  context.setExitCode(1);
147540
148380
  return;
147541
148381
  }
147542
- const migrations = await loadSqlMigrations(join87(context.cwd, "drizzle")).catch(() => {
148382
+ const migrations = await loadSqlMigrations(join88(context.cwd, "drizzle")).catch(() => {
147543
148383
  return;
147544
148384
  });
147545
148385
  const db = await context.getDb();
@@ -147554,22 +148394,28 @@ function registerMigrateCommand(program, context, options = {}) {
147554
148394
  init_src3();
147555
148395
  init_src2();
147556
148396
  init_dist5();
147557
- import { basename as basename19, dirname as dirname42, join as join88, resolve as resolve33 } from "path";
148397
+ import { basename as basename19, dirname as dirname42, join as join89, resolve as resolve33 } from "path";
147558
148398
  function registerProjectsCommand(program, context) {
147559
148399
  const projectsCmd = program.command("projects").summary("manage the Spur multi-project registry");
147560
- projectsCmd.command("add").argument("<path>", "Project root directory path").option(...SHARED_OPTIONS.nameProjectDisplay).option(...SHARED_OPTIONS.jsonProjectsResponse).option(...SHARED_OPTIONS.jsonEnvelope).action(async (pathArg, options) => {
148400
+ projectsCmd.command("add").argument("<path>", "Project root directory path").option("--host <host>", 'Host for local or remote project (default: "localhost")').option(...SHARED_OPTIONS.nameProjectDisplay).option(...SHARED_OPTIONS.jsonProjectsResponse).option(...SHARED_OPTIONS.jsonEnvelope).action(async (pathArg, options) => {
147561
148401
  try {
147562
- const absolutePath = resolve33(context.cwd, pathArg);
147563
- if (!await context.fs.exists(absolutePath)) {
147564
- throw new Error(`Directory does not exist: ${absolutePath}`);
148402
+ const host = options.host ?? "localhost";
148403
+ const isLocal = isLocalHost(host);
148404
+ let absolutePath = pathArg;
148405
+ if (isLocal) {
148406
+ absolutePath = resolve33(context.cwd, pathArg);
148407
+ if (!await context.fs.exists(absolutePath)) {
148408
+ throw new Error(`Directory does not exist: ${absolutePath}`);
148409
+ }
147565
148410
  }
147566
148411
  const name = options.name ?? basename19(absolutePath);
147567
148412
  const registry = new ProjectRegistry;
147568
- const entry = await registry.upsert({ path: absolutePath, name, port: 0 });
148413
+ const entry = await registry.upsert({ path: absolutePath, name, host, port: 0 });
147569
148414
  if (options.json) {
147570
148415
  context.output.write(toEnvelopeJson({ ok: true, project: entry }, { enveloped: options.jsonEnvelope }));
147571
148416
  } else {
147572
- context.output.write(`Registered project "${entry.name}" at ${entry.path}`);
148417
+ const hostLabel = !isLocalHost(entry.host) ? ` on host ${entry.host}` : "";
148418
+ context.output.write(`Registered project "${entry.name}" at ${entry.path}${hostLabel}`);
147573
148419
  }
147574
148420
  } catch (err) {
147575
148421
  const message = err instanceof Error ? err.message : String(err);
@@ -147614,14 +148460,17 @@ function registerProjectsCommand(program, context) {
147614
148460
  ok: true,
147615
148461
  purgedCount: result.purgedProjects.length,
147616
148462
  purgedProjects: result.purgedProjects,
147617
- terminatedProcesses: result.terminatedProcesses
148463
+ terminatedProcesses: result.terminatedProcesses,
148464
+ ...result.resetPorts && result.resetPorts.length > 0 ? { resetPorts: result.resetPorts } : {}
147618
148465
  }, { enveloped: options.jsonEnvelope }));
147619
- } else if (result.purgedProjects.length === 0) {
148466
+ } else if (result.purgedProjects.length === 0 && (!result.resetPorts || result.resetPorts.length === 0)) {
147620
148467
  context.output.write("Registry is clean. No stale project entries found.");
147621
148468
  } else {
147622
- context.output.write(`Purged ${result.purgedProjects.length} stale project(s):`);
147623
- for (const project of result.purgedProjects) {
147624
- context.output.write(`- ${project.name} (${project.path})`);
148469
+ if (result.purgedProjects.length > 0) {
148470
+ context.output.write(`Purged ${result.purgedProjects.length} stale project(s):`);
148471
+ for (const project of result.purgedProjects) {
148472
+ context.output.write(`- ${project.name} (${project.path})`);
148473
+ }
147625
148474
  }
147626
148475
  if (result.terminatedProcesses.length > 0) {
147627
148476
  context.output.write(`Terminated ${result.terminatedProcesses.length} lingering process(es):`);
@@ -147629,6 +148478,13 @@ function registerProjectsCommand(program, context) {
147629
148478
  context.output.write(`- PID ${proc.pid} on port ${proc.port} (${proc.signal})`);
147630
148479
  }
147631
148480
  }
148481
+ if (result.resetPorts && result.resetPorts.length > 0) {
148482
+ context.output.write(`Reset ${result.resetPorts.length} closed port(s):`);
148483
+ for (const project of result.resetPorts) {
148484
+ const hostLabel = !isLocalHost(project.host) ? `${project.host}:` : "";
148485
+ context.output.write(`- ${project.name} (${hostLabel}${project.port} -> 0)`);
148486
+ }
148487
+ }
147632
148488
  }
147633
148489
  } catch (err) {
147634
148490
  const message = err instanceof Error ? err.message : String(err);
@@ -147646,10 +148502,10 @@ function registerProjectsCommand(program, context) {
147646
148502
  const rawProjects = await registry.list();
147647
148503
  const projects = await Promise.all(rawProjects.map(async (p) => ({
147648
148504
  ...p,
147649
- running: p.port > 0 ? await isPortLive(p.port) : false
148505
+ running: p.port > 0 ? await isPortLive(p.port, undefined, p.host) : false
147650
148506
  })));
147651
148507
  const openProjectDb = async (projectPath) => {
147652
- const url = join88(projectPath, ".spur", "spur.db");
148508
+ const url = join89(projectPath, ".spur", "spur.db");
147653
148509
  await context.fs.ensureDir(dirname42(url));
147654
148510
  return createMigratedDb({ url });
147655
148511
  };
@@ -147715,8 +148571,9 @@ function registerProjectsCommand(program, context) {
147715
148571
  context.output.write(`Registered Projects:
147716
148572
  `);
147717
148573
  for (const [i, p] of projects.entries()) {
148574
+ const hostLabel = !isLocalHost(p.host) ? ` (${p.host})` : "";
147718
148575
  const status = p.running ? `[RUNNING: ${p.port}]` : "[STOPPED]";
147719
- context.output.write(`- ${p.name} ${status} (${p.path})`);
148576
+ context.output.write(`- ${p.name}${hostLabel} ${status} (${p.path})`);
147720
148577
  const f = fleets?.[i];
147721
148578
  if (f === undefined)
147722
148579
  continue;
@@ -148078,7 +148935,7 @@ function formatTraceDetail(detail) {
148078
148935
  // src/commands/serve.ts
148079
148936
  init_src();
148080
148937
  import { statSync as statSync18 } from "fs";
148081
- import { join as join93, resolve as resolve36 } from "path";
148938
+ import { join as join94, resolve as resolve36 } from "path";
148082
148939
 
148083
148940
  // ../server/src/index.ts
148084
148941
  init_src();
@@ -148089,11 +148946,11 @@ init_src();
148089
148946
  init_loader2();
148090
148947
  init_src2();
148091
148948
  import { homedir as homedir17 } from "os";
148092
- import { basename as basename21, dirname as dirname44, isAbsolute as isAbsolute15, join as join92 } from "path";
148949
+ import { basename as basename21, dirname as dirname44, isAbsolute as isAbsolute15, join as join93 } from "path";
148093
148950
  init_dist5();
148094
148951
 
148095
148952
  // ../server/src/bootstrap.ts
148096
- import { join as join90 } from "path";
148953
+ import { join as join91 } from "path";
148097
148954
 
148098
148955
  // ../../node_modules/.bun/@orpc+shared@1.14.4+e40b0dfdd726a224/node_modules/@orpc/shared/dist/index.mjs
148099
148956
  function resolveMaybeOptionalOptions(rest) {
@@ -154946,9 +155803,9 @@ init_loader2();
154946
155803
  init_src2();
154947
155804
  init_dist5();
154948
155805
  import { homedir as homedir16 } from "os";
154949
- import { basename as basename20, join as join89, relative as relative19, resolve as resolve35 } from "path";
155806
+ import { basename as basename20, join as join90, relative as relative19, resolve as resolve35 } from "path";
154950
155807
  // ../server/package.json
154951
- var version3 = "0.4.9";
155808
+ var version3 = "0.4.11";
154952
155809
 
154953
155810
  // ../server/src/modules/health/index.ts
154954
155811
  var startedAt = Date.now();
@@ -155421,8 +156278,8 @@ var healthModule = {
155421
156278
  }
155422
156279
  });
155423
156280
  }
155424
- const projectPath = join89(ctx.cwd, ".spur", "config.yaml");
155425
- const globalPath = join89(homedir16(), ".config", "spur", "config.yaml");
156281
+ const projectPath = join90(ctx.cwd, ".spur", "config.yaml");
156282
+ const globalPath = join90(homedir16(), ".config", "spur", "config.yaml");
155426
156283
  let projectExists = false;
155427
156284
  let projectContent = "";
155428
156285
  let projectSizeBytes = 0;
@@ -155539,7 +156396,7 @@ var healthModule = {
155539
156396
  const projectRoot = ctx.cwd ?? process.cwd();
155540
156397
  const fs = ctx.fs ?? createNodeFileSystem2(projectRoot);
155541
156398
  const files = [];
155542
- const rootDesignPath = join89(projectRoot, "DESIGN.md");
156399
+ const rootDesignPath = join90(projectRoot, "DESIGN.md");
155543
156400
  if (await fs.exists(rootDesignPath)) {
155544
156401
  let title = "UI/UX Design System";
155545
156402
  try {
@@ -155556,7 +156413,7 @@ var healthModule = {
155556
156413
  category: "root"
155557
156414
  });
155558
156415
  }
155559
- const doc04Path = join89(projectRoot, "docs", "04_DESIGN.md");
156416
+ const doc04Path = join90(projectRoot, "docs", "04_DESIGN.md");
155560
156417
  if (await fs.exists(doc04Path)) {
155561
156418
  let title = "Concrete Surfaces & Contracts";
155562
156419
  try {
@@ -155573,7 +156430,7 @@ var healthModule = {
155573
156430
  category: "architecture"
155574
156431
  });
155575
156432
  }
155576
- const designDir = join89(projectRoot, "docs", "design");
156433
+ const designDir = join90(projectRoot, "docs", "design");
155577
156434
  if (await fs.exists(designDir)) {
155578
156435
  let mdFiles = [];
155579
156436
  try {
@@ -155582,7 +156439,7 @@ var healthModule = {
155582
156439
  } catch {}
155583
156440
  mdFiles.sort((a, b) => a.localeCompare(b));
155584
156441
  for (const filename of mdFiles) {
155585
- const fullPath = join89(designDir, filename);
156442
+ const fullPath = join90(designDir, filename);
155586
156443
  const relPath = `docs/design/${filename}`;
155587
156444
  let title = filename.replace(/\.md$/, "").replace(/-/g, " ");
155588
156445
  try {
@@ -155620,7 +156477,7 @@ var healthModule = {
155620
156477
  try {
155621
156478
  const projectRoot = ctx.cwd ?? process.cwd();
155622
156479
  const fs = ctx.fs ?? createNodeFileSystem2(projectRoot);
155623
- const fullPath = join89(projectRoot, reqPath);
156480
+ const fullPath = join90(projectRoot, reqPath);
155624
156481
  if (!await fs.exists(fullPath)) {
155625
156482
  return c.json({ ok: false, error: "File not found" }, 404);
155626
156483
  }
@@ -155646,7 +156503,7 @@ var healthModule = {
155646
156503
  const projectRoot = ctx.cwd ?? process.cwd();
155647
156504
  const fs = ctx.fs ?? createNodeFileSystem2(projectRoot);
155648
156505
  const files = [];
155649
- const roadmapPath = join89(projectRoot, "docs", "02_ROADMAP.md");
156506
+ const roadmapPath = join90(projectRoot, "docs", "02_ROADMAP.md");
155650
156507
  if (await fs.exists(roadmapPath)) {
155651
156508
  let title = "02 Roadmap \u2014 Spur";
155652
156509
  try {
@@ -155663,7 +156520,7 @@ var healthModule = {
155663
156520
  category: "roadmap"
155664
156521
  });
155665
156522
  }
155666
- const plansDir = join89(projectRoot, "docs", "plans");
156523
+ const plansDir = join90(projectRoot, "docs", "plans");
155667
156524
  if (await fs.exists(plansDir)) {
155668
156525
  let mdFiles = [];
155669
156526
  try {
@@ -155672,7 +156529,7 @@ var healthModule = {
155672
156529
  } catch {}
155673
156530
  mdFiles.sort((a, b) => b.localeCompare(a));
155674
156531
  for (const filename of mdFiles) {
155675
- const fullPath = join89(plansDir, filename);
156532
+ const fullPath = join90(plansDir, filename);
155676
156533
  const relPath = `docs/plans/${filename}`;
155677
156534
  let title = filename.replace(/\.md$/, "").replace(/-/g, " ");
155678
156535
  try {
@@ -155710,7 +156567,7 @@ var healthModule = {
155710
156567
  try {
155711
156568
  const projectRoot = ctx.cwd ?? process.cwd();
155712
156569
  const fs = ctx.fs ?? createNodeFileSystem2(projectRoot);
155713
- const fullPath = join89(projectRoot, reqPath);
156570
+ const fullPath = join90(projectRoot, reqPath);
155714
156571
  if (!await fs.exists(fullPath)) {
155715
156572
  return c.json({ ok: false, error: "File not found" }, 404);
155716
156573
  }
@@ -158224,7 +159081,7 @@ function createApp(appRt, opts) {
158224
159081
  app.use("*", async (c, next) => {
158225
159082
  const pathname = c.req.path === "/" ? "/index.html" : c.req.path;
158226
159083
  try {
158227
- const file = Bun.file(join90(webDistPath, pathname));
159084
+ const file = Bun.file(join91(webDistPath, pathname));
158228
159085
  if (await file.exists()) {
158229
159086
  const headers = new Headers({ "content-type": file.type });
158230
159087
  return new Response(file.stream(), { headers });
@@ -158237,7 +159094,7 @@ function createApp(appRt, opts) {
158237
159094
  return c.json({ error: "Not Found" }, 404);
158238
159095
  }
158239
159096
  try {
158240
- const indexFile = Bun.file(join90(webDistPath, "index.html"));
159097
+ const indexFile = Bun.file(join91(webDistPath, "index.html"));
158241
159098
  if (await indexFile.exists()) {
158242
159099
  return new Response(indexFile.stream(), {
158243
159100
  headers: { "content-type": "text/html; charset=utf-8" }
@@ -158259,7 +159116,7 @@ init_src();
158259
159116
  init_loader2();
158260
159117
  init_src2();
158261
159118
  init_dist5();
158262
- import { dirname as dirname43, join as join91 } from "path";
159119
+ import { dirname as dirname43, join as join92 } from "path";
158263
159120
  var NOOP_OUTPUT = { write: (_s) => {}, error: (_s) => {} };
158264
159121
 
158265
159122
  class LazyPlanningEventEmitter {
@@ -158295,7 +159152,7 @@ class NotConfiguredError extends Error {
158295
159152
  function createServerContext(appRt, options) {
158296
159153
  const cwd = options.cwd;
158297
159154
  const fs = options.fs;
158298
- const dbUrl = options.dbUrl ?? join91(cwd, DEFAULT_DATABASE_URL);
159155
+ const dbUrl = options.dbUrl ?? join92(cwd, DEFAULT_DATABASE_URL);
158299
159156
  const eventsBus = options.eventsBus ?? appRt.events;
158300
159157
  const jobQueueEnabled = options.jobQueueEnabled ?? false;
158301
159158
  const eventProjectContext = systemEventProjectContext(cwd);
@@ -158331,7 +159188,7 @@ function createServerContext(appRt, options) {
158331
159188
  return {
158332
159189
  cwd,
158333
159190
  fs,
158334
- runDir: join91(cwd, ".spur", "run"),
159191
+ runDir: join92(cwd, ".spur", "run"),
158335
159192
  webDistPath: options.webDistPath,
158336
159193
  boardModules: options.boardModules,
158337
159194
  logger: appRt.logger,
@@ -158695,7 +159552,7 @@ var BOARD_RUNTIME_MANIFEST_FILE = "board-runtime.json";
158695
159552
  async function readBoardHostRuntime(webDistPath, hasContributions) {
158696
159553
  if (!webDistPath)
158697
159554
  return null;
158698
- const manifestPath = join92(webDistPath, BOARD_RUNTIME_MANIFEST_FILE);
159555
+ const manifestPath = join93(webDistPath, BOARD_RUNTIME_MANIFEST_FILE);
158699
159556
  try {
158700
159557
  const file = Bun.file(manifestPath);
158701
159558
  if (!await file.exists())
@@ -158732,7 +159589,7 @@ function defaultUsageSnapshotPath(env) {
158732
159589
  const override = env.SPUR_AGENT_USAGE_SNAPSHOT;
158733
159590
  if (override !== undefined && override.length > 0)
158734
159591
  return override;
158735
- return join92(env.HOME ?? homedir17(), ".config", "spur", "agent-usage.json");
159592
+ return join93(env.HOME ?? homedir17(), ".config", "spur", "agent-usage.json");
158736
159593
  }
158737
159594
  var JOB_WORKER_START_DELAY_MS = 30000;
158738
159595
  var defaultDeps = {
@@ -158996,15 +159853,15 @@ async function handleFeatureActionJob(ctx, env, payload) {
158996
159853
  await runFeatureActionJob(ctx, env, payload);
158997
159854
  }
158998
159855
  async function resolveWebDistPath(configuredPath, projectRoot = process.cwd()) {
158999
- const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute15(configuredPath) ? configuredPath : join92(projectRoot, configuredPath)] : [
159000
- join92(projectRoot, "dist/web"),
159001
- join92(import.meta.dir, "web"),
159002
- join92(dirname44(process.execPath), "web"),
159003
- join92(dirname44(process.execPath), "../web"),
159004
- join92(import.meta.dir, "../../../dist/web")
159856
+ const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute15(configuredPath) ? configuredPath : join93(projectRoot, configuredPath)] : [
159857
+ join93(projectRoot, "dist/web"),
159858
+ join93(import.meta.dir, "web"),
159859
+ join93(dirname44(process.execPath), "web"),
159860
+ join93(dirname44(process.execPath), "../web"),
159861
+ join93(import.meta.dir, "../../../dist/web")
159005
159862
  ];
159006
159863
  for (const candidate of candidates) {
159007
- if (await Bun.file(join92(candidate, "index.html")).exists()) {
159864
+ if (await Bun.file(join93(candidate, "index.html")).exists()) {
159008
159865
  return candidate;
159009
159866
  }
159010
159867
  }
@@ -159479,7 +160336,7 @@ var SQLITE_BUSY_MESSAGE_CONSTANTS = Object.freeze({
159479
160336
 
159480
160337
  // src/commands/serve.ts
159481
160338
  function resolveServeDbUrl(cwd, env, configuredUrl) {
159482
- return env.DATABASE_URL === undefined ? join93(cwd, DEFAULT_DATABASE_URL) : configuredUrl;
160339
+ return env.DATABASE_URL === undefined ? join94(cwd, DEFAULT_DATABASE_URL) : configuredUrl;
159483
160340
  }
159484
160341
  function resolveServeCwd(cwd) {
159485
160342
  const resolved = resolve36(cwd);
@@ -159543,7 +160400,7 @@ function registerServeCommand(program, context, options = {}) {
159543
160400
  }
159544
160401
 
159545
160402
  // src/commands/status.ts
159546
- import { join as join94 } from "path";
160403
+ import { join as join95 } from "path";
159547
160404
 
159548
160405
  // src/git-context.ts
159549
160406
  init_dist5();
@@ -159591,8 +160448,8 @@ function registerStatusCommand(program, context, options = {}) {
159591
160448
  }
159592
160449
  async function runStatusCore(path, options, context) {
159593
160450
  const [packageJsonExists, spurConfigExists, git, agentSpecs] = await Promise.all([
159594
- context.fs.exists(join94(context.cwd, "package.json")),
159595
- context.fs.exists(join94(context.cwd, ".spur", "config.yaml")),
160451
+ context.fs.exists(join95(context.cwd, "package.json")),
160452
+ context.fs.exists(join95(context.cwd, ".spur", "config.yaml")),
159596
160453
  gitContext(context.cwd),
159597
160454
  listAgentSpecIds(context)
159598
160455
  ]);
@@ -159623,14 +160480,14 @@ async function runStatusCore(path, options, context) {
159623
160480
  return status.ok ? 0 : 1;
159624
160481
  }
159625
160482
  async function listAgentSpecIds(context) {
159626
- const dir = join94(context.cwd, ".spur", "agents");
160483
+ const dir = join95(context.cwd, ".spur", "agents");
159627
160484
  if (!await context.fs.exists(dir))
159628
160485
  return [];
159629
160486
  const entries = await context.fs.readDir(dir);
159630
160487
  return entries.filter((entry) => entry.endsWith(".yaml") || entry.endsWith(".yml")).map((entry) => entry.replace(/\.ya?ml$/, "")).sort();
159631
160488
  }
159632
160489
  async function readTargetStatus(context, targetPath) {
159633
- const resolved = join94(context.cwd, targetPath);
160490
+ const resolved = join95(context.cwd, targetPath);
159634
160491
  const stat = await context.fs.stat(resolved);
159635
160492
  if (stat === null)
159636
160493
  throw new CommandError(`status failed: path does not exist at ${resolved}`);
@@ -159881,11 +160738,11 @@ function registerWorkflowCleanCommand(workflow, context) {
159881
160738
  // ../../packages/app/src/workflow/commit-guard.ts
159882
160739
  init_dist5();
159883
160740
  import { mkdirSync as mkdirSync23, readFileSync as readFileSync28, renameSync as renameSync11, writeFileSync as writeFileSync20 } from "fs";
159884
- import { join as join95 } from "path";
160741
+ import { join as join96 } from "path";
159885
160742
  var COMMIT_GUARD_USAGE = "usage: spur workflow commit-guard start --run <id> | stage --run <id> [--base <sha>] -- <paths\u2026> | check --run <id> [--base <sha>]";
159886
160743
  var isBookkeeping = (path) => path === ".spur" || path.startsWith(".spur/");
159887
- var at2 = (options, rel) => options.cwd ? join95(options.cwd, rel) : rel;
159888
- var artifactPath = (run) => join95(".spur", "run", `${run}-tree-start.json`);
160744
+ var at2 = (options, rel) => options.cwd ? join96(options.cwd, rel) : rel;
160745
+ var artifactPath = (run) => join96(".spur", "run", `${run}-tree-start.json`);
159889
160746
  function gitOf(options) {
159890
160747
  if (options.git)
159891
160748
  return options.git;
@@ -159954,7 +160811,7 @@ function runContext(run, options) {
159954
160811
  let base = options.base ?? "";
159955
160812
  if (!base) {
159956
160813
  try {
159957
- const anchored = readFileSync28(at2(options, join95(".spur", "run", `${run}-base.sha`)), "utf8").trim();
160814
+ const anchored = readFileSync28(at2(options, join96(".spur", "run", `${run}-base.sha`)), "utf8").trim();
159958
160815
  if (/^[0-9a-f]{7,40}$/i.test(anchored))
159959
160816
  base = anchored;
159960
160817
  } catch {}
@@ -159994,7 +160851,7 @@ function commitGuardStart(run, options = {}) {
159994
160851
  dirty: parsePorcelainZ(status.stdout).map((path) => ({ path, sha: hashPath(path, git) }))
159995
160852
  };
159996
160853
  const abs = at2(options, artifactPath(run));
159997
- mkdirSync23(join95(abs, ".."), { recursive: true });
160854
+ mkdirSync23(join96(abs, ".."), { recursive: true });
159998
160855
  writeFileSync20(`${abs}.tmp`, `${JSON.stringify(snapshot, null, 2)}
159999
160856
  `);
160000
160857
  renameSync11(`${abs}.tmp`, abs);
@@ -160266,14 +161123,14 @@ function registerWorkflowContinueCommand(workflow, context) {
160266
161123
  }
160267
161124
 
160268
161125
  // src/commands/workflow/cost.ts
160269
- import { join as join97 } from "path";
161126
+ import { join as join98 } from "path";
160270
161127
 
160271
161128
  // ../../packages/app/src/workflow/run-cost.ts
160272
161129
  init_src2();
160273
161130
  init_dist5();
160274
161131
  init_dist2();
160275
161132
  init_composition_baseline();
160276
- import { join as join96 } from "path";
161133
+ import { join as join97 } from "path";
160277
161134
 
160278
161135
  // ../../packages/app/src/workflow/run-measurements.ts
160279
161136
  init_src2();
@@ -160507,7 +161364,7 @@ async function loadQueryCounts(workflowsDir, fs = createNodeFileSystem2()) {
160507
161364
  const name = /^(.+)\.ya?ml$/.exec(entry)?.[1];
160508
161365
  if (!name)
160509
161366
  continue;
160510
- const def = $parse(await fs.readFile(join96(workflowsDir, entry)));
161367
+ const def = $parse(await fs.readFile(join97(workflowsDir, entry)));
160511
161368
  out[name] = extractResolvedWorkflowFacts(def).modelQueries.length;
160512
161369
  }
160513
161370
  return out;
@@ -160533,7 +161390,7 @@ async function runBudgetGate(opts) {
160533
161390
  const fs = opts.fs ?? createNodeFileSystem2();
160534
161391
  const gitShow = opts.gitShow ?? defaultGitShow;
160535
161392
  const openDb = opts.openDb ?? defaultDbOpener;
160536
- const absBudget = opts.budgetPath.startsWith("/") ? opts.budgetPath : join96(opts.cwd, opts.budgetPath);
161393
+ const absBudget = opts.budgetPath.startsWith("/") ? opts.budgetPath : join97(opts.cwd, opts.budgetPath);
160537
161394
  const relBudget = absBudget.startsWith(`${opts.cwd}/`) ? absBudget.slice(opts.cwd.length + 1) : opts.budgetPath;
160538
161395
  const config = await loadPipelineBudgets(absBudget, fs);
160539
161396
  let head = null;
@@ -160549,7 +161406,7 @@ async function runBudgetGate(opts) {
160549
161406
  const queryCounts = await loadQueryCounts(opts.workflowsDir, fs);
160550
161407
  let measured;
160551
161408
  if (opts.measuredFile !== undefined) {
160552
- const measuredPath = opts.measuredFile.startsWith("/") ? opts.measuredFile : join96(opts.cwd, opts.measuredFile);
161409
+ const measuredPath = opts.measuredFile.startsWith("/") ? opts.measuredFile : join97(opts.cwd, opts.measuredFile);
160553
161410
  measured = JSON.parse(await fs.readFile(measuredPath));
160554
161411
  } else if (!await fs.exists(opts.dbPath)) {
160555
161412
  measured = {};
@@ -160598,8 +161455,8 @@ function registerWorkflowCostCommand(workflow, context, deps = {}) {
160598
161455
  ].join(`
160599
161456
  `)).option("--workflow <name>", "restrict to a workflow (repeatable)", collect3, []).option("--by-state", "report per-state rows instead of per-workflow rows").option("--include-bookkeeping", "keep bookkeeping workflows in the default cohort").option("--since <YYYY-MM-DD>", "only runs created on/after this UTC date").option("--budget <file>", "run the pipeline budget gate against this budget file").option("--measured-file <json>", "with --budget: read measured values from this file instead of the DB").option(...SHARED_OPTIONS.json).action(async (options) => {
160600
161457
  const cwd = context.cwd;
160601
- const dbPath = join97(cwd, ".spur/spur.db");
160602
- const workflowsDir = join97(cwd, ".spur", "workflows");
161458
+ const dbPath = join98(cwd, ".spur/spur.db");
161459
+ const workflowsDir = join98(cwd, ".spur", "workflows");
160603
161460
  const openDb = deps.openDb ?? defaultDbOpener;
160604
161461
  try {
160605
161462
  if (options.budget !== undefined) {
@@ -160663,9 +161520,9 @@ init_run_summary_render();
160663
161520
  // ../../packages/app/src/workflow/persist-check.ts
160664
161521
  init_dist5();
160665
161522
  import { existsSync as existsSync33, readdirSync as readdirSync15, readFileSync as readFileSync30, statSync as statSync19 } from "fs";
160666
- import { basename as basename22, dirname as dirname45, isAbsolute as isAbsolute16, join as join99, relative as relative20, resolve as resolve37 } from "path";
160667
- var EVIDENCE_DIR = join99(".spur", "memory", "evidence");
160668
- var RUN_DIR = join99(".spur", "run");
161523
+ import { basename as basename22, dirname as dirname45, isAbsolute as isAbsolute16, join as join100, relative as relative20, resolve as resolve37 } from "path";
161524
+ var EVIDENCE_DIR = join100(".spur", "memory", "evidence");
161525
+ var RUN_DIR = join100(".spur", "run");
160669
161526
  var NAMED_CAP = 32;
160670
161527
  var PREFIX_CAP = 64;
160671
161528
  var DURABLE_FAMILY_RE = /^\d{4}-(?:verdict|feature-verification)\.json$/;
@@ -160680,7 +161537,7 @@ function walkFiles(dir, root) {
160680
161537
  const out = [];
160681
161538
  const walk = (cur) => {
160682
161539
  for (const e of readdirSync15(cur, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
160683
- const p = join99(cur, e.name);
161540
+ const p = join100(cur, e.name);
160684
161541
  if (e.isDirectory())
160685
161542
  walk(p);
160686
161543
  else if (e.isFile())
@@ -160707,8 +161564,8 @@ function listObligations(wtRoot, wbsList, runIds) {
160707
161564
  const prefixes = [...new Set([...wbsList, ...runIds].filter(Boolean))];
160708
161565
  if (prefixes.length === 0)
160709
161566
  return null;
160710
- const evFiles = walkFiles(join99(wtRoot, EVIDENCE_DIR), wtRoot);
160711
- const runFiles = walkFiles(join99(wtRoot, RUN_DIR), wtRoot);
161567
+ const evFiles = walkFiles(join100(wtRoot, EVIDENCE_DIR), wtRoot);
161568
+ const runFiles = walkFiles(join100(wtRoot, RUN_DIR), wtRoot);
160712
161569
  const files = [];
160713
161570
  const counts = new Map;
160714
161571
  if (!collectOwned(evFiles, prefixes, counts, files))
@@ -160725,13 +161582,13 @@ function compareTrees(wtRoot, invokeRoot, files, foreignDivergent = new Set) {
160725
161582
  for (const rel of files) {
160726
161583
  if (foreignDivergent.has(rel) || foreignDivergent.has(basename22(rel)))
160727
161584
  continue;
160728
- const invPath = join99(invokeRoot, rel);
161585
+ const invPath = join100(invokeRoot, rel);
160729
161586
  if (!existsSync33(invPath)) {
160730
161587
  missing.push(rel);
160731
161588
  continue;
160732
161589
  }
160733
161590
  try {
160734
- if (readFileSync30(join99(wtRoot, rel)).equals(readFileSync30(invPath)))
161591
+ if (readFileSync30(join100(wtRoot, rel)).equals(readFileSync30(invPath)))
160735
161592
  ok++;
160736
161593
  else
160737
161594
  divergent.push(rel);
@@ -160748,7 +161605,7 @@ function defaultInvokeRoot(cwd) {
160748
161605
  });
160749
161606
  const common = run.stdout.trim();
160750
161607
  if (run.exitCode === 0 && common)
160751
- return dirname45(isAbsolute16(common) ? common : join99(cwd, common));
161608
+ return dirname45(isAbsolute16(common) ? common : join100(cwd, common));
160752
161609
  return cwd;
160753
161610
  }
160754
161611
  function readPersistOutSkips(candidates) {
@@ -160763,7 +161620,7 @@ function readPersistOutSkips(candidates) {
160763
161620
  for (const item of parsed.evidenceSkipped) {
160764
161621
  if (item.reason === "foreign-divergent" && typeof item.name === "string") {
160765
161622
  foreignDivergent.add(item.name);
160766
- foreignDivergent.add(join99(EVIDENCE_DIR, item.name));
161623
+ foreignDivergent.add(join100(EVIDENCE_DIR, item.name));
160767
161624
  }
160768
161625
  }
160769
161626
  }
@@ -160796,8 +161653,8 @@ function runPersistCheck(input) {
160796
161653
  const invokeRoot = input.root === undefined ? defaultInvokeRoot(input.cwd) : resolve37(input.cwd, input.root);
160797
161654
  const { foreignDivergent, skips } = readPersistOutSkips([
160798
161655
  input.successJson === undefined || input.successJson === "" ? "" : resolve37(input.cwd, input.successJson),
160799
- join99(invokeRoot, ".spur", "run", "persist-out.json"),
160800
- join99(wtRoot, ".spur", "run", "persist-out.json")
161656
+ join100(invokeRoot, ".spur", "run", "persist-out.json"),
161657
+ join100(wtRoot, ".spur", "run", "persist-out.json")
160801
161658
  ].filter(Boolean));
160802
161659
  const obligations = listObligations(wtRoot, wbsList, runIds);
160803
161660
  if (obligations === null) {
@@ -161166,7 +162023,7 @@ init_src3();
161166
162023
  init_capability_attestation();
161167
162024
  init_src();
161168
162025
  init_dist7();
161169
- import { join as join100 } from "path";
162026
+ import { join as join101 } from "path";
161170
162027
  import { createInterface } from "readline";
161171
162028
  import { setTimeout as sleep6 } from "timers/promises";
161172
162029
  var WORKFLOW_RUN_ACTIVE_ENV = "SPUR_WORKFLOW_RUN_ACTIVE";
@@ -161270,9 +162127,9 @@ async function writeWorkflowPlanArtifact(cwd, runId, resolved, secretValues, sta
161270
162127
  }))
161271
162128
  });
161272
162129
  const { mkdir, writeFile } = await import("fs/promises");
161273
- const artifactsDir = join100(cwd, ".spur", "run");
162130
+ const artifactsDir = join101(cwd, ".spur", "run");
161274
162131
  await mkdir(artifactsDir, { recursive: true });
161275
- const artifactPath = join100(artifactsDir, `${runId}-workflow-plan.json`);
162132
+ const artifactPath = join101(artifactsDir, `${runId}-workflow-plan.json`);
161276
162133
  await writeFile(artifactPath, payload);
161277
162134
  return artifactPath;
161278
162135
  }
@@ -161743,7 +162600,7 @@ function registerWorkflowSummaryCommand(workflow, context) {
161743
162600
  // src/commands/workflow/trace.ts
161744
162601
  init_src3();
161745
162602
  import { closeSync as closeSync11, fstatSync as fstatSync5, openSync as openSync11, readFileSync as readFileSync32, readSync as readSync3 } from "fs";
161746
- import { join as join101 } from "path";
162603
+ import { join as join102 } from "path";
161747
162604
  import { setTimeout as sleep7 } from "timers/promises";
161748
162605
  function formatTraceList2(result) {
161749
162606
  if (result.entries.length === 0) {
@@ -161912,8 +162769,8 @@ function readRunLogChunk(logPath, offset) {
161912
162769
  async function followRunLog(service, runId, dir, pollMs, write, wait = (ms) => sleep7(ms), now = Date.now, deadlineMs) {
161913
162770
  const start = now();
161914
162771
  const deadline = deadlineMs === undefined ? Number.POSITIVE_INFINITY : start + deadlineMs;
161915
- const runDir = join101(dir, ".spur", "run");
161916
- const pendingMdPath = join101(runDir, `${runId}.md`);
162772
+ const runDir = join102(dir, ".spur", "run");
162773
+ const pendingMdPath = join102(runDir, `${runId}.md`);
161917
162774
  let logPath;
161918
162775
  let record = { kind: "missing" };
161919
162776
  let offset = 0;
@@ -162328,8 +163185,14 @@ function shouldRenderBanner(argv) {
162328
163185
  return false;
162329
163186
  if (argv[0] === "history" && argv[1] === "timeline")
162330
163187
  return false;
163188
+ if (argv[0] === "builder" && argv[1] === "show-branches" && requestsJsonFormat(argv))
163189
+ return false;
162331
163190
  return !argv.some((arg) => arg === "--no-logo" || arg === "--json" || arg === "--quiet" || arg === "--silent");
162332
163191
  }
163192
+ function requestsJsonFormat(argv) {
163193
+ const index = argv.indexOf("--format");
163194
+ return argv.includes("--format=json") || index !== -1 && argv[index + 1] === "json";
163195
+ }
162333
163196
  async function runCli() {
162334
163197
  const argv = process.argv.slice(2);
162335
163198
  if (shouldRenderBanner(argv)) {