@gobing-ai/spur 0.4.8 → 0.4.9

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 (145) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/pipeline-budgets.json +2 -2
  3. package/config/rules/boundary/config-loading-ownership.yaml +1 -0
  4. package/config/rules/boundary/sp-plugin-standalone.yaml +1 -1
  5. package/config/rules/boundary/sp-script-placement.yaml +1 -1
  6. package/config/rules/docs/sourced-api-claims.yaml +45 -7
  7. package/config/rules/strict/runtime-boundaries.yaml +40 -1
  8. package/config/rules/structure/protected-files.yaml +1 -1
  9. package/config/rules/surface/check-cli-surface.yaml +33 -0
  10. package/config/rules/typescript/no-debugger.yaml +3 -0
  11. package/config/workflow-candidates.json +2 -2
  12. package/config/workflows/feature-verification.yaml +6 -11
  13. package/config/workflows/history-anatomy.yaml +14 -28
  14. package/config/workflows/idea-pipeline.yaml +6 -19
  15. package/config/workflows/pr-review.yaml +9 -16
  16. package/config/workflows/task-pipeline.yaml +16 -23
  17. package/config/workflows/wrapup-pipeline.yaml +21 -63
  18. package/package.json +3 -3
  19. package/plugins/sp/README.md +70 -37
  20. package/plugins/sp/agents/super-planner.md +5 -5
  21. package/plugins/sp/agents/super-reviewer.md +2 -2
  22. package/plugins/sp/commands/dev-changelog.md +1 -1
  23. package/plugins/sp/commands/dev-daily.md +1 -1
  24. package/plugins/sp/commands/dev-pr-review.md +1 -1
  25. package/plugins/sp/commands/dev-review-session.md +1 -1
  26. package/plugins/sp/commands/dev-runall.md +4 -4
  27. package/plugins/sp/commands/dev-verify.md +4 -6
  28. package/plugins/sp/commands/dev-verifyall.md +4 -5
  29. package/plugins/sp/hooks/hooks.json +11 -1
  30. package/plugins/sp/lib/context-post-tool.ts +1 -1
  31. package/plugins/sp/lib/context-session-start.ts +1 -1
  32. package/plugins/sp/lib/context-session-stop.ts +1 -1
  33. package/plugins/sp/lib/destructive-policy.ts +65 -1
  34. package/plugins/sp/plugin.json +2 -5
  35. package/plugins/sp/{hooks → scripts/hooks}/agent-lifecycle.ts +1 -1
  36. package/plugins/sp/scripts/hooks/careful-guard.ts +94 -0
  37. package/plugins/sp/{hooks → scripts/hooks}/context-post-tool.ts +2 -2
  38. package/plugins/sp/{hooks → scripts/hooks}/context-session-start.ts +1 -1
  39. package/plugins/sp/{hooks → scripts/hooks}/context-session-stop.ts +1 -1
  40. package/plugins/sp/{hooks → scripts/hooks}/fleet-guest-stop.ts +6 -4
  41. package/plugins/sp/{hooks → scripts/hooks}/task-write-guard.ts +38 -21
  42. package/plugins/sp/skills/code-implementation/SKILL.md +1 -1
  43. package/plugins/sp/skills/code-verification/SKILL.md +8 -8
  44. package/plugins/sp/skills/code-verification/references/secu-review.md +4 -3
  45. package/plugins/sp/skills/code-verification/references/verdict-schema.md +9 -1
  46. package/plugins/sp/skills/daily-summary/SKILL.md +4 -5
  47. package/plugins/sp/skills/dogfood-testing/SKILL.md +9 -9
  48. package/plugins/sp/skills/dogfood-testing/references/report-template.md +1 -1
  49. package/plugins/sp/skills/history-anatomy/references/modes.md +2 -2
  50. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  51. package/plugins/sp/skills/parallel-execution/SKILL.md +1 -1
  52. package/plugins/sp/skills/pr-reviewing/SKILL.md +14 -15
  53. package/plugins/sp/skills/session-review/SKILL.md +3 -3
  54. package/plugins/sp/skills/source-driven-development/SKILL.md +7 -4
  55. package/plugins/sp/skills/spur-check/SKILL.md +14 -13
  56. package/plugins/sp/skills/spur-cli/SKILL.md +1 -1
  57. package/plugins/sp/skills/spur-cli/references/agent.md +12 -0
  58. package/plugins/sp/skills/spur-cli/references/builder.md +27 -6
  59. package/plugins/sp/skills/spur-cli/references/features.md +6 -1
  60. package/plugins/sp/skills/spur-cli/references/history.md +3 -0
  61. package/plugins/sp/skills/spur-cli/references/self.md +5 -3
  62. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +3 -0
  63. package/plugins/sp/skills/spur-cli/references/tasks.md +13 -3
  64. package/plugins/sp/skills/spur-cli/references/workflows.md +36 -1
  65. package/plugins/sp/skills/spur-dev/SKILL.md +1 -1
  66. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +1 -1
  67. package/plugins/sp/skills/spur-dev/references/dev-operations.md +12 -12
  68. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +10 -11
  69. package/plugins/sp/skills/spur-dev/references/execution-batch.md +16 -17
  70. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +1 -1
  71. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +8 -11
  72. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +6 -8
  73. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +1 -1
  74. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +33 -40
  75. package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +24 -24
  76. package/plugins/sp/skills/spur-doctor/SKILL.md +5 -4
  77. package/spur.js +41901 -30598
  78. package/config/plugin-scripts.json +0 -116
  79. package/config/script-placement-baseline.json +0 -33
  80. package/plugins/sp/hooks/careful-guard.ts +0 -74
  81. package/plugins/sp/hooks/pi/guard-extension.ts +0 -304
  82. package/plugins/sp/lib/dogfood-pipeline.ts +0 -265
  83. package/plugins/sp/lib/dogfood-report.ts +0 -278
  84. package/plugins/sp/lib/history-anatomy.generated.d.mts +0 -112
  85. package/plugins/sp/lib/history-anatomy.generated.mjs +0 -691
  86. package/plugins/sp/lib/idea-handoff.generated.d.mts +0 -17
  87. package/plugins/sp/lib/idea-handoff.generated.mjs +0 -1446
  88. package/plugins/sp/lib/inline-run-app.ts +0 -68
  89. package/plugins/sp/lib/inline-run.generated.d.mts +0 -37
  90. package/plugins/sp/lib/inline-run.generated.mjs +0 -3103
  91. package/plugins/sp/lib/plan-projection.generated.d.mts +0 -197
  92. package/plugins/sp/lib/plan-projection.generated.mjs +0 -982
  93. package/plugins/sp/lib/quality-gate.generated.d.mts +0 -139
  94. package/plugins/sp/lib/quality-gate.generated.mjs +0 -30510
  95. package/plugins/sp/lib/residual-scan.generated.d.mts +0 -93
  96. package/plugins/sp/lib/residual-scan.generated.mjs +0 -383
  97. package/plugins/sp/lib/run-summary-core.ts +0 -225
  98. package/plugins/sp/lib/spur-bin.ts +0 -36
  99. package/plugins/sp/lib/step-profile.generated.d.mts +0 -71
  100. package/plugins/sp/lib/step-profile.generated.mjs +0 -174
  101. package/plugins/sp/lib/transcript.ts +0 -112
  102. package/plugins/sp/scripts/batch-plan.mjs +0 -1096
  103. package/plugins/sp/scripts/batch-plan.ts +0 -145
  104. package/plugins/sp/scripts/batch-preflight.mjs +0 -423
  105. package/plugins/sp/scripts/batch-preflight.ts +0 -270
  106. package/plugins/sp/scripts/commit-guard.mjs +0 -226
  107. package/plugins/sp/scripts/commit-guard.ts +0 -248
  108. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +0 -626
  109. package/plugins/sp/scripts/daily-summary/daily-summary.ts +0 -855
  110. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +0 -211
  111. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +0 -110
  112. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +0 -282
  113. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +0 -98
  114. package/plugins/sp/scripts/feature-verification-steps.mjs +0 -82
  115. package/plugins/sp/scripts/feature-verification-steps.ts +0 -164
  116. package/plugins/sp/scripts/history-anatomy-cache.mjs +0 -931
  117. package/plugins/sp/scripts/history-anatomy-cache.ts +0 -247
  118. package/plugins/sp/scripts/idea-handoff.mjs +0 -32
  119. package/plugins/sp/scripts/idea-handoff.ts +0 -47
  120. package/plugins/sp/scripts/inline-run-dispatch.mjs +0 -131
  121. package/plugins/sp/scripts/inline-run-dispatch.ts +0 -115
  122. package/plugins/sp/scripts/inline-run-setup.mjs +0 -746
  123. package/plugins/sp/scripts/inline-run-setup.ts +0 -198
  124. package/plugins/sp/scripts/persist-out-check.mjs +0 -217
  125. package/plugins/sp/scripts/persist-out-check.ts +0 -249
  126. package/plugins/sp/scripts/pr-reviewing.mjs +0 -712
  127. package/plugins/sp/scripts/pr-reviewing.ts +0 -952
  128. package/plugins/sp/scripts/quality-gate.mjs +0 -30491
  129. package/plugins/sp/scripts/quality-gate.ts +0 -80
  130. package/plugins/sp/scripts/residual-scan.mjs +0 -634
  131. package/plugins/sp/scripts/residual-scan.ts +0 -248
  132. package/plugins/sp/scripts/run-summary.mjs +0 -758
  133. package/plugins/sp/scripts/run-summary.ts +0 -107
  134. package/plugins/sp/scripts/script-root.mjs +0 -129
  135. package/plugins/sp/scripts/script-root.ts +0 -199
  136. package/plugins/sp/scripts/session-timeline.mjs +0 -359
  137. package/plugins/sp/scripts/session-timeline.ts +0 -190
  138. package/plugins/sp/scripts/task-diffstat.mjs +0 -181
  139. package/plugins/sp/scripts/task-diffstat.ts +0 -219
  140. package/plugins/sp/scripts/workflow-step-profile.mjs +0 -338
  141. package/plugins/sp/scripts/workflow-step-profile.ts +0 -165
  142. package/plugins/sp/scripts/wrapup-drift-probe.mjs +0 -190
  143. package/plugins/sp/scripts/wrapup-drift-probe.ts +0 -261
  144. package/plugins/sp/scripts/wrapup-steps.mjs +0 -627
  145. package/plugins/sp/scripts/wrapup-steps.ts +0 -891
@@ -1,982 +0,0 @@
1
- // packages/app/src/services/task-freeze.ts
2
- function isTaskFrozen(status) {
3
- return status?.toLowerCase() === "deprecated";
4
- }
5
- function undeprecateTaskCommand(wbs) {
6
- return `spur task update ${wbs} todo`;
7
- }
8
-
9
- // packages/app/src/services/planning-admission.ts
10
- var NON_ABORTING_PREFLIGHT_CODES = new Set([
11
- "L4.scenario-unverified",
12
- "L4.verifying-incomplete-tasks"
13
- ]);
14
- function classifyFeaturePreflightFindings(findings) {
15
- const aborting = [];
16
- const nonAborting = [];
17
- for (const f of findings) {
18
- if (f.severity === "error") {
19
- if (NON_ABORTING_PREFLIGHT_CODES.has(f.code)) {
20
- nonAborting.push(f);
21
- } else {
22
- aborting.push(f);
23
- }
24
- } else {
25
- nonAborting.push(f);
26
- }
27
- }
28
- return {
29
- aborting,
30
- nonAborting,
31
- shouldAbort: aborting.length > 0
32
- };
33
- }
34
- function quickReadiness(input) {
35
- const status = (input.status ?? "").toLowerCase();
36
- const operation = input.operation;
37
- if (operation !== "run" && operation !== "refine" && operation !== "verify") {
38
- return {
39
- action: "invalid",
40
- code: "IV",
41
- reason: `quick-readiness: unknown operation '${operation}' (${input.wbs})`
42
- };
43
- }
44
- if (input.filteredCount !== undefined) {
45
- if (input.filteredCount < 0) {
46
- return {
47
- action: "invalid",
48
- code: "IV",
49
- reason: `quick-readiness: invalid negative filtered count '${input.filteredCount}' (${input.wbs})`
50
- };
51
- }
52
- if (input.filteredCount === 0) {
53
- return {
54
- action: "skipped",
55
- code: "EMPTY",
56
- reason: `quick-readiness: empty status-filtered set — nothing to ${operation} (${input.wbs})`
57
- };
58
- }
59
- }
60
- if (isTaskFrozen(status)) {
61
- return {
62
- action: "skipped",
63
- code: "DEPRECATED",
64
- reason: `quick-readiness: deprecated (frozen) — no ${operation} hop; un-deprecate with \`${undeprecateTaskCommand(input.wbs)}\` (${input.wbs})`
65
- };
66
- }
67
- if (status === "cancelled" || status === "done") {
68
- if (operation === "verify" && input.force === true) {
69
- return {
70
- action: "runnable",
71
- code: "FORCE",
72
- reason: `quick-readiness: verify --force re-verification of ${status} task ${input.wbs}`
73
- };
74
- }
75
- return {
76
- action: "skipped",
77
- code: status === "done" ? "DONE" : "CANCELLED",
78
- reason: `quick-readiness: already ${status} — no ${operation} hop (${input.wbs})`
79
- };
80
- }
81
- if (status === "blocked") {
82
- return {
83
- action: "blocked",
84
- code: "BLK",
85
- reason: `quick-readiness: blocked — human/handover first (${input.wbs})`
86
- };
87
- }
88
- if (operation === "run" && input.dependencies && input.dependencies.length > 0) {
89
- const unmet = input.dependencies.filter((d) => (input.depStatuses?.[d] ?? "missing").toLowerCase() !== "done");
90
- if (unmet.length > 0) {
91
- const frozen = unmet.filter((d) => isTaskFrozen(input.depStatuses?.[d]));
92
- const hint = frozen.length > 0 ? ` — ${frozen.map((d) => `${d} deprecated (frozen); re-point the edge or un-deprecate: \`${undeprecateTaskCommand(d)}\``).join("; ")}` : "";
93
- return {
94
- action: "blocked",
95
- code: "DEP",
96
- reason: `quick-readiness: unmet deps — ${unmet.join(", ")} (${input.wbs})${hint}`,
97
- unmetDeps: unmet
98
- };
99
- }
100
- }
101
- const required = input.requiredSections ?? [];
102
- const present = input.presentSections ?? [];
103
- const gaps = required.filter((s) => {
104
- const finding = input.sectionFindings?.[s];
105
- return !present.includes(s) || finding !== undefined && finding !== "";
106
- });
107
- if (operation === "refine") {
108
- if (status !== "backlog" && status !== "todo") {
109
- return {
110
- action: "skipped",
111
- code: "NONPLAN",
112
- reason: `quick-readiness: refine targets backlog/todo only, not '${status}' (${input.wbs})`
113
- };
114
- }
115
- return {
116
- action: "runnable",
117
- code: "OK",
118
- reason: `quick-readiness: refine ready for ${input.wbs} (${gaps.length} planning gap(s) to fill)`
119
- };
120
- }
121
- if (operation === "verify") {
122
- if (status !== "testing" && status !== "wip") {
123
- return {
124
- action: "invalid",
125
- code: "NOVERIFY",
126
- reason: `quick-readiness: verify needs testing/wip, not '${status}' (${input.wbs})`
127
- };
128
- }
129
- return {
130
- action: "runnable",
131
- code: "OK",
132
- reason: `quick-readiness: verify ready for ${input.wbs}`
133
- };
134
- }
135
- if (status !== "todo" && status !== "wip" && status !== "testing") {
136
- return {
137
- action: "invalid",
138
- code: "NORUN",
139
- reason: `quick-readiness: run needs todo/wip/testing, not '${status}' (${input.wbs})`
140
- };
141
- }
142
- if (gaps.length > 0) {
143
- return {
144
- action: "needs-refinement",
145
- code: "REFINE",
146
- reason: `quick-readiness: implementation sections incomplete (${input.wbs})`,
147
- gaps
148
- };
149
- }
150
- return {
151
- action: "runnable",
152
- code: "OK",
153
- reason: `quick-readiness: run ready for ${input.wbs}`
154
- };
155
- }
156
- function preflightTask(input) {
157
- const status = (input.status ?? "").toLowerCase();
158
- const deps = input.dependencies ?? [];
159
- const depStatuses = input.depStatuses ?? {};
160
- if (status === "cancelled") {
161
- return {
162
- action: "skip",
163
- code: "A9",
164
- reason: `dev-next: cancelled — nothing to advance (${input.wbs})`
165
- };
166
- }
167
- if (status === "done") {
168
- return {
169
- action: "skip",
170
- code: "A8",
171
- reason: `dev-next: already done — batch does not auto-wrap (${input.wbs})`
172
- };
173
- }
174
- if (status === "blocked") {
175
- return {
176
- action: "skip",
177
- code: "A7",
178
- reason: `dev-next: blocked — do not launch pipeline; human/handover first (${input.wbs})`
179
- };
180
- }
181
- if (isTaskFrozen(status)) {
182
- return {
183
- action: "skip",
184
- code: "DEPRECATED",
185
- reason: `dev-next: deprecated (frozen) — un-deprecate first: ${undeprecateTaskCommand(input.wbs)} (${input.wbs})`
186
- };
187
- }
188
- if (status === "todo" || status === "backlog") {
189
- const unmet = deps.filter((d) => {
190
- const st = (depStatuses[d] ?? "missing").toLowerCase();
191
- return st !== "done";
192
- });
193
- if (unmet.length > 0) {
194
- return {
195
- action: "skip",
196
- code: "A2",
197
- reason: `dev-next: blocked by deps — unmet: ${unmet.join(", ")} (${input.wbs})`,
198
- unmetDeps: unmet
199
- };
200
- }
201
- }
202
- return { action: "run", code: "OK", reason: `preflight clear — launch task-pipeline for ${input.wbs}` };
203
- }
204
- // packages/app/src/workflow/run-summary.ts
205
- var zeroTokens = () => ({ input: 0, cacheCreate: 0, cacheRead: 0, output: 0 });
206
- function formatDuration(ms) {
207
- const s = Math.round(ms / 1000);
208
- const h = Math.floor(s / 3600);
209
- const m = Math.floor(s % 3600 / 60);
210
- const ss = String(s % 60).padStart(2, "0");
211
- return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
212
- }
213
- function formatTokens(n) {
214
- if (n >= 999500)
215
- return `${(n / 1e6).toFixed(1)}M`;
216
- return n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);
217
- }
218
- function formatTokenSplit(t) {
219
- if (!t)
220
- return "n/a";
221
- const nonCached = t.input + t.cacheCreate + t.output;
222
- return `${formatTokens(nonCached + t.cacheRead)} / ${formatTokens(nonCached)}`;
223
- }
224
- var OPERATOR_TOOLS = new Set(["AskUserQuestion", "ask_user_question"]);
225
- function isCompaction(row) {
226
- return row.type === "compaction" || row.type === "system" && row.subtype === "compact_boundary";
227
- }
228
- function firstTextContent(row) {
229
- const content = row.message?.content;
230
- if (typeof content === "string")
231
- return content;
232
- if (!Array.isArray(content))
233
- return;
234
- return content.find((b) => b.type === "text")?.text;
235
- }
236
- function skillInvocationPrompt(text) {
237
- const name = /^\s*<skill name="([^"]+)"/.exec(text)?.[1];
238
- if (name === undefined)
239
- return;
240
- const close = text.lastIndexOf("</skill>");
241
- const args = close < 0 ? "" : text.slice(close + "</skill>".length).trim();
242
- return args === "" ? `/${name}` : `/${name} ${args}`;
243
- }
244
- function promptText(row) {
245
- if (row.type === "custom_message") {
246
- if (row.display === false)
247
- return;
248
- const text = typeof row.content === "string" ? row.content.trim() : "";
249
- return text === "" ? undefined : text;
250
- }
251
- if (row.type === "message") {
252
- if (row.message?.role !== "user")
253
- return;
254
- const text = firstTextContent(row);
255
- if (text === undefined)
256
- return;
257
- return skillInvocationPrompt(text) ?? text;
258
- }
259
- if (row.type !== "user" || row.isMeta || row.isCompactSummary)
260
- return;
261
- const content = row.message?.content;
262
- if (typeof content === "string")
263
- return content;
264
- if (!Array.isArray(content) || content.some((b) => b.type === "tool_result"))
265
- return;
266
- return content.find((b) => b.type === "text")?.text;
267
- }
268
- function sumTokens(all) {
269
- const total = zeroTokens();
270
- for (const t of all)
271
- for (const k of Object.keys(total))
272
- total[k] += t[k];
273
- return total;
274
- }
275
- function sumSpans(spans) {
276
- return {
277
- workMs: spans.reduce((n, s) => n + s.workMs, 0),
278
- waitMs: spans.reduce((n, s) => n + s.waitMs, 0),
279
- toolCalls: spans.reduce((n, s) => n + s.toolCalls, 0),
280
- tokens: sumTokens(spans.map((s) => s.tokens))
281
- };
282
- }
283
- var newAcc = (ts) => ({
284
- last: ts,
285
- askMs: 0,
286
- asks: new Map,
287
- toolIds: new Set,
288
- messages: new Map,
289
- compactions: 0
290
- });
291
- function accumulate(acc, row, ts) {
292
- acc.last = Math.max(acc.last, ts);
293
- if (row.type === "message") {
294
- const m = row.message;
295
- if (m?.role === "assistant") {
296
- const blocks = Array.isArray(m.content) ? m.content : [];
297
- for (const b of blocks) {
298
- if (b.type === "toolCall" && b.id) {
299
- acc.toolIds.add(b.id);
300
- if (b.name && OPERATOR_TOOLS.has(b.name))
301
- acc.asks.set(b.id, ts);
302
- }
303
- }
304
- const u = m.usage;
305
- if (u && row.id)
306
- acc.messages.set(row.id, {
307
- input: u.input ?? 0,
308
- cacheCreate: u.cacheWrite ?? 0,
309
- cacheRead: u.cacheRead ?? 0,
310
- output: u.output ?? 0
311
- });
312
- } else if (m?.role === "toolResult" && m.toolCallId && acc.asks.has(m.toolCallId)) {
313
- acc.askMs += ts - (acc.asks.get(m.toolCallId) ?? ts);
314
- }
315
- return;
316
- }
317
- if (row.type === "compaction")
318
- acc.compactions++;
319
- const blocks = Array.isArray(row.message?.content) ? row.message.content : [];
320
- for (const b of blocks) {
321
- if (b.type === "tool_use" && b.id) {
322
- acc.toolIds.add(b.id);
323
- if (b.name && OPERATOR_TOOLS.has(b.name))
324
- acc.asks.set(b.id, ts);
325
- } else if (b.type === "tool_result" && b.tool_use_id && acc.asks.has(b.tool_use_id)) {
326
- acc.askMs += ts - (acc.asks.get(b.tool_use_id) ?? ts);
327
- }
328
- }
329
- const u = row.message?.usage;
330
- if (row.type === "assistant" && u && row.message?.id) {
331
- acc.messages.set(row.message.id, {
332
- input: u.input_tokens ?? 0,
333
- cacheCreate: u.cache_creation_input_tokens ?? 0,
334
- cacheRead: u.cache_read_input_tokens ?? 0,
335
- output: u.output_tokens ?? 0
336
- });
337
- }
338
- }
339
- var IDLE_GAP_MS = 300000;
340
- var SHELL_TOOLS = new Set(["bash", "shell", "exec", "exec_command", "run_command", "command"]);
341
- var SUBAGENT_TOOLS = new Set(["agent", "task", "invoke_subagent", "subagent", "dispatch_agent"]);
342
- var LOWERCASE_OPERATOR_TOOLS = new Set(["askuserquestion", "ask_user_question"]);
343
- var ACTOR_PRECEDENCE = ["operator", "subagent", "shell", "other"];
344
- function actorOf(tool) {
345
- const t = tool.toLowerCase().replace(/[^a-z_]/g, "");
346
- if (LOWERCASE_OPERATOR_TOOLS.has(t))
347
- return "operator";
348
- if (SUBAGENT_TOOLS.has(t))
349
- return "subagent";
350
- if (SHELL_TOOLS.has(t))
351
- return "shell";
352
- return "other";
353
- }
354
- function unionMs(spans, start, end) {
355
- 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]);
356
- let total = 0;
357
- let cursor = Number.NaN;
358
- let openUntil = Number.NaN;
359
- for (const [a, b] of clipped) {
360
- if (!Number.isFinite(cursor)) {
361
- cursor = a;
362
- openUntil = b;
363
- continue;
364
- }
365
- if (a > openUntil) {
366
- total += openUntil - cursor;
367
- cursor = a;
368
- openUntil = b;
369
- } else {
370
- openUntil = Math.max(openUntil, b);
371
- }
372
- }
373
- if (Number.isFinite(cursor))
374
- total += openUntil - cursor;
375
- return total;
376
- }
377
- function subtract(spans, claimed) {
378
- let free = spans;
379
- for (const [ca, cb] of claimed) {
380
- const next = [];
381
- for (const [a, b] of free) {
382
- if (cb <= a || ca >= b) {
383
- next.push([a, b]);
384
- continue;
385
- }
386
- if (ca > a)
387
- next.push([a, ca]);
388
- if (cb < b)
389
- next.push([cb, b]);
390
- }
391
- free = next;
392
- }
393
- return free;
394
- }
395
- function actorSplit(rows, start, end) {
396
- const empty = {
397
- operatorMs: 0,
398
- longestOperatorWaitMs: 0,
399
- modelMs: 0,
400
- shellMs: 0,
401
- subagentMs: 0,
402
- otherToolMs: 0,
403
- idleMs: 0,
404
- compactions: 0,
405
- subagentErrors: 0
406
- };
407
- const window = end - start;
408
- if (window <= 0)
409
- return empty;
410
- const calls = new Map;
411
- const results = new Map;
412
- const eventTs = [];
413
- let compactions = 0;
414
- for (const [row, ts] of rows) {
415
- if (ts < start || ts > end)
416
- continue;
417
- eventTs.push(ts);
418
- if (isCompaction(row))
419
- compactions++;
420
- const blocks = Array.isArray(row.message?.content) ? row.message?.content : [];
421
- if (row.message?.role === "toolResult" && row.message.toolCallId !== undefined) {
422
- results.set(row.message.toolCallId, { ts, isError: row.message.isError === true });
423
- }
424
- for (const b of blocks) {
425
- if ((b.type === "tool_use" || b.type === "toolCall") && b.id !== undefined && b.name !== undefined) {
426
- calls.set(b.id, { id: b.id, actor: actorOf(b.name), ts });
427
- } else if (b.type === "tool_result" && b.tool_use_id !== undefined) {
428
- results.set(b.tool_use_id, { ts, isError: b.is_error === true });
429
- }
430
- }
431
- }
432
- let subagentErrors = 0;
433
- const resolved = [];
434
- for (const call of calls.values()) {
435
- const result = results.get(call.id);
436
- if (result?.isError === true && call.actor === "subagent")
437
- subagentErrors++;
438
- resolved.push({ ...call, end: Math.min(result?.ts ?? end, end) });
439
- }
440
- const claimed = [];
441
- const byActor = new Map;
442
- for (const actor of ACTOR_PRECEDENCE) {
443
- const mine = resolved.filter((r) => r.actor === actor).map((r) => [r.ts, r.end]);
444
- const free = subtract(mine, claimed);
445
- for (const span of free)
446
- claimed.push(span);
447
- const ms = unionMs(free, start, end);
448
- byActor.set(actor, ms);
449
- if (actor === "operator") {
450
- empty.longestOperatorWaitMs = Math.max(0, ...mine.map(([a, b]) => b - a));
451
- }
452
- }
453
- const boundaries = [...new Set([start, end, ...eventTs, ...claimed.flat()])].sort((a, b) => a - b);
454
- const claimedSorted = [...claimed].sort((a, b) => a[0] - b[0]);
455
- let idleMs = 0;
456
- for (let i = 0;i < boundaries.length - 1; i++) {
457
- const a = boundaries[i];
458
- const b = boundaries[i + 1];
459
- if (b - a < IDLE_GAP_MS)
460
- continue;
461
- if (claimedSorted.some(([ca, cb]) => ca < b && cb > a))
462
- continue;
463
- idleMs += b - a;
464
- }
465
- const accounted = (byActor.get("operator") ?? 0) + (byActor.get("subagent") ?? 0) + (byActor.get("shell") ?? 0) + (byActor.get("other") ?? 0) + idleMs;
466
- return {
467
- ...empty,
468
- operatorMs: byActor.get("operator") ?? 0,
469
- subagentMs: byActor.get("subagent") ?? 0,
470
- shellMs: byActor.get("shell") ?? 0,
471
- otherToolMs: byActor.get("other") ?? 0,
472
- idleMs,
473
- modelMs: Math.max(0, window - accounted),
474
- compactions,
475
- subagentErrors
476
- };
477
- }
478
- function attemptWindow(attempts) {
479
- const starts = attempts.map((a) => Date.parse(a.startedAt ?? "")).filter((n) => !Number.isNaN(n));
480
- const ends = attempts.map((a) => Date.parse(a.completedAt ?? "")).filter((n) => !Number.isNaN(n));
481
- return starts.length && ends.length ? [Math.min(...starts), Math.max(...ends)] : undefined;
482
- }
483
- function summaryRow(stage, status, span, measured) {
484
- return {
485
- stage,
486
- status,
487
- workMs: span.workMs,
488
- waitMs: span.waitMs,
489
- toolCalls: measured ? span.toolCalls : null,
490
- tokens: measured ? span.tokens : null,
491
- time: formatDuration(span.workMs),
492
- wait: formatDuration(span.waitMs),
493
- token: formatTokenSplit(measured ? span.tokens : null)
494
- };
495
- }
496
- function buildRunSummary(rows, inputs, opts = {}) {
497
- const { since, until } = opts;
498
- const windows = [];
499
- for (const { label, progress } of inputs) {
500
- const states = (progress.states ?? []).flatMap((s) => {
501
- const w = attemptWindow((s.actions ?? []).flatMap((a) => a.attempts ?? []));
502
- return w ? [{ s, w }] : [];
503
- });
504
- if (!states.length)
505
- throw new Error(`progress for run ${progress.runId} has no timed attempts`);
506
- if (inputs.length === 1) {
507
- for (const { s, w } of states) {
508
- const stage = (s.visit ?? 1) > 1 ? `${s.state} (visit ${s.visit})` : s.state;
509
- windows.push({ stage, status: s.status ?? "", start: w[0], end: w[1] });
510
- }
511
- } else {
512
- const stage = label ?? `${progress.workflow ?? "run"} ${progress.runId.slice(0, 8)}`;
513
- const start = Math.min(...states.map(({ w }) => w[0]));
514
- windows.push({
515
- stage,
516
- status: progress.status ?? "",
517
- start,
518
- end: Math.max(...states.map(({ w }) => w[1]))
519
- });
520
- }
521
- }
522
- windows.sort((a, b) => a.start - b.start);
523
- const measure = (start, end) => {
524
- const acc = newAcc(start);
525
- for (const [row, ts] of rows)
526
- if (ts >= start && ts <= end && promptText(row) === undefined)
527
- accumulate(acc, row, ts);
528
- const span = {
529
- workMs: end - start - acc.askMs,
530
- waitMs: acc.askMs,
531
- toolCalls: acc.toolIds.size,
532
- tokens: sumTokens([...acc.messages.values()])
533
- };
534
- return { span, measured: acc.toolIds.size > 0 || acc.messages.size > 0 };
535
- };
536
- const sinceMs = since ? Date.parse(since) : Number.NaN;
537
- const totalStart = Number.isNaN(sinceMs) ? Math.min(...windows.map((w) => w.start)) : sinceMs;
538
- let totalEnd = Math.max(...windows.map((w) => w.end));
539
- if (!Number.isNaN(sinceMs)) {
540
- for (const [, ts] of rows)
541
- if (ts >= sinceMs)
542
- totalEnd = Math.max(totalEnd, ts);
543
- }
544
- const untilMs = until ? Date.parse(until) : Number.NaN;
545
- if (!Number.isNaN(untilMs) && untilMs > totalStart)
546
- totalEnd = untilMs;
547
- const stageRows = windows.map((w) => {
548
- const { span, measured } = measure(w.start, w.end);
549
- return summaryRow(w.stage, w.status, span, measured);
550
- });
551
- const totalMeasure = measure(totalStart, totalEnd);
552
- const statuses = [...new Set(inputs.map((i) => i.progress.status ?? ""))].join("/");
553
- const total = summaryRow("Total", statuses, totalMeasure.span, totalMeasure.measured);
554
- const overlaps = windows.some((w, i) => i > 0 && w.start < (windows[i - 1]?.end ?? 0));
555
- const sum = sumSpans(stageRows.map((r) => ({ ...r, toolCalls: r.toolCalls ?? 0, tokens: r.tokens ?? zeroTokens() })));
556
- const overhead = {
557
- workMs: total.workMs - sum.workMs,
558
- waitMs: total.waitMs - sum.waitMs,
559
- toolCalls: (total.toolCalls ?? 0) - sum.toolCalls,
560
- tokens: Object.fromEntries(Object.keys(sum.tokens).map((k) => [k, (total.tokens?.[k] ?? 0) - sum.tokens[k]]))
561
- };
562
- if (!overlaps && overhead.workMs > 0)
563
- stageRows.push(summaryRow("driver overhead", "", overhead, totalMeasure.measured));
564
- return {
565
- available: true,
566
- rows: stageRows,
567
- total,
568
- actors: actorSplit(rows, totalStart, totalEnd),
569
- gate: { ms: null, reason: "no matching check receipt" },
570
- skippedLines: opts.skippedLines ?? 0
571
- };
572
- }
573
- // packages/app/src/workflow/task-triage.ts
574
- var SENSITIVE_PREFIXES = ["drizzle/", "packages/config/", ".github/", "plugins/sp/hooks/"];
575
- var SENSITIVE_GLOBS = ["apps/server/src/**/auth*", "**/*secret*", "**/*.sql"];
576
- function globToRegex(glob) {
577
- const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replaceAll("**/", "\x00").replaceAll("**", "\x01").replaceAll("*", "[^/]*").replaceAll("\x00", "(?:.*/)?").replaceAll("\x01", ".*");
578
- return new RegExp(`^${escaped}$`, "s");
579
- }
580
- var SENSITIVE_MATCHERS = [
581
- ...SENSITIVE_PREFIXES.map((prefix) => ({
582
- label: prefix,
583
- test: (path) => path.startsWith(prefix)
584
- })),
585
- ...SENSITIVE_GLOBS.map((glob) => {
586
- const regex = globToRegex(glob);
587
- return { label: glob, test: (path) => regex.test(path) };
588
- })
589
- ];
590
- function sensitiveReasonForPath(path) {
591
- const match = SENSITIVE_MATCHERS.find((entry) => entry.test(path));
592
- return match ? `matches sensitive pattern ${match.label}` : null;
593
- }
594
-
595
- // packages/app/src/workflow/plan-projection.ts
596
- function planLetter(index) {
597
- if (!Number.isInteger(index) || index < 0 || index > 25) {
598
- throw new Error(`plan letter out of range: ${index} (expected 0..25)`);
599
- }
600
- return String.fromCharCode(65 + index);
601
- }
602
- function planChild(parent, index) {
603
- if (!Number.isInteger(index) || index < 0 || index > 8) {
604
- throw new Error(`plan child index out of range: ${index} (expected 0..8 under ${parent})`);
605
- }
606
- if (parent.length !== 1 || !/[A-Z]/.test(parent)) {
607
- throw new Error(`plan child parent must be a single letter: ${parent}`);
608
- }
609
- return `${parent}${index + 1}`;
610
- }
611
- var PREPARE_ID = "prepare";
612
- var REPORT_ID = "report";
613
- var PHASE_ID_PREFIX = "phase.";
614
- var TASK_ID_PREFIX = "task.";
615
- var WAVE_SIZE = 24;
616
- var TITLE_CAP = 60;
617
- function isStateMachine(def) {
618
- return def.kind !== "transition-flow" && def.kind !== "dag";
619
- }
620
- function hasDisplay(s) {
621
- return s.display !== undefined;
622
- }
623
- function resolvePhaseTitle(states, phase) {
624
- for (const s of states) {
625
- if (s.display?.phase === phase && s.display.phaseTitle !== undefined)
626
- return s.display.phaseTitle;
627
- }
628
- return phase;
629
- }
630
- function childText(label, title, id) {
631
- return id.startsWith("prepare.") || id.startsWith("task.") || id === PREPARE_ID || id === REPORT_ID ? `${label} ${title}` : `${label} ${title} · ${id}`;
632
- }
633
- function buildPhasedPlan(def) {
634
- if (!isStateMachine(def))
635
- return null;
636
- const annotated = def.states.filter(hasDisplay);
637
- if (annotated.length === 0)
638
- return null;
639
- const items = [
640
- { label: "A", id: PREPARE_ID, outcome: "pending", title: "Prepare", text: "A Prepare" },
641
- {
642
- label: "A1",
643
- id: "prepare.1",
644
- outcome: "pending",
645
- parent: "A",
646
- title: "Quick readiness",
647
- text: "A1 Quick readiness"
648
- },
649
- { label: "A2", id: "prepare.2", outcome: "pending", parent: "A", title: "Prepare Git", text: "A2 Prepare Git" },
650
- {
651
- label: "A3",
652
- id: "prepare.3",
653
- outcome: "pending",
654
- parent: "A",
655
- title: "Publish plan",
656
- text: "A3 Publish plan"
657
- }
658
- ];
659
- const phases = [];
660
- for (const s of def.states) {
661
- if (s.display !== undefined && !phases.includes(s.display.phase))
662
- phases.push(s.display.phase);
663
- }
664
- for (const [phaseIndex, phase] of phases.entries()) {
665
- const letter = planLetter(phaseIndex + 1);
666
- const phaseTitle = resolvePhaseTitle(def.states, phase);
667
- items.push({
668
- label: letter,
669
- id: `${PHASE_ID_PREFIX}${phase}`,
670
- outcome: "pending",
671
- title: phaseTitle,
672
- text: `${letter} ${phaseTitle}`
673
- });
674
- let childIndex = 0;
675
- for (const s of def.states) {
676
- if (s.display?.phase !== phase || s.display.show === "on-entry")
677
- continue;
678
- const label = planChild(letter, childIndex++);
679
- const title = s.display.title ?? s.id;
680
- items.push({
681
- label,
682
- id: s.id,
683
- outcome: "pending",
684
- parent: letter,
685
- title,
686
- text: childText(label, title, s.id)
687
- });
688
- }
689
- }
690
- return items;
691
- }
692
- function validatePhaseTable(def) {
693
- if (!isStateMachine(def))
694
- return [];
695
- const annotated = def.states.filter(hasDisplay);
696
- if (annotated.length === 0)
697
- return [];
698
- const errors = [];
699
- const terminals = new Set(def.terminalStates ?? []);
700
- for (const s of def.states) {
701
- if (s.display === undefined && !terminals.has(s.id)) {
702
- errors.push(`state "${s.id}" has no display phase — every non-terminal state must declare display when any state does`);
703
- }
704
- if (s.display !== undefined && terminals.has(s.id)) {
705
- errors.push(`terminal state "${s.id}" must not declare display`);
706
- }
707
- }
708
- const phases = [];
709
- for (const s of annotated) {
710
- if (!phases.includes(s.display.phase))
711
- phases.push(s.display.phase);
712
- }
713
- if (phases.length > 25) {
714
- errors.push(`${phases.length} phases exceed the 25 plan letters (B-Z); split the workflow or drop phase rows`);
715
- }
716
- for (const phase of phases) {
717
- const members = annotated.filter((s) => s.display.phase === phase);
718
- if (members.length > 9) {
719
- errors.push(`phase "${phase}" has ${members.length} states — at most 9 digit children fit under one letter`);
720
- }
721
- const titles = [...new Set(members.map((s) => s.display.phaseTitle).filter((t) => t !== undefined))];
722
- if (titles.length > 1) {
723
- errors.push(`phase "${phase}" has conflicting phaseTitle values: ${titles.map((t) => `"${t}"`).join(", ")}`);
724
- }
725
- }
726
- return errors;
727
- }
728
- function insertOnEntry(items, stateId, def) {
729
- if (!isStateMachine(def))
730
- return items;
731
- if (items.some((i) => i.id === stateId))
732
- return items;
733
- const display = def.states.find((s) => s.id === stateId)?.display;
734
- if (display === undefined || (def.terminalStates ?? []).includes(stateId))
735
- return items;
736
- const phaseRow = items.find((i) => i.id === `${PHASE_ID_PREFIX}${display.phase}`);
737
- if (phaseRow === undefined)
738
- return items;
739
- const children = items.filter((i) => i.parent === phaseRow.label);
740
- const label = planChild(phaseRow.label, children.length);
741
- const title = display.title ?? stateId;
742
- const item = {
743
- label,
744
- id: stateId,
745
- outcome: "pending",
746
- parent: phaseRow.label,
747
- title,
748
- text: childText(label, title, stateId)
749
- };
750
- const last = children.at(-1) ?? phaseRow;
751
- const at = items.indexOf(last) + 1;
752
- return [...items.slice(0, at), item, ...items.slice(at)];
753
- }
754
- function truncateTitle(name) {
755
- if (name.length <= TITLE_CAP)
756
- return name;
757
- return `${name.slice(0, TITLE_CAP - 1).replace(/[\uD800-\uDBFF]$/, "")}…`;
758
- }
759
- function buildBatchPlan(tasks) {
760
- const waves = [];
761
- for (let offset = 0;offset < tasks.length; offset += WAVE_SIZE) {
762
- const items = [
763
- { label: "A", id: PREPARE_ID, outcome: "pending", title: "Prepare batch", text: "A Prepare batch" },
764
- {
765
- label: "A1",
766
- id: "prepare.1",
767
- outcome: "pending",
768
- parent: "A",
769
- title: "Resolve and freeze task set",
770
- text: "A1 Resolve and freeze task set"
771
- },
772
- {
773
- label: "A2",
774
- id: "prepare.2",
775
- outcome: "pending",
776
- parent: "A",
777
- title: "Order by dependencies",
778
- text: "A2 Order by dependencies"
779
- },
780
- {
781
- label: "A3",
782
- id: "prepare.3",
783
- outcome: "pending",
784
- parent: "A",
785
- title: "Prepare Git",
786
- text: "A3 Prepare Git"
787
- },
788
- {
789
- label: "A4",
790
- id: "prepare.4",
791
- outcome: "pending",
792
- parent: "A",
793
- title: "Publish plan",
794
- text: "A4 Publish plan"
795
- }
796
- ];
797
- tasks.slice(offset, offset + WAVE_SIZE).forEach((task, i) => {
798
- const label = planLetter(i + 1);
799
- const title = `${task.wbs} ${truncateTitle(task.name)}`;
800
- items.push({
801
- label,
802
- id: `${TASK_ID_PREFIX}${task.wbs}`,
803
- outcome: "pending",
804
- title,
805
- text: `${label} ${title}`
806
- });
807
- });
808
- items.push({ label: "Z", id: REPORT_ID, outcome: "pending", title: "Batch report", text: "Z Batch report" });
809
- waves.push(items);
810
- }
811
- return waves;
812
- }
813
- var byWbs = (a, b) => a.localeCompare(b, "en", { numeric: true });
814
- function kahnOrder(nodes, preds) {
815
- const indegree = new Map(nodes.map((n) => [n, preds.get(n)?.size ?? 0]));
816
- const succ = new Map(nodes.map((n) => [n, []]));
817
- for (const [n, ps] of preds)
818
- for (const p of ps)
819
- succ.get(p)?.push(n);
820
- const ready = nodes.filter((n) => indegree.get(n) === 0).sort(byWbs);
821
- const order = [];
822
- for (let n = ready.shift();n !== undefined; n = ready.shift()) {
823
- order.push(n);
824
- for (const s of succ.get(n) ?? []) {
825
- const d = (indegree.get(s) ?? 0) - 1;
826
- indegree.set(s, d);
827
- if (d === 0) {
828
- ready.push(s);
829
- ready.sort(byWbs);
830
- }
831
- }
832
- }
833
- return order;
834
- }
835
- function findCycle(remaining, preds) {
836
- const state = new Map;
837
- const stack = [];
838
- const visit = (n) => {
839
- state.set(n, "visiting");
840
- stack.push(n);
841
- for (const p of [...preds.get(n) ?? []].sort(byWbs)) {
842
- if (!remaining.has(p))
843
- continue;
844
- if (state.get(p) === "visiting")
845
- return [...stack.slice(stack.indexOf(p)), p];
846
- if (!state.has(p)) {
847
- const cycle = visit(p);
848
- if (cycle)
849
- return cycle;
850
- }
851
- }
852
- stack.pop();
853
- state.set(n, "done");
854
- return null;
855
- };
856
- for (const n of [...remaining].sort(byWbs)) {
857
- if (state.has(n))
858
- continue;
859
- const cycle = visit(n);
860
- if (cycle)
861
- return cycle;
862
- }
863
- return [];
864
- }
865
- function scheduleBatch(tasks, opts = {}) {
866
- const byId = new Map;
867
- for (const t of tasks)
868
- if (!byId.has(t.wbs))
869
- byId.set(t.wbs, t);
870
- const nodes = [...byId.keys()].sort(byWbs);
871
- const preds = new Map(nodes.map((n) => [n, new Set((byId.get(n)?.dependencies ?? []).filter((d) => byId.has(d)))]));
872
- const depOrder = kahnOrder(nodes, preds);
873
- if (depOrder.length < nodes.length) {
874
- const cycle = findCycle(new Set(nodes.filter((n) => !depOrder.includes(n))), preds);
875
- return {
876
- order: [],
877
- waves: [],
878
- cycle,
879
- overlaps: [],
880
- mode: "sequential",
881
- reason: `dependency cycle ${cycle.join(" → ")}`
882
- };
883
- }
884
- const files = new Map(nodes.map((n) => [n, new Set((byId.get(n)?.files ?? []).map(normalizeTaskPath))]));
885
- const overlaps = [];
886
- for (let i = 0;i < depOrder.length; i++) {
887
- const before = depOrder[i];
888
- const beforeFiles = files.get(before) ?? new Set;
889
- for (const after of depOrder.slice(i + 1)) {
890
- const shared = [...beforeFiles].filter((f) => files.get(after)?.has(f)).sort();
891
- if (shared.length === 0)
892
- continue;
893
- overlaps.push({ before, after, files: shared });
894
- preds.get(after)?.add(before);
895
- }
896
- }
897
- const order = kahnOrder(nodes, preds);
898
- const level = new Map;
899
- for (const n of order) {
900
- let l = 0;
901
- for (const p of preds.get(n) ?? [])
902
- l = Math.max(l, (level.get(p) ?? 0) + 1);
903
- level.set(n, l);
904
- }
905
- const waves = [];
906
- for (const n of order) {
907
- const l = level.get(n) ?? 0;
908
- waves[l] = [...waves[l] ?? [], n].sort(byWbs);
909
- }
910
- const widest = Math.max(0, ...waves.map((w) => w.length));
911
- const concurrency = opts.concurrency ?? 2;
912
- let reason = `widest wave holds ${widest} independent tasks`;
913
- if (nodes.length < 2)
914
- reason = "fewer than two tasks";
915
- else if (concurrency < 2)
916
- reason = "concurrency is 1";
917
- else if (widest < 2)
918
- reason = "every wave holds one task (dependency or file-overlap chain)";
919
- const mode = nodes.length >= 2 && concurrency >= 2 && widest >= 2 ? "parallel" : "sequential";
920
- return { order, waves, cycle: null, overlaps, mode, reason };
921
- }
922
- function normalizeTaskPath(path) {
923
- return path.trim().replace(/^\.\//, "");
924
- }
925
- function taskPhaseChildren(taskLetter, plan) {
926
- if (!Array.isArray(plan))
927
- return [];
928
- return plan.filter((i) => i.id.startsWith(PHASE_ID_PREFIX) && i.label.length === 1).map((row, i) => {
929
- const label = planChild(taskLetter, i);
930
- return {
931
- label,
932
- id: row.id,
933
- outcome: row.outcome,
934
- parent: taskLetter,
935
- title: row.title,
936
- text: `${label} ${row.title}`
937
- };
938
- });
939
- }
940
- function hostStatus(outcome) {
941
- if (outcome === "completed")
942
- return "completed";
943
- if (outcome === "active")
944
- return "in_progress";
945
- return "pending";
946
- }
947
- function hostText(item) {
948
- const hostDecided = item.outcome === "skipped" || item.outcome === "failed" || item.outcome === "unattempted" || item.outcome === "blocked";
949
- return hostDecided ? `${item.text} [${item.outcome}]` : item.text;
950
- }
951
- export {
952
- NON_ABORTING_PREFLIGHT_CODES,
953
- SENSITIVE_GLOBS,
954
- SENSITIVE_PREFIXES,
955
- accumulate,
956
- actorSplit,
957
- buildBatchPlan,
958
- buildPhasedPlan,
959
- buildRunSummary,
960
- classifyFeaturePreflightFindings,
961
- formatDuration,
962
- formatTokenSplit,
963
- formatTokens,
964
- hostStatus,
965
- hostText,
966
- insertOnEntry,
967
- isCompaction,
968
- newAcc,
969
- planChild,
970
- planLetter,
971
- preflightTask,
972
- promptText,
973
- quickReadiness,
974
- scheduleBatch,
975
- sensitiveReasonForPath,
976
- skillInvocationPrompt,
977
- sumSpans,
978
- sumTokens,
979
- taskPhaseChildren,
980
- validatePhaseTable,
981
- zeroTokens
982
- };