pi-crew 0.9.68 → 0.10.2

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 (253) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/NOTICE.md +21 -0
  3. package/README.md +44 -2
  4. package/agents/analyst.md +1 -1
  5. package/agents/cold-verifier.md +3 -1
  6. package/agents/critic.md +1 -1
  7. package/agents/executor.md +1 -1
  8. package/agents/explorer.md +1 -1
  9. package/agents/planner.md +1 -1
  10. package/agents/reviewer.md +1 -1
  11. package/agents/security-reviewer.md +1 -1
  12. package/agents/test-engineer.md +1 -1
  13. package/agents/verifier.md +1 -1
  14. package/agents/writer.md +1 -1
  15. package/dist/index.mjs +68113 -60774
  16. package/docs/README.md +2 -0
  17. package/docs/actions-reference.md +31 -0
  18. package/docs/commands-reference.md +17 -6
  19. package/docs/resource-formats.md +13 -0
  20. package/package.json +4 -2
  21. package/schema.json +503 -91
  22. package/scripts/resource-sampler.mjs +36 -2
  23. package/skills/requirements-to-task-packet/SKILL.md +26 -0
  24. package/skills/widget-rendering/SKILL.md +7 -7
  25. package/src/agents/agent-config.ts +2 -1
  26. package/src/agents/discover-agents.ts +23 -14
  27. package/src/config/config-merge.ts +183 -0
  28. package/src/config/config-validation.ts +687 -0
  29. package/src/config/config.ts +22 -864
  30. package/src/config/defaults.ts +43 -2
  31. package/src/config/drift-detector.ts +1 -1
  32. package/src/config/env-vars.ts +691 -0
  33. package/src/config/role-tools.ts +11 -9
  34. package/src/config/sanitize-project-config.ts +172 -0
  35. package/src/config/types.ts +49 -1
  36. package/src/extension/async-notifier.ts +25 -2
  37. package/src/extension/crew-cleanup.ts +13 -0
  38. package/src/extension/crew-vibes/config.ts +2 -1
  39. package/src/extension/crew-vibes/footer.ts +19 -0
  40. package/src/extension/crew-vibes/index.ts +11 -1
  41. package/src/extension/plan-orchestrate.ts +132 -0
  42. package/src/extension/register.ts +8 -0
  43. package/src/extension/registration/command-registration.ts +1 -0
  44. package/src/extension/registration/commands/dashboard.ts +158 -0
  45. package/src/extension/registration/commands/index.ts +35 -0
  46. package/src/extension/registration/commands/manage.ts +303 -0
  47. package/src/extension/registration/commands/run.ts +228 -0
  48. package/src/extension/registration/commands/shared.ts +639 -0
  49. package/src/extension/registration/commands/status.ts +60 -0
  50. package/src/extension/registration/commands.ts +13 -1224
  51. package/src/extension/registration/foreground-run-controller.ts +10 -2
  52. package/src/extension/registration/lifecycle-handlers.ts +178 -17
  53. package/src/extension/registration/runtime-cleanup.ts +23 -5
  54. package/src/extension/registration/subagent-tools.ts +218 -9
  55. package/src/extension/registration/team-tool.ts +5 -1
  56. package/src/extension/registration/ui.ts +5 -4
  57. package/src/extension/rpc-hmac.ts +5 -3
  58. package/src/extension/team-tool/api/heartbeat.ts +47 -10
  59. package/src/extension/team-tool/api/plan-approval.ts +9 -0
  60. package/src/extension/team-tool/api/task-claims.ts +109 -40
  61. package/src/extension/team-tool/cancel.ts +84 -50
  62. package/src/extension/team-tool/dispatch/index.ts +1 -0
  63. package/src/extension/team-tool/dispatch/run.ts +4 -1
  64. package/src/extension/team-tool/doctor.ts +103 -1
  65. package/src/extension/team-tool/orchestrate.ts +66 -1
  66. package/src/extension/team-tool/plans.ts +192 -0
  67. package/src/extension/team-tool/respond.ts +197 -65
  68. package/src/extension/team-tool/run-deadline.ts +35 -3
  69. package/src/extension/team-tool/run-intent.ts +63 -0
  70. package/src/extension/team-tool/run.ts +74 -20
  71. package/src/extension/team-tool/status.ts +84 -26
  72. package/src/extension/team-tool.ts +11 -2
  73. package/src/hooks/registry.ts +1 -6
  74. package/src/i18n.ts +9 -0
  75. package/src/prompt/prompt-runtime.ts +521 -2
  76. package/src/prompt/worker-events-channel.ts +173 -0
  77. package/src/runtime/README.md +8 -8
  78. package/src/runtime/async-runner.ts +7 -3
  79. package/src/runtime/background-runner.ts +42 -14
  80. package/src/runtime/broker/broker-issuer.ts +9 -2
  81. package/src/runtime/broker/crew-broker-tokens.ts +43 -6
  82. package/src/runtime/broker/crew-broker.ts +838 -10
  83. package/src/runtime/broker/wait-status-cache.ts +157 -0
  84. package/src/runtime/budget-enforcement.ts +281 -0
  85. package/src/runtime/child-pi/child-pi-constants.ts +8 -0
  86. package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
  87. package/src/runtime/child-pi/child-pi-streams.ts +21 -1
  88. package/src/runtime/child-pi/child-pi-timers.ts +324 -0
  89. package/src/runtime/child-pi/child-pi.ts +97 -201
  90. package/src/runtime/child-pi/mock-fixtures.ts +16 -2
  91. package/src/runtime/crew-agent-records.ts +259 -14
  92. package/src/runtime/delegate-spawn.ts +148 -0
  93. package/src/runtime/detached-run-results.ts +90 -0
  94. package/src/runtime/deterministic-ast.ts +2 -1
  95. package/src/runtime/dispatch-batch.ts +945 -0
  96. package/src/runtime/finalize-run.ts +557 -0
  97. package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
  98. package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
  99. package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
  100. package/src/runtime/group-join.ts +11 -125
  101. package/src/runtime/live-session/live-session-runtime.ts +26 -1
  102. package/src/runtime/merge-gate.ts +32 -10
  103. package/src/runtime/merge-loop.ts +130 -0
  104. package/src/runtime/model/model-budget-summary.ts +53 -0
  105. package/src/runtime/model/model-fallback.ts +36 -2
  106. package/src/runtime/model/pi-args.ts +10 -0
  107. package/src/runtime/model/provider-extensions.ts +10 -0
  108. package/src/runtime/orphan-worker-registry.ts +1 -1
  109. package/src/runtime/output/output-validator.ts +45 -0
  110. package/src/runtime/parent-guard.ts +3 -1
  111. package/src/runtime/peer-dep.ts +2 -1
  112. package/src/runtime/per-write-validator.ts +0 -5
  113. package/src/runtime/pi-spawn.ts +61 -15
  114. package/src/runtime/plan-approval.ts +125 -0
  115. package/src/runtime/plan-replan.ts +151 -0
  116. package/src/runtime/process-status.ts +16 -1
  117. package/src/runtime/recovery/checkpoint.ts +0 -18
  118. package/src/runtime/recovery/crash-recovery.ts +111 -46
  119. package/src/runtime/run-tracker.ts +77 -10
  120. package/src/runtime/scheduler-context.ts +98 -0
  121. package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
  122. package/src/runtime/scheduling/global-worker-cap.ts +2 -1
  123. package/src/runtime/scheduling/nested-slots.ts +70 -0
  124. package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
  125. package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
  126. package/src/runtime/settings-store.ts +219 -0
  127. package/src/runtime/spawn-policy.ts +217 -0
  128. package/src/runtime/stale-reconciler.ts +87 -6
  129. package/src/runtime/subagent-manager.ts +25 -1
  130. package/src/runtime/task-output-context.ts +230 -9
  131. package/src/runtime/task-packet.ts +23 -1
  132. package/src/runtime/task-runner/child-executor.ts +106 -7
  133. package/src/runtime/task-runner/post-execution.ts +125 -1
  134. package/src/runtime/task-runner/pre-execution.ts +39 -1
  135. package/src/runtime/task-runner/prompt-builder.ts +51 -1
  136. package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
  137. package/src/runtime/task-runner/spec-evidence.ts +403 -0
  138. package/src/runtime/task-runner/state-helpers.ts +26 -24
  139. package/src/runtime/task-runner.ts +11 -0
  140. package/src/runtime/team-runner.ts +132 -1673
  141. package/src/runtime/verification/spec-sandbox.ts +255 -0
  142. package/src/runtime/verification/verification-gates.ts +3 -2
  143. package/src/runtime/verification/verification-worktree.ts +2 -1
  144. package/src/runtime/workflow-phase-advance.ts +100 -0
  145. package/src/runtime/workspace-tree.ts +9 -0
  146. package/src/schema/config-schema.ts +66 -26
  147. package/src/schema/sensitive-config-paths.ts +64 -0
  148. package/src/schema/team-tool-schema.ts +13 -3
  149. package/src/state/README.md +4 -10
  150. package/src/state/atomic-write.ts +20 -3
  151. package/src/state/contracts.ts +38 -0
  152. package/src/state/coordination/mailbox.ts +12 -2
  153. package/src/state/event-log/cursor.ts +223 -0
  154. package/src/state/event-log/event-log-rotation.ts +12 -4
  155. package/src/state/event-log/event-log.ts +152 -369
  156. package/src/state/event-log/sequence-cache.ts +373 -0
  157. package/src/state/event-log/worker-atomic-writer.ts +2 -1
  158. package/src/state/stores/active-run-registry.ts +3 -2
  159. package/src/state/stores/manifest-io.ts +237 -0
  160. package/src/state/stores/ownership-map.ts +162 -0
  161. package/src/state/stores/plan-store.ts +241 -0
  162. package/src/state/stores/run-cache.ts +0 -90
  163. package/src/state/stores/spec-store.ts +189 -0
  164. package/src/state/stores/state-store.ts +139 -232
  165. package/src/state/types.ts +199 -0
  166. package/src/ui/dashboard-panes/plan-pane.ts +136 -0
  167. package/src/ui/dashboard-panes/progress-pane.ts +6 -0
  168. package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
  169. package/src/ui/dock-footer.ts +49 -0
  170. package/src/ui/heartbeat-aggregator.ts +9 -1
  171. package/src/ui/inline-panel/agent-pane.ts +375 -0
  172. package/src/ui/inline-panel/agent-transcript.ts +338 -0
  173. package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
  174. package/src/ui/inline-panel/crew-editor.ts +192 -0
  175. package/src/ui/inline-panel/index.ts +290 -0
  176. package/src/ui/inline-panel/panel-rows.ts +37 -0
  177. package/src/ui/inline-panel/panel-selection.ts +157 -0
  178. package/src/ui/inline-panel/panel-store.ts +111 -0
  179. package/src/ui/inline-panel/view-session-store.ts +36 -0
  180. package/src/ui/keybinding-map.ts +54 -13
  181. package/src/ui/pi-ui-compat.ts +9 -0
  182. package/src/ui/powerbar-publisher.ts +52 -1
  183. package/src/ui/run-dashboard.ts +31 -5
  184. package/src/ui/run-snapshot-cache.ts +57 -30
  185. package/src/ui/snapshot-types.ts +6 -1
  186. package/src/ui/widget/index.ts +176 -22
  187. package/src/ui/widget/task-list.ts +198 -0
  188. package/src/ui/widget/widget-formatters.ts +240 -4
  189. package/src/ui/widget/widget-renderer.ts +243 -38
  190. package/src/ui/widget/widget-types.ts +11 -0
  191. package/src/utils/child-process-shield.ts +106 -0
  192. package/src/utils/file-coalescer.ts +0 -4
  193. package/src/utils/fs-errno.ts +66 -0
  194. package/src/utils/fs-watch.ts +1 -1
  195. package/src/utils/internal-error.ts +3 -1
  196. package/src/utils/paths.ts +11 -3
  197. package/src/utils/redaction.ts +7 -0
  198. package/src/utils/safe-abort.ts +45 -0
  199. package/src/utils/task-name-generator.ts +1 -8
  200. package/src/workflows/discover-workflows.ts +20 -2
  201. package/src/workflows/validate-workflow.ts +7 -1
  202. package/src/workflows/workflow-config.ts +17 -0
  203. package/src/workflows/workflow-serializer.ts +3 -0
  204. package/src/worktree/worktree-manager.ts +22 -0
  205. package/workflows/default.workflow.md +36 -26
  206. package/workflows/strict-fast-fix.workflow.md +26 -0
  207. package/src/agents/agent-search.ts +0 -98
  208. package/src/benchmark/benchmark-runner.ts +0 -313
  209. package/src/benchmark/feedback-loop.ts +0 -73
  210. package/src/config/resilient-parser.ts +0 -117
  211. package/src/extension/crew-vibes/cat-frames.ts +0 -18
  212. package/src/extension/result-watcher.ts +0 -139
  213. package/src/observability/exporters/prometheus-exporter.ts +0 -54
  214. package/src/observability/metric-retention.ts +0 -64
  215. package/src/runtime/compaction/compaction-summary.ts +0 -278
  216. package/src/runtime/errors/crew-errors.ts +0 -162
  217. package/src/runtime/live-session/intercom-bridge.ts +0 -187
  218. package/src/runtime/loop-gates.ts +0 -128
  219. package/src/runtime/metric-parser.ts +0 -36
  220. package/src/runtime/output/stream-preview.ts +0 -184
  221. package/src/runtime/output/tool-progress.ts +0 -278
  222. package/src/runtime/phase-tracker.ts +0 -385
  223. package/src/runtime/pipeline-runner.ts +0 -523
  224. package/src/runtime/process/process-lifecycle.ts +0 -491
  225. package/src/runtime/recovery/retry-runner.ts +0 -330
  226. package/src/runtime/run-drift.ts +0 -219
  227. package/src/runtime/task-quality.ts +0 -199
  228. package/src/runtime/task-runner/run-projection.ts +0 -128
  229. package/src/runtime/verification/post-checks.ts +0 -142
  230. package/src/state/coordination/schedule.ts +0 -166
  231. package/src/state/event-log/jsonl-writer.ts +0 -115
  232. package/src/state/hook-instinct-bridge.ts +0 -94
  233. package/src/state/hook-integrations.ts +0 -51
  234. package/src/state/session-state-map.ts +0 -51
  235. package/src/state/stores/blob-store.ts +0 -308
  236. package/src/state/stores/instinct-store.ts +0 -275
  237. package/src/state/stores/observation-store.ts +0 -176
  238. package/src/state/tiered-eval.ts +0 -480
  239. package/src/state/types-eval.ts +0 -58
  240. package/src/tools/safe-bash-extension.ts +0 -54
  241. package/src/tools/safe-bash.ts +0 -505
  242. package/src/ui/agent-management-overlay.ts +0 -160
  243. package/src/ui/crew-footer.ts +0 -102
  244. package/src/ui/crew-select-list.ts +0 -114
  245. package/src/ui/dashboard-panes/capability-pane.ts +0 -77
  246. package/src/ui/transcript-entries.ts +0 -256
  247. package/src/utils/conflict-detect.ts +0 -721
  248. package/src/utils/fingerprint.ts +0 -180
  249. package/src/utils/gh-protocol.ts +0 -556
  250. package/src/utils/project-detector.ts +0 -160
  251. package/src/utils/sse-parser.ts +0 -131
  252. package/src/workflows/cost-estimator.ts +0 -34
  253. package/src/workflows/intermediate-store.ts +0 -166
@@ -1,160 +0,0 @@
1
- import { execFileSync } from "node:child_process";
2
- import { createHash } from "node:crypto";
3
- import * as fs from "node:fs";
4
- import * as path from "node:path";
5
-
6
- /**
7
- * Information about a detected project.
8
- */
9
- export interface ProjectInfo {
10
- projectId: string;
11
- projectName: string;
12
- }
13
-
14
- /**
15
- * Create a SHA256 hash of a string, returning the first 16 characters.
16
- */
17
- function hashPath(input: string): string {
18
- return createHash("sha256").update(input).digest("hex").slice(0, 16);
19
- }
20
-
21
- /**
22
- * Extract the repository name from a git remote URL.
23
- * Handles formats like:
24
- * - https://github.com/user/repo.git
25
- * - https://github.com/user/repo
26
- * - git@github.com:user/repo.git
27
- * - ssh://git@github.com/user/repo.git
28
- */
29
- function extractRepoName(remoteUrl: string): string | null {
30
- // Remove .git suffix
31
- const withoutGit = remoteUrl.replace(/\.git$/, "");
32
-
33
- // Handle SSH format: git@github.com:user/repo
34
- const sshMatch = withoutGit.match(/^git@[^:]+:(.+)$/);
35
- if (sshMatch) {
36
- const parts = sshMatch[1].split("/");
37
- return parts[parts.length - 1] ?? null;
38
- }
39
-
40
- // Handle HTTPS/HTTP/SSH URL format
41
- try {
42
- const url = new URL(withoutGit);
43
- const pathParts = url.pathname.split("/").filter(Boolean);
44
- return pathParts[pathParts.length - 1] ?? null;
45
- } catch {
46
- // Not a valid URL, try to extract from path-like string
47
- const parts = withoutGit.split("/").filter(Boolean);
48
- return parts[parts.length - 1] ?? null;
49
- }
50
- }
51
-
52
- /**
53
- * Try to get the repository name from git remote origin.
54
- */
55
- function tryGitRemote(cwd: string): string | null {
56
- try {
57
- const remoteUrl = execFileSync("git", ["remote", "get-url", "origin"], {
58
- cwd,
59
- encoding: "utf-8",
60
- stdio: ["pipe", "pipe", "ignore"],
61
- }).trim();
62
-
63
- if (!remoteUrl) return null;
64
-
65
- const repoName = extractRepoName(remoteUrl);
66
- if (!repoName) return null;
67
-
68
- // Verify it's a valid directory name (no path traversal attempts)
69
- if (repoName.includes("..") || repoName.includes("/")) return null;
70
-
71
- return repoName;
72
- } catch {
73
- return null;
74
- }
75
- }
76
-
77
- /**
78
- * Try to get the directory name from git toplevel.
79
- */
80
- function tryGitToplevel(cwd: string): string | null {
81
- try {
82
- const toplevel = execFileSync("git", ["rev-parse", "--show-toplevel"], {
83
- cwd,
84
- encoding: "utf-8",
85
- stdio: ["pipe", "pipe", "ignore"],
86
- }).trim();
87
-
88
- if (!toplevel || !fs.existsSync(toplevel)) return null;
89
-
90
- const dirName = path.basename(toplevel);
91
- // Verify it's a valid directory name
92
- if (!dirName || dirName.includes("..") || dirName.includes("/")) return null;
93
-
94
- return dirName;
95
- } catch {
96
- return null;
97
- }
98
- }
99
-
100
- /**
101
- * Detect project information from a working directory.
102
- * Uses a hierarchy of detection methods, falling through on failure.
103
- *
104
- * Detection hierarchy (first successful wins):
105
- * 1. CLAUDE_PROJECT_DIR env var → hash the path, use dir name as name
106
- * 2. git remote get-url origin → extract repo name from URL
107
- * 3. git rev-parse --show-toplevel → use dir name
108
- * 4. Fallback: hash(cwd) for projectId, path.basename(cwd) for name
109
- */
110
- export function detectProjectId(cwd: string): ProjectInfo {
111
- // Normalize the cwd path
112
- const normalizedCwd = path.resolve(cwd);
113
-
114
- // Method 1: Check CLAUDE_PROJECT_DIR env var
115
- const claudeProjectDir = process.env.CLAUDE_PROJECT_DIR;
116
- if (claudeProjectDir?.trim()) {
117
- const resolvedPath = path.resolve(claudeProjectDir.trim());
118
- const projectName = path.basename(resolvedPath);
119
- const projectId = hashPath(resolvedPath);
120
- return { projectId, projectName };
121
- }
122
-
123
- // Method 2: Try git remote origin
124
- const repoName = tryGitRemote(normalizedCwd);
125
- if (repoName) {
126
- const projectId = hashPath(normalizedCwd);
127
- return { projectId, projectName: repoName };
128
- }
129
-
130
- // Method 3: Try git toplevel
131
- const toplevelName = tryGitToplevel(normalizedCwd);
132
- if (toplevelName) {
133
- const projectId = hashPath(normalizedCwd);
134
- return { projectId, projectName: toplevelName };
135
- }
136
-
137
- // Method 4: Fallback - hash the cwd and use basename
138
- const projectId = hashPath(normalizedCwd);
139
- const projectName = path.basename(normalizedCwd) || "unknown";
140
- return { projectId, projectName };
141
- }
142
-
143
- /**
144
- * Get the storage directory for a specific project's instincts.
145
- * @param projectId - The project identifier (from detectProjectId)
146
- * @param crewRoot - The crew root directory (e.g., from projectCrewRoot)
147
- * @returns The path to the project's storage directory
148
- */
149
- export function getProjectStorageDir(projectId: string, crewRoot: string): string {
150
- return path.join(crewRoot, "instincts", "projects", projectId);
151
- }
152
-
153
- /**
154
- * Get the global storage directory for instincts.
155
- * @param crewRoot - The crew root directory
156
- * @returns The path to the global storage directory
157
- */
158
- export function getGlobalStorageDir(crewRoot: string): string {
159
- return path.join(crewRoot, "instincts", "global");
160
- }
@@ -1,131 +0,0 @@
1
- /** A single Server-Sent Event */
2
- export interface ServerSentEvent {
3
- event: string | null;
4
- data: string;
5
- raw: string[];
6
- }
7
-
8
- /** L1: Maximum number of raw lines before discarding an oversized event. */
9
- const MAX_EVENT_LINES = 1000;
10
-
11
- /** L2: Maximum data size per line to prevent unbounded memory usage. */
12
- const MAX_DATA_SIZE = 100000; // 100KB per line
13
-
14
- /** Read newline-delimited lines from a text ReadableStream, buffering partial chunks. */
15
- async function* readLines(stream: ReadableStream<string>, signal?: AbortSignal): AsyncGenerator<string> {
16
- const reader = stream.getReader();
17
- let buffer = "";
18
- try {
19
- while (true) {
20
- if (signal?.aborted) return;
21
- const { done, value } = await reader.read();
22
- if (done) {
23
- if (buffer.length > 0) yield buffer;
24
- return;
25
- }
26
- buffer += value;
27
- let idx: number;
28
- while ((idx = buffer.indexOf("\n")) !== -1) {
29
- const line = buffer.endsWith("\r\n", idx + 1) ? buffer.slice(0, idx - 1) : buffer.slice(0, idx);
30
- yield line;
31
- buffer = buffer.slice(idx + 1);
32
- }
33
- }
34
- } finally {
35
- reader.releaseLock();
36
- }
37
- }
38
-
39
- /** Parse SSE events from a byte stream */
40
- export async function* readSseEvents(stream: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<ServerSentEvent> {
41
- const textStream = stream.pipeThrough(new TextDecoderStream() as unknown as TransformStream<Uint8Array, string>);
42
- let currentEvent: string | null = null;
43
- let currentData: string[] = [];
44
- let currentRaw: string[] = [];
45
-
46
- function flush(): ServerSentEvent | null {
47
- if (currentData.length === 0 && currentEvent === null) return null;
48
- const evt: ServerSentEvent = {
49
- event: currentEvent,
50
- data: currentData.join("\n"),
51
- raw: currentRaw,
52
- };
53
- currentEvent = null;
54
- currentData = [];
55
- currentRaw = [];
56
- return evt;
57
- }
58
-
59
- for await (const line of readLines(textStream, signal)) {
60
- if (signal?.aborted) return;
61
-
62
- // Empty line → dispatch
63
- if (line === "") {
64
- const evt = flush();
65
- if (evt) yield evt;
66
- continue;
67
- }
68
-
69
- // Comment
70
- if (line.startsWith(":")) continue;
71
-
72
- // [DONE] sentinel
73
- if (line === "[DONE]") return;
74
-
75
- const colonIdx = line.indexOf(":");
76
- if (colonIdx === -1) {
77
- // Field with no value
78
- const field = line;
79
- if (field === "event") {
80
- currentEvent = "";
81
- } else if (field === "data") {
82
- currentData.push("");
83
- }
84
- currentRaw.push(line);
85
- continue;
86
- }
87
-
88
- const field = line.slice(0, colonIdx);
89
- let value = line.slice(colonIdx + 1);
90
- // Remove leading space after colon per SSE spec
91
- if (value.startsWith(" ")) value = value.slice(1);
92
-
93
- currentRaw.push(line);
94
-
95
- // L1: Guard against unbounded memory growth (line count)
96
- if (currentRaw.length > MAX_EVENT_LINES) {
97
- const evt = flush();
98
- if (evt) yield evt;
99
- continue;
100
- }
101
-
102
- // L2: Guard against unbounded memory growth (data size per line)
103
- if (value.length > MAX_DATA_SIZE) {
104
- // Truncate oversized data to prevent memory issues
105
- value = value.slice(0, MAX_DATA_SIZE);
106
- }
107
-
108
- if (field === "event") {
109
- currentEvent = value;
110
- } else if (field === "data") {
111
- currentData.push(value);
112
- }
113
- // id / retry fields ignored
114
- }
115
-
116
- // Flush any remaining event at end of stream
117
- const evt = flush();
118
- if (evt) yield evt;
119
- }
120
-
121
- /** Parse SSE events and yield parsed JSON data objects */
122
- export async function* readSseJson<T>(stream: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<T> {
123
- for await (const evt of readSseEvents(stream, signal)) {
124
- try {
125
- const parsed: T = JSON.parse(evt.data) as T;
126
- yield parsed;
127
- } catch {
128
- // intentional parse fallback
129
- }
130
- }
131
- }
@@ -1,34 +0,0 @@
1
- import type { WorkflowConfig } from "./workflow-config.ts";
2
-
3
- // Rough token estimates per role (input + output combined)
4
- const ROLE_TOKEN_ESTIMATES: Record<string, number> = {
5
- explorer: 15000,
6
- planner: 20000,
7
- executor: 25000,
8
- reviewer: 12000,
9
- "security-reviewer": 12000,
10
- "test-engineer": 15000,
11
- analyst: 18000,
12
- writer: 15000,
13
- verifier: 10000,
14
- critic: 12000,
15
- };
16
-
17
- // Rough cost per 1K tokens (USD) — using a mid-range model price
18
- const COST_PER_1K_TOKENS = 0.003;
19
-
20
- export interface CostEstimate {
21
- totalTokens: number;
22
- estimatedCostUSD: number;
23
- perStep: Array<{ stepId: string; role: string; tokens: number }>;
24
- }
25
-
26
- export function estimateWorkflowCost(workflow: WorkflowConfig): CostEstimate {
27
- const perStep = workflow.steps.map((step) => {
28
- const tokens = ROLE_TOKEN_ESTIMATES[step.role] ?? 15000; // default 15K
29
- return { stepId: step.id, role: step.role, tokens };
30
- });
31
- const totalTokens = perStep.reduce((sum, s) => sum + s.tokens, 0);
32
- const estimatedCostUSD = (totalTokens / 1000) * COST_PER_1K_TOKENS;
33
- return { totalTokens, estimatedCostUSD, perStep };
34
- }
@@ -1,166 +0,0 @@
1
- /**
2
- * Phase-gated intermediate store — persist workflow step outputs to disk.
3
- *
4
- * Pattern origin: Understand-Anything 7-phase pipeline where each phase
5
- * writes structured JSON to intermediate/ directory. Phase N reads Phase N-1
6
- * output from disk (not context). Enables:
7
- * - Context isolation between steps
8
- * - Incremental re-runs (skip completed phases)
9
- * - Debugging (inspect intermediate outputs)
10
- */
11
-
12
- import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
13
- import path from "node:path";
14
- import { logInternalError } from "../utils/internal-error.ts";
15
- import { isSafePathId } from "../utils/safe-paths.ts";
16
-
17
- // ── Types ────────────────────────────────────────────────────────────────
18
-
19
- export interface IntermediateOutput {
20
- phase: string;
21
- stepId: string;
22
- timestamp: string;
23
- data: unknown;
24
- }
25
-
26
- export interface IntermediateStoreConfig {
27
- /** Root directory for intermediates (e.g., ".crew/intermediate/") */
28
- intermediateDir: string;
29
- /** File patterns to preserve across runs (e.g., ["scan-result.json"]) */
30
- preservePatterns: string[];
31
- }
32
-
33
- // ── Store Operations ─────────────────────────────────────────────────────
34
-
35
- const DEFAULT_CONFIG: IntermediateStoreConfig = {
36
- intermediateDir: ".crew/intermediate",
37
- preservePatterns: [],
38
- };
39
-
40
- /**
41
- * Ensure the intermediate directory exists.
42
- */
43
- export function ensureIntermediateDir(config: Partial<IntermediateStoreConfig> = {}): string {
44
- const dir = config.intermediateDir ?? DEFAULT_CONFIG.intermediateDir;
45
- mkdirSync(dir, { recursive: true });
46
- return dir;
47
- }
48
-
49
- /**
50
- * Write an intermediate output for a phase.
51
- *
52
- * @param config - Store configuration
53
- * @param phase - Phase name (e.g., "explore", "analyze")
54
- * @param stepId - Step ID for correlation
55
- * @param data - Phase output data
56
- * @returns Path to the written file
57
- */
58
- export function writeIntermediate(config: Partial<IntermediateStoreConfig>, phase: string, stepId: string, data: unknown): string {
59
- // Security: validate phase/stepId before building the filename to prevent
60
- // path traversal via poisoned phase or stepId.
61
- if (!isSafePathId(phase) || !isSafePathId(stepId)) {
62
- throw new Error("Invalid phase or stepId for intermediate store");
63
- }
64
- const dir = ensureIntermediateDir(config);
65
- const filename = `${phase}-${stepId}.json`;
66
- const filePath = path.join(dir, filename);
67
-
68
- const output: IntermediateOutput = {
69
- phase,
70
- stepId,
71
- timestamp: new Date().toISOString(),
72
- data,
73
- };
74
-
75
- writeFileSync(filePath, JSON.stringify(output, null, 2), "utf-8");
76
- return filePath;
77
- }
78
-
79
- /**
80
- * Read an intermediate output for a phase.
81
- *
82
- * @param config - Store configuration
83
- * @param phase - Phase name
84
- * @param stepId - Step ID
85
- * @returns Parsed intermediate output, or undefined if not found
86
- */
87
- export function readIntermediate(config: Partial<IntermediateStoreConfig>, phase: string, stepId: string): IntermediateOutput | undefined {
88
- // M-2 fix (code-review 2026-06-23): validate phase/stepId before building the
89
- // filename to prevent path traversal via a poisoned stepId.
90
- if (!isSafePathId(phase) || !isSafePathId(stepId)) return undefined;
91
- const dir = config.intermediateDir ?? DEFAULT_CONFIG.intermediateDir;
92
- const filename = `${phase}-${stepId}.json`;
93
- const filePath = path.join(dir, filename);
94
-
95
- if (!existsSync(filePath)) return undefined;
96
-
97
- try {
98
- const content = readFileSync(filePath, "utf-8");
99
- return JSON.parse(content) as IntermediateOutput;
100
- } catch (error) {
101
- logInternalError("intermediate-store.read", error, `filePath=${filePath}`);
102
- return undefined;
103
- }
104
- }
105
-
106
- /**
107
- * Read the most recent intermediate output for any phase.
108
- *
109
- * Useful when you don't know the exact stepId but want the latest
110
- * output from a phase (e.g., for incremental re-runs).
111
- */
112
- export function readLatestIntermediate(config: Partial<IntermediateStoreConfig>, phase: string): IntermediateOutput | undefined {
113
- const dir = config.intermediateDir ?? DEFAULT_CONFIG.intermediateDir;
114
- if (!existsSync(dir)) return undefined;
115
-
116
- const files = readdirSync(dir)
117
- .filter((f) => f.startsWith(`${phase}-`) && f.endsWith(".json"))
118
- .sort()
119
- .reverse(); // most recent first
120
-
121
- if (files.length === 0) return undefined;
122
-
123
- try {
124
- const content = readFileSync(path.join(dir, files[0]!), "utf-8");
125
- return JSON.parse(content) as IntermediateOutput;
126
- } catch {
127
- return undefined;
128
- }
129
- }
130
-
131
- /**
132
- * Clean up intermediate files, preserving specified patterns.
133
- *
134
- * @param config - Store configuration
135
- * @returns Number of files removed
136
- */
137
- export function cleanupIntermediates(config: Partial<IntermediateStoreConfig> = {}): number {
138
- const dir = config.intermediateDir ?? DEFAULT_CONFIG.intermediateDir;
139
- const preserve = config.preservePatterns ?? DEFAULT_CONFIG.preservePatterns;
140
-
141
- if (!existsSync(dir)) return 0;
142
-
143
- const files = readdirSync(dir);
144
- let removed = 0;
145
-
146
- for (const file of files) {
147
- const shouldPreserve = preserve.some((pattern) => file.includes(pattern));
148
- if (!shouldPreserve) {
149
- try {
150
- unlinkSync(path.join(dir, file));
151
- removed++;
152
- } catch (error) {
153
- logInternalError("intermediate-store.cleanup", error, `file=${file}`);
154
- }
155
- }
156
- }
157
-
158
- return removed;
159
- }
160
-
161
- /**
162
- * Check if a phase has completed (intermediate exists).
163
- */
164
- export function hasPhaseCompleted(config: Partial<IntermediateStoreConfig>, phase: string, stepId: string): boolean {
165
- return readIntermediate(config, phase, stepId) !== undefined;
166
- }