@gobing-ai/spur 0.3.94 → 0.3.96

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 (198) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +42 -0
  3. package/board/index.d.ts +38 -0
  4. package/config/plugin-scripts.json +3 -52
  5. package/config/rules/boundary/sp-script-placement.yaml +19 -0
  6. package/config/rules/strict/runtime-boundaries.yaml +5 -0
  7. package/config/rules/structure/test-location.yaml +2 -0
  8. package/config/rules/typescript/no-syscall-emulation-in-boundary-mock.yaml +1 -1
  9. package/config/rules/typescript/output-boundaries.yaml +15 -2
  10. package/config/script-placement-baseline.json +51 -0
  11. package/config/workflows/feature-verification.yaml +7 -4
  12. package/config/workflows/history-anatomy.yaml +23 -11
  13. package/config/workflows/idea-pipeline.yaml +87 -86
  14. package/config/workflows/pr-review.yaml +18 -9
  15. package/config/workflows/task-pipeline.yaml +84 -109
  16. package/config/workflows/wrapup-pipeline.yaml +173 -46
  17. package/package.json +15 -11
  18. package/plugins/sp/README.md +8 -10
  19. package/plugins/sp/agents/super-reviewer.md +43 -18
  20. package/plugins/sp/commands/dev-fixgha.md +83 -0
  21. package/plugins/sp/commands/dev-gitmsg.md +8 -6
  22. package/plugins/sp/commands/dev-gtd.md +2 -2
  23. package/plugins/sp/commands/dev-idea.md +22 -12
  24. package/plugins/sp/commands/dev-plan.md +8 -9
  25. package/plugins/sp/commands/dev-review.md +22 -13
  26. package/plugins/sp/commands/dev-verify.md +7 -4
  27. package/plugins/sp/commands/dev-verifyall.md +5 -4
  28. package/plugins/sp/commands/dev-wrap.md +1 -1
  29. package/plugins/sp/commands/spur-init.md +2 -2
  30. package/plugins/sp/hooks/context-post-tool.ts +2 -2
  31. package/plugins/sp/hooks/context-session-start.ts +2 -1
  32. package/plugins/sp/hooks/context-session-stop.ts +22 -14
  33. package/plugins/sp/hooks/pi/guard-extension.ts +109 -150
  34. package/plugins/sp/lib/history-anatomy.generated.d.mts +112 -0
  35. package/plugins/sp/lib/history-anatomy.generated.mjs +686 -0
  36. package/plugins/sp/lib/idea-handoff.generated.mjs +6 -5
  37. package/plugins/sp/lib/inline-run.generated.d.mts +13 -2
  38. package/plugins/sp/lib/inline-run.generated.mjs +33 -16
  39. package/plugins/sp/lib/quality-gate.generated.d.mts +104 -0
  40. package/plugins/sp/lib/quality-gate.generated.mjs +438 -0
  41. package/plugins/sp/lib/residual-scan.generated.d.mts +62 -0
  42. package/plugins/sp/lib/residual-scan.generated.mjs +210 -0
  43. package/plugins/sp/lib/spur-bin.ts +36 -0
  44. package/plugins/sp/lib/step-profile.generated.d.mts +71 -0
  45. package/plugins/sp/lib/step-profile.generated.mjs +174 -0
  46. package/plugins/sp/plugin.json +1 -1
  47. package/plugins/sp/references/roles.md +1 -1
  48. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +3 -3
  49. package/plugins/sp/scripts/daily-summary/daily-summary.ts +3 -3
  50. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +2 -0
  51. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +1 -0
  52. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +9 -7
  53. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +8 -7
  54. package/plugins/sp/scripts/feature-verification-steps.ts +6 -6
  55. package/plugins/sp/scripts/history-anatomy-cache.mjs +23 -22
  56. package/plugins/sp/scripts/history-anatomy-cache.ts +23 -928
  57. package/plugins/sp/scripts/inline-run-setup.mjs +90 -315
  58. package/plugins/sp/scripts/inline-run-setup.ts +109 -639
  59. package/plugins/sp/scripts/pr-reviewing.mjs +2 -0
  60. package/plugins/sp/scripts/pr-reviewing.ts +1 -0
  61. package/plugins/sp/scripts/quality-gate.mjs +38 -21
  62. package/plugins/sp/scripts/quality-gate.ts +20 -660
  63. package/plugins/sp/scripts/residual-scan.mjs +138 -159
  64. package/plugins/sp/scripts/residual-scan.ts +102 -499
  65. package/plugins/sp/scripts/script-root.mjs +129 -0
  66. package/plugins/sp/scripts/script-root.ts +199 -0
  67. package/plugins/sp/scripts/workflow-step-profile.mjs +27 -16
  68. package/plugins/sp/scripts/workflow-step-profile.ts +22 -314
  69. package/plugins/sp/scripts/wrapup-drift-probe.mjs +12 -3
  70. package/plugins/sp/scripts/wrapup-drift-probe.ts +6 -3
  71. package/plugins/sp/scripts/wrapup-steps.mjs +42 -40
  72. package/plugins/sp/scripts/wrapup-steps.ts +64 -46
  73. package/plugins/sp/skills/code-improvement/SKILL.md +5 -4
  74. package/plugins/sp/skills/code-verification/SKILL.md +46 -16
  75. package/plugins/sp/skills/code-verification/references/verdict-schema.md +13 -2
  76. package/plugins/sp/skills/functional-review/SKILL.md +8 -5
  77. package/plugins/sp/skills/functional-review/references/verdict-schema.md +1 -1
  78. package/plugins/sp/skills/history-anatomy/references/modes.md +2 -1
  79. package/plugins/sp/skills/next-feature/references/handoff-routing.md +1 -1
  80. package/plugins/sp/skills/next-router/references/routing-table.md +2 -2
  81. package/plugins/sp/skills/spur-cli/SKILL.md +3 -3
  82. package/plugins/sp/skills/spur-cli/references/agent.md +10 -10
  83. package/plugins/sp/skills/spur-cli/references/features.md +17 -6
  84. package/plugins/sp/skills/spur-cli/references/init.md +17 -16
  85. package/plugins/sp/skills/spur-cli/references/self.md +3 -2
  86. package/plugins/sp/skills/spur-cli/references/serve.md +10 -10
  87. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +28 -5
  88. package/plugins/sp/skills/spur-cli/references/tasks.md +14 -8
  89. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +4 -3
  90. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +11 -9
  91. package/plugins/sp/skills/spur-dev/references/decision-brief.md +1 -1
  92. package/plugins/sp/skills/spur-dev/references/dev-operations.md +86 -70
  93. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +3 -3
  94. package/plugins/sp/skills/spur-dev/references/execution-batch.md +107 -44
  95. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +5 -9
  96. package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +3 -3
  97. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +31 -21
  98. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +19 -19
  99. package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +4 -3
  100. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +35 -6
  101. package/plugins/sp/skills/spur-doctor/SKILL.md +1 -1
  102. package/plugins/sp/skills/sys-architecture/SKILL.md +3 -2
  103. package/plugins/sp/tsconfig.json +8 -0
  104. package/spur.js +6638 -4775
  105. package/web/_astro/BoardApp.BIjMatT1.js +1 -0
  106. package/web/_astro/{BoardApp.FTEs3-N8.js → BoardApp.GvjIe9Z6.js} +150 -153
  107. package/web/_astro/TaskDetail.BZM3EAFx.js +1 -0
  108. package/web/_astro/_commonjsHelpers.CqkleIqs.js +1 -0
  109. package/web/_astro/{arc.uuAf51IT.js → arc.HkRiZnoI.js} +1 -1
  110. package/web/_astro/architectureDiagram-3BPJPVTR.BDdK-tgZ.js +36 -0
  111. package/web/_astro/{blockDiagram-GPEHLZMM.D1mGCq3p.js → blockDiagram-GPEHLZMM.CZ9kOBxx.js} +1 -1
  112. package/web/_astro/board-facade-react-dom-client.B2U5wMvk.js +1 -0
  113. package/web/_astro/board-facade-react-dom.Cd2MuMhF.js +1 -0
  114. package/web/_astro/board-facade-react-jsx-dev-runtime.B_L3JzfC.js +1 -0
  115. package/web/_astro/board-facade-react-jsx-runtime.BY2yfHRi.js +1 -0
  116. package/web/_astro/board-facade-react-router-dom.Dy5pjzOw.js +1 -0
  117. package/web/_astro/board-facade-react-router.DjjqU27_.js +1 -0
  118. package/web/_astro/board-facade-react.Dgf4GRyj.js +1 -0
  119. package/web/_astro/{c4Diagram-AAUBKEIU.CMsolcde.js → c4Diagram-AAUBKEIU.DXK9qeRD.js} +1 -1
  120. package/web/_astro/channel.DlhX2MEt.js +1 -0
  121. package/web/_astro/{chunk-2J33WTMH.CrGA3fik.js → chunk-2J33WTMH.BTD2WeX5.js} +1 -1
  122. package/web/_astro/{chunk-4BX2VUAB.DsLVVla0.js → chunk-4BX2VUAB.Ci6Qbcvk.js} +1 -1
  123. package/web/_astro/{chunk-55IACEB6.Dxc59Tfi.js → chunk-55IACEB6.yl3zsj7p.js} +1 -1
  124. package/web/_astro/{chunk-727SXJPM.CaEVE2Wy.js → chunk-727SXJPM.ClXZsfyR.js} +1 -1
  125. package/web/_astro/{chunk-AQP2D5EJ.BiJ4HXeI.js → chunk-AQP2D5EJ.BmKWZBcP.js} +1 -1
  126. package/web/_astro/{chunk-FMBD7UC4.Mxf1fru5.js → chunk-FMBD7UC4.By30fcb7.js} +1 -1
  127. package/web/_astro/chunk-JMJ3UQ3L.BQJEJHu4.js +34 -0
  128. package/web/_astro/{chunk-ND2GUHAM.pyOWQixH.js → chunk-ND2GUHAM.24NmrD-k.js} +1 -1
  129. package/web/_astro/{chunk-QZHKN3VN.BmEsg4vr.js → chunk-QZHKN3VN.BN4sKdcS.js} +1 -1
  130. package/web/_astro/chunk-YNUBSHFH.CxeAygfi.js +7 -0
  131. package/web/_astro/{classDiagram-4FO5ZUOK.BHhhMFTO.js → classDiagram-4FO5ZUOK.CJpoMPb5.js} +1 -1
  132. package/web/_astro/{classDiagram-v2-Q7XG4LA2.BHhhMFTO.js → classDiagram-v2-Q7XG4LA2.CJpoMPb5.js} +1 -1
  133. package/web/_astro/client.BhluLQqe.js +1 -0
  134. package/web/_astro/client.DBpSRLnx.js +9 -0
  135. package/web/_astro/cose-bilkent-S5V4N54A.BraxQ2Nt.js +1 -0
  136. package/web/_astro/{cynefin-OW5HDTMX.C2j1_lKL.js → cynefin-OW5HDTMX.gzoU73oL.js} +1 -1
  137. package/web/_astro/{dagre-BM42HDAG.hZ2NCTdT.js → dagre-BM42HDAG.wyDfWySU.js} +1 -1
  138. package/web/_astro/{diagram-2AECGRRQ.Bfw5_EzK.js → diagram-2AECGRRQ.h2_EbMxV.js} +1 -1
  139. package/web/_astro/{diagram-5GNKFQAL.Bt1V_Tmk.js → diagram-5GNKFQAL.Db6dcs89.js} +1 -1
  140. package/web/_astro/{diagram-KO2AKTUF.DEk-YFwp.js → diagram-KO2AKTUF.NVwzxu_U.js} +1 -1
  141. package/web/_astro/{diagram-LMA3HP47.CDjndtQm.js → diagram-LMA3HP47.B-eNgDQk.js} +1 -1
  142. package/web/_astro/{diagram-OG6HWLK6.cFnUHScG.js → diagram-OG6HWLK6.Bsa8ZRbJ.js} +1 -1
  143. package/web/_astro/{erDiagram-TEJ5UH35.DlhYp7NV.js → erDiagram-TEJ5UH35.BospRE5Q.js} +1 -1
  144. package/web/_astro/{flowDiagram-I6XJVG4X.DNTpsxfx.js → flowDiagram-I6XJVG4X.DMzvHwAA.js} +1 -1
  145. package/web/_astro/ganttDiagram-6RSMTGT7.Dmdxgd-M.js +292 -0
  146. package/web/_astro/{gitGraphDiagram-PVQCEYII.DkEqNI0P.js → gitGraphDiagram-PVQCEYII.CaqdO0rL.js} +1 -1
  147. package/web/_astro/index.5dn-Zm-2.js +1 -0
  148. package/web/_astro/index.C-IqPpW2.js +1 -0
  149. package/web/_astro/index.Cyc_GA4f.js +1 -0
  150. package/web/_astro/index.EoZlzLK-.css +1 -0
  151. package/web/_astro/index.tZfqwSut.js +1 -0
  152. package/web/_astro/{infoDiagram-5YYISTIA.CrjioCTG.js → infoDiagram-5YYISTIA.DDVD9Wn7.js} +1 -1
  153. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BcK0CN8n.js → ishikawaDiagram-YF4QCWOH.EKyCUFsL.js} +1 -1
  154. package/web/_astro/{journeyDiagram-JHISSGLW.p0CbBDX1.js → journeyDiagram-JHISSGLW.By2-RkYB.js} +1 -1
  155. package/web/_astro/jsx-runtime.DWOiEOII.js +1 -0
  156. package/web/_astro/{kanban-definition-UN3LZRKU.puHIFt6J.js → kanban-definition-UN3LZRKU.ar-WmlyR.js} +1 -1
  157. package/web/_astro/{linear.Bb-3a1d7.js → linear.BnzMBgo_.js} +1 -1
  158. package/web/_astro/{mermaid.core.CJDgXOJs.js → mermaid.core.DSqeFu2Y.js} +4 -4
  159. package/web/_astro/{mindmap-definition-RKZ34NQL.BoAZEI9t.js → mindmap-definition-RKZ34NQL.1XcF8Wh2.js} +1 -1
  160. package/web/_astro/{pieDiagram-4H26LBE5.CJNSdqY7.js → pieDiagram-4H26LBE5.DnItHOdK.js} +1 -1
  161. package/web/_astro/{quadrantDiagram-W4KKPZXB.DtTy7_0Z.js → quadrantDiagram-W4KKPZXB.BQQyrnBt.js} +1 -1
  162. package/web/_astro/{requirementDiagram-4Y6WPE33.BNYV98Xg.js → requirementDiagram-4Y6WPE33.Dmzxun82.js} +1 -1
  163. package/web/_astro/{sankeyDiagram-5OEKKPKP.DRq9DGHp.js → sankeyDiagram-5OEKKPKP.BBIeRKYB.js} +1 -1
  164. package/web/_astro/{sequenceDiagram-3UESZ5HK.B7KdBFCm.js → sequenceDiagram-3UESZ5HK.CUNVdBBf.js} +1 -1
  165. package/web/_astro/{stateDiagram-AJRCARHV.CD62ZJ2H.js → stateDiagram-AJRCARHV.DlFf2sJm.js} +1 -1
  166. package/web/_astro/{stateDiagram-v2-BHNVJYJU.CeVk1TAj.js → stateDiagram-v2-BHNVJYJU.DXR7I1H5.js} +1 -1
  167. package/web/_astro/{timeline-definition-PNZ67QCA.C_ZPA5tT.js → timeline-definition-PNZ67QCA.BcO-GyX2.js} +1 -1
  168. package/web/_astro/{vennDiagram-CIIHVFJN.CrThO_FQ.js → vennDiagram-CIIHVFJN.ChCECmmW.js} +1 -1
  169. package/web/_astro/{wardleyDiagram-YWT4CUSO.DSZCA5nl.js → wardleyDiagram-YWT4CUSO.CRaT12mD.js} +1 -1
  170. package/web/_astro/{xychartDiagram-2RQKCTM6.KI7baTuk.js → xychartDiagram-2RQKCTM6.B3lsHVpj.js} +1 -1
  171. package/web/board-runtime.json +69 -0
  172. package/web/index.html +2 -2
  173. package/plugins/sp/lib/artifact-digest.generated.d.mts +0 -7
  174. package/plugins/sp/lib/artifact-digest.generated.mjs +0 -48
  175. package/plugins/sp/scripts/feature-sync-bounded.mjs +0 -298
  176. package/plugins/sp/scripts/feature-sync-bounded.ts +0 -479
  177. package/plugins/sp/scripts/idea-coverage-check.ts +0 -168
  178. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +0 -298
  179. package/plugins/sp/scripts/record-feature-sync.mjs +0 -63
  180. package/plugins/sp/scripts/record-feature-sync.ts +0 -84
  181. package/plugins/sp/scripts/script-contract-check.ts +0 -377
  182. package/plugins/sp/scripts/stage-registry-adapter.ts +0 -1533
  183. package/plugins/sp/scripts/surface-drift-inventory.ts +0 -989
  184. package/plugins/sp/scripts/task-evidence-precheck.ts +0 -187
  185. package/plugins/sp/scripts/task-size-precheck.ts +0 -210
  186. package/plugins/sp/scripts/transition-shim-check.ts +0 -238
  187. package/plugins/sp/scripts/validate-commands.ts +0 -689
  188. package/plugins/sp/scripts/validate-flag-contracts.ts +0 -878
  189. package/plugins/sp/scripts/verify-answer-lint.ts +0 -547
  190. package/web/_astro/BoardApp.C02hAHPO.js +0 -1
  191. package/web/_astro/TaskDetail.C-GdsS-t.js +0 -1
  192. package/web/_astro/architectureDiagram-3BPJPVTR.CGe629A1.js +0 -36
  193. package/web/_astro/channel.fsgl7o5j.js +0 -1
  194. package/web/_astro/client.yhYJvxCU.js +0 -9
  195. package/web/_astro/cose-bilkent-S5V4N54A.2fH4YOlp.js +0 -1
  196. package/web/_astro/ganttDiagram-6RSMTGT7.DC_p36PI.js +0 -292
  197. package/web/_astro/index.De90oHcH.js +0 -1
  198. package/web/_astro/index.Hjbr15fG.css +0 -1
@@ -1,298 +0,0 @@
1
- #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
-
5
- // plugins/sp/scripts/feature-sync-bounded.ts
6
- import { spawnSync } from "node:child_process";
7
- import { createHash } from "node:crypto";
8
- import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
9
- import { dirname } from "node:path";
10
- import { fileURLToPath } from "node:url";
11
-
12
- // plugins/sp/lib/env.ts
13
- function getEnvVar(name, fallback) {
14
- const raw = process.env[name];
15
- return raw === undefined ? fallback : raw;
16
- }
17
-
18
- // plugins/sp/scripts/feature-sync-bounded.ts
19
- function classifySyncResult(result) {
20
- if (result.proposal.gateBlocked === true)
21
- return "blocked";
22
- if (result.applied === true)
23
- return "applied";
24
- if (result.proposal.from !== result.proposal.to)
25
- return "blocked";
26
- return "no-op";
27
- }
28
- function computeFingerprint(input) {
29
- const material = [
30
- input.featureContentHash,
31
- ...[...input.taskStatusVector].sort(),
32
- ...[...input.verdictMtimeVector].sort()
33
- ].join(`
34
- `);
35
- return createHash("sha256").update(material).digest("hex").slice(0, 32);
36
- }
37
- var blockedStateFile = (featureId, runDir) => `${runDir.replace(/\/$/, "")}/feature-sync-blocked-${featureId}.json`;
38
- function serializeBlockedState(state) {
39
- return `${JSON.stringify(state)}
40
- `;
41
- }
42
- function parseBlockedState(raw) {
43
- const trimmed = raw.trim();
44
- if (trimmed.length === 0)
45
- return null;
46
- try {
47
- const parsed = JSON.parse(trimmed);
48
- if (typeof parsed.featureId !== "string" || typeof parsed.inputFingerprint !== "string" || typeof parsed.proposal !== "object" || parsed.proposal === null) {
49
- return null;
50
- }
51
- return parsed;
52
- } catch {
53
- return null;
54
- }
55
- }
56
- function shouldSuppressBlocked(prior, currentFingerprint) {
57
- if (!prior)
58
- return { suppress: false };
59
- if (prior.inputFingerprint === currentFingerprint) {
60
- return { suppress: true, replay: prior.result };
61
- }
62
- return { suppress: false };
63
- }
64
- function decideBoundedSync(prior, currentFingerprint) {
65
- const decision = shouldSuppressBlocked(prior, currentFingerprint);
66
- if (decision.suppress && decision.replay) {
67
- return { kind: "suppress", replay: decision.replay };
68
- }
69
- return { kind: "invoke" };
70
- }
71
- function processSyncResult(result, currentFingerprint, persistedAt, wasSuppressed) {
72
- const classification = classifySyncResult(result);
73
- if (classification === "blocked") {
74
- return {
75
- classification,
76
- emit: result,
77
- persist: {
78
- featureId: result.proposal.featureId,
79
- inputFingerprint: currentFingerprint,
80
- proposal: result.proposal,
81
- classification,
82
- result,
83
- persistedAt
84
- },
85
- annotation: wasSuppressed ? `feature-sync-bounded: suppressed duplicate blocked sync for ${result.proposal.featureId} (inputs unchanged)` : `feature-sync-bounded: blocked proposal for ${result.proposal.featureId} — ${result.proposal.reason}`
86
- };
87
- }
88
- return {
89
- classification,
90
- emit: result,
91
- annotation: ""
92
- };
93
- }
94
- var BOUNDED_SYNC_CLI_USAGE = `Usage:
95
- bun plugins/sp/scripts/feature-sync-bounded.ts <feature-id> \\
96
- --spur-bin <spur|bun apps/cli/src/index.ts> \\
97
- [--run-dir .spur/run] [--json]
98
-
99
- Wraps 'spur feature sync <id> --json' with bounded retry-suppression: an identical
100
- blocked proposal is reported once and suppressed until feature file content, linked
101
- task statuses, or verdict artifact mtimes change. Applied and no-op results pass
102
- through unchanged.
103
-
104
- Exit: 0 = sync handled (applied / no-op / suppressed-blocked / live-blocked).`;
105
- function defaultSpurBin() {
106
- if (getEnvVar("SPUR_BIN"))
107
- return getEnvVar("SPUR_BIN");
108
- const local = fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url));
109
- if (existsSync(local))
110
- return `bun ${local}`;
111
- return "spur";
112
- }
113
- function parseBoundedSyncCliArgs(argv) {
114
- let featureId = "";
115
- let spurBin = defaultSpurBin();
116
- let runDir = ".spur/run";
117
- let json = false;
118
- let help = false;
119
- for (let i = 0;i < argv.length; i++) {
120
- const a = argv[i];
121
- if (a === "--help" || a === "-h")
122
- help = true;
123
- else if (a === "--json")
124
- json = true;
125
- else if (a === "--spur-bin")
126
- spurBin = argv[++i] ?? spurBin;
127
- else if (a === "--run-dir")
128
- runDir = argv[++i] ?? runDir;
129
- else if (!a.startsWith("--") && featureId === "")
130
- featureId = a;
131
- }
132
- return { featureId, spurBin, runDir, json, help };
133
- }
134
- function runSpurJson(spurBin, args) {
135
- const binParts = spurBin.split(/\s+/).filter(Boolean);
136
- const cmd = binParts[0] ?? "spur";
137
- const cmdArgs = [...binParts.slice(1), ...args];
138
- const r = spawnSync(cmd, cmdArgs, { stdio: ["ignore", "pipe", "pipe"], encoding: "utf8" });
139
- const decode = (b) => typeof b === "string" ? b : Buffer.from(b ?? []).toString("utf8");
140
- return {
141
- stdout: typeof r.stdout === "string" ? r.stdout : decode(r.stdout),
142
- stderr: typeof r.stderr === "string" ? r.stderr : decode(r.stderr),
143
- exitCode: r.status ?? (r.error ? 1 : 0),
144
- ok: (r.status ?? (r.error ? 1 : 0)) === 0
145
- };
146
- }
147
- function readFeatureContentHash(spurBin, featureId) {
148
- const r = runSpurJson(spurBin, ["feature", "show", featureId, "--json"]);
149
- if (!r.ok)
150
- return null;
151
- try {
152
- const parsed = JSON.parse(r.stdout);
153
- if (typeof parsed.content !== "string")
154
- return null;
155
- return createHash("sha256").update(parsed.content).digest("hex");
156
- } catch {
157
- return null;
158
- }
159
- }
160
- function readTaskStatusVector(spurBin, featureId) {
161
- const r = runSpurJson(spurBin, ["task", "list", "--feature", featureId, "--json"]);
162
- if (!r.ok)
163
- return null;
164
- try {
165
- const parsed = JSON.parse(r.stdout);
166
- return parsed.filter((t) => typeof t.wbs === "string" && typeof t.status === "string").map((t) => `${t.wbs}:${t.status}`).sort();
167
- } catch {
168
- return null;
169
- }
170
- }
171
- function readVerdictMtimeVector(runDir) {
172
- const dir = runDir.replace(/\/$/, "");
173
- let entries;
174
- try {
175
- entries = readdirSync(dir).filter((f) => f.endsWith("-verdict.json"));
176
- } catch {
177
- return [];
178
- }
179
- const vector = [];
180
- for (const entry of entries) {
181
- try {
182
- const mtime = statSync(`${dir}/${entry}`).mtimeMs;
183
- vector.push(`${entry.replace("-verdict.json", "")}:${mtime}`);
184
- } catch {}
185
- }
186
- return vector.sort();
187
- }
188
- function writeBlockedState(state, path) {
189
- try {
190
- mkdirSync(dirname(path), { recursive: true });
191
- writeFileSync(path, serializeBlockedState(state));
192
- } catch (err) {
193
- const reason = err instanceof Error ? err.message : String(err);
194
- process.stderr.write(`feature-sync-bounded: could not persist blocked state to ${path} — ${reason}
195
- `);
196
- }
197
- }
198
- function readBlockedState(path) {
199
- try {
200
- if (!existsSync(path))
201
- return null;
202
- const text = readFileSync(path, "utf8");
203
- return parseBlockedState(text);
204
- } catch {
205
- return null;
206
- }
207
- }
208
- function runBoundedCli(argv) {
209
- const args = parseBoundedSyncCliArgs(argv);
210
- if (args.help)
211
- return { exitCode: 0, stdout: "", stderr: BOUNDED_SYNC_CLI_USAGE };
212
- if (!args.featureId)
213
- return { exitCode: 1, stdout: "", stderr: BOUNDED_SYNC_CLI_USAGE };
214
- const statePath = blockedStateFile(args.featureId, args.runDir);
215
- const prior = readBlockedState(statePath);
216
- const featureContentHash = readFeatureContentHash(args.spurBin, args.featureId);
217
- const taskStatusVector = readTaskStatusVector(args.spurBin, args.featureId);
218
- if (featureContentHash === null || taskStatusVector === null) {
219
- return invokeLiveSync(args, statePath);
220
- }
221
- const verdictMtimeVector = readVerdictMtimeVector(args.runDir);
222
- const currentFingerprint = computeFingerprint({
223
- featureContentHash,
224
- taskStatusVector,
225
- verdictMtimeVector
226
- });
227
- const decision = decideBoundedSync(prior, currentFingerprint);
228
- if (decision.kind === "suppress") {
229
- const processed = processSyncResult(decision.replay, currentFingerprint, new Date().toISOString(), true);
230
- emitResult(processed.emit, processed.annotation, args.json);
231
- return { exitCode: 0, stdout: "", stderr: "" };
232
- }
233
- return invokeLiveSync(args, statePath, currentFingerprint);
234
- }
235
- function invokeLiveSync(args, statePath, fingerprint) {
236
- const r = runSpurJson(args.spurBin, ["feature", "sync", args.featureId, "--json"]);
237
- if (!r.ok) {
238
- return { exitCode: r.exitCode, stdout: r.stdout, stderr: r.stderr };
239
- }
240
- let result;
241
- try {
242
- result = JSON.parse(r.stdout);
243
- } catch {
244
- return { exitCode: r.exitCode, stdout: r.stdout, stderr: "" };
245
- }
246
- if (!result || typeof result !== "object" || !result.proposal) {
247
- return {
248
- exitCode: r.exitCode,
249
- stdout: r.stdout,
250
- stderr: "unrecognized feature sync envelope: missing proposal"
251
- };
252
- }
253
- const currentFingerprint = fingerprint ?? computeFingerprint({
254
- featureContentHash: readFeatureContentHash(args.spurBin, args.featureId) ?? "",
255
- taskStatusVector: readTaskStatusVector(args.spurBin, args.featureId) ?? [],
256
- verdictMtimeVector: readVerdictMtimeVector(args.runDir)
257
- });
258
- const processed = processSyncResult(result, currentFingerprint, new Date().toISOString(), false);
259
- if (processed.persist)
260
- writeBlockedState(processed.persist, statePath);
261
- emitResult(processed.emit, processed.annotation, args.json);
262
- return { exitCode: 0, stdout: "", stderr: "" };
263
- }
264
- function emitResult(result, annotation, json) {
265
- if (json) {
266
- process.stdout.write(`${JSON.stringify(result)}
267
- `);
268
- } else {
269
- process.stdout.write(`${result.proposal.featureId}: ${result.proposal.from} → ${result.proposal.to} (applied=${result.applied})
270
- `);
271
- }
272
- if (annotation.length > 0)
273
- process.stderr.write(`${annotation}
274
- `);
275
- }
276
- {
277
- const { exitCode, stdout, stderr } = runBoundedCli(process.argv.slice(2));
278
- if (stdout)
279
- process.stdout.write(stdout);
280
- if (stderr)
281
- process.stderr.write(`${stderr}
282
- `);
283
- process.exit(exitCode);
284
- }
285
- export {
286
- shouldSuppressBlocked,
287
- serializeBlockedState,
288
- runBoundedCli,
289
- processSyncResult,
290
- parseBoundedSyncCliArgs,
291
- parseBlockedState,
292
- defaultSpurBin,
293
- decideBoundedSync,
294
- computeFingerprint,
295
- classifySyncResult,
296
- blockedStateFile,
297
- BOUNDED_SYNC_CLI_USAGE
298
- };