@gobing-ai/spur 0.4.2 → 0.4.4

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 (168) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/decisions/maker-liveness.yaml +39 -0
  3. package/config/plugin-scripts.json +0 -4
  4. package/config/rules/README.md +1 -0
  5. package/config/rules/docs/sourced-api-claims.yaml +56 -12
  6. package/config/rules/docs/verdict-confidence.yaml +68 -0
  7. package/config/rules/quality/coverage-gate.yaml +13 -0
  8. package/config/rules/recommended-pre-check.yaml +6 -2
  9. package/config/rules/strict/runtime-boundaries.yaml +10 -0
  10. package/config/rules/surface/check-cli-surface.yaml +1 -3
  11. package/config/script-placement-baseline.json +1 -19
  12. package/config/templates/AGENTS.md +7 -0
  13. package/config/workflows/feature-lifecycle.yaml +46 -1
  14. package/config/workflows/feature-verification.yaml +20 -1
  15. package/config/workflows/task-lifecycle.yaml +50 -0
  16. package/config/workflows/task-pipeline.yaml +15 -9
  17. package/package.json +10 -10
  18. package/plugins/sp/README.md +22 -16
  19. package/plugins/sp/agents/super-reviewer.md +46 -0
  20. package/plugins/sp/bunfig.toml +8 -5
  21. package/plugins/sp/commands/dev-changelog.md +1 -1
  22. package/plugins/sp/commands/dev-gitmsg.md +6 -2
  23. package/plugins/sp/commands/dev-handover.md +1 -0
  24. package/plugins/sp/commands/dev-job-dump.md +2 -1
  25. package/plugins/sp/commands/dev-refine.md +14 -2
  26. package/plugins/sp/commands/dev-refineall.md +7 -1
  27. package/plugins/sp/commands/dev-review.md +1 -0
  28. package/plugins/sp/hooks/careful-guard.ts +1 -1
  29. package/plugins/sp/hooks/context-post-tool.ts +7 -427
  30. package/plugins/sp/hooks/context-session-start.ts +5 -213
  31. package/plugins/sp/hooks/context-session-stop.ts +5 -191
  32. package/plugins/sp/hooks/pi/guard-extension.ts +6 -5
  33. package/plugins/sp/hooks/task-write-guard.ts +1 -1
  34. package/plugins/sp/{hooks → lib}/agent-hint.ts +1 -1
  35. package/plugins/sp/lib/context-post-tool.ts +432 -0
  36. package/plugins/sp/lib/context-session-start.ts +214 -0
  37. package/plugins/sp/lib/context-session-stop.ts +196 -0
  38. package/plugins/sp/lib/dogfood-pipeline.ts +265 -0
  39. package/plugins/sp/lib/dogfood-report.ts +278 -0
  40. package/plugins/sp/lib/history-anatomy.generated.mjs +18 -18
  41. package/plugins/sp/lib/idea-handoff.generated.mjs +91 -91
  42. package/plugins/sp/lib/inline-run-app.ts +40 -0
  43. package/plugins/sp/lib/inline-run.generated.d.mts +3 -3
  44. package/plugins/sp/lib/inline-run.generated.mjs +1425 -212
  45. package/plugins/sp/lib/plan-projection.generated.d.mts +150 -0
  46. package/plugins/sp/lib/plan-projection.generated.mjs +623 -9
  47. package/plugins/sp/lib/quality-gate.generated.d.mts +1 -0
  48. package/plugins/sp/lib/quality-gate.generated.mjs +13809 -13969
  49. package/plugins/sp/lib/residual-scan.generated.d.mts +20 -1
  50. package/plugins/sp/lib/residual-scan.generated.mjs +120 -25
  51. package/plugins/sp/lib/run-summary-core.ts +34 -370
  52. package/plugins/sp/lib/step-profile.generated.mjs +8 -8
  53. package/plugins/sp/lib/transcript.ts +37 -228
  54. package/plugins/sp/plugin.json +1 -1
  55. package/plugins/sp/scripts/batch-plan.mjs +619 -11
  56. package/plugins/sp/scripts/batch-preflight.mjs +57 -13
  57. package/plugins/sp/scripts/batch-preflight.ts +35 -295
  58. package/plugins/sp/scripts/commit-guard.mjs +6 -6
  59. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +127 -108
  60. package/plugins/sp/scripts/daily-summary/daily-summary.ts +111 -104
  61. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +13 -12
  62. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +7 -265
  63. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +7 -4
  64. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +6 -272
  65. package/plugins/sp/scripts/feature-verification-steps.mjs +20 -125
  66. package/plugins/sp/scripts/feature-verification-steps.ts +57 -181
  67. package/plugins/sp/scripts/history-anatomy-cache.mjs +19 -19
  68. package/plugins/sp/scripts/idea-handoff.mjs +2 -2
  69. package/plugins/sp/scripts/inline-run-dispatch.mjs +25 -24
  70. package/plugins/sp/scripts/inline-run-dispatch.ts +1 -21
  71. package/plugins/sp/scripts/inline-run-setup.mjs +164 -137
  72. package/plugins/sp/scripts/inline-run-setup.ts +1 -21
  73. package/plugins/sp/scripts/persist-out-check.mjs +5 -5
  74. package/plugins/sp/scripts/pr-reviewing.mjs +13 -13
  75. package/plugins/sp/scripts/quality-gate.mjs +13730 -13887
  76. package/plugins/sp/scripts/residual-scan.mjs +129 -31
  77. package/plugins/sp/scripts/residual-scan.ts +16 -13
  78. package/plugins/sp/scripts/run-summary.mjs +166 -112
  79. package/plugins/sp/scripts/run-summary.ts +7 -5
  80. package/plugins/sp/scripts/script-root.mjs +6 -6
  81. package/plugins/sp/scripts/session-timeline.mjs +62 -35
  82. package/plugins/sp/scripts/task-diffstat.mjs +19 -9
  83. package/plugins/sp/scripts/task-diffstat.ts +8 -39
  84. package/plugins/sp/scripts/workflow-step-profile.mjs +12 -12
  85. package/plugins/sp/scripts/wrapup-drift-probe.mjs +9 -9
  86. package/plugins/sp/scripts/wrapup-steps.mjs +25 -25
  87. package/plugins/sp/skills/code-implementation/SKILL.md +7 -1
  88. package/plugins/sp/skills/code-verification/references/secu-review.md +2 -1
  89. package/plugins/sp/skills/code-verification/references/verdict-schema.md +4 -0
  90. package/plugins/sp/skills/dogfood-testing/SKILL.md +2 -2
  91. package/plugins/sp/skills/next-feature/SKILL.md +1 -1
  92. package/plugins/sp/skills/next-router/SKILL.md +3 -1
  93. package/plugins/sp/skills/next-router/references/routing-table.md +9 -3
  94. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
  95. package/plugins/sp/skills/source-driven-development/SKILL.md +7 -4
  96. package/plugins/sp/skills/spur-cli/references/builder.md +3 -2
  97. package/plugins/sp/skills/spur-cli/references/decision.md +32 -7
  98. package/plugins/sp/skills/spur-cli/references/features.md +46 -5
  99. package/plugins/sp/skills/spur-cli/references/tasks.md +24 -4
  100. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +8 -1
  101. package/plugins/sp/skills/spur-dev/references/dev-operations.md +37 -11
  102. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +1 -1
  103. package/plugins/sp/skills/spur-dev/references/execution-batch.md +19 -6
  104. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +22 -0
  105. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +14 -0
  106. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +4 -2
  107. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +49 -12
  108. package/schemas/section-matrix.schema.json +3 -2
  109. package/spur.js +66591 -73262
  110. package/web/_astro/{BoardApp.B3GQZvj8.js → BoardApp.Niubpe6z.js} +1 -1
  111. package/web/_astro/{BoardApp.BT7L2kSb.js → BoardApp.hF0RAIrg.js} +150 -150
  112. package/web/_astro/TaskDetail.DJpapt38.js +1 -0
  113. package/web/_astro/{arc.vaT7tPOB.js → arc.BgKcK0i8.js} +1 -1
  114. package/web/_astro/{architectureDiagram-3BPJPVTR.DRMmTjkN.js → architectureDiagram-3BPJPVTR.ChP-Ig0V.js} +1 -1
  115. package/web/_astro/{blockDiagram-GPEHLZMM.cla0LO_q.js → blockDiagram-GPEHLZMM.BVaCBoeN.js} +1 -1
  116. package/web/_astro/{c4Diagram-AAUBKEIU.D4YAIBKn.js → c4Diagram-AAUBKEIU.CHSDpEO5.js} +1 -1
  117. package/web/_astro/channel.DkpvN2tD.js +1 -0
  118. package/web/_astro/{chunk-2J33WTMH.BrSJbMRZ.js → chunk-2J33WTMH.MYRcYVRa.js} +1 -1
  119. package/web/_astro/{chunk-4BX2VUAB.CuLMgKF5.js → chunk-4BX2VUAB.S5j22Zqa.js} +1 -1
  120. package/web/_astro/{chunk-55IACEB6.zkvgu54F.js → chunk-55IACEB6.gciRPvmZ.js} +1 -1
  121. package/web/_astro/{chunk-727SXJPM.DxWkACh8.js → chunk-727SXJPM.CdqrSQkX.js} +1 -1
  122. package/web/_astro/{chunk-AQP2D5EJ.42XrmIlH.js → chunk-AQP2D5EJ.CyokdHT3.js} +1 -1
  123. package/web/_astro/{chunk-FMBD7UC4.Bao2wMK9.js → chunk-FMBD7UC4.Bg9KJhjW.js} +1 -1
  124. package/web/_astro/{chunk-ND2GUHAM.BhydojD1.js → chunk-ND2GUHAM.1gxxI-vD.js} +1 -1
  125. package/web/_astro/{chunk-QZHKN3VN.CmZ82g-H.js → chunk-QZHKN3VN.DDEYtCKU.js} +1 -1
  126. package/web/_astro/{classDiagram-4FO5ZUOK.Blr83Gt_.js → classDiagram-4FO5ZUOK.Ch3zi37T.js} +1 -1
  127. package/web/_astro/{classDiagram-v2-Q7XG4LA2.Blr83Gt_.js → classDiagram-v2-Q7XG4LA2.Ch3zi37T.js} +1 -1
  128. package/web/_astro/{cose-bilkent-S5V4N54A.qmfv0SPn.js → cose-bilkent-S5V4N54A.D2q5AINI.js} +1 -1
  129. package/web/_astro/{cynefin-OW5HDTMX.BtELhXPR.js → cynefin-OW5HDTMX.BA9kC2YT.js} +1 -1
  130. package/web/_astro/{dagre-BM42HDAG.AWmaAvXo.js → dagre-BM42HDAG.CHO08nRp.js} +1 -1
  131. package/web/_astro/{diagram-2AECGRRQ.DdH34KDi.js → diagram-2AECGRRQ.BfFQt0LW.js} +1 -1
  132. package/web/_astro/{diagram-5GNKFQAL.BmNPix1k.js → diagram-5GNKFQAL.CHMwMTe4.js} +1 -1
  133. package/web/_astro/{diagram-KO2AKTUF.BNQd_Ppo.js → diagram-KO2AKTUF.BJ-7gK-w.js} +1 -1
  134. package/web/_astro/{diagram-LMA3HP47.BAPgDi0e.js → diagram-LMA3HP47.C5xqcc0t.js} +1 -1
  135. package/web/_astro/{diagram-OG6HWLK6.DZ4zyLdC.js → diagram-OG6HWLK6.C2pQjfrC.js} +1 -1
  136. package/web/_astro/{erDiagram-TEJ5UH35.Q1TySMmw.js → erDiagram-TEJ5UH35._FoNyxgC.js} +1 -1
  137. package/web/_astro/{flowDiagram-I6XJVG4X.BXhFBicG.js → flowDiagram-I6XJVG4X.B_ENkT2I.js} +1 -1
  138. package/web/_astro/{ganttDiagram-6RSMTGT7.vHSgcuo2.js → ganttDiagram-6RSMTGT7.B3q8h77C.js} +1 -1
  139. package/web/_astro/{gitGraphDiagram-PVQCEYII.Bi1f73C7.js → gitGraphDiagram-PVQCEYII.QWZFDboZ.js} +1 -1
  140. package/web/_astro/index.BFyW13JJ.css +1 -0
  141. package/web/_astro/{infoDiagram-5YYISTIA.DPvXZEWm.js → infoDiagram-5YYISTIA.Cs6eEc92.js} +1 -1
  142. package/web/_astro/{ishikawaDiagram-YF4QCWOH.Cz829fsV.js → ishikawaDiagram-YF4QCWOH.BU9DJMHA.js} +1 -1
  143. package/web/_astro/{journeyDiagram-JHISSGLW.enRnB9Me.js → journeyDiagram-JHISSGLW.D35k6Jpe.js} +1 -1
  144. package/web/_astro/{kanban-definition-UN3LZRKU.DnTNk6d2.js → kanban-definition-UN3LZRKU.DBqcAT4m.js} +1 -1
  145. package/web/_astro/{linear.BzGnBwNL.js → linear.D-LSNK8W.js} +1 -1
  146. package/web/_astro/{mermaid.core.CSbTpIVz.js → mermaid.core.CSruWVOa.js} +4 -4
  147. package/web/_astro/{mindmap-definition-RKZ34NQL.DGJDy8zT.js → mindmap-definition-RKZ34NQL.BiFaUsfj.js} +1 -1
  148. package/web/_astro/{pieDiagram-4H26LBE5.HSPdi5_8.js → pieDiagram-4H26LBE5.D1KTKqb9.js} +1 -1
  149. package/web/_astro/{quadrantDiagram-W4KKPZXB.6Gv8mz2Y.js → quadrantDiagram-W4KKPZXB.DBHflsMG.js} +1 -1
  150. package/web/_astro/{requirementDiagram-4Y6WPE33.CnBCtFwa.js → requirementDiagram-4Y6WPE33.hwX9CNdk.js} +1 -1
  151. package/web/_astro/{sankeyDiagram-5OEKKPKP.5M8VWAu8.js → sankeyDiagram-5OEKKPKP.DkI8mJiE.js} +1 -1
  152. package/web/_astro/{sequenceDiagram-3UESZ5HK.DN41XN-U.js → sequenceDiagram-3UESZ5HK.DMz7jBDx.js} +1 -1
  153. package/web/_astro/{stateDiagram-AJRCARHV.ErOO3W8j.js → stateDiagram-AJRCARHV.B6Ef2xJ1.js} +1 -1
  154. package/web/_astro/{stateDiagram-v2-BHNVJYJU.B_ZHICM-.js → stateDiagram-v2-BHNVJYJU.CgoaufB9.js} +1 -1
  155. package/web/_astro/{timeline-definition-PNZ67QCA.CWdosr07.js → timeline-definition-PNZ67QCA.B3c8h1t-.js} +1 -1
  156. package/web/_astro/{vennDiagram-CIIHVFJN.BHzYjNV5.js → vennDiagram-CIIHVFJN.BdHPAxAb.js} +1 -1
  157. package/web/_astro/{wardleyDiagram-YWT4CUSO.h8x4IwPX.js → wardleyDiagram-YWT4CUSO.BrJXPPAj.js} +1 -1
  158. package/web/_astro/{xychartDiagram-2RQKCTM6.RTapW2X8.js → xychartDiagram-2RQKCTM6.CMH5yFsV.js} +1 -1
  159. package/web/index.html +2 -2
  160. package/web/spur_logo.svg +1 -1
  161. package/config/proportional-route-table.ts +0 -155
  162. package/plugins/sp/scripts/daily-summary/logger.ts +0 -28
  163. package/web/_astro/TaskDetail.Cxov1_N_.js +0 -1
  164. package/web/_astro/channel.D4nG9YLf.js +0 -1
  165. package/web/_astro/index.BhSwN8yY.css +0 -1
  166. /package/plugins/sp/{hooks → lib}/context-owner.ts +0 -0
  167. /package/plugins/sp/{hooks → lib}/destructive-policy.ts +0 -0
  168. /package/plugins/sp/{hooks → lib}/task-file-policy.ts +0 -0
@@ -5,13 +5,11 @@
5
5
  * `loadInlineApp` seam the setup facade uses. It is a SEPARATE script from `inline-run-setup.ts`
6
6
  * because it is a separate operation and the ADR-130 glue budget is per script. */
7
7
  import { spawnSync } from 'node:child_process';
8
- import { existsSync } from 'node:fs';
9
- import { dirname, join, resolve } from 'node:path';
10
8
  import { fileURLToPath } from 'node:url';
11
9
  // Type-only namespace import (erased before bundling; the smoke scanner exempts `import type`).
12
10
  import type * as spurApp from '@gobing-ai/spur-app';
13
11
  import { getEnvVar } from '../lib/env';
14
- import { loadInlineApp } from '../lib/inline-run-app';
12
+ import { loadInlineApp, resolveAppEntry } from '../lib/inline-run-app';
15
13
 
16
14
  // EMBEDDED_SPUR_SCHEMAS is declared by the bundle twin, never exported by the barrel.
17
15
  type InlineApp = typeof spurApp & { EMBEDDED_SPUR_SCHEMAS?: ReadonlyMap<string, string> };
@@ -32,24 +30,6 @@ function usage(): never {
32
30
  const SAFE_RUN_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
33
31
 
34
32
  /** Resolve the app entry: --spur-bin/SPUR_BIN main module if it proves a repo checkout, else the bundle. */
35
- function resolveAppEntry(spurBin: string): { entry: string; portable: boolean } {
36
- const fallback = fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url));
37
- const candidates = spurBin !== '' ? [spurBin] : [fallback];
38
- for (const candidate of candidates) {
39
- const tokens = candidate.split(/\s+/).filter(Boolean);
40
- // Main module = last path-like token; only a .ts source entry proves a repo checkout.
41
- const mainModule = [...tokens].reverse().find((t) => t.endsWith('.ts'));
42
- if (mainModule === undefined || !existsSync(mainModule)) continue;
43
- const repoRoot = resolve(dirname(mainModule), '..', '..', '..');
44
- const appEntry = join(repoRoot, 'packages', 'app', 'src', 'index.ts');
45
- if (existsSync(appEntry)) return { entry: appEntry, portable: false };
46
- }
47
- const entry = fileURLToPath(new URL('../lib/inline-run.generated.mjs', import.meta.url));
48
- if (!existsSync(entry)) {
49
- throw new Error('inline application bundle is missing — rebuild/install the sp plugin before running inline');
50
- }
51
- return { entry, portable: true };
52
- }
53
33
 
54
34
  async function main(): Promise<void> {
55
35
  // The portable Node twin has no workspace imports; the app bundle still uses Spur's Bun runtime.
@@ -3,9 +3,7 @@
3
3
 
4
4
  // plugins/sp/scripts/inline-run-setup.ts
5
5
  import { spawnSync } from "child_process";
6
- import { existsSync as existsSync3 } from "fs";
7
- import { dirname, join as join3, resolve } from "path";
8
- import { fileURLToPath } from "url";
6
+ import { fileURLToPath as fileURLToPath2 } from "url";
9
7
 
10
8
  // plugins/sp/lib/env.ts
11
9
  function getEnvVar(name, fallback) {
@@ -14,6 +12,28 @@ function getEnvVar(name, fallback) {
14
12
  }
15
13
 
16
14
  // plugins/sp/lib/inline-run-app.ts
15
+ import { existsSync } from "node:fs";
16
+ import { dirname, join, resolve } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ function resolveAppEntry(spurBin) {
19
+ const fallback = fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url));
20
+ const candidates = spurBin !== "" ? [spurBin] : [fallback];
21
+ for (const candidate of candidates) {
22
+ const tokens = candidate.split(/\s+/).filter(Boolean);
23
+ const mainModule = [...tokens].reverse().find((t) => t.endsWith(".ts"));
24
+ if (mainModule === undefined || !existsSync(mainModule))
25
+ continue;
26
+ const repoRoot = resolve(dirname(mainModule), "..", "..", "..");
27
+ const appEntry = join(repoRoot, "packages", "app", "src", "index.ts");
28
+ if (existsSync(appEntry))
29
+ return { entry: appEntry, portable: false };
30
+ }
31
+ const entry = fileURLToPath(new URL("../lib/inline-run.generated.mjs", import.meta.url));
32
+ if (!existsSync(entry)) {
33
+ throw new Error("inline application bundle is missing — rebuild/install the sp plugin before running inline");
34
+ }
35
+ return { entry, portable: true };
36
+ }
17
37
  async function loadInlineApp(spurBin, resolveAppEntry, required) {
18
38
  const { entry, portable } = resolveAppEntry(spurBin);
19
39
  const app = await import(entry);
@@ -30,16 +50,14 @@ function getEnvVars() {
30
50
  }
31
51
 
32
52
  // 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";
53
+ import { existsSync as existsSync3, mkdirSync, readFileSync, writeFileSync } from "node:fs";
54
+ import { join as join3 } from "node:path";
35
55
 
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"]);
56
+ // plugins/sp/lib/plan-projection.generated.mjs
57
+ var NON_ABORTING_PREFLIGHT_CODES = new Set([
58
+ "L4.scenario-unverified",
59
+ "L4.verifying-incomplete-tasks"
60
+ ]);
43
61
  var zeroTokens = () => ({ input: 0, cacheCreate: 0, cacheRead: 0, output: 0 });
44
62
  function formatDuration(ms) {
45
63
  const s = Math.round(ms / 1000);
@@ -59,6 +77,26 @@ function formatTokenSplit(t) {
59
77
  const nonCached = t.input + t.cacheCreate + t.output;
60
78
  return `${formatTokens(nonCached + t.cacheRead)} / ${formatTokens(nonCached)}`;
61
79
  }
80
+ var OPERATOR_TOOLS = new Set(["AskUserQuestion", "ask_user_question"]);
81
+ function isCompaction(row) {
82
+ return row.type === "compaction" || row.type === "system" && row.subtype === "compact_boundary";
83
+ }
84
+ function firstTextContent(row) {
85
+ const content = row.message?.content;
86
+ if (typeof content === "string")
87
+ return content;
88
+ if (!Array.isArray(content))
89
+ return;
90
+ return content.find((b) => b.type === "text")?.text;
91
+ }
92
+ function skillInvocationPrompt(text) {
93
+ const name = /^\s*<skill name="([^"]+)"/.exec(text)?.[1];
94
+ if (name === undefined)
95
+ return;
96
+ const close = text.lastIndexOf("</skill>");
97
+ const args = close < 0 ? "" : text.slice(close + "</skill>".length).trim();
98
+ return args === "" ? `/${name}` : `/${name} ${args}`;
99
+ }
62
100
  function promptText(row) {
63
101
  if (row.type === "custom_message") {
64
102
  if (row.display === false)
@@ -83,22 +121,6 @@ function promptText(row) {
83
121
  return;
84
122
  return content.find((b) => b.type === "text")?.text;
85
123
  }
86
- function firstTextContent(row) {
87
- const content = row.message?.content;
88
- if (typeof content === "string")
89
- return content;
90
- if (!Array.isArray(content))
91
- return;
92
- return content.find((b) => b.type === "text")?.text;
93
- }
94
- function skillInvocationPrompt(text) {
95
- const name = /^\s*<skill name="([^"]+)"/.exec(text)?.[1];
96
- if (name === undefined)
97
- return;
98
- const close = text.lastIndexOf("</skill>");
99
- const args = close < 0 ? "" : text.slice(close + "</skill>".length).trim();
100
- return args === "" ? `/${name}` : `/${name} ${args}`;
101
- }
102
124
  function sumTokens(all) {
103
125
  const total = zeroTokens();
104
126
  for (const t of all)
@@ -127,21 +149,21 @@ function accumulate(acc, row, ts) {
127
149
  if (row.type === "message") {
128
150
  const m = row.message;
129
151
  if (m?.role === "assistant") {
130
- const blocks2 = Array.isArray(m.content) ? m.content : [];
131
- for (const b of blocks2) {
152
+ const blocks = Array.isArray(m.content) ? m.content : [];
153
+ for (const b of blocks) {
132
154
  if (b.type === "toolCall" && b.id) {
133
155
  acc.toolIds.add(b.id);
134
156
  if (b.name && OPERATOR_TOOLS.has(b.name))
135
157
  acc.asks.set(b.id, ts);
136
158
  }
137
159
  }
138
- const u2 = m.usage;
139
- if (u2 && row.id)
160
+ const u = m.usage;
161
+ if (u && row.id)
140
162
  acc.messages.set(row.id, {
141
- input: u2.input ?? 0,
142
- cacheCreate: u2.cacheWrite ?? 0,
143
- cacheRead: u2.cacheRead ?? 0,
144
- output: u2.output ?? 0
163
+ input: u.input ?? 0,
164
+ cacheCreate: u.cacheWrite ?? 0,
165
+ cacheRead: u.cacheRead ?? 0,
166
+ output: u.output ?? 0
145
167
  });
146
168
  } else if (m?.role === "toolResult" && m.toolCallId && acc.asks.has(m.toolCallId)) {
147
169
  acc.askMs += ts - (acc.asks.get(m.toolCallId) ?? ts);
@@ -170,72 +192,14 @@ function accumulate(acc, row, ts) {
170
192
  });
171
193
  }
172
194
  }
173
- function isCompaction(row) {
174
- return row.type === "compaction" || row.type === "system" && row.subtype === "compact_boundary";
175
- }
176
- function sniffFormat(rows) {
177
- for (const row of rows) {
178
- if (row.type === "message" && PI_ROLES.has(row.message?.role ?? ""))
179
- return "pi";
180
- if (row.type === "user" || row.type === "assistant")
181
- return "claude";
182
- }
183
- return "unknown";
184
- }
185
- function parseRows(lines) {
186
- const rows = [];
187
- let skippedLines = 0;
188
- for (const line of lines) {
189
- if (!line.trim())
190
- continue;
191
- let row;
192
- try {
193
- row = JSON.parse(line);
194
- } catch {
195
- skippedLines++;
196
- continue;
197
- }
198
- const ts = row.timestamp ? Date.parse(row.timestamp) : Number.NaN;
199
- if (!Number.isNaN(ts))
200
- rows.push([row, ts]);
201
- }
202
- return { rows, skippedLines, format: sniffFormat(rows.map(([r]) => r)) };
203
- }
204
- function resolveTranscript(env, projectsRoot = join(homedir(), ".claude", "projects"), override) {
205
- if (override)
206
- return existsSync(override) ? { ok: true, path: override } : { ok: false, reason: "no transcript" };
207
- const id = env.CLAUDE_CODE_SESSION_ID;
208
- if (!id) {
209
- const piFile = env.PI_SESSION_FILE;
210
- if (piFile !== undefined && piFile !== "") {
211
- return existsSync(piFile) ? { ok: true, path: piFile } : { ok: false, reason: `PI_SESSION_FILE ${piFile} does not exist` };
212
- }
213
- return {
214
- ok: false,
215
- reason: "no host session id; pass --transcript <path> (pi: ~/.pi/agent/sessions/<cwd-slug>/<file>.jsonl, " + "or set PI_SESSION_FILE)"
216
- };
217
- }
218
- if (!SESSION_ID.test(id))
219
- return { ok: false, reason: "refusing a session id with path characters" };
220
- if (!existsSync(projectsRoot))
221
- return { ok: false, reason: `no transcript root ${projectsRoot}` };
222
- for (const dir of readdirSync(projectsRoot)) {
223
- const path = join(projectsRoot, dir, `${id}.jsonl`);
224
- if (existsSync(path))
225
- return { ok: true, path };
226
- }
227
- return { ok: false, reason: `no transcript for session ${id}` };
228
- }
229
-
230
- // plugins/sp/lib/run-summary-core.ts
231
195
  var IDLE_GAP_MS = 300000;
232
196
  var SHELL_TOOLS = new Set(["bash", "shell", "exec", "exec_command", "run_command", "command"]);
233
197
  var SUBAGENT_TOOLS = new Set(["agent", "task", "invoke_subagent", "subagent", "dispatch_agent"]);
234
- var OPERATOR_TOOLS2 = new Set(["askuserquestion", "ask_user_question"]);
198
+ var LOWERCASE_OPERATOR_TOOLS = new Set(["askuserquestion", "ask_user_question"]);
235
199
  var ACTOR_PRECEDENCE = ["operator", "subagent", "shell", "other"];
236
200
  function actorOf(tool) {
237
201
  const t = tool.toLowerCase().replace(/[^a-z_]/g, "");
238
- if (OPERATOR_TOOLS2.has(t))
202
+ if (LOWERCASE_OPERATOR_TOOLS.has(t))
239
203
  return "operator";
240
204
  if (SUBAGENT_TOOLS.has(t))
241
205
  return "subagent";
@@ -385,7 +349,8 @@ function summaryRow(stage, status, span, measured) {
385
349
  token: formatTokenSplit(measured ? span.tokens : null)
386
350
  };
387
351
  }
388
- function buildRunSummary(lines, inputs, since, until) {
352
+ function buildRunSummary(rows, inputs, opts = {}) {
353
+ const { since, until } = opts;
389
354
  const windows = [];
390
355
  for (const { label, progress } of inputs) {
391
356
  const states = (progress.states ?? []).flatMap((s) => {
@@ -411,7 +376,6 @@ function buildRunSummary(lines, inputs, since, until) {
411
376
  }
412
377
  }
413
378
  windows.sort((a, b) => a.start - b.start);
414
- const { rows, skippedLines } = parseRows(lines);
415
379
  const measure = (start, end) => {
416
380
  const acc = newAcc(start);
417
381
  for (const [row, ts] of rows)
@@ -459,9 +423,87 @@ function buildRunSummary(lines, inputs, since, until) {
459
423
  total,
460
424
  actors: actorSplit(rows, totalStart, totalEnd),
461
425
  gate: { ms: null, reason: "no matching check receipt" },
462
- skippedLines
426
+ skippedLines: opts.skippedLines ?? 0
463
427
  };
464
428
  }
429
+ var SENSITIVE_PREFIXES = ["drizzle/", "packages/config/", ".github/", "plugins/sp/hooks/"];
430
+ var SENSITIVE_GLOBS = ["apps/server/src/**/auth*", "**/*secret*", "**/*.sql"];
431
+ function globToRegex(glob) {
432
+ const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replaceAll("**/", "\x00").replaceAll("**", "\x01").replaceAll("*", "[^/]*").replaceAll("\x00", "(?:.*/)?").replaceAll("\x01", ".*");
433
+ return new RegExp(`^${escaped}$`, "s");
434
+ }
435
+ var SENSITIVE_MATCHERS = [
436
+ ...SENSITIVE_PREFIXES.map((prefix) => ({
437
+ label: prefix,
438
+ test: (path) => path.startsWith(prefix)
439
+ })),
440
+ ...SENSITIVE_GLOBS.map((glob) => {
441
+ const regex = globToRegex(glob);
442
+ return { label: glob, test: (path) => regex.test(path) };
443
+ })
444
+ ];
445
+
446
+ // plugins/sp/lib/transcript.ts
447
+ import { existsSync as existsSync2, readdirSync } from "node:fs";
448
+ import { homedir } from "node:os";
449
+ import { join as join2 } from "node:path";
450
+ var SESSION_ID = /^[A-Za-z0-9_-]+$/;
451
+ var PI_ROLES = new Set(["user", "assistant", "toolResult"]);
452
+ function sniffFormat(rows) {
453
+ for (const row of rows) {
454
+ if (row.type === "message" && PI_ROLES.has(row.message?.role ?? ""))
455
+ return "pi";
456
+ if (row.type === "user" || row.type === "assistant")
457
+ return "claude";
458
+ }
459
+ return "unknown";
460
+ }
461
+ function parseRows(lines) {
462
+ const rows = [];
463
+ let skippedLines = 0;
464
+ for (const line of lines) {
465
+ if (!line.trim())
466
+ continue;
467
+ let row;
468
+ try {
469
+ row = JSON.parse(line);
470
+ } catch {
471
+ skippedLines++;
472
+ continue;
473
+ }
474
+ const ts = row.timestamp ? Date.parse(row.timestamp) : Number.NaN;
475
+ if (!Number.isNaN(ts))
476
+ rows.push([row, ts]);
477
+ }
478
+ return { rows, skippedLines, format: sniffFormat(rows.map(([r]) => r)) };
479
+ }
480
+ function resolveTranscript(env, projectsRoot = join2(homedir(), ".claude", "projects"), override) {
481
+ if (override)
482
+ return existsSync2(override) ? { ok: true, path: override } : { ok: false, reason: "no transcript" };
483
+ const id = env.CLAUDE_CODE_SESSION_ID;
484
+ if (!id) {
485
+ const piFile = env.PI_SESSION_FILE;
486
+ if (piFile !== undefined && piFile !== "") {
487
+ return existsSync2(piFile) ? { ok: true, path: piFile } : { ok: false, reason: `PI_SESSION_FILE ${piFile} does not exist` };
488
+ }
489
+ return {
490
+ ok: false,
491
+ reason: "no host session id; pass --transcript <path> (pi: ~/.pi/agent/sessions/<cwd-slug>/<file>.jsonl, " + "or set PI_SESSION_FILE)"
492
+ };
493
+ }
494
+ if (!SESSION_ID.test(id))
495
+ return { ok: false, reason: "refusing a session id with path characters" };
496
+ if (!existsSync2(projectsRoot))
497
+ return { ok: false, reason: `no transcript root ${projectsRoot}` };
498
+ for (const dir of readdirSync(projectsRoot)) {
499
+ const path = join2(projectsRoot, dir, `${id}.jsonl`);
500
+ if (existsSync2(path))
501
+ return { ok: true, path };
502
+ }
503
+ return { ok: false, reason: `no transcript for session ${id}` };
504
+ }
505
+
506
+ // plugins/sp/lib/run-summary-core.ts
465
507
  function renderSummaryMarkdown(summary) {
466
508
  const line = (r, bold = false) => {
467
509
  const stage = bold ? `**${r.stage}**` : r.stage;
@@ -493,9 +535,9 @@ function renderSummaryMarkdown(summary) {
493
535
  }
494
536
  function writeCloseSummary(run, opts = {}) {
495
537
  const cwd = opts.cwd ?? process.cwd();
496
- const runDir = join2(cwd, ".spur", "run");
497
- const rel = join2(".spur", "run", `${run.runId}-summary.md`);
498
- const abs = join2(cwd, rel);
538
+ const runDir = join3(cwd, ".spur", "run");
539
+ const rel = join3(".spur", "run", `${run.runId}-summary.md`);
540
+ const abs = join3(cwd, rel);
499
541
  const na = (reason) => {
500
542
  mkdirSync(runDir, { recursive: true });
501
543
  writeFileSync(abs, `Execution summary: n/a (${reason})
@@ -508,15 +550,19 @@ function writeCloseSummary(run, opts = {}) {
508
550
  const resolved = resolveTranscript(opts.env ?? getEnvVars(), opts.projectsRoot);
509
551
  if (!resolved.ok)
510
552
  return na(resolved.reason);
511
- const lines = readFileSync(resolved.path, "utf8").split(`
512
- `);
513
- const summary = buildRunSummary(lines, [{ progress: run.progress }], run.startedAt, run.completedAt);
553
+ const { rows, skippedLines } = parseRows(readFileSync(resolved.path, "utf8").split(`
554
+ `));
555
+ const summary = buildRunSummary(rows, [{ progress: run.progress }], {
556
+ since: run.startedAt,
557
+ until: run.completedAt,
558
+ skippedLines
559
+ });
514
560
  if (summary.rows.length === 0)
515
561
  return na("no timed attempts");
516
562
  mkdirSync(runDir, { recursive: true });
517
563
  writeFileSync(abs, `${renderSummaryMarkdown(summary)}
518
564
  `);
519
- writeFileSync(join2(runDir, `${run.runId}-summary.json`), JSON.stringify({ ...summary, runId: run.runId, startedAt: run.startedAt, completedAt: run.completedAt }, null, 2));
565
+ writeFileSync(join3(runDir, `${run.runId}-summary.json`), JSON.stringify({ ...summary, runId: run.runId, startedAt: run.startedAt, completedAt: run.completedAt }, null, 2));
520
566
  return rel;
521
567
  } catch (error) {
522
568
  return na(error instanceof Error ? error.message : String(error));
@@ -552,29 +598,10 @@ function refuseUnsafeRunId(runId) {
552
598
  console.error(" The run id must be a single safe filename component (alphanumeric/._-, no leading dot, no path separators, interpolation or traversal; same class as the task-pipeline route-reason guard, task 0804 R8). Allocate a fresh run id (uuid or timestamp slug) and retry.");
553
599
  process.exit(1);
554
600
  }
555
- function resolveAppEntry(spurBin) {
556
- const fallback = fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url));
557
- const candidates = spurBin !== "" ? [spurBin] : [fallback];
558
- for (const candidate of candidates) {
559
- const tokens = candidate.split(/\s+/).filter(Boolean);
560
- const mainModule = [...tokens].reverse().find((t) => t.endsWith(".ts"));
561
- if (mainModule === undefined || !existsSync3(mainModule))
562
- continue;
563
- const repoRoot = resolve(dirname(mainModule), "..", "..", "..");
564
- const appEntry = join3(repoRoot, "packages", "app", "src", "index.ts");
565
- if (existsSync3(appEntry))
566
- return { entry: appEntry, portable: false };
567
- }
568
- const entry = fileURLToPath(new URL("../lib/inline-run.generated.mjs", import.meta.url));
569
- if (!existsSync3(entry)) {
570
- throw new Error("inline application bundle is missing \u2014 rebuild/install the sp plugin before running inline");
571
- }
572
- return { entry, portable: true };
573
- }
574
601
  var SETUP_EXPORTS = ["readInstalledInventory", "runInlineRunSetup", "writeInlineRunOutcome"];
575
602
  async function main() {
576
603
  if (!process.versions.bun) {
577
- const child = spawnSync("bun", [fileURLToPath(import.meta.url), ...process.argv.slice(2)], {
604
+ const child = spawnSync("bun", [fileURLToPath2(import.meta.url), ...process.argv.slice(2)], {
578
605
  stdio: "inherit"
579
606
  });
580
607
  if (child.error)
@@ -631,8 +658,8 @@ async function main() {
631
658
  if (fingerprint) {
632
659
  if (runId !== "" || file !== "" || taskFiles.length !== 1 || (taskFiles[0] ?? "").trim() === "")
633
660
  usage();
634
- const { app: app2 } = await loadInlineApp(spurBin, resolveAppEntry, ["runInlineRunFingerprint"]);
635
- process.exit(await app2.runInlineRunFingerprint({ taskFile: taskFiles[0] ?? "", featureFile }));
661
+ const { app } = await loadInlineApp(spurBin, resolveAppEntry, ["runInlineRunFingerprint"]);
662
+ process.exit(await app.runInlineRunFingerprint({ taskFile: taskFiles[0] ?? "", featureFile }));
636
663
  }
637
664
  if (decide) {
638
665
  if (action || close || fingerprint || file !== "" || taskFiles.length > 0)
@@ -642,14 +669,14 @@ async function main() {
642
669
  if (!SAFE_RUN_ID_RE.test(runId))
643
670
  refuseUnsafeRunId(runId);
644
671
  try {
645
- const { app: app2, portable: portable2 } = await loadInlineApp(spurBin, resolveAppEntry, ["runInlineRunDecide"]);
646
- const { app: lib } = await loadInlineApp(spurBin, () => ({ entry: fileURLToPath(new URL("../lib/inline-run.generated.mjs", import.meta.url)), portable: portable2 }), ["resolveDecideDecisionMakerEnabled", "loadSpurConfig"]);
647
- const loadOpts = portable2 ? { embeddedSchemas: lib.EMBEDDED_SPUR_SCHEMAS } : undefined;
672
+ const { app, portable } = await loadInlineApp(spurBin, resolveAppEntry, ["runInlineRunDecide"]);
673
+ const { app: lib } = await loadInlineApp(spurBin, () => ({ entry: fileURLToPath2(new URL("../lib/inline-run.generated.mjs", import.meta.url)), portable }), ["resolveDecideDecisionMakerEnabled", "loadSpurConfig"]);
674
+ const loadOpts = portable ? { embeddedSchemas: lib.EMBEDDED_SPUR_SCHEMAS } : undefined;
648
675
  const [enabled, spurConfig] = await Promise.all([
649
676
  lib.resolveDecideDecisionMakerEnabled(process.cwd(), loadOpts),
650
677
  lib.loadSpurConfig(process.cwd(), loadOpts)
651
678
  ]);
652
- process.exit(await app2.runInlineRunDecide({ runId, node, optionsFile: optionsJson, enabled, spurConfig }));
679
+ process.exit(await app.runInlineRunDecide({ runId, node, optionsFile: optionsJson, enabled, spurConfig }));
653
680
  } catch (error) {
654
681
  const message = error instanceof Error ? error.message : String(error);
655
682
  process.stdout.write(`${JSON.stringify({ ok: false, runId, error: message })}
@@ -662,15 +689,15 @@ async function main() {
662
689
  usage();
663
690
  if (from.trim() === "" || taskFiles.some((taskFile) => taskFile.trim() === ""))
664
691
  usage();
665
- const { app: app2 } = await loadInlineApp(spurBin, resolveAppEntry, ["runInlineRunPersistOut"]);
666
- process.exit(await app2.runInlineRunPersistOut({ from, taskFiles }));
692
+ const { app } = await loadInlineApp(spurBin, resolveAppEntry, ["runInlineRunPersistOut"]);
693
+ process.exit(await app.runInlineRunPersistOut({ from, taskFiles }));
667
694
  }
668
695
  if (nodeEnter || actionsFile !== "" || action || close) {
669
696
  if (!SAFE_RUN_ID_RE.test(runId))
670
697
  refuseUnsafeRunId(runId);
671
698
  const mode = nodeEnter ? "node-enter" : actionsFile !== "" ? "actions-file" : close ? "close" : "action";
672
- const { app: app2 } = await loadInlineApp(spurBin, resolveAppEntry, ["runInlineRunTraceMode"]);
673
- const code = await app2.runInlineRunTraceMode({
699
+ const { app } = await loadInlineApp(spurBin, resolveAppEntry, ["runInlineRunTraceMode"]);
700
+ const code = await app.runInlineRunTraceMode({
674
701
  mode,
675
702
  runId,
676
703
  node,
@@ -699,7 +726,7 @@ async function main() {
699
726
  inventory = await app.readInstalledInventory({
700
727
  file,
701
728
  spurBin,
702
- localCli: fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url))
729
+ localCli: fileURLToPath2(new URL("../../../apps/cli/src/index.ts", import.meta.url))
703
730
  });
704
731
  } catch (error) {
705
732
  const message = `could not resolve the workflow definition: ${error instanceof Error ? error.message : String(error)}`;
@@ -3,13 +3,11 @@
3
3
  * handshake, mode dispatch. Mode bodies live in the bundled app service (`runInlineRun*` exports); the `.mjs`
4
4
  * twin re-enters Bun in `main()` (SQLite) and imports the bundle dynamically, so bare `node` never loads app code. */
5
5
  import { spawnSync } from 'node:child_process';
6
- import { existsSync } from 'node:fs';
7
- import { dirname, join, resolve } from 'node:path';
8
6
  import { fileURLToPath } from 'node:url';
9
7
  // Type-only namespace import (erased before bundling; the smoke scanner exempts `import type`).
10
8
  import type * as spurApp from '@gobing-ai/spur-app';
11
9
  import { getEnvVar } from '../lib/env';
12
- import { loadInlineApp } from '../lib/inline-run-app';
10
+ import { loadInlineApp, resolveAppEntry } from '../lib/inline-run-app';
13
11
  import { writeCloseSummary } from './run-summary';
14
12
 
15
13
  // EMBEDDED_SPUR_SCHEMAS: declared by the bundle twin, never exported by the barrel — optional
@@ -47,24 +45,6 @@ function refuseUnsafeRunId(runId: string): never {
47
45
  }
48
46
 
49
47
  /** Resolve the app entry: --spur-bin/SPUR_BIN main module if it proves a repo checkout, else the adjacent bundle. */
50
- function resolveAppEntry(spurBin: string): { entry: string; portable: boolean } {
51
- const fallback = fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url));
52
- const candidates = spurBin !== '' ? [spurBin] : [fallback];
53
- for (const candidate of candidates) {
54
- const tokens = candidate.split(/\s+/).filter(Boolean);
55
- // Main module = last path-like token; only a .ts source entry proves a repo checkout.
56
- const mainModule = [...tokens].reverse().find((t) => t.endsWith('.ts'));
57
- if (mainModule === undefined || !existsSync(mainModule)) continue;
58
- const repoRoot = resolve(dirname(mainModule), '..', '..', '..');
59
- const appEntry = join(repoRoot, 'packages', 'app', 'src', 'index.ts');
60
- if (existsSync(appEntry)) return { entry: appEntry, portable: false };
61
- }
62
- const entry = fileURLToPath(new URL('../lib/inline-run.generated.mjs', import.meta.url));
63
- if (!existsSync(entry)) {
64
- throw new Error('inline application bundle is missing — rebuild/install the sp plugin before running inline');
65
- }
66
- return { entry, portable: true };
67
- }
68
48
 
69
49
  const SETUP_EXPORTS = ['readInstalledInventory', 'runInlineRunSetup', 'writeInlineRunOutcome'] as const;
70
50
 
@@ -204,10 +204,10 @@ persist-out-check: --from must be an existing worktree directory
204
204
  process.exit(main(process.argv.slice(2)));
205
205
  }
206
206
  export {
207
- wbsOfTaskFile,
208
- persistOutCheckUsage,
209
- main,
210
- listObligations,
207
+ compareTrees,
211
208
  defaultInvokeRoot,
212
- compareTrees
209
+ listObligations,
210
+ main,
211
+ persistOutCheckUsage,
212
+ wbsOfTaskFile
213
213
  };
@@ -695,18 +695,18 @@ function main(argv) {
695
695
  process.exit(main(process.argv.slice(2)));
696
696
  }
697
697
  export {
698
- spawnRunner,
699
- setCommandRunner,
700
- scanHygiene,
701
- renderFindings,
702
- parseArgs,
703
- normalizeFindings,
704
- main,
705
- isHeadReviewed,
706
- isExplicitCleanReview,
707
- isCodexAuthor,
708
- hasCurrentCleanReview,
709
- hasCodeReviewRules,
698
+ buildRequestBody,
710
699
  extractSeverity,
711
- buildRequestBody
700
+ hasCodeReviewRules,
701
+ hasCurrentCleanReview,
702
+ isCodexAuthor,
703
+ isExplicitCleanReview,
704
+ isHeadReviewed,
705
+ main,
706
+ normalizeFindings,
707
+ parseArgs,
708
+ renderFindings,
709
+ scanHygiene,
710
+ setCommandRunner,
711
+ spawnRunner
712
712
  };