@gobing-ai/spur 0.3.99 → 0.4.1

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 (172) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.global.yaml +17 -0
  3. package/config/decisions/gates.yaml +36 -0
  4. package/config/decisions/history-anatomy.yaml +35 -0
  5. package/config/decisions/idea-pipeline.yaml +38 -0
  6. package/config/decisions/task-pipeline.yaml +55 -0
  7. package/config/plugin-scripts.json +25 -0
  8. package/config/rules/boundary/sp-runtime-path.yaml +2 -0
  9. package/config/rules/docs/sourced-api-claims.yaml +60 -0
  10. package/config/rules/quality/coverage-gate.yaml +5 -3
  11. package/config/rules/recommended-pre-check.yaml +8 -3
  12. package/config/rules/strict/runtime-boundaries.yaml +11 -0
  13. package/config/rules/structure/protected-files.yaml +3 -0
  14. package/config/rules/structure/scratch-evidence-pointer.yaml +35 -0
  15. package/config/script-placement-baseline.json +2 -2
  16. package/config/workflows/feature-lifecycle.yaml +9 -2
  17. package/config/workflows/history-anatomy.yaml +22 -0
  18. package/config/workflows/idea-pipeline.yaml +75 -0
  19. package/config/workflows/task-pipeline.yaml +368 -49
  20. package/config/workflows/wrapup-pipeline.yaml +106 -13
  21. package/package.json +9 -9
  22. package/plugins/sp/README.md +25 -2
  23. package/plugins/sp/agents/super-planner.md +11 -0
  24. package/plugins/sp/agents/super-reviewer.md +39 -11
  25. package/plugins/sp/bunfig.toml +8 -0
  26. package/plugins/sp/commands/dev-brainstorm.md +29 -16
  27. package/plugins/sp/commands/dev-changelog.md +5 -4
  28. package/plugins/sp/commands/dev-find-way.md +80 -0
  29. package/plugins/sp/commands/dev-idea.md +6 -0
  30. package/plugins/sp/commands/dev-parallel.md +8 -2
  31. package/plugins/sp/commands/dev-plan.md +6 -0
  32. package/plugins/sp/commands/dev-review-session.md +14 -7
  33. package/plugins/sp/commands/dev-run.md +17 -4
  34. package/plugins/sp/commands/dev-runall.md +21 -10
  35. package/plugins/sp/hooks/agent-lifecycle.ts +155 -0
  36. package/plugins/sp/hooks/context-session-start.ts +1 -1
  37. package/plugins/sp/hooks/fleet-guest-stop.ts +180 -0
  38. package/plugins/sp/hooks/hooks.json +45 -1
  39. package/plugins/sp/hooks/pi/guard-extension.ts +21 -0
  40. package/plugins/sp/lib/history-anatomy.generated.mjs +5 -0
  41. package/plugins/sp/lib/idea-handoff.generated.mjs +72 -11
  42. package/plugins/sp/lib/inline-run-app.ts +28 -0
  43. package/plugins/sp/lib/inline-run.generated.d.mts +6 -0
  44. package/plugins/sp/lib/inline-run.generated.mjs +364 -46
  45. package/plugins/sp/lib/plan-projection.generated.d.mts +28 -0
  46. package/plugins/sp/lib/plan-projection.generated.mjs +255 -0
  47. package/plugins/sp/lib/quality-gate.generated.d.mts +47 -1
  48. package/plugins/sp/lib/quality-gate.generated.mjs +30489 -82
  49. package/plugins/sp/lib/residual-scan.generated.d.mts +19 -0
  50. package/plugins/sp/lib/residual-scan.generated.mjs +66 -0
  51. package/plugins/sp/lib/run-summary-core.ts +561 -0
  52. package/plugins/sp/lib/transcript.ts +275 -0
  53. package/plugins/sp/plugin.json +1 -1
  54. package/plugins/sp/references/roles.md +1 -1
  55. package/plugins/sp/scripts/batch-plan.mjs +335 -0
  56. package/plugins/sp/scripts/batch-plan.ts +100 -0
  57. package/plugins/sp/scripts/batch-preflight.mjs +51 -3
  58. package/plugins/sp/scripts/batch-preflight.ts +72 -1
  59. package/plugins/sp/scripts/commit-guard.mjs +216 -0
  60. package/plugins/sp/scripts/commit-guard.ts +249 -0
  61. package/plugins/sp/scripts/history-anatomy-cache.mjs +5 -0
  62. package/plugins/sp/scripts/inline-run-dispatch.mjs +130 -0
  63. package/plugins/sp/scripts/inline-run-dispatch.ts +135 -0
  64. package/plugins/sp/scripts/inline-run-setup.mjs +563 -91
  65. package/plugins/sp/scripts/inline-run-setup.ts +65 -97
  66. package/plugins/sp/scripts/persist-out-check.mjs +90 -41
  67. package/plugins/sp/scripts/persist-out-check.ts +112 -57
  68. package/plugins/sp/scripts/quality-gate.mjs +30407 -73
  69. package/plugins/sp/scripts/quality-gate.ts +21 -5
  70. package/plugins/sp/scripts/residual-scan.mjs +88 -38
  71. package/plugins/sp/scripts/residual-scan.ts +35 -38
  72. package/plugins/sp/scripts/run-summary.mjs +693 -0
  73. package/plugins/sp/scripts/run-summary.ts +105 -0
  74. package/plugins/sp/scripts/session-timeline.mjs +331 -0
  75. package/plugins/sp/scripts/session-timeline.ts +204 -0
  76. package/plugins/sp/scripts/wrapup-steps.mjs +150 -2
  77. package/plugins/sp/scripts/wrapup-steps.ts +203 -1
  78. package/plugins/sp/skills/brainstorm/SKILL.md +9 -47
  79. package/plugins/sp/skills/branch-workflow/SKILL.md +1 -0
  80. package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +1 -0
  81. package/plugins/sp/skills/code-implementation/SKILL.md +48 -1
  82. package/plugins/sp/skills/code-verification/SKILL.md +8 -4
  83. package/plugins/sp/skills/code-verification/references/secu-review.md +3 -0
  84. package/plugins/sp/skills/code-verification/references/verdict-schema.md +68 -0
  85. package/plugins/sp/skills/fleet-join/SKILL.md +63 -0
  86. package/plugins/sp/skills/session-review/SKILL.md +77 -24
  87. package/plugins/sp/skills/source-driven-development/SKILL.md +5 -0
  88. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +5 -3
  89. package/plugins/sp/skills/spur-cli/SKILL.md +6 -2
  90. package/plugins/sp/skills/spur-cli/references/agent.md +124 -17
  91. package/plugins/sp/skills/spur-cli/references/decision.md +98 -0
  92. package/plugins/sp/skills/spur-cli/references/message.md +7 -7
  93. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +41 -3
  94. package/plugins/sp/skills/spur-cli/references/tasks.md +10 -7
  95. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +7 -0
  96. package/plugins/sp/skills/spur-cli/references/workflows.md +7 -2
  97. package/plugins/sp/skills/spur-composer/SKILL.md +28 -2
  98. package/plugins/sp/skills/spur-dev/SKILL.md +18 -14
  99. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +6 -3
  100. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +49 -14
  101. package/plugins/sp/skills/spur-dev/references/dev-operations.md +51 -18
  102. package/plugins/sp/skills/spur-dev/references/execution-batch-continuation.md +68 -0
  103. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +194 -0
  104. package/plugins/sp/skills/spur-dev/references/execution-batch.md +113 -963
  105. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +130 -0
  106. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +20 -0
  107. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +471 -0
  108. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +379 -0
  109. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +51 -9
  110. package/plugins/sp/skills/spur-dev/references/glossary.md +2 -2
  111. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +361 -156
  112. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +6 -0
  113. package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +178 -0
  114. package/plugins/sp/skills/wayfinder/SKILL.md +10 -14
  115. package/schemas/decision-catalog.schema.json +226 -0
  116. package/schemas/spur-config.schema.json +25 -0
  117. package/schemas/state-machine-workflow.schema.json +23 -0
  118. package/spur.js +28165 -19051
  119. package/web/_astro/{BoardApp.i4eh_fHA.js → BoardApp.D66k_s_T.js} +1 -1
  120. package/web/_astro/BoardApp.D6sjAJr_.js +324 -0
  121. package/web/_astro/{TaskDetail.C5Cu8LdB.js → TaskDetail.BynLnMNc.js} +1 -1
  122. package/web/_astro/{arc.Bu5XpBEz.js → arc._BUadrdH.js} +1 -1
  123. package/web/_astro/{architectureDiagram-3BPJPVTR.9vHQTvhF.js → architectureDiagram-3BPJPVTR.Dh7xS1Pp.js} +1 -1
  124. package/web/_astro/{blockDiagram-GPEHLZMM.Cn2Xl7D6.js → blockDiagram-GPEHLZMM.DFDVpfXm.js} +1 -1
  125. package/web/_astro/{c4Diagram-AAUBKEIU.7o0P_QDo.js → c4Diagram-AAUBKEIU.V7_3_glh.js} +1 -1
  126. package/web/_astro/channel.DZVfto-r.js +1 -0
  127. package/web/_astro/{chunk-2J33WTMH.C3zZFsNt.js → chunk-2J33WTMH.De1yx7Jz.js} +1 -1
  128. package/web/_astro/{chunk-4BX2VUAB.CNy_M7fO.js → chunk-4BX2VUAB.BDhHFuxx.js} +1 -1
  129. package/web/_astro/{chunk-55IACEB6.2TiPnLBL.js → chunk-55IACEB6.DDLkWnmC.js} +1 -1
  130. package/web/_astro/{chunk-727SXJPM.5CGSyKAp.js → chunk-727SXJPM.CwfiLnA7.js} +1 -1
  131. package/web/_astro/{chunk-AQP2D5EJ.w-ZIgfsK.js → chunk-AQP2D5EJ.CtGDmUnM.js} +1 -1
  132. package/web/_astro/{chunk-FMBD7UC4.BkcEyhKa.js → chunk-FMBD7UC4.BOLjpw73.js} +1 -1
  133. package/web/_astro/{chunk-ND2GUHAM.mxKKbBG4.js → chunk-ND2GUHAM.BDhQls6O.js} +1 -1
  134. package/web/_astro/{chunk-QZHKN3VN.cyA0z9aT.js → chunk-QZHKN3VN.76qBynDF.js} +1 -1
  135. package/web/_astro/{classDiagram-4FO5ZUOK.CqtUzwhX.js → classDiagram-4FO5ZUOK.N0mpPHT0.js} +1 -1
  136. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CqtUzwhX.js → classDiagram-v2-Q7XG4LA2.N0mpPHT0.js} +1 -1
  137. package/web/_astro/{cose-bilkent-S5V4N54A.BExPTopI.js → cose-bilkent-S5V4N54A.CKknFfjB.js} +1 -1
  138. package/web/_astro/{cynefin-OW5HDTMX.Bq2i10Cn.js → cynefin-OW5HDTMX.Cw3ZWBAh.js} +1 -1
  139. package/web/_astro/{dagre-BM42HDAG.By_HsOuv.js → dagre-BM42HDAG.D3yIKCRD.js} +1 -1
  140. package/web/_astro/{diagram-2AECGRRQ.BtU0b7hz.js → diagram-2AECGRRQ.D51dO83Z.js} +1 -1
  141. package/web/_astro/{diagram-5GNKFQAL.D00ztMdT.js → diagram-5GNKFQAL.Bb-FViDj.js} +1 -1
  142. package/web/_astro/{diagram-KO2AKTUF.DMVJWaUN.js → diagram-KO2AKTUF.D3mN8S6x.js} +1 -1
  143. package/web/_astro/{diagram-LMA3HP47.Cxqrjg5-.js → diagram-LMA3HP47.dMMyPcbJ.js} +1 -1
  144. package/web/_astro/{diagram-OG6HWLK6.2lz_1B5h.js → diagram-OG6HWLK6.AzbvzY4C.js} +1 -1
  145. package/web/_astro/{erDiagram-TEJ5UH35.BVQEuJ3T.js → erDiagram-TEJ5UH35.BR5I4OZb.js} +1 -1
  146. package/web/_astro/{flowDiagram-I6XJVG4X.Qlyo90ky.js → flowDiagram-I6XJVG4X.DFqvhGkn.js} +1 -1
  147. package/web/_astro/{ganttDiagram-6RSMTGT7.DRwb8HHZ.js → ganttDiagram-6RSMTGT7.ByJbhH6J.js} +1 -1
  148. package/web/_astro/{gitGraphDiagram-PVQCEYII.YfoPthfS.js → gitGraphDiagram-PVQCEYII.CInHjQR3.js} +1 -1
  149. package/web/_astro/index.COh_kBxN.css +1 -0
  150. package/web/_astro/{infoDiagram-5YYISTIA.DvFREck1.js → infoDiagram-5YYISTIA.Cm0dGVjJ.js} +1 -1
  151. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BzCj9Q0-.js → ishikawaDiagram-YF4QCWOH.C3nOP6Sj.js} +1 -1
  152. package/web/_astro/{journeyDiagram-JHISSGLW.BDMT_AFb.js → journeyDiagram-JHISSGLW.4IvLa2MW.js} +1 -1
  153. package/web/_astro/{kanban-definition-UN3LZRKU.D6Fh3gj9.js → kanban-definition-UN3LZRKU.DS87Cl-k.js} +1 -1
  154. package/web/_astro/{linear.Dxw00fGQ.js → linear.MzmRrNER.js} +1 -1
  155. package/web/_astro/{mermaid.core.CsoxUWa0.js → mermaid.core.fK-WNswS.js} +4 -4
  156. package/web/_astro/{mindmap-definition-RKZ34NQL.CN8oeHLu.js → mindmap-definition-RKZ34NQL.BqipOHhY.js} +1 -1
  157. package/web/_astro/{pieDiagram-4H26LBE5.C7ADqQ_7.js → pieDiagram-4H26LBE5.0nBiPqFG.js} +1 -1
  158. package/web/_astro/{quadrantDiagram-W4KKPZXB.Cw9lKyHP.js → quadrantDiagram-W4KKPZXB.Szj7Zpx5.js} +1 -1
  159. package/web/_astro/{requirementDiagram-4Y6WPE33.B1ni-z4q.js → requirementDiagram-4Y6WPE33.D4UR6TWo.js} +1 -1
  160. package/web/_astro/{sankeyDiagram-5OEKKPKP.BhexdjVr.js → sankeyDiagram-5OEKKPKP.DAE7BdOs.js} +1 -1
  161. package/web/_astro/{sequenceDiagram-3UESZ5HK.C3Rm7dpQ.js → sequenceDiagram-3UESZ5HK.BP9ZVq25.js} +1 -1
  162. package/web/_astro/{stateDiagram-AJRCARHV.CkuMt27u.js → stateDiagram-AJRCARHV.BQW7mIxk.js} +1 -1
  163. package/web/_astro/{stateDiagram-v2-BHNVJYJU.Dc_aazUA.js → stateDiagram-v2-BHNVJYJU.PbK4nDih.js} +1 -1
  164. package/web/_astro/{timeline-definition-PNZ67QCA.ja7sUFhH.js → timeline-definition-PNZ67QCA.Ci_GvUpo.js} +1 -1
  165. package/web/_astro/{vennDiagram-CIIHVFJN.BxIshX_K.js → vennDiagram-CIIHVFJN.DNw1Y5qx.js} +1 -1
  166. package/web/_astro/{wardleyDiagram-YWT4CUSO.DfHlC0sh.js → wardleyDiagram-YWT4CUSO.CwlkaaNe.js} +1 -1
  167. package/web/_astro/{xychartDiagram-2RQKCTM6.DSnhHwCh.js → xychartDiagram-2RQKCTM6.MI6ZuiH_.js} +1 -1
  168. package/web/board-runtime.json +1 -1
  169. package/web/index.html +2 -2
  170. package/web/_astro/BoardApp.BVp-LYi6.js +0 -322
  171. package/web/_astro/channel.6IFIKBhu.js +0 -1
  172. package/web/_astro/index.CbNz17Sx.css +0 -1
@@ -3,8 +3,8 @@
3
3
 
4
4
  // plugins/sp/scripts/inline-run-setup.ts
5
5
  import { spawnSync } from "child_process";
6
- import { existsSync } from "fs";
7
- import { dirname, join, resolve } from "path";
6
+ import { existsSync as existsSync3 } from "fs";
7
+ import { dirname, join as join3, resolve } from "path";
8
8
  import { fileURLToPath } from "url";
9
9
 
10
10
  // plugins/sp/lib/env.ts
@@ -13,14 +13,519 @@ function getEnvVar(name, fallback) {
13
13
  return raw === undefined ? fallback : raw;
14
14
  }
15
15
 
16
+ // plugins/sp/lib/inline-run-app.ts
17
+ async function loadInlineApp(spurBin, resolveAppEntry, required) {
18
+ const { entry, portable } = resolveAppEntry(spurBin);
19
+ const app = await import(entry);
20
+ const missing = portable ? required.filter((name) => typeof app[name] !== "function") : [];
21
+ if (missing.length > 0) {
22
+ throw new Error(`inline application bundle ${entry} is older than its scripts — missing ${missing.join(", ")}. Reinstall sp with a superskill release that stages lib/ (commit b42961b), or pass --spur-bin <spur checkout>/apps/cli/src/index.ts.`);
23
+ }
24
+ return { app, portable };
25
+ }
26
+
27
+ // plugins/sp/lib/env.ts
28
+ function getEnvVars() {
29
+ return process.env;
30
+ }
31
+
32
+ // plugins/sp/lib/run-summary-core.ts
33
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "node:fs";
34
+ import { join as join2 } from "node:path";
35
+
36
+ // plugins/sp/lib/transcript.ts
37
+ import { existsSync, readdirSync } from "node:fs";
38
+ import { homedir } from "node:os";
39
+ import { join } from "node:path";
40
+ var SESSION_ID = /^[A-Za-z0-9_-]+$/;
41
+ var OPERATOR_TOOLS = new Set(["AskUserQuestion", "ask_user_question"]);
42
+ var PI_ROLES = new Set(["user", "assistant", "toolResult"]);
43
+ var zeroTokens = () => ({ input: 0, cacheCreate: 0, cacheRead: 0, output: 0 });
44
+ function formatDuration(ms) {
45
+ const s = Math.round(ms / 1000);
46
+ const h = Math.floor(s / 3600);
47
+ const m = Math.floor(s % 3600 / 60);
48
+ const ss = String(s % 60).padStart(2, "0");
49
+ return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
50
+ }
51
+ function formatTokens(n) {
52
+ if (n >= 999500)
53
+ return `${(n / 1e6).toFixed(1)}M`;
54
+ return n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);
55
+ }
56
+ function formatTokenSplit(t) {
57
+ if (!t)
58
+ return "n/a";
59
+ const nonCached = t.input + t.cacheCreate + t.output;
60
+ return `${formatTokens(nonCached + t.cacheRead)} / ${formatTokens(nonCached)}`;
61
+ }
62
+ function promptText(row) {
63
+ if (row.type === "message") {
64
+ if (row.message?.role !== "user")
65
+ return;
66
+ if (isInjectedPrompt(row))
67
+ return;
68
+ const content2 = row.message.content;
69
+ if (typeof content2 === "string")
70
+ return content2;
71
+ if (!Array.isArray(content2))
72
+ return;
73
+ return content2.find((b) => b.type === "text")?.text;
74
+ }
75
+ if (row.type !== "user" || row.isMeta || row.isCompactSummary)
76
+ return;
77
+ const content = row.message?.content;
78
+ if (typeof content === "string")
79
+ return content;
80
+ if (!Array.isArray(content) || content.some((b) => b.type === "tool_result"))
81
+ return;
82
+ return content.find((b) => b.type === "text")?.text;
83
+ }
84
+ function isInjectedPrompt(row) {
85
+ if (row.type !== "message" || row.message?.role !== "user")
86
+ return false;
87
+ const content = row.message.content;
88
+ const text = typeof content === "string" ? content : Array.isArray(content) ? content.find((b) => b.type === "text")?.text : undefined;
89
+ return typeof text === "string" && text.replace(/^\s+/, "").startsWith('<skill name="');
90
+ }
91
+ function sumTokens(all) {
92
+ const total = zeroTokens();
93
+ for (const t of all)
94
+ for (const k of Object.keys(total))
95
+ total[k] += t[k];
96
+ return total;
97
+ }
98
+ function sumSpans(spans) {
99
+ return {
100
+ workMs: spans.reduce((n, s) => n + s.workMs, 0),
101
+ waitMs: spans.reduce((n, s) => n + s.waitMs, 0),
102
+ toolCalls: spans.reduce((n, s) => n + s.toolCalls, 0),
103
+ tokens: sumTokens(spans.map((s) => s.tokens))
104
+ };
105
+ }
106
+ var newAcc = (ts) => ({
107
+ last: ts,
108
+ askMs: 0,
109
+ asks: new Map,
110
+ toolIds: new Set,
111
+ messages: new Map,
112
+ compactions: 0
113
+ });
114
+ function accumulate(acc, row, ts) {
115
+ acc.last = Math.max(acc.last, ts);
116
+ if (row.type === "message") {
117
+ const m = row.message;
118
+ if (m?.role === "assistant") {
119
+ const blocks2 = Array.isArray(m.content) ? m.content : [];
120
+ for (const b of blocks2) {
121
+ if (b.type === "toolCall" && b.id) {
122
+ acc.toolIds.add(b.id);
123
+ if (b.name && OPERATOR_TOOLS.has(b.name))
124
+ acc.asks.set(b.id, ts);
125
+ }
126
+ }
127
+ const u2 = m.usage;
128
+ if (u2 && row.id)
129
+ acc.messages.set(row.id, {
130
+ input: u2.input ?? 0,
131
+ cacheCreate: u2.cacheWrite ?? 0,
132
+ cacheRead: u2.cacheRead ?? 0,
133
+ output: u2.output ?? 0
134
+ });
135
+ } else if (m?.role === "toolResult" && m.toolCallId && acc.asks.has(m.toolCallId)) {
136
+ acc.askMs += ts - (acc.asks.get(m.toolCallId) ?? ts);
137
+ }
138
+ return;
139
+ }
140
+ if (row.type === "compaction")
141
+ acc.compactions++;
142
+ const blocks = Array.isArray(row.message?.content) ? row.message.content : [];
143
+ for (const b of blocks) {
144
+ if (b.type === "tool_use" && b.id) {
145
+ acc.toolIds.add(b.id);
146
+ if (b.name && OPERATOR_TOOLS.has(b.name))
147
+ acc.asks.set(b.id, ts);
148
+ } else if (b.type === "tool_result" && b.tool_use_id && acc.asks.has(b.tool_use_id)) {
149
+ acc.askMs += ts - (acc.asks.get(b.tool_use_id) ?? ts);
150
+ }
151
+ }
152
+ const u = row.message?.usage;
153
+ if (row.type === "assistant" && u && row.message?.id) {
154
+ acc.messages.set(row.message.id, {
155
+ input: u.input_tokens ?? 0,
156
+ cacheCreate: u.cache_creation_input_tokens ?? 0,
157
+ cacheRead: u.cache_read_input_tokens ?? 0,
158
+ output: u.output_tokens ?? 0
159
+ });
160
+ }
161
+ }
162
+ function isCompaction(row) {
163
+ return row.type === "compaction" || row.type === "system" && row.subtype === "compact_boundary";
164
+ }
165
+ function sniffFormat(rows) {
166
+ for (const row of rows) {
167
+ if (row.type === "message" && PI_ROLES.has(row.message?.role ?? ""))
168
+ return "pi";
169
+ if (row.type === "user" || row.type === "assistant")
170
+ return "claude";
171
+ }
172
+ return "unknown";
173
+ }
174
+ function parseRows(lines) {
175
+ const rows = [];
176
+ let skippedLines = 0;
177
+ for (const line of lines) {
178
+ if (!line.trim())
179
+ continue;
180
+ let row;
181
+ try {
182
+ row = JSON.parse(line);
183
+ } catch {
184
+ skippedLines++;
185
+ continue;
186
+ }
187
+ const ts = row.timestamp ? Date.parse(row.timestamp) : Number.NaN;
188
+ if (!Number.isNaN(ts))
189
+ rows.push([row, ts]);
190
+ }
191
+ return { rows, skippedLines, format: sniffFormat(rows.map(([r]) => r)) };
192
+ }
193
+ function resolveTranscript(env, projectsRoot = join(homedir(), ".claude", "projects"), override) {
194
+ if (override)
195
+ return existsSync(override) ? { ok: true, path: override } : { ok: false, reason: "no transcript" };
196
+ const id = env.CLAUDE_CODE_SESSION_ID;
197
+ if (!id) {
198
+ const piFile = env.PI_SESSION_FILE;
199
+ if (piFile !== undefined && piFile !== "") {
200
+ return existsSync(piFile) ? { ok: true, path: piFile } : { ok: false, reason: `PI_SESSION_FILE ${piFile} does not exist` };
201
+ }
202
+ return {
203
+ ok: false,
204
+ reason: "no host session id; pass --transcript <path> (pi: ~/.pi/agent/sessions/<cwd-slug>/<file>.jsonl, " + "or set PI_SESSION_FILE)"
205
+ };
206
+ }
207
+ if (!SESSION_ID.test(id))
208
+ return { ok: false, reason: "refusing a session id with path characters" };
209
+ if (!existsSync(projectsRoot))
210
+ return { ok: false, reason: `no transcript root ${projectsRoot}` };
211
+ for (const dir of readdirSync(projectsRoot)) {
212
+ const path = join(projectsRoot, dir, `${id}.jsonl`);
213
+ if (existsSync(path))
214
+ return { ok: true, path };
215
+ }
216
+ return { ok: false, reason: `no transcript for session ${id}` };
217
+ }
218
+
219
+ // plugins/sp/lib/run-summary-core.ts
220
+ var IDLE_GAP_MS = 300000;
221
+ var SHELL_TOOLS = new Set(["bash", "shell", "exec", "exec_command", "run_command", "command"]);
222
+ var SUBAGENT_TOOLS = new Set(["agent", "task", "invoke_subagent", "subagent", "dispatch_agent"]);
223
+ var OPERATOR_TOOLS2 = new Set(["askuserquestion", "ask_user_question"]);
224
+ var ACTOR_PRECEDENCE = ["operator", "subagent", "shell", "other"];
225
+ function actorOf(tool) {
226
+ const t = tool.toLowerCase().replace(/[^a-z_]/g, "");
227
+ if (OPERATOR_TOOLS2.has(t))
228
+ return "operator";
229
+ if (SUBAGENT_TOOLS.has(t))
230
+ return "subagent";
231
+ if (SHELL_TOOLS.has(t))
232
+ return "shell";
233
+ return "other";
234
+ }
235
+ function unionMs(spans, start, end) {
236
+ const clipped = spans.map(([a, b]) => [Math.max(a, start), Math.min(b, end)]).filter(([a, b]) => b > a).sort((x, y) => x[0] - y[0]);
237
+ let total = 0;
238
+ let cursor = Number.NaN;
239
+ let openUntil = Number.NaN;
240
+ for (const [a, b] of clipped) {
241
+ if (!Number.isFinite(cursor)) {
242
+ cursor = a;
243
+ openUntil = b;
244
+ continue;
245
+ }
246
+ if (a > openUntil) {
247
+ total += openUntil - cursor;
248
+ cursor = a;
249
+ openUntil = b;
250
+ } else {
251
+ openUntil = Math.max(openUntil, b);
252
+ }
253
+ }
254
+ if (Number.isFinite(cursor))
255
+ total += openUntil - cursor;
256
+ return total;
257
+ }
258
+ function subtract(spans, claimed) {
259
+ let free = spans;
260
+ for (const [ca, cb] of claimed) {
261
+ const next = [];
262
+ for (const [a, b] of free) {
263
+ if (cb <= a || ca >= b) {
264
+ next.push([a, b]);
265
+ continue;
266
+ }
267
+ if (ca > a)
268
+ next.push([a, ca]);
269
+ if (cb < b)
270
+ next.push([cb, b]);
271
+ }
272
+ free = next;
273
+ }
274
+ return free;
275
+ }
276
+ function actorSplit(rows, start, end) {
277
+ const empty = {
278
+ operatorMs: 0,
279
+ longestOperatorWaitMs: 0,
280
+ modelMs: 0,
281
+ shellMs: 0,
282
+ subagentMs: 0,
283
+ otherToolMs: 0,
284
+ idleMs: 0,
285
+ compactions: 0,
286
+ subagentErrors: 0
287
+ };
288
+ const window = end - start;
289
+ if (window <= 0)
290
+ return empty;
291
+ const calls = new Map;
292
+ const results = new Map;
293
+ const eventTs = [];
294
+ let compactions = 0;
295
+ for (const [row, ts] of rows) {
296
+ if (ts < start || ts > end)
297
+ continue;
298
+ eventTs.push(ts);
299
+ if (isCompaction(row))
300
+ compactions++;
301
+ const blocks = Array.isArray(row.message?.content) ? row.message?.content : [];
302
+ if (row.message?.role === "toolResult" && row.message.toolCallId !== undefined) {
303
+ results.set(row.message.toolCallId, { ts, isError: row.message.isError === true });
304
+ }
305
+ for (const b of blocks) {
306
+ if ((b.type === "tool_use" || b.type === "toolCall") && b.id !== undefined && b.name !== undefined) {
307
+ calls.set(b.id, { id: b.id, actor: actorOf(b.name), ts });
308
+ } else if (b.type === "tool_result" && b.tool_use_id !== undefined) {
309
+ results.set(b.tool_use_id, { ts, isError: b.is_error === true });
310
+ }
311
+ }
312
+ }
313
+ let subagentErrors = 0;
314
+ const resolved = [];
315
+ for (const call of calls.values()) {
316
+ const result = results.get(call.id);
317
+ if (result?.isError === true && call.actor === "subagent")
318
+ subagentErrors++;
319
+ resolved.push({ ...call, end: Math.min(result?.ts ?? end, end) });
320
+ }
321
+ const claimed = [];
322
+ const byActor = new Map;
323
+ for (const actor of ACTOR_PRECEDENCE) {
324
+ const mine = resolved.filter((r) => r.actor === actor).map((r) => [r.ts, r.end]);
325
+ const free = subtract(mine, claimed);
326
+ for (const span of free)
327
+ claimed.push(span);
328
+ const ms = unionMs(free, start, end);
329
+ byActor.set(actor, ms);
330
+ if (actor === "operator") {
331
+ empty.longestOperatorWaitMs = Math.max(0, ...mine.map(([a, b]) => b - a));
332
+ }
333
+ }
334
+ const boundaries = [...new Set([start, end, ...eventTs, ...claimed.flat()])].sort((a, b) => a - b);
335
+ const claimedSorted = [...claimed].sort((a, b) => a[0] - b[0]);
336
+ let idleMs = 0;
337
+ for (let i = 0;i < boundaries.length - 1; i++) {
338
+ const a = boundaries[i];
339
+ const b = boundaries[i + 1];
340
+ if (b - a < IDLE_GAP_MS)
341
+ continue;
342
+ if (claimedSorted.some(([ca, cb]) => ca < b && cb > a))
343
+ continue;
344
+ idleMs += b - a;
345
+ }
346
+ const accounted = (byActor.get("operator") ?? 0) + (byActor.get("subagent") ?? 0) + (byActor.get("shell") ?? 0) + (byActor.get("other") ?? 0) + idleMs;
347
+ return {
348
+ ...empty,
349
+ operatorMs: byActor.get("operator") ?? 0,
350
+ subagentMs: byActor.get("subagent") ?? 0,
351
+ shellMs: byActor.get("shell") ?? 0,
352
+ otherToolMs: byActor.get("other") ?? 0,
353
+ idleMs,
354
+ modelMs: Math.max(0, window - accounted),
355
+ compactions,
356
+ subagentErrors
357
+ };
358
+ }
359
+ function attemptWindow(attempts) {
360
+ const starts = attempts.map((a) => Date.parse(a.startedAt ?? "")).filter((n) => !Number.isNaN(n));
361
+ const ends = attempts.map((a) => Date.parse(a.completedAt ?? "")).filter((n) => !Number.isNaN(n));
362
+ return starts.length && ends.length ? [Math.min(...starts), Math.max(...ends)] : undefined;
363
+ }
364
+ function summaryRow(stage, status, span, measured) {
365
+ return {
366
+ stage,
367
+ status,
368
+ workMs: span.workMs,
369
+ waitMs: span.waitMs,
370
+ toolCalls: measured ? span.toolCalls : null,
371
+ tokens: measured ? span.tokens : null,
372
+ time: formatDuration(span.workMs),
373
+ wait: formatDuration(span.waitMs),
374
+ token: formatTokenSplit(measured ? span.tokens : null)
375
+ };
376
+ }
377
+ function buildRunSummary(lines, inputs, since, until) {
378
+ const windows = [];
379
+ for (const { label, progress } of inputs) {
380
+ const states = (progress.states ?? []).flatMap((s) => {
381
+ const w = attemptWindow((s.actions ?? []).flatMap((a) => a.attempts ?? []));
382
+ return w ? [{ s, w }] : [];
383
+ });
384
+ if (!states.length)
385
+ throw new Error(`progress for run ${progress.runId} has no timed attempts`);
386
+ if (inputs.length === 1) {
387
+ for (const { s, w } of states) {
388
+ const stage = (s.visit ?? 1) > 1 ? `${s.state} (visit ${s.visit})` : s.state;
389
+ windows.push({ stage, status: s.status ?? "", start: w[0], end: w[1] });
390
+ }
391
+ } else {
392
+ const stage = label ?? `${progress.workflow ?? "run"} ${progress.runId.slice(0, 8)}`;
393
+ const start = Math.min(...states.map(({ w }) => w[0]));
394
+ windows.push({
395
+ stage,
396
+ status: progress.status ?? "",
397
+ start,
398
+ end: Math.max(...states.map(({ w }) => w[1]))
399
+ });
400
+ }
401
+ }
402
+ windows.sort((a, b) => a.start - b.start);
403
+ const { rows, skippedLines } = parseRows(lines);
404
+ const measure = (start, end) => {
405
+ const acc = newAcc(start);
406
+ for (const [row, ts] of rows)
407
+ if (ts >= start && ts <= end && promptText(row) === undefined)
408
+ accumulate(acc, row, ts);
409
+ const span = {
410
+ workMs: end - start - acc.askMs,
411
+ waitMs: acc.askMs,
412
+ toolCalls: acc.toolIds.size,
413
+ tokens: sumTokens([...acc.messages.values()])
414
+ };
415
+ return { span, measured: acc.toolIds.size > 0 || acc.messages.size > 0 };
416
+ };
417
+ const sinceMs = since ? Date.parse(since) : Number.NaN;
418
+ const totalStart = Number.isNaN(sinceMs) ? Math.min(...windows.map((w) => w.start)) : sinceMs;
419
+ let totalEnd = Math.max(...windows.map((w) => w.end));
420
+ if (!Number.isNaN(sinceMs)) {
421
+ for (const [, ts] of rows)
422
+ if (ts >= sinceMs)
423
+ totalEnd = Math.max(totalEnd, ts);
424
+ }
425
+ const untilMs = until ? Date.parse(until) : Number.NaN;
426
+ if (!Number.isNaN(untilMs) && untilMs > totalStart)
427
+ totalEnd = untilMs;
428
+ const stageRows = windows.map((w) => {
429
+ const { span, measured } = measure(w.start, w.end);
430
+ return summaryRow(w.stage, w.status, span, measured);
431
+ });
432
+ const totalMeasure = measure(totalStart, totalEnd);
433
+ const statuses = [...new Set(inputs.map((i) => i.progress.status ?? ""))].join("/");
434
+ const total = summaryRow("Total", statuses, totalMeasure.span, totalMeasure.measured);
435
+ const overlaps = windows.some((w, i) => i > 0 && w.start < (windows[i - 1]?.end ?? 0));
436
+ const sum = sumSpans(stageRows.map((r) => ({ ...r, toolCalls: r.toolCalls ?? 0, tokens: r.tokens ?? zeroTokens() })));
437
+ const overhead = {
438
+ workMs: total.workMs - sum.workMs,
439
+ waitMs: total.waitMs - sum.waitMs,
440
+ toolCalls: (total.toolCalls ?? 0) - sum.toolCalls,
441
+ tokens: Object.fromEntries(Object.keys(sum.tokens).map((k) => [k, (total.tokens?.[k] ?? 0) - sum.tokens[k]]))
442
+ };
443
+ if (!overlaps && overhead.workMs > 0)
444
+ stageRows.push(summaryRow("driver overhead", "", overhead, totalMeasure.measured));
445
+ return {
446
+ available: true,
447
+ rows: stageRows,
448
+ total,
449
+ actors: actorSplit(rows, totalStart, totalEnd),
450
+ gate: { ms: null, reason: "no matching check receipt" },
451
+ skippedLines
452
+ };
453
+ }
454
+ function renderSummaryMarkdown(summary) {
455
+ const line = (r, bold = false) => {
456
+ const stage = bold ? `**${r.stage}**` : r.stage;
457
+ return `| ${stage} | ${r.status} | ${r.time} | ${r.wait} | ${r.toolCalls ?? "n/a"} | ${r.token} |`;
458
+ };
459
+ const a = summary.actors;
460
+ const window = summary.total.workMs + summary.total.waitMs;
461
+ const share = (ms) => window > 0 ? `${Math.round(ms / window * 100)}%` : "n/a";
462
+ const gate = summary.gate.ms === null ? `n/a (${summary.gate.reason})` : formatDuration(summary.gate.ms);
463
+ return [
464
+ "| Stage | Status | Time | Wait | Tool calls | Token (total / non-cached) |",
465
+ "| --- | --- | ---: | ---: | ---: | ---: |",
466
+ ...summary.rows.map((r) => line(r)),
467
+ line(summary.total, true),
468
+ "",
469
+ "| Actor | Time | Share |",
470
+ "| --- | ---: | ---: |",
471
+ `| operator wait (longest ${formatDuration(a.longestOperatorWaitMs)}) | ${formatDuration(a.operatorMs)} | ${share(a.operatorMs)} |`,
472
+ `| model | ${formatDuration(a.modelMs)} | ${share(a.modelMs)} |`,
473
+ `| shell | ${formatDuration(a.shellMs)} | ${share(a.shellMs)} |`,
474
+ `| of which gate (informational) | ${gate} | — |`,
475
+ `| subagent | ${formatDuration(a.subagentMs)} | ${share(a.subagentMs)} |`,
476
+ `| other tools | ${formatDuration(a.otherToolMs)} | ${share(a.otherToolMs)} |`,
477
+ `| idle / unattributed | ${formatDuration(a.idleMs)} | ${share(a.idleMs)} |`,
478
+ "",
479
+ `Compactions: ${a.compactions} · Subagent errors: ${a.subagentErrors}`
480
+ ].join(`
481
+ `);
482
+ }
483
+ function writeCloseSummary(run, opts = {}) {
484
+ const cwd = opts.cwd ?? process.cwd();
485
+ const runDir = join2(cwd, ".spur", "run");
486
+ const rel = join2(".spur", "run", `${run.runId}-summary.md`);
487
+ const abs = join2(cwd, rel);
488
+ const na = (reason) => {
489
+ mkdirSync(runDir, { recursive: true });
490
+ writeFileSync(abs, `Execution summary: n/a (${reason})
491
+ `);
492
+ return rel;
493
+ };
494
+ try {
495
+ if (run.startedAt === undefined)
496
+ return na("run row has no started_at");
497
+ const resolved = resolveTranscript(opts.env ?? getEnvVars(), opts.projectsRoot);
498
+ if (!resolved.ok)
499
+ return na(resolved.reason);
500
+ const lines = readFileSync(resolved.path, "utf8").split(`
501
+ `);
502
+ const summary = buildRunSummary(lines, [{ progress: run.progress }], run.startedAt, run.completedAt);
503
+ if (summary.rows.length === 0)
504
+ return na("no timed attempts");
505
+ mkdirSync(runDir, { recursive: true });
506
+ writeFileSync(abs, `${renderSummaryMarkdown(summary)}
507
+ `);
508
+ writeFileSync(join2(runDir, `${run.runId}-summary.json`), JSON.stringify({ ...summary, runId: run.runId, startedAt: run.startedAt, completedAt: run.completedAt }, null, 2));
509
+ return rel;
510
+ } catch (error) {
511
+ return na(error instanceof Error ? error.message : String(error));
512
+ }
513
+ }
514
+ // plugins/sp/scripts/run-summary.ts
515
+ if (false)
516
+ ;
517
+
16
518
  // plugins/sp/scripts/inline-run-setup.ts
17
519
  function usage() {
18
520
  const text = [
19
521
  "Usage: bun plugins/sp/scripts/inline-run-setup.ts --run-id <id> --file <definition> [--spur-bin <path>]",
20
522
  " bun plugins/sp/scripts/inline-run-setup.ts --fingerprint --task-file <path> [--feature-file <path>] [--spur-bin <path>]",
21
- " bun plugins/sp/scripts/inline-run-setup.ts --action --run-id <id> --node <state> --kind <kind> --status <done|failed> --ok <true|false> --duration-ms <n> [--spur-bin <path>]",
22
- " bun plugins/sp/scripts/inline-run-setup.ts --actions-file <json-file> --run-id <id> [--spur-bin <path>] (1007 R5 batch trace emission)",
23
- " bun plugins/sp/scripts/inline-run-setup.ts --close --run-id <id> --status <done|failed|paused> [--reason <terminal-reason>] [--spur-bin <path>]",
523
+ " bun plugins/sp/scripts/inline-run-setup.ts --action --run-id <id> --node <state> --kind <kind> --status <done|failed> --ok <true|false> [--duration-ms <n>] [--estimated] [--project-root <path>] [--spur-bin <path>]",
524
+ " bun plugins/sp/scripts/inline-run-setup.ts --node-enter --run-id <id> --node <state> [--project-root <path>] [--spur-bin <path>] (1136 R4: stamps the node enter time)",
525
+ " bun plugins/sp/scripts/inline-run-setup.ts --actions-file <json-file> --run-id <id> [--project-root <path>] [--spur-bin <path>] (1007 R5 batch trace emission)",
526
+ " bun plugins/sp/scripts/inline-run-setup.ts --close --run-id <id> --status <done|failed|paused> [--reason <terminal-reason>] [--no-summary] [--project-root <path>] [--spur-bin <path>]",
527
+ " --project-root (1136 R1) names the tree that OWNS the run row; without it the run row is read from the cwd tree, and a miss exits 1 with RUN_NOT_FOUND",
528
+ " --duration-ms is optional on --action (1136 R4): omitted, the duration is measured from the calling --node-enter; supplied, it records provenance host-reported",
24
529
  " bun plugins/sp/scripts/inline-run-setup.ts --persist-out --from <worktree-path> [--task-file <path>]... [--spur-bin <path>]",
25
530
  " terminal-reason is a closed enum (0937 R2): done, paused-operator, failed-check, failed-agent, failed-timeout, failed-guard, cancelled, interrupted, retry-exhausted",
26
531
  " close defaults (1051 AC1): --status done \u2192 reason done, --status paused \u2192 reason paused-operator; --status failed requires an explicit --reason",
@@ -42,46 +547,20 @@ function resolveAppEntry(spurBin) {
42
547
  for (const candidate of candidates) {
43
548
  const tokens = candidate.split(/\s+/).filter(Boolean);
44
549
  const mainModule = [...tokens].reverse().find((t) => t.endsWith(".ts"));
45
- if (mainModule === undefined || !existsSync(mainModule))
550
+ if (mainModule === undefined || !existsSync3(mainModule))
46
551
  continue;
47
552
  const repoRoot = resolve(dirname(mainModule), "..", "..", "..");
48
- const appEntry = join(repoRoot, "packages", "app", "src", "index.ts");
49
- if (existsSync(appEntry))
553
+ const appEntry = join3(repoRoot, "packages", "app", "src", "index.ts");
554
+ if (existsSync3(appEntry))
50
555
  return { entry: appEntry, portable: false };
51
556
  }
52
557
  const entry = fileURLToPath(new URL("../lib/inline-run.generated.mjs", import.meta.url));
53
- if (!existsSync(entry)) {
558
+ if (!existsSync3(entry)) {
54
559
  throw new Error("inline application bundle is missing \u2014 rebuild/install the sp plugin before running inline");
55
560
  }
56
561
  return { entry, portable: true };
57
562
  }
58
- async function readInstalledInventory(file, spurBin) {
59
- const localCli = fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url));
60
- const bundle = fileURLToPath(new URL("../lib/inline-run.generated.mjs", import.meta.url));
61
- const { splitLaunchCommand } = await import(bundle);
62
- const launch = spurBin ? splitLaunchCommand(spurBin, 'inline-run-setup "spurBin"') : existsSync(localCli) ? { command: "bun", leadingArgs: [localCli] } : { command: "spur", leadingArgs: [] };
63
- if ("error" in launch)
64
- throw new Error(launch.error);
65
- const result = spawnSync(launch.command, [...launch.leadingArgs, "workflow", "show", file, "--format", "todo", "--json"], { cwd: process.cwd(), encoding: "utf8", timeout: 30000, maxBuffer: 4194304 });
66
- if (result.status !== 0) {
67
- throw new Error(`could not resolve the workflow definition with the installed CLI: ${result.error?.message ?? result.stderr}`);
68
- }
69
- const value = JSON.parse(result.stdout);
70
- if (value && typeof value === "object" && "ok" in value && "data" in value && value.ok === true)
71
- return value.data;
72
- return value;
73
- }
74
- var TERMINAL_REASONS = new Set([
75
- "done",
76
- "paused-operator",
77
- "failed-check",
78
- "failed-agent",
79
- "failed-timeout",
80
- "failed-guard",
81
- "cancelled",
82
- "interrupted",
83
- "retry-exhausted"
84
- ]);
563
+ var SETUP_EXPORTS = ["readInstalledInventory", "runInlineRunSetup", "writeInlineRunOutcome"];
85
564
  async function main() {
86
565
  if (!process.versions.bun) {
87
566
  const child = spawnSync("bun", [fileURLToPath(import.meta.url), ...process.argv.slice(2)], {
@@ -92,7 +571,7 @@ async function main() {
92
571
  process.exit(child.status ?? 1);
93
572
  }
94
573
  const flags = new Map;
95
- let fingerprint = false, action = false, close = false, decide = false, persistOut = false;
574
+ let fingerprint = false, action = false, close = false, decide = false, persistOut = false, nodeEnter = false, estimated = false, noSummary = false;
96
575
  const taskFiles = [];
97
576
  let spurBin = getEnvVar("SPUR_BIN") ?? "";
98
577
  const argv = process.argv.slice(2);
@@ -106,8 +585,14 @@ async function main() {
106
585
  close = true;
107
586
  else if (flag === "--decide")
108
587
  decide = true;
588
+ else if (flag === "--node-enter")
589
+ nodeEnter = true;
109
590
  else if (flag === "--persist-out")
110
591
  persistOut = true;
592
+ else if (flag === "--estimated")
593
+ estimated = true;
594
+ else if (flag === "--no-summary")
595
+ noSummary = true;
111
596
  else if (flag === "--task-file")
112
597
  taskFiles.push(argv[++i] ?? "");
113
598
  else if (flag === "--spur-bin")
@@ -122,29 +607,22 @@ async function main() {
122
607
  const optionsJson = flags.get("--options-json") ?? "";
123
608
  const node = flags.get("--node") ?? "";
124
609
  const kind = flags.get("--kind") ?? "";
610
+ const projectRoot = flags.get("--project-root") ?? "";
125
611
  const status = flags.get("--status") ?? "";
126
612
  const reason = flags.get("--reason") ?? "";
127
613
  const okRaw = flags.get("--ok") ?? "";
128
614
  const durationRaw = flags.get("--duration-ms") ?? "";
129
615
  const actionsFile = flags.get("--actions-file") ?? "";
616
+ if (estimated && !action)
617
+ usage();
618
+ if (noSummary && !close)
619
+ usage();
130
620
  if (fingerprint) {
131
621
  if (runId !== "" || file !== "" || taskFiles.length !== 1 || (taskFiles[0] ?? "").trim() === "")
132
622
  usage();
133
- const app2 = await import(resolveAppEntry(spurBin).entry);
623
+ const { app: app2 } = await loadInlineApp(spurBin, resolveAppEntry, ["runInlineRunFingerprint"]);
134
624
  process.exit(await app2.runInlineRunFingerprint({ taskFile: taskFiles[0] ?? "", featureFile }));
135
625
  }
136
- if (actionsFile !== "") {
137
- if (action || close || fingerprint || decide || persistOut || file !== "" || taskFiles.length > 0)
138
- usage();
139
- if (runId.trim() === "" || status !== "" || node !== "" || kind !== "")
140
- usage();
141
- if (okRaw !== "" || durationRaw !== "")
142
- usage();
143
- if (!SAFE_RUN_ID_RE.test(runId))
144
- refuseUnsafeRunId(runId);
145
- const app2 = await import(resolveAppEntry(spurBin).entry);
146
- process.exit(await app2.runInlineRunTraceBatch({ runId, actionsFile }));
147
- }
148
626
  if (decide) {
149
627
  if (action || close || fingerprint || file !== "" || taskFiles.length > 0)
150
628
  usage();
@@ -153,12 +631,14 @@ async function main() {
153
631
  if (!SAFE_RUN_ID_RE.test(runId))
154
632
  refuseUnsafeRunId(runId);
155
633
  try {
156
- const { entry: entry2, portable: portable2 } = resolveAppEntry(spurBin);
157
- const app2 = await import(entry2);
158
- const bundlePath = fileURLToPath(new URL("../lib/inline-run.generated.mjs", import.meta.url));
159
- const lib = await import(bundlePath);
160
- const enabled = await lib.resolveDecideDecisionMakerEnabled(process.cwd(), portable2 ? { embeddedSchemas: lib.EMBEDDED_SPUR_SCHEMAS } : undefined);
161
- process.exit(await app2.runInlineRunDecide({ runId, node, optionsFile: optionsJson, enabled }));
634
+ const { app: app2, portable: portable2 } = await loadInlineApp(spurBin, resolveAppEntry, ["runInlineRunDecide"]);
635
+ const { app: lib } = await loadInlineApp(spurBin, () => ({ entry: fileURLToPath(new URL("../lib/inline-run.generated.mjs", import.meta.url)), portable: portable2 }), ["resolveDecideDecisionMakerEnabled", "loadSpurConfig"]);
636
+ const loadOpts = portable2 ? { embeddedSchemas: lib.EMBEDDED_SPUR_SCHEMAS } : undefined;
637
+ const [enabled, spurConfig] = await Promise.all([
638
+ lib.resolveDecideDecisionMakerEnabled(process.cwd(), loadOpts),
639
+ lib.loadSpurConfig(process.cwd(), loadOpts)
640
+ ]);
641
+ process.exit(await app2.runInlineRunDecide({ runId, node, optionsFile: optionsJson, enabled, spurConfig }));
162
642
  } catch (error) {
163
643
  const message = error instanceof Error ? error.message : String(error);
164
644
  process.stdout.write(`${JSON.stringify({ ok: false, runId, error: message })}
@@ -171,53 +651,45 @@ async function main() {
171
651
  usage();
172
652
  if (from.trim() === "" || taskFiles.some((taskFile) => taskFile.trim() === ""))
173
653
  usage();
174
- const app2 = await import(resolveAppEntry(spurBin).entry);
654
+ const { app: app2 } = await loadInlineApp(spurBin, resolveAppEntry, ["runInlineRunPersistOut"]);
175
655
  process.exit(await app2.runInlineRunPersistOut({ from, taskFiles }));
176
656
  }
177
- if (action || close) {
178
- if (action && close)
179
- usage();
180
- if (runId.trim() === "" || status.trim() === "")
181
- usage();
657
+ if (nodeEnter || actionsFile !== "" || action || close) {
182
658
  if (!SAFE_RUN_ID_RE.test(runId))
183
659
  refuseUnsafeRunId(runId);
184
- const app2 = await import(resolveAppEntry(spurBin).entry);
185
- if (close) {
186
- if (!app2.isInlineRunCloseStatus(status))
187
- usage();
188
- if (reason.trim() === "" ? status === "failed" : !TERMINAL_REASONS.has(reason))
189
- usage();
190
- process.exit(await app2.runInlineRunTrace({
191
- runId,
192
- close: true,
193
- node: "",
194
- kind: "",
195
- status,
196
- ok: true,
197
- durationMs: 0,
198
- ...reason.trim() === "" ? {} : { reason }
199
- }));
200
- }
201
- if (node.trim() === "" || kind.trim() === "")
202
- usage();
203
- if (!app2.isInlineRunActionStatus(status))
204
- usage();
205
- if (okRaw !== "true" && okRaw !== "false")
206
- usage();
207
- const durationMs = Number(durationRaw);
208
- if (durationRaw.trim() === "" || !Number.isFinite(durationMs) || durationMs < 0)
660
+ const mode = nodeEnter ? "node-enter" : actionsFile !== "" ? "actions-file" : close ? "close" : "action";
661
+ const { app: app2 } = await loadInlineApp(spurBin, resolveAppEntry, ["runInlineRunTraceMode"]);
662
+ const code = await app2.runInlineRunTraceMode({
663
+ mode,
664
+ runId,
665
+ node,
666
+ kind,
667
+ status,
668
+ reason,
669
+ ok: okRaw,
670
+ durationMs: durationRaw,
671
+ actionsFile,
672
+ projectRoot,
673
+ estimated,
674
+ noSummary,
675
+ ...noSummary ? {} : { summarize: async (run) => writeCloseSummary(run) }
676
+ });
677
+ if (code === 2)
209
678
  usage();
210
- process.exit(await app2.runInlineRunTrace({ runId, close: false, node, kind, status, ok: okRaw === "true", durationMs }));
679
+ process.exit(code);
211
680
  }
212
681
  if (runId.trim() === "" || file.trim() === "")
213
682
  usage();
214
683
  if (!SAFE_RUN_ID_RE.test(runId))
215
684
  refuseUnsafeRunId(runId);
216
- const { entry, portable } = resolveAppEntry(spurBin);
217
- const app = await import(entry);
685
+ const { app, portable } = await loadInlineApp(spurBin, resolveAppEntry, SETUP_EXPORTS);
218
686
  let inventory;
219
687
  try {
220
- inventory = await readInstalledInventory(file, spurBin);
688
+ inventory = await app.readInstalledInventory({
689
+ file,
690
+ spurBin,
691
+ localCli: fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url))
692
+ });
221
693
  } catch (error) {
222
694
  const message = `could not resolve the workflow definition: ${error instanceof Error ? error.message : String(error)}`;
223
695
  app.writeInlineRunOutcome(runId, { ok: false, runId, error: message });