@gobing-ai/spur 0.3.95 → 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 (155) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/plugin-scripts.json +0 -54
  3. package/config/rules/boundary/sp-script-placement.yaml +19 -0
  4. package/config/rules/strict/runtime-boundaries.yaml +5 -0
  5. package/config/rules/structure/test-location.yaml +2 -0
  6. package/config/rules/typescript/no-syscall-emulation-in-boundary-mock.yaml +1 -1
  7. package/config/rules/typescript/output-boundaries.yaml +15 -2
  8. package/config/script-placement-baseline.json +51 -0
  9. package/config/workflows/feature-verification.yaml +1 -1
  10. package/config/workflows/history-anatomy.yaml +23 -11
  11. package/config/workflows/idea-pipeline.yaml +80 -85
  12. package/config/workflows/pr-review.yaml +18 -9
  13. package/config/workflows/task-pipeline.yaml +42 -95
  14. package/config/workflows/wrapup-pipeline.yaml +89 -37
  15. package/package.json +1 -1
  16. package/plugins/sp/README.md +8 -10
  17. package/plugins/sp/agents/super-reviewer.md +43 -18
  18. package/plugins/sp/commands/dev-fixgha.md +83 -0
  19. package/plugins/sp/commands/dev-gitmsg.md +8 -6
  20. package/plugins/sp/commands/dev-gtd.md +2 -2
  21. package/plugins/sp/commands/dev-idea.md +22 -12
  22. package/plugins/sp/commands/dev-plan.md +8 -9
  23. package/plugins/sp/commands/dev-review.md +22 -13
  24. package/plugins/sp/commands/dev-verifyall.md +1 -1
  25. package/plugins/sp/commands/spur-init.md +2 -2
  26. package/plugins/sp/lib/history-anatomy.generated.d.mts +112 -0
  27. package/plugins/sp/lib/history-anatomy.generated.mjs +686 -0
  28. package/plugins/sp/lib/idea-handoff.generated.mjs +5 -4
  29. package/plugins/sp/lib/inline-run.generated.d.mts +11 -0
  30. package/plugins/sp/lib/inline-run.generated.mjs +25 -8
  31. package/plugins/sp/lib/quality-gate.generated.d.mts +104 -0
  32. package/plugins/sp/lib/quality-gate.generated.mjs +438 -0
  33. package/plugins/sp/lib/residual-scan.generated.d.mts +62 -0
  34. package/plugins/sp/lib/residual-scan.generated.mjs +210 -0
  35. package/plugins/sp/lib/spur-bin.ts +36 -0
  36. package/plugins/sp/lib/step-profile.generated.d.mts +71 -0
  37. package/plugins/sp/lib/step-profile.generated.mjs +174 -0
  38. package/plugins/sp/plugin.json +1 -1
  39. package/plugins/sp/references/roles.md +1 -1
  40. package/plugins/sp/scripts/history-anatomy-cache.mjs +20 -19
  41. package/plugins/sp/scripts/history-anatomy-cache.ts +23 -928
  42. package/plugins/sp/scripts/inline-run-setup.mjs +85 -320
  43. package/plugins/sp/scripts/inline-run-setup.ts +104 -667
  44. package/plugins/sp/scripts/quality-gate.mjs +34 -19
  45. package/plugins/sp/scripts/quality-gate.ts +12 -658
  46. package/plugins/sp/scripts/residual-scan.mjs +135 -156
  47. package/plugins/sp/scripts/residual-scan.ts +102 -499
  48. package/plugins/sp/scripts/script-root.mjs +5 -1
  49. package/plugins/sp/scripts/script-root.ts +5 -1
  50. package/plugins/sp/scripts/workflow-step-profile.mjs +25 -17
  51. package/plugins/sp/scripts/workflow-step-profile.ts +21 -315
  52. package/plugins/sp/scripts/wrapup-drift-probe.mjs +8 -2
  53. package/plugins/sp/scripts/wrapup-drift-probe.ts +3 -2
  54. package/plugins/sp/scripts/wrapup-steps.mjs +10 -32
  55. package/plugins/sp/scripts/wrapup-steps.ts +8 -36
  56. package/plugins/sp/skills/code-improvement/SKILL.md +5 -4
  57. package/plugins/sp/skills/code-verification/SKILL.md +34 -9
  58. package/plugins/sp/skills/code-verification/references/verdict-schema.md +3 -3
  59. package/plugins/sp/skills/functional-review/SKILL.md +7 -4
  60. package/plugins/sp/skills/functional-review/references/verdict-schema.md +1 -1
  61. package/plugins/sp/skills/history-anatomy/references/modes.md +2 -1
  62. package/plugins/sp/skills/next-feature/references/handoff-routing.md +1 -1
  63. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  64. package/plugins/sp/skills/spur-cli/SKILL.md +3 -3
  65. package/plugins/sp/skills/spur-cli/references/agent.md +10 -10
  66. package/plugins/sp/skills/spur-cli/references/features.md +17 -6
  67. package/plugins/sp/skills/spur-cli/references/init.md +17 -16
  68. package/plugins/sp/skills/spur-cli/references/self.md +3 -2
  69. package/plugins/sp/skills/spur-cli/references/serve.md +10 -10
  70. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +21 -6
  71. package/plugins/sp/skills/spur-cli/references/tasks.md +14 -8
  72. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +4 -3
  73. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +11 -9
  74. package/plugins/sp/skills/spur-dev/references/decision-brief.md +1 -1
  75. package/plugins/sp/skills/spur-dev/references/dev-operations.md +86 -70
  76. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +3 -3
  77. package/plugins/sp/skills/spur-dev/references/execution-batch.md +77 -34
  78. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +5 -9
  79. package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +3 -3
  80. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +31 -21
  81. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +19 -19
  82. package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +4 -3
  83. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +29 -3
  84. package/plugins/sp/skills/spur-doctor/SKILL.md +1 -1
  85. package/plugins/sp/skills/sys-architecture/SKILL.md +3 -2
  86. package/spur.js +1495 -369
  87. package/web/_astro/{BoardApp.Cpxntzad.js → BoardApp.BIjMatT1.js} +1 -1
  88. package/web/_astro/{BoardApp.BxJuwD7I.js → BoardApp.GvjIe9Z6.js} +42 -42
  89. package/web/_astro/{TaskDetail.C5bW4WGV.js → TaskDetail.BZM3EAFx.js} +1 -1
  90. package/web/_astro/{arc.CyjRvNMY.js → arc.HkRiZnoI.js} +1 -1
  91. package/web/_astro/{architectureDiagram-3BPJPVTR.B4lWRzJA.js → architectureDiagram-3BPJPVTR.BDdK-tgZ.js} +1 -1
  92. package/web/_astro/{blockDiagram-GPEHLZMM.Be38USQ2.js → blockDiagram-GPEHLZMM.CZ9kOBxx.js} +1 -1
  93. package/web/_astro/{c4Diagram-AAUBKEIU.DT9Fj5Qx.js → c4Diagram-AAUBKEIU.DXK9qeRD.js} +1 -1
  94. package/web/_astro/channel.DlhX2MEt.js +1 -0
  95. package/web/_astro/{chunk-2J33WTMH.SeSBWLg5.js → chunk-2J33WTMH.BTD2WeX5.js} +1 -1
  96. package/web/_astro/{chunk-4BX2VUAB.DoO4VE14.js → chunk-4BX2VUAB.Ci6Qbcvk.js} +1 -1
  97. package/web/_astro/{chunk-55IACEB6.DrmTFA53.js → chunk-55IACEB6.yl3zsj7p.js} +1 -1
  98. package/web/_astro/{chunk-727SXJPM.Bi-TUb_V.js → chunk-727SXJPM.ClXZsfyR.js} +1 -1
  99. package/web/_astro/{chunk-AQP2D5EJ.B-xp8Uhi.js → chunk-AQP2D5EJ.BmKWZBcP.js} +1 -1
  100. package/web/_astro/{chunk-FMBD7UC4.DGWrDnUU.js → chunk-FMBD7UC4.By30fcb7.js} +1 -1
  101. package/web/_astro/{chunk-ND2GUHAM._BagvPDy.js → chunk-ND2GUHAM.24NmrD-k.js} +1 -1
  102. package/web/_astro/{chunk-QZHKN3VN.VOboYWQ0.js → chunk-QZHKN3VN.BN4sKdcS.js} +1 -1
  103. package/web/_astro/{classDiagram-4FO5ZUOK.D_apfHS7.js → classDiagram-4FO5ZUOK.CJpoMPb5.js} +1 -1
  104. package/web/_astro/{classDiagram-v2-Q7XG4LA2.D_apfHS7.js → classDiagram-v2-Q7XG4LA2.CJpoMPb5.js} +1 -1
  105. package/web/_astro/{cose-bilkent-S5V4N54A.DNLU_L-x.js → cose-bilkent-S5V4N54A.BraxQ2Nt.js} +1 -1
  106. package/web/_astro/{cynefin-OW5HDTMX.D8borhV-.js → cynefin-OW5HDTMX.gzoU73oL.js} +1 -1
  107. package/web/_astro/{dagre-BM42HDAG.Uc0lvjcV.js → dagre-BM42HDAG.wyDfWySU.js} +1 -1
  108. package/web/_astro/{diagram-2AECGRRQ.DDIxtDBo.js → diagram-2AECGRRQ.h2_EbMxV.js} +1 -1
  109. package/web/_astro/{diagram-5GNKFQAL.DVoDnD3H.js → diagram-5GNKFQAL.Db6dcs89.js} +1 -1
  110. package/web/_astro/{diagram-KO2AKTUF.CRubTJ-0.js → diagram-KO2AKTUF.NVwzxu_U.js} +1 -1
  111. package/web/_astro/{diagram-LMA3HP47.m9SeYcXQ.js → diagram-LMA3HP47.B-eNgDQk.js} +1 -1
  112. package/web/_astro/{diagram-OG6HWLK6.C6s2ZjV5.js → diagram-OG6HWLK6.Bsa8ZRbJ.js} +1 -1
  113. package/web/_astro/{erDiagram-TEJ5UH35.nSzr5KTj.js → erDiagram-TEJ5UH35.BospRE5Q.js} +1 -1
  114. package/web/_astro/{flowDiagram-I6XJVG4X.DrZcys16.js → flowDiagram-I6XJVG4X.DMzvHwAA.js} +1 -1
  115. package/web/_astro/{ganttDiagram-6RSMTGT7.Do4M1b-K.js → ganttDiagram-6RSMTGT7.Dmdxgd-M.js} +1 -1
  116. package/web/_astro/{gitGraphDiagram-PVQCEYII.BWc9oJ4l.js → gitGraphDiagram-PVQCEYII.CaqdO0rL.js} +1 -1
  117. package/web/_astro/index.EoZlzLK-.css +1 -0
  118. package/web/_astro/{infoDiagram-5YYISTIA.BIdOctFk.js → infoDiagram-5YYISTIA.DDVD9Wn7.js} +1 -1
  119. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CV5SuG2t.js → ishikawaDiagram-YF4QCWOH.EKyCUFsL.js} +1 -1
  120. package/web/_astro/{journeyDiagram-JHISSGLW.CxE-rEJ-.js → journeyDiagram-JHISSGLW.By2-RkYB.js} +1 -1
  121. package/web/_astro/{kanban-definition-UN3LZRKU.BPRdKWs_.js → kanban-definition-UN3LZRKU.ar-WmlyR.js} +1 -1
  122. package/web/_astro/{linear.zRsuuDTE.js → linear.BnzMBgo_.js} +1 -1
  123. package/web/_astro/{mermaid.core.jAJTcMKc.js → mermaid.core.DSqeFu2Y.js} +4 -4
  124. package/web/_astro/{mindmap-definition-RKZ34NQL.BffJxfCr.js → mindmap-definition-RKZ34NQL.1XcF8Wh2.js} +1 -1
  125. package/web/_astro/{pieDiagram-4H26LBE5.CacwWaE3.js → pieDiagram-4H26LBE5.DnItHOdK.js} +1 -1
  126. package/web/_astro/{quadrantDiagram-W4KKPZXB.hXELD06-.js → quadrantDiagram-W4KKPZXB.BQQyrnBt.js} +1 -1
  127. package/web/_astro/{requirementDiagram-4Y6WPE33.DdEbPcqT.js → requirementDiagram-4Y6WPE33.Dmzxun82.js} +1 -1
  128. package/web/_astro/{sankeyDiagram-5OEKKPKP.DgaDocKv.js → sankeyDiagram-5OEKKPKP.BBIeRKYB.js} +1 -1
  129. package/web/_astro/{sequenceDiagram-3UESZ5HK.B0KScnAu.js → sequenceDiagram-3UESZ5HK.CUNVdBBf.js} +1 -1
  130. package/web/_astro/{stateDiagram-AJRCARHV.CV9M_WNc.js → stateDiagram-AJRCARHV.DlFf2sJm.js} +1 -1
  131. package/web/_astro/{stateDiagram-v2-BHNVJYJU.BOAo74Et.js → stateDiagram-v2-BHNVJYJU.DXR7I1H5.js} +1 -1
  132. package/web/_astro/{timeline-definition-PNZ67QCA.CqqepUMe.js → timeline-definition-PNZ67QCA.BcO-GyX2.js} +1 -1
  133. package/web/_astro/{vennDiagram-CIIHVFJN.DDqe4dOG.js → vennDiagram-CIIHVFJN.ChCECmmW.js} +1 -1
  134. package/web/_astro/{wardleyDiagram-YWT4CUSO.B3R9OPdO.js → wardleyDiagram-YWT4CUSO.CRaT12mD.js} +1 -1
  135. package/web/_astro/{xychartDiagram-2RQKCTM6.B3SvgXpQ.js → xychartDiagram-2RQKCTM6.B3lsHVpj.js} +1 -1
  136. package/web/index.html +2 -2
  137. package/plugins/sp/lib/artifact-digest.generated.d.mts +0 -7
  138. package/plugins/sp/lib/artifact-digest.generated.mjs +0 -48
  139. package/plugins/sp/scripts/feature-sync-bounded.mjs +0 -301
  140. package/plugins/sp/scripts/feature-sync-bounded.ts +0 -481
  141. package/plugins/sp/scripts/idea-coverage-check.ts +0 -168
  142. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +0 -298
  143. package/plugins/sp/scripts/record-feature-sync.mjs +0 -63
  144. package/plugins/sp/scripts/record-feature-sync.ts +0 -84
  145. package/plugins/sp/scripts/script-contract-check.ts +0 -506
  146. package/plugins/sp/scripts/stage-registry-adapter.ts +0 -1533
  147. package/plugins/sp/scripts/surface-drift-inventory.ts +0 -989
  148. package/plugins/sp/scripts/task-evidence-precheck.ts +0 -189
  149. package/plugins/sp/scripts/task-size-precheck.ts +0 -212
  150. package/plugins/sp/scripts/transition-shim-check.ts +0 -238
  151. package/plugins/sp/scripts/validate-commands.ts +0 -689
  152. package/plugins/sp/scripts/validate-flag-contracts.ts +0 -890
  153. package/plugins/sp/scripts/verify-answer-lint.ts +0 -549
  154. package/web/_astro/channel.CI6N_tCg.js +0 -1
  155. package/web/_astro/index.CENnIEqT.css +0 -1
@@ -1,301 +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
- const fromEnv = getEnvVar("SPUR_BIN");
107
- if (fromEnv)
108
- return fromEnv;
109
- const local = fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url));
110
- if (existsSync(local))
111
- return `bun ${local}`;
112
- return "spur";
113
- }
114
- function parseBoundedSyncCliArgs(argv) {
115
- let featureId = "";
116
- let spurBin = defaultSpurBin();
117
- let runDir = ".spur/run";
118
- let json = false;
119
- let help = false;
120
- for (let i = 0;i < argv.length; i++) {
121
- const a = argv[i];
122
- if (a === undefined)
123
- continue;
124
- if (a === "--help" || a === "-h")
125
- help = true;
126
- else if (a === "--json")
127
- json = true;
128
- else if (a === "--spur-bin")
129
- spurBin = argv[++i] ?? spurBin;
130
- else if (a === "--run-dir")
131
- runDir = argv[++i] ?? runDir;
132
- else if (!a.startsWith("--") && featureId === "")
133
- featureId = a;
134
- }
135
- return { featureId, spurBin, runDir, json, help };
136
- }
137
- function runSpurJson(spurBin, args) {
138
- const binParts = spurBin.split(/\s+/).filter(Boolean);
139
- const cmd = binParts[0] ?? "spur";
140
- const cmdArgs = [...binParts.slice(1), ...args];
141
- const r = spawnSync(cmd, cmdArgs, { stdio: ["ignore", "pipe", "pipe"], encoding: "utf8" });
142
- const decode = (b) => typeof b === "string" ? b : Buffer.from(b ?? []).toString("utf8");
143
- return {
144
- stdout: typeof r.stdout === "string" ? r.stdout : decode(r.stdout),
145
- stderr: typeof r.stderr === "string" ? r.stderr : decode(r.stderr),
146
- exitCode: r.status ?? (r.error ? 1 : 0),
147
- ok: (r.status ?? (r.error ? 1 : 0)) === 0
148
- };
149
- }
150
- function readFeatureContentHash(spurBin, featureId) {
151
- const r = runSpurJson(spurBin, ["feature", "show", featureId, "--json"]);
152
- if (!r.ok)
153
- return null;
154
- try {
155
- const parsed = JSON.parse(r.stdout);
156
- if (typeof parsed.content !== "string")
157
- return null;
158
- return createHash("sha256").update(parsed.content).digest("hex");
159
- } catch {
160
- return null;
161
- }
162
- }
163
- function readTaskStatusVector(spurBin, featureId) {
164
- const r = runSpurJson(spurBin, ["task", "list", "--feature", featureId, "--json"]);
165
- if (!r.ok)
166
- return null;
167
- try {
168
- const parsed = JSON.parse(r.stdout);
169
- return parsed.filter((t) => typeof t.wbs === "string" && typeof t.status === "string").map((t) => `${t.wbs}:${t.status}`).sort();
170
- } catch {
171
- return null;
172
- }
173
- }
174
- function readVerdictMtimeVector(runDir) {
175
- const dir = runDir.replace(/\/$/, "");
176
- let entries;
177
- try {
178
- entries = readdirSync(dir).filter((f) => f.endsWith("-verdict.json"));
179
- } catch {
180
- return [];
181
- }
182
- const vector = [];
183
- for (const entry of entries) {
184
- try {
185
- const mtime = statSync(`${dir}/${entry}`).mtimeMs;
186
- vector.push(`${entry.replace("-verdict.json", "")}:${mtime}`);
187
- } catch {}
188
- }
189
- return vector.sort();
190
- }
191
- function writeBlockedState(state, path) {
192
- try {
193
- mkdirSync(dirname(path), { recursive: true });
194
- writeFileSync(path, serializeBlockedState(state));
195
- } catch (err) {
196
- const reason = err instanceof Error ? err.message : String(err);
197
- process.stderr.write(`feature-sync-bounded: could not persist blocked state to ${path} — ${reason}
198
- `);
199
- }
200
- }
201
- function readBlockedState(path) {
202
- try {
203
- if (!existsSync(path))
204
- return null;
205
- const text = readFileSync(path, "utf8");
206
- return parseBlockedState(text);
207
- } catch {
208
- return null;
209
- }
210
- }
211
- function runBoundedCli(argv) {
212
- const args = parseBoundedSyncCliArgs(argv);
213
- if (args.help)
214
- return { exitCode: 0, stdout: "", stderr: BOUNDED_SYNC_CLI_USAGE };
215
- if (!args.featureId)
216
- return { exitCode: 1, stdout: "", stderr: BOUNDED_SYNC_CLI_USAGE };
217
- const statePath = blockedStateFile(args.featureId, args.runDir);
218
- const prior = readBlockedState(statePath);
219
- const featureContentHash = readFeatureContentHash(args.spurBin, args.featureId);
220
- const taskStatusVector = readTaskStatusVector(args.spurBin, args.featureId);
221
- if (featureContentHash === null || taskStatusVector === null) {
222
- return invokeLiveSync(args, statePath);
223
- }
224
- const verdictMtimeVector = readVerdictMtimeVector(args.runDir);
225
- const currentFingerprint = computeFingerprint({
226
- featureContentHash,
227
- taskStatusVector,
228
- verdictMtimeVector
229
- });
230
- const decision = decideBoundedSync(prior, currentFingerprint);
231
- if (decision.kind === "suppress") {
232
- const processed = processSyncResult(decision.replay, currentFingerprint, new Date().toISOString(), true);
233
- emitResult(processed.emit, processed.annotation, args.json);
234
- return { exitCode: 0, stdout: "", stderr: "" };
235
- }
236
- return invokeLiveSync(args, statePath, currentFingerprint);
237
- }
238
- function invokeLiveSync(args, statePath, fingerprint) {
239
- const r = runSpurJson(args.spurBin, ["feature", "sync", args.featureId, "--json"]);
240
- if (!r.ok) {
241
- return { exitCode: r.exitCode, stdout: r.stdout, stderr: r.stderr };
242
- }
243
- let result;
244
- try {
245
- result = JSON.parse(r.stdout);
246
- } catch {
247
- return { exitCode: r.exitCode, stdout: r.stdout, stderr: "" };
248
- }
249
- if (!result || typeof result !== "object" || !result.proposal) {
250
- return {
251
- exitCode: r.exitCode,
252
- stdout: r.stdout,
253
- stderr: "unrecognized feature sync envelope: missing proposal"
254
- };
255
- }
256
- const currentFingerprint = fingerprint ?? computeFingerprint({
257
- featureContentHash: readFeatureContentHash(args.spurBin, args.featureId) ?? "",
258
- taskStatusVector: readTaskStatusVector(args.spurBin, args.featureId) ?? [],
259
- verdictMtimeVector: readVerdictMtimeVector(args.runDir)
260
- });
261
- const processed = processSyncResult(result, currentFingerprint, new Date().toISOString(), false);
262
- if (processed.persist)
263
- writeBlockedState(processed.persist, statePath);
264
- emitResult(processed.emit, processed.annotation, args.json);
265
- return { exitCode: 0, stdout: "", stderr: "" };
266
- }
267
- function emitResult(result, annotation, json) {
268
- if (json) {
269
- process.stdout.write(`${JSON.stringify(result)}
270
- `);
271
- } else {
272
- process.stdout.write(`${result.proposal.featureId}: ${result.proposal.from} → ${result.proposal.to} (applied=${result.applied})
273
- `);
274
- }
275
- if (annotation.length > 0)
276
- process.stderr.write(`${annotation}
277
- `);
278
- }
279
- {
280
- const { exitCode, stdout, stderr } = runBoundedCli(process.argv.slice(2));
281
- if (stdout)
282
- process.stdout.write(stdout);
283
- if (stderr)
284
- process.stderr.write(`${stderr}
285
- `);
286
- process.exit(exitCode);
287
- }
288
- export {
289
- shouldSuppressBlocked,
290
- serializeBlockedState,
291
- runBoundedCli,
292
- processSyncResult,
293
- parseBoundedSyncCliArgs,
294
- parseBlockedState,
295
- defaultSpurBin,
296
- decideBoundedSync,
297
- computeFingerprint,
298
- classifySyncResult,
299
- blockedStateFile,
300
- BOUNDED_SYNC_CLI_USAGE
301
- };