@midhunxavier/code-flow 0.0.0-stage → 0.1.0-preview.3

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 (319) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -2
  3. package/bin/codeflow.mjs +8 -0
  4. package/bin/launcher.mjs +127 -0
  5. package/dist/remote-host.json +1 -0
  6. package/licenses/inter.txt +92 -0
  7. package/licenses/preact.txt +21 -0
  8. package/licenses/signals-core.txt +21 -0
  9. package/licenses/signals.txt +21 -0
  10. package/licenses/typescript-notices.txt +193 -0
  11. package/licenses/typescript.txt +55 -0
  12. package/licenses/uqr.txt +22 -0
  13. package/licenses/xterm-fit.txt +19 -0
  14. package/licenses/xterm.txt +21 -0
  15. package/package.json +34 -4
  16. package/public/assets/changes-62ZIEQSN.js +1 -0
  17. package/public/assets/chunk-2PZVOKID.js +1 -0
  18. package/public/assets/chunk-4D5IENEB.js +1 -0
  19. package/public/assets/chunk-5SARP5MU.js +1 -0
  20. package/public/assets/chunk-63YLZ3PR.js +1 -0
  21. package/public/assets/chunk-7FUHKF2M.js +1 -0
  22. package/public/assets/chunk-APQEVJ2K.js +1 -0
  23. package/public/assets/chunk-CGOZQMEZ.js +2 -0
  24. package/public/assets/chunk-J4VYS3KV.js +2 -0
  25. package/public/assets/chunk-L7TUCIYF.js +1 -0
  26. package/public/assets/chunk-SIHDWM2Y.js +154 -0
  27. package/public/assets/chunk-TEJVYIKC.js +1 -0
  28. package/public/assets/code-browser-VB2HGZSG.js +4 -0
  29. package/public/assets/filtered-tasks-ES5CP4KV.js +1 -0
  30. package/public/assets/interactive-browser-7F5ZXRUV.js +7 -0
  31. package/public/assets/join-project-6RTFHNCA.js +1 -0
  32. package/public/assets/main-65EW73SB.js +1 -0
  33. package/public/assets/project-invitations-view-GMPWGWYM.js +1 -0
  34. package/public/assets/settings-UB5Q4AQM.js +9 -0
  35. package/public/assets/styles-18792BB4.css +32 -0
  36. package/public/assets/team-server-wizard-2GLDWS6D.js +8 -0
  37. package/public/assets/terminal-IWAIM4DE.js +62 -0
  38. package/public/assets.json +1 -0
  39. package/public/bundle-meta.json +1 -0
  40. package/public/docs/.nojekyll +0 -0
  41. package/public/docs/accessibility.html +19 -0
  42. package/public/docs/acp-agents.html +117 -0
  43. package/public/docs/agent-containers.html +79 -0
  44. package/public/docs/api.html +1130 -0
  45. package/public/docs/app.js +342 -0
  46. package/public/docs/application-improvements.html +275 -0
  47. package/public/docs/architecture-explorer.html +19 -0
  48. package/public/docs/architecture.html +149 -0
  49. package/public/docs/auto-team.html +148 -0
  50. package/public/docs/automations.html +109 -0
  51. package/public/docs/checkpoint-review.html +37 -0
  52. package/public/docs/code-hosts.html +35 -0
  53. package/public/docs/code-intelligence.html +35 -0
  54. package/public/docs/configuration.html +346 -0
  55. package/public/docs/context-inspection.html +23 -0
  56. package/public/docs/coordinated-changes.html +28 -0
  57. package/public/docs/deferred-runs.html +25 -0
  58. package/public/docs/dependency-discovery.html +19 -0
  59. package/public/docs/documentation.html +84 -0
  60. package/public/docs/dynamic-teams-evaluation.html +174 -0
  61. package/public/docs/dynamic-teams-implementation-plan.html +882 -0
  62. package/public/docs/dynamic-teams-implementation-review-1.html +141 -0
  63. package/public/docs/dynamic-teams-plan-review-0.html +204 -0
  64. package/public/docs/extensions.html +42 -0
  65. package/public/docs/external-sources.html +96 -0
  66. package/public/docs/filesystem-policy.html +26 -0
  67. package/public/docs/filtered-tasks.html +44 -0
  68. package/public/docs/font-license.txt +92 -0
  69. package/public/docs/getting-started.html +143 -0
  70. package/public/docs/github-review-threads.html +26 -0
  71. package/public/docs/github.html +157 -0
  72. package/public/docs/help.html +69 -0
  73. package/public/docs/index.html +88 -0
  74. package/public/docs/installation.html +80 -0
  75. package/public/docs/launch-profiles.html +48 -0
  76. package/public/docs/maintenance.html +60 -0
  77. package/public/docs/memory-claims.html +31 -0
  78. package/public/docs/memory-storage.html +58 -0
  79. package/public/docs/notifications.html +39 -0
  80. package/public/docs/patterns.html +195 -0
  81. package/public/docs/performance-results.html +210 -0
  82. package/public/docs/performance.html +166 -0
  83. package/public/docs/project-conventions.html +22 -0
  84. package/public/docs/project-first-navigation-plan.html +587 -0
  85. package/public/docs/project-teams-implementation-plan.html +772 -0
  86. package/public/docs/project-teams.html +129 -0
  87. package/public/docs/provider-compatibility.html +20 -0
  88. package/public/docs/releasing.html +65 -0
  89. package/public/docs/remote-workers.html +64 -0
  90. package/public/docs/review-inbox.html +26 -0
  91. package/public/docs/run-storage.html +23 -0
  92. package/public/docs/runs.html +141 -0
  93. package/public/docs/search.json +1 -0
  94. package/public/docs/security.html +133 -0
  95. package/public/docs/shared-projects.html +31 -0
  96. package/public/docs/ssh-projects.html +60 -0
  97. package/public/docs/style.css +768 -0
  98. package/public/docs/task-reruns.html +30 -0
  99. package/public/docs/team-collaboration.html +412 -0
  100. package/public/docs/verification.html +22 -0
  101. package/public/docs/vscode.html +53 -0
  102. package/public/docs/worker-permission-verification.html +131 -0
  103. package/public/index.html +17 -0
  104. package/scripts/check-node.mjs +7 -0
  105. package/scripts/codeflow.mjs +55 -0
  106. package/scripts/computer-use.swift +64 -0
  107. package/scripts/pty.py +273 -0
  108. package/src/acp-connections.js +110 -0
  109. package/src/adapters/acp-presets.js +37 -0
  110. package/src/adapters/acp-types.js +0 -0
  111. package/src/adapters/acp.js +328 -0
  112. package/src/adapters/agent-tools.js +28 -0
  113. package/src/adapters/agy-auth.js +67 -0
  114. package/src/adapters/agy-isolation.js +242 -0
  115. package/src/adapters/agy-tools.js +45 -0
  116. package/src/adapters/agy.js +326 -0
  117. package/src/adapters/claude-stream.js +58 -0
  118. package/src/adapters/claude.js +282 -0
  119. package/src/adapters/codex-app-server.js +555 -0
  120. package/src/adapters/codex.js +311 -0
  121. package/src/adapters/errors.js +57 -0
  122. package/src/adapters/index.js +91 -0
  123. package/src/adapters/managed-permissions.js +37 -0
  124. package/src/adapters/model-discovery.js +100 -0
  125. package/src/adapters/process.js +433 -0
  126. package/src/adapters/team-tools.js +103 -0
  127. package/src/adapters/types.js +0 -0
  128. package/src/agent-browser.js +688 -0
  129. package/src/agent-computer.js +88 -0
  130. package/src/agent-container.js +155 -0
  131. package/src/agent-team-definitions.js +131 -0
  132. package/src/agent-tool-client.mjs +79 -0
  133. package/src/agent-tools.js +296 -0
  134. package/src/approvals.js +134 -0
  135. package/src/architecture.js +456 -0
  136. package/src/assets.js +44 -0
  137. package/src/automation.js +901 -0
  138. package/src/browser-files.js +293 -0
  139. package/src/browser-ownership.js +150 -0
  140. package/src/certificate-renewal.js +75 -0
  141. package/src/chat-family.js +25 -0
  142. package/src/chat-handoff.js +87 -0
  143. package/src/checkpoint-review.js +72 -0
  144. package/src/checks.js +303 -0
  145. package/src/code-host-git.js +102 -0
  146. package/src/code-hosts-api.js +99 -0
  147. package/src/code-hosts.js +482 -0
  148. package/src/code-intelligence-worker.mjs +452 -0
  149. package/src/code-intelligence.js +141 -0
  150. package/src/collab/api.js +365 -0
  151. package/src/collab/checkpoints.js +198 -0
  152. package/src/collab/connections.js +139 -0
  153. package/src/collab/host-execution.js +347 -0
  154. package/src/collab/hub-client.js +210 -0
  155. package/src/collab/link-api.js +229 -0
  156. package/src/collab/link.js +595 -0
  157. package/src/collab/model.js +496 -0
  158. package/src/collab/runner.js +1014 -0
  159. package/src/collab/store.js +1452 -0
  160. package/src/collab/terminals.js +361 -0
  161. package/src/collaboration-reviews.js +116 -0
  162. package/src/config.js +48 -0
  163. package/src/context-budget.js +14 -0
  164. package/src/context-inspection.js +204 -0
  165. package/src/context.js +154 -0
  166. package/src/conventions.js +283 -0
  167. package/src/coordinated-api.js +247 -0
  168. package/src/deferred-launch.js +82 -0
  169. package/src/deferred-runs.js +515 -0
  170. package/src/dependency-health.js +35 -0
  171. package/src/diff.js +43 -0
  172. package/src/docs-help.js +316 -0
  173. package/src/doctor.js +232 -0
  174. package/src/documentation-changes.js +20 -0
  175. package/src/engine.js +5258 -0
  176. package/src/env.js +44 -0
  177. package/src/execution-budget.js +8 -0
  178. package/src/experience.js +50 -0
  179. package/src/extensions.js +144 -0
  180. package/src/filesystem-access.js +107 -0
  181. package/src/filesystem-policy.js +238 -0
  182. package/src/filtered-task-limits.js +93 -0
  183. package/src/filtered-tasks-api.js +53 -0
  184. package/src/filtered-tasks.js +576 -0
  185. package/src/frame-policy.js +66 -0
  186. package/src/fs.js +57 -0
  187. package/src/git.js +626 -0
  188. package/src/github-review-threads.js +181 -0
  189. package/src/github.js +538 -0
  190. package/src/http.js +69 -0
  191. package/src/improve-settings.js +38 -0
  192. package/src/improve.js +187 -0
  193. package/src/integrations.js +373 -0
  194. package/src/integrity.js +259 -0
  195. package/src/journey-api.js +77 -0
  196. package/src/journey-replay.js +65 -0
  197. package/src/journey-store.js +97 -0
  198. package/src/journeys.js +60 -0
  199. package/src/json.js +81 -0
  200. package/src/launch-profiles.js +292 -0
  201. package/src/learnings.js +80 -0
  202. package/src/local-app-url.js +19 -0
  203. package/src/log.js +39 -0
  204. package/src/maintenance.js +335 -0
  205. package/src/memory-analysis.js +182 -0
  206. package/src/memory-claims.js +153 -0
  207. package/src/memory-sqlite.js +236 -0
  208. package/src/memory.js +552 -0
  209. package/src/merge-validation.js +58 -0
  210. package/src/model-catalog.js +239 -0
  211. package/src/model-research.js +91 -0
  212. package/src/notes.js +176 -0
  213. package/src/notifications.js +413 -0
  214. package/src/patterns/auto-team-model.js +230 -0
  215. package/src/patterns/auto-team.js +816 -0
  216. package/src/patterns/council.js +199 -0
  217. package/src/patterns/debate.js +188 -0
  218. package/src/patterns/findings.js +141 -0
  219. package/src/patterns/index.js +21 -0
  220. package/src/patterns/loop.js +158 -0
  221. package/src/patterns/pipeline.js +125 -0
  222. package/src/patterns/prompts.js +181 -0
  223. package/src/patterns/race.js +147 -0
  224. package/src/patterns/schemas.js +40 -0
  225. package/src/patterns/solo.js +46 -0
  226. package/src/patterns/supervisor.js +235 -0
  227. package/src/patterns/task-draft.js +29 -0
  228. package/src/patterns/task-review.js +45 -0
  229. package/src/patterns/tdd.js +231 -0
  230. package/src/patterns/team-strategies.js +67 -0
  231. package/src/performance.js +43 -0
  232. package/src/persistence-status.js +35 -0
  233. package/src/phone-sessions.js +96 -0
  234. package/src/preview-overlay.js +392 -0
  235. package/src/preview.js +230 -0
  236. package/src/privacy.js +23 -0
  237. package/src/project-report.js +22 -0
  238. package/src/project-team-budget.js +22 -0
  239. package/src/project-team-model.js +225 -0
  240. package/src/project-team-policy.js +42 -0
  241. package/src/project-team-store.js +234 -0
  242. package/src/project-team-usage.js +24 -0
  243. package/src/project-teams-api.js +43 -0
  244. package/src/project-teams.js +644 -0
  245. package/src/projects.js +290 -0
  246. package/src/provider-eligibility.js +9 -0
  247. package/src/readiness.js +98 -0
  248. package/src/remote-tls.js +66 -0
  249. package/src/remote-workers/bundles.js +198 -0
  250. package/src/remote-workers/client.js +332 -0
  251. package/src/remote-workers/durability.js +37 -0
  252. package/src/remote-workers/protocol.js +68 -0
  253. package/src/remote-workers/stdio.js +138 -0
  254. package/src/remote-workers/worker.js +304 -0
  255. package/src/remote.js +204 -0
  256. package/src/repo-config.js +201 -0
  257. package/src/repo-coordination.js +725 -0
  258. package/src/repo-tree.js +106 -0
  259. package/src/repository-identity.js +13 -0
  260. package/src/retention-policy.js +466 -0
  261. package/src/review-inbox.js +38 -0
  262. package/src/routing.js +49 -0
  263. package/src/run-analysis.js +111 -0
  264. package/src/run-cold-store.js +231 -0
  265. package/src/run-environment.js +15 -0
  266. package/src/run-history.js +91 -0
  267. package/src/run-log.js +160 -0
  268. package/src/run-permissions.js +111 -0
  269. package/src/run-projection.js +77 -0
  270. package/src/run-recovery.js +53 -0
  271. package/src/run-status.js +56 -0
  272. package/src/serve.js +138 -0
  273. package/src/server.js +2871 -0
  274. package/src/shared-access.js +103 -0
  275. package/src/shared-accounts.js +381 -0
  276. package/src/shared-api.js +239 -0
  277. package/src/shell.js +346 -0
  278. package/src/ship.js +344 -0
  279. package/src/slots.js +74 -0
  280. package/src/snapshot.js +388 -0
  281. package/src/source-context.js +17 -0
  282. package/src/sse-writer.js +70 -0
  283. package/src/ssh-projects/bootstrap.mjs +95 -0
  284. package/src/ssh-projects/config.js +45 -0
  285. package/src/ssh-projects/gateway.js +143 -0
  286. package/src/ssh-projects/manager.js +320 -0
  287. package/src/ssh-projects/team-autostart.mjs +93 -0
  288. package/src/ssh-projects/team-remove.mjs +69 -0
  289. package/src/ssh-projects/team-status.mjs +39 -0
  290. package/src/ssh-projects/transport.js +133 -0
  291. package/src/stats.js +139 -0
  292. package/src/store.js +544 -0
  293. package/src/stream.js +91 -0
  294. package/src/sweeps.js +241 -0
  295. package/src/tailscale.js +101 -0
  296. package/src/task-draft.js +23 -0
  297. package/src/task-team.js +164 -0
  298. package/src/team-evaluation.js +243 -0
  299. package/src/team-mailbox.js +160 -0
  300. package/src/team-memory.js +84 -0
  301. package/src/team-rerun-graph.js +15 -0
  302. package/src/team-reruns.js +130 -0
  303. package/src/team-scheduler.js +121 -0
  304. package/src/team-steering.js +21 -0
  305. package/src/team-template-store.js +98 -0
  306. package/src/team-templates.js +117 -0
  307. package/src/team-tool-client.mjs +79 -0
  308. package/src/team-tools.js +235 -0
  309. package/src/teams.js +0 -0
  310. package/src/templates.js +62 -0
  311. package/src/transcript.js +134 -0
  312. package/src/verification-integrity.js +27 -0
  313. package/src/verification.js +183 -0
  314. package/src/watch.js +195 -0
  315. package/src/work-queue.js +47 -0
  316. package/src/workflow-control.js +83 -0
  317. package/src/workspace-api.js +207 -0
  318. package/src/workspace-changes.js +61 -0
  319. package/src/workspace.js +146 -0
@@ -0,0 +1,243 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { mkdir, open, readFile, readdir, rename, rm } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { CODEFLOW_HOME } from "./config.js";
5
+ const finite = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0;
6
+ const interval = (start, end) => finite(start) && finite(end) && end >= start ? [[start, end]] : [];
7
+ function intervalDuration(intervals) {
8
+ let total = 0, start = -1, end = -1;
9
+ for (const [a, b] of [...intervals].sort((a2, b2) => a2[0] - b2[0])) {
10
+ if (a > end) {
11
+ if (start >= 0) total += end - start;
12
+ start = a;
13
+ end = b;
14
+ } else end = Math.max(end, b);
15
+ }
16
+ return total + (start < 0 ? 0 : end - start);
17
+ }
18
+ function buildTeamEvaluation(run, options = {}) {
19
+ const team = run.team;
20
+ const turns = run.steps.filter((step) => step.kind === "agent");
21
+ const coordinator = (step) => step.coordination === true || !team && step.role === "supervisor";
22
+ const workers = turns.filter((step) => {
23
+ if (!team) return step.role === "workers" || step.role === "worker";
24
+ const member = team.members.find((item) => item.id === step.memberId);
25
+ if (member) return member.role === "worker" && !coordinator(step);
26
+ return !!step.taskId && !coordinator(step) && step.role !== "coordinator" && step.role !== "reviewer";
27
+ });
28
+ const invocations = team?.metrics.invocations ?? turns.reduce((sum, step) => sum + (step.invocations ?? 1), 0);
29
+ const coordination = team?.metrics.coordinatorInvocations ?? turns.filter(coordinator).reduce((sum, step) => sum + (step.invocations ?? 1), 0);
30
+ const peer = (team?.messages ?? []).filter((message) => message.kind === "question" && message.recipient !== "user" && message.originalRecipient !== "user");
31
+ const peerIntervals = peer.flatMap((message) => interval(message.createdAt, message.answeredAt));
32
+ const barriers = [];
33
+ for (const message of peer) {
34
+ if (!finite(message.answeredAt)) continue;
35
+ const task = team?.tasks.find((item) => item.id === message.taskId);
36
+ if (!task || !task.dependsOn.every((id) => {
37
+ const dependency = team?.tasks.find((item) => item.id === id);
38
+ return finite(dependency?.finishedAt) && dependency.finishedAt <= message.answeredAt;
39
+ })) continue;
40
+ const next = workers.filter((step) => step.taskId === task.id && step.startedAt >= message.answeredAt).sort((a, b) => a.startedAt - b.startedAt)[0];
41
+ if (next) barriers.push([message.answeredAt, next.startedAt]);
42
+ }
43
+ const usage = turns.filter((step) => step.usage);
44
+ const integrityFlags = Object.values(run.integrity ?? {}).reduce((sum, issues) => sum + issues.length, 0);
45
+ const check = team?.verification?.check === "not_required" ? "not_required" : run.verdict === "passed" ? "passed" : run.verdict === "failing" ? "failed" : "unknown";
46
+ const approved = team ? !!team.verification?.reviewed && !!team.verification.approved : run.facts?.approved === true;
47
+ const requirements = team?.requirements.length ?? 0;
48
+ const covered = team?.requirements.filter((requirement) => {
49
+ const tasks = team.tasks.filter((task) => task.requirements.includes(requirement.id));
50
+ return tasks.length > 0 && tasks.every((task) => task.status === "completed" && !task.evidenceStale && (!task.retainedBaseline || approved));
51
+ }).length ?? 0;
52
+ const gaps = team?.gaps.length ?? 0;
53
+ const unchangedRerun = !!run.teamRerun && run.facts?.proofNotRequired === "unchanged-rerun" && check === "passed" && approved;
54
+ const documentationOnly = run.facts?.proofNotRequired === "documentation-only" && check === "passed" && approved;
55
+ const verified = run.status === "done" && approved && (check === "passed" || check === "not_required") && !gaps && !integrityFlags && (!team || team.status === "completed" && covered === requirements) && (run.prove === false || check === "not_required" || run.facts?.proof === "proven" || unchangedRerun || documentationOnly);
56
+ const mode = options.mode ?? "unknown";
57
+ const approvalAnswers = options.journal ? [...options.journal].filter(([key]) => key.startsWith("approval:")).map(([, value]) => value) : void 0;
58
+ const rated = options.memoryEntries?.flatMap((entry) => entry.feedback?.filter((feedback) => feedback.runId === run.id && run.memoryManifest?.included.some((ref) => ref.id === entry.id && ref.revision === feedback.revision)) ?? []);
59
+ return {
60
+ version: 1,
61
+ id: `evaluation-${run.id}`,
62
+ runId: run.id,
63
+ at: run.endedAt ?? Date.now(),
64
+ mode,
65
+ caseId: options.caseId ?? "unspecified",
66
+ pairId: options.pairId ?? run.group?.id,
67
+ taskHash: createHash("sha256").update(run.task).digest("hex"),
68
+ baseRef: run.baseRef,
69
+ pattern: run.pattern,
70
+ cliVersions: { ...options.cliVersions },
71
+ status: run.status,
72
+ verified,
73
+ reproducibility: {
74
+ nodeVersion: process.version,
75
+ platform: `${process.platform}/${process.arch}`,
76
+ ...options.reproducibility,
77
+ requestedSettings: Object.fromEntries(Object.entries(run.agentSettings ?? {}).filter((entry) => entry[1] !== void 0).map(([id, settings]) => [id, structuredClone(settings)])),
78
+ stepSettings: [...new Map(turns.filter((step) => step.agent).map((step) => {
79
+ const settings = { provider: step.agent, model: step.settings?.model, effort: step.settings?.effort };
80
+ return [JSON.stringify(settings), settings];
81
+ })).values()]
82
+ },
83
+ verification: { checks: check, review: approved ? "approved" : run.facts?.approved === false || team?.verification?.reviewed ? "rejected" : "unknown", proof: run.facts?.proof, proofNotRequired: run.facts?.proofNotRequired, gaps, requirements, covered, integrityFlags, ...run.teamRerun ? { proofNotRequired: run.facts?.proofNotRequired, retainedTasks: team?.tasks.filter((task) => task.retainedBaseline).length ?? 0, newlyExecutedTasks: team?.tasks.filter((task) => !task.retainedBaseline && (task.attempts ?? 0) > 0).length ?? 0, requirementsUsingBaseline: team?.requirements.filter((requirement) => team.tasks.some((task) => task.retainedBaseline && task.requirements.includes(requirement.id))).length ?? 0 } : {} },
84
+ metrics: {
85
+ invocations,
86
+ coordinatorInvocations: coordination,
87
+ coordinatorFraction: invocations ? coordination / invocations : null,
88
+ workerTurns: workers.length,
89
+ multiWorker: new Set(workers.map((step) => team ? step.memberId ?? step.agent : step.worktree ?? step.agent)).size > 1,
90
+ elapsedMs: run.endedAt === void 0 ? void 0 : Math.max(0, run.endedAt - run.createdAt),
91
+ activeWorkMs: intervalDuration(run.steps.flatMap((step) => interval(step.startedAt, step.endedAt))),
92
+ coordinatorMs: turns.some(coordinator) ? turns.filter(coordinator).reduce((sum, step) => sum + (finite(step.endedAt) ? Math.max(0, step.endedAt - step.startedAt) : 0), 0) : void 0,
93
+ reportedTokens: usage.length ? usage.reduce((sum, step) => sum + step.usage.inputTokens + (step.usage.cacheReadTokens ?? 0) + step.usage.outputTokens, 0) : void 0,
94
+ reportedApiCostUsd: usage.some((step) => step.usage.costUsd > 0) ? usage.reduce((sum, step) => sum + step.usage.costUsd, 0) : void 0,
95
+ tokenReportingTurns: usage.length,
96
+ questions: team?.metrics.questionCount ?? 0,
97
+ peerQuestions: peer.length,
98
+ blockedTurns: team?.metrics.blockedTurns ?? 0,
99
+ peerYieldRate: workers.length ? new Set(peer.filter((message) => message.blocking).map((message) => `${message.sender}:${message.taskId}:${message.sourceAttempt ?? message.sourceStepId ?? message.id}`)).size / workers.length : null,
100
+ peerWaitingMs: peerIntervals.length ? peerIntervals.reduce((sum, [a, b]) => sum + b - a, 0) : peer.length ? void 0 : 0,
101
+ peerWaitingWallMs: peerIntervals.length ? intervalDuration(peerIntervals) : peer.length ? void 0 : 0,
102
+ avoidableBarrierEstimateMs: barriers.length ? intervalDuration(barriers) : peer.length ? void 0 : 0,
103
+ timedPeerQuestions: peerIntervals.length,
104
+ repairTurns: turns.filter((step) => !step.taskId && /repair|fix/i.test(step.title)).length,
105
+ // engine-titled repair/fix steps, never model-titled task names
106
+ selectedMemories: run.memoryManifest?.included.length ?? 0,
107
+ omittedMemories: run.memoryManifest?.omitted.length ?? 0,
108
+ coordinatorRevisions: team?.metrics.revisions ?? 0,
109
+ approvals: approvalAnswers ? { approved: approvalAnswers.filter((answer) => answer.decision === "approve").length, revised: approvalAnswers.filter((answer) => answer.decision === "revise").length, rejected: approvalAnswers.filter((answer) => answer.decision === "reject").length, withReason: approvalAnswers.filter((answer) => !!answer.note?.trim()).length } : void 0,
110
+ memoryFeedback: rated ? { useful: rated.filter((item) => item.rating === "useful").length, irrelevant: rated.filter((item) => item.rating === "irrelevant").length, stale: rated.filter((item) => item.rating === "stale").length, incorrect: rated.filter((item) => item.rating === "incorrect").length } : void 0,
111
+ replay: options.replay
112
+ },
113
+ limitations: [
114
+ ...mode !== "real" ? [mode === "fake" ? "Deterministic fake CLIs validate orchestration, not model quality or subscription savings." : "Execution provenance is unknown; excluded from real-run rollout evidence."] : [],
115
+ "API-equivalent cost is not a subscription charge; unreported values remain unknown.",
116
+ "Model identities record requested CLI settings. A provider default or alias is not an immutable model snapshot and may change between trials.",
117
+ "Avoidable barrier time is a counterfactual upper-bound estimate after answered peer questions; it does not prove spare process capacity or an asynchronous speedup.",
118
+ "Active work is the union of observed step intervals; it excludes gaps and is not a full run-clock measurement.",
119
+ ...run.teamRerun ? ["Selective reruns retain explicitly pinned source task inputs. Baseline task counts are not newly executed work; final review still covers every original requirement."] : [],
120
+ ...!team ? ["Supervisor coordination includes its planning, integration and repair turns; legacy requirements have no structured coverage manifest."] : []
121
+ ]
122
+ };
123
+ }
124
+ function assessTeamRollout(reports) {
125
+ const real = reports.filter((report) => report.mode === "real" && report.status !== "running");
126
+ const auto = real.filter((report) => report.pattern === "auto-team");
127
+ const pairs = /* @__PURE__ */ new Map();
128
+ for (const report of real) if (report.pairId) {
129
+ const key = `${report.pairId}:${report.caseId}:${report.taskHash}:${report.baseRef}`;
130
+ pairs.set(key, [...pairs.get(key) ?? [], report]);
131
+ }
132
+ const matched = [...pairs.values()].filter((items) => items.some((item) => item.pattern === "auto-team") && items.some((item) => item.pattern === "supervisor"));
133
+ const verifiedMatch = matched.some((items) => items.some((item) => item.pattern === "auto-team" && item.verified));
134
+ const pairedQuality = matched.every((items) => !items.some((item) => item.pattern === "supervisor" && item.verified) || items.some((item) => item.pattern === "auto-team" && item.verified));
135
+ const multi = auto.filter((report) => report.metrics.multiWorker);
136
+ const denominator = multi.reduce((sum, report) => sum + report.metrics.invocations, 0);
137
+ const overhead = denominator ? multi.reduce((sum, report) => sum + report.metrics.coordinatorInvocations, 0) / denominator : null;
138
+ const workerTurns = auto.reduce((sum, report) => sum + report.metrics.workerTurns, 0);
139
+ const peerYields = auto.reduce((sum, report) => sum + (report.metrics.peerYieldRate ?? 0) * report.metrics.workerTurns, 0);
140
+ const blockers = [];
141
+ if (auto.length < 20) blockers.push(`Need 20 real Auto runs; recorded ${auto.length}.`);
142
+ if (workerTurns < 100) blockers.push(`Need 100 real worker turns; recorded ${workerTurns}.`);
143
+ if (matched.length < 20) blockers.push(`Need 20 matched Auto/Supervisor comparisons; recorded ${matched.length}.`);
144
+ if (!pairedQuality) blockers.push("Auto verification regressed against a matched Supervisor result.");
145
+ if (auto.some((report) => !report.verified)) blockers.push("Some real Auto results are incomplete or unverified.");
146
+ if (overhead === null || overhead > 0.25) blockers.push(overhead === null ? "No multi-worker coordinator overhead sample." : "Observed multi-worker coordinator calls exceed 25% of invocations.");
147
+ if (real.some((report) => !Object.keys(report.cliVersions).length)) blockers.push("Some real results have no recorded CLI versions.");
148
+ return { realRuns: real.length, realAutoRuns: auto.length, realWorkerTurns: workerTurns, matchedPairs: matched.length, verifiedAutoRuns: auto.filter((report) => report.verified).length, overheadFraction: overhead, peerYieldRate: workerTurns ? peerYields / workerTurns : null, eligibleBeta: verifiedMatch && pairedQuality && real.every((report) => Object.keys(report.cliVersions).length > 0), eligibleDefault: blockers.length === 0, blockers };
149
+ }
150
+ class TeamEvaluationStore {
151
+ constructor(directory = join(CODEFLOW_HOME, "team-evaluations")) {
152
+ this.directory = directory;
153
+ }
154
+ queue = Promise.resolve();
155
+ async write(file, value) {
156
+ await mkdir(this.directory, { recursive: true });
157
+ const temporary = join(this.directory, `.${file}.${randomBytes(8).toString("hex")}.tmp`);
158
+ try {
159
+ const handle = await open(temporary, "wx", 384);
160
+ try {
161
+ await handle.writeFile(JSON.stringify(value, null, 2) + "\n");
162
+ await handle.sync();
163
+ } finally {
164
+ await handle.close();
165
+ }
166
+ await rename(temporary, join(this.directory, file));
167
+ if (process.platform !== "win32") {
168
+ const dir = await open(this.directory, "r");
169
+ try {
170
+ await dir.sync();
171
+ } finally {
172
+ await dir.close();
173
+ }
174
+ }
175
+ } finally {
176
+ await rm(temporary, { force: true });
177
+ }
178
+ }
179
+ async list() {
180
+ const names = await readdir(this.directory).catch((error) => {
181
+ if (error.code === "ENOENT") return [];
182
+ throw error;
183
+ });
184
+ const reports = await Promise.all(names.filter((name) => /^evaluation-[\w-]+\.json$/.test(name)).map(async (name) => {
185
+ const value = JSON.parse(await readFile(join(this.directory, name), "utf8"));
186
+ if (value.version !== 1 || value.id + ".json" !== name) throw new Error(`Unsupported or damaged evaluation ${name}`);
187
+ return value;
188
+ }));
189
+ return reports.sort((a, b) => b.at - a.at || a.id.localeCompare(b.id));
190
+ }
191
+ async record(report) {
192
+ if (report.version !== 1 || !/^evaluation-[\w-]+$/.test(report.id) || !["fake", "real", "unknown"].includes(report.mode)) throw new Error("Invalid evaluation report");
193
+ const operation = this.queue.then(async () => {
194
+ const previous = await readFile(join(this.directory, `${report.id}.json`), "utf8").catch((error) => {
195
+ if (error.code === "ENOENT") return void 0;
196
+ throw error;
197
+ });
198
+ if (previous) {
199
+ if (JSON.stringify(JSON.parse(previous)) !== JSON.stringify(report)) throw new Error("An evaluation is immutable; use a new run for another trial");
200
+ return report;
201
+ }
202
+ await this.write(`${report.id}.json`, report);
203
+ return report;
204
+ });
205
+ this.queue = operation.catch(() => void 0);
206
+ return operation;
207
+ }
208
+ async rollout(enabled = true) {
209
+ const decision = await readFile(join(this.directory, "rollout.json"), "utf8").then((text) => JSON.parse(text), (error) => {
210
+ if (error.code === "ENOENT") return void 0;
211
+ throw error;
212
+ });
213
+ if (decision && (!["experimental", "beta", "default"].includes(decision.stage) || !Array.isArray(decision.evaluationIds))) throw new Error("Damaged team rollout decision");
214
+ return { enabled, stage: decision?.stage ?? "experimental", reason: enabled ? decision?.rationale ?? "No evaluation promotion decision has been recorded. The application default is configured separately." : "New Auto teams are disabled. Existing teams can still be viewed and resumed.", decision };
215
+ }
216
+ async decide(input) {
217
+ if (!["experimental", "beta", "default"].includes(input.stage) || typeof input.rationale !== "string" || !input.rationale.trim() || input.rationale.length > 4e3 || !Array.isArray(input.evaluationIds)) throw new Error("Choose a stage, evaluation reports and a written rationale");
218
+ const operation = this.queue.then(async () => {
219
+ const all = await this.list();
220
+ const ids = [...new Set(input.evaluationIds)];
221
+ if (ids.some((id) => !all.some((report) => report.id === id))) throw new Error("Unknown evaluation report");
222
+ const selected = all.filter((report) => ids.includes(report.id));
223
+ const assessment = assessTeamRollout(all);
224
+ if (input.stage === "beta" && !assessment.eligibleBeta) throw new Error("Beta needs a verified real Auto result and a matched Supervisor comparison with CLI versions and no verification regression");
225
+ if (input.stage === "default" && !assessment.eligibleDefault) throw new Error(`Default rollout is not supported: ${assessment.blockers.join(" ")}`);
226
+ if (input.stage !== "experimental" && !selected.length) throw new Error("Cite the evaluation reports the decision rests on");
227
+ const decision = { at: Date.now(), stage: input.stage, rationale: input.rationale.trim(), evaluationIds: ids };
228
+ await this.write(`decision-${decision.at}-${randomBytes(4).toString("hex")}.json`, decision);
229
+ await this.write("rollout.json", decision);
230
+ return this.rollout();
231
+ });
232
+ this.queue = operation.catch(() => void 0);
233
+ return operation;
234
+ }
235
+ }
236
+ const teamEvaluationStore = new TeamEvaluationStore();
237
+ export {
238
+ TeamEvaluationStore,
239
+ assessTeamRollout,
240
+ buildTeamEvaluation,
241
+ intervalDuration,
242
+ teamEvaluationStore
243
+ };
@@ -0,0 +1,160 @@
1
+ import { createHash } from "node:crypto";
2
+ const TYPES = /* @__PURE__ */ new Set(["send_message", "ask_member", "reply_request", "yield_task", "publish_finding", "propose_contract", "submit_task", "request_help"]);
3
+ const FIELDS = /* @__PURE__ */ new Set(["type", "operationId", "recipient", "body", "requestId", "blocking", "deadlineMs", "checkpoint", "evidence", "title", "paths", "artifacts"]);
4
+ const nonempty = (v, max) => typeof v === "string" && !!v.trim() && v.length <= max;
5
+ const list = (v, max) => v === void 0 || Array.isArray(v) && v.length <= max && v.every((x) => nonempty(x, 2e3));
6
+ const stable = (value) => JSON.stringify(value, (_key, v) => v && !Array.isArray(v) && typeof v === "object" ? Object.fromEntries(Object.keys(v).sort().map((key) => [key, v[key]])) : v);
7
+ function mailboxCommandProblem(command) {
8
+ if (!command || !TYPES.has(command.type) || Object.keys(command).some((key) => !FIELDS.has(key))) return "Unknown collaboration command or field";
9
+ if (!nonempty(command.operationId, 240)) return "operationId must be a stable nonempty ID of at most 240 characters";
10
+ if (command.body !== void 0 && !nonempty(command.body, 8e3)) return "body must contain 1\u20138000 characters";
11
+ if (command.checkpoint !== void 0 && !nonempty(command.checkpoint, 12e3)) return "checkpoint must contain 1\u201312000 characters";
12
+ if (command.title !== void 0 && !nonempty(command.title, 200)) return "title must contain 1\u2013200 characters";
13
+ if (!list(command.evidence, 16) || !list(command.artifacts, 24) || !list(command.paths, 24)) return "Message evidence, artifacts, or paths exceed bounds";
14
+ if (command.recipient !== void 0 && !nonempty(command.recipient, 40)) return "Invalid recipient";
15
+ if (command.requestId !== void 0 && !nonempty(command.requestId, 300)) return "Invalid request ID";
16
+ if (command.blocking !== void 0 && typeof command.blocking !== "boolean") return "blocking must be boolean";
17
+ if (command.deadlineMs !== void 0 && (!Number.isSafeInteger(command.deadlineMs) || command.deadlineMs < 1e3 || command.deadlineMs > 36e5)) return "deadlineMs must be between 1000 and 3600000";
18
+ if (["send_message", "ask_member", "reply_request", "publish_finding", "propose_contract", "request_help"].includes(command.type) && !command.body) return "This command needs a body";
19
+ if (["send_message", "ask_member"].includes(command.type) && !command.recipient) return "This command needs an addressed recipient";
20
+ if (command.type === "reply_request" && (!command.requestId || !command.evidence?.length)) return "A reply needs a request ID and evidence";
21
+ if (["yield_task", "submit_task"].includes(command.type) && !command.checkpoint) return "Task transitions need a portable checkpoint";
22
+ if (["publish_finding", "propose_contract"].includes(command.type) && !command.evidence?.length) return "Shared proposals need supporting evidence";
23
+ return void 0;
24
+ }
25
+ function applyMailboxCommand(input, command, authority, now) {
26
+ const state = structuredClone(input);
27
+ return { state, receipt: applyMailboxCommandInPlace(state, command, authority, now) };
28
+ }
29
+ function applyMailboxCommandInPlace(state, command, authority, now) {
30
+ const problem = mailboxCommandProblem(command);
31
+ if (problem) throw new Error(problem);
32
+ const { stepId: _physicalStep, ...logicalAuthority } = authority;
33
+ const fingerprint = createHash("sha256").update(stable({ command, authority: logicalAuthority })).digest("hex");
34
+ const previous = state.commandReceipts?.find((r) => r.operationId === command.operationId);
35
+ if (previous) {
36
+ if (previous.fingerprint !== fingerprint) throw new Error("operationId was already used for different collaboration content");
37
+ return previous;
38
+ }
39
+ if ((state.commandReceipts?.length ?? 0) >= 4096) throw new Error("Run collaboration command limit reached");
40
+ state.commandReceipts ??= [];
41
+ state.waits ??= [];
42
+ const receipt = { operationId: command.operationId, fingerprint, status: "accepted" };
43
+ const done = () => {
44
+ state.commandReceipts.push(receipt);
45
+ return receipt;
46
+ };
47
+ const stale = (reason) => {
48
+ receipt.status = "stale";
49
+ receipt.reason = reason;
50
+ return done();
51
+ };
52
+ const member = state.members.find((m) => m.id === authority.memberId);
53
+ const task = state.tasks.find((t) => t.id === authority.taskId);
54
+ if (authority.origin === "agent" && (!member || member.status === "retired")) return stale("The sending member is absent or retired");
55
+ if (authority.origin === "user" && authority.memberId !== "user") throw new Error("User origin requires authenticated user identity");
56
+ if (authority.taskId && (!task || task.owner !== authority.memberId || authority.attempt !== task.attempts)) return stale("The task has been reassigned or this attempt is no longer current");
57
+ const messageId = `mail:${createHash("sha256").update(command.operationId).digest("hex").slice(0, 24)}`;
58
+ const effectiveRecipient = (requested) => requested === "user" ? "user" : state.members.some((m) => m.id === requested && m.status !== "retired") ? requested : "coordinator";
59
+ const add = (kind, recipient, body, blocking = false) => {
60
+ const target = effectiveRecipient(recipient);
61
+ const message = { id: messageId, kind, sender: authority.memberId, recipient: target, taskId: task?.id ?? "", body, blocking, evidence: command.evidence ?? [], status: kind === "question" ? "open" : "answered", sourceStepId: authority.stepId ?? "collaboration", origin: authority.origin, sourceAttempt: authority.attempt, revision: state.revision, createdAt: now, delivery: { status: "queued" }, ...target !== recipient ? { originalRecipient: recipient } : {} };
62
+ state.messages.push(message);
63
+ receipt.messageId = messageId;
64
+ return message;
65
+ };
66
+ if (command.type === "reply_request") {
67
+ const question = state.messages.find((m) => m.id === command.requestId && m.kind === "question");
68
+ if (!question) throw new Error("Unknown request ID");
69
+ if (question.recipient === "user" ? authority.origin !== "user" : authority.memberId !== question.recipient && authority.memberId !== "coordinator") throw new Error("Only the addressed member or coordinator may answer this request");
70
+ if (question.status !== "open") return stale("The request already has an accepted answer");
71
+ const owner = state.tasks.find((t) => t.id === question.taskId);
72
+ const reply = add("reply", owner?.owner ?? question.sender, command.body);
73
+ reply.taskId = question.taskId;
74
+ reply.requestId = question.id;
75
+ question.status = "answered";
76
+ question.answeredAt = now;
77
+ receipt.requestId = question.id;
78
+ for (const wait of state.waits) if (wait.requestId === question.id && wait.status !== "resolved") {
79
+ wait.status = "resolved";
80
+ wait.resolvedAt = now;
81
+ }
82
+ if (owner?.status === "needs_info" && !state.messages.some((m) => m.kind === "question" && m.taskId === owner.id && m.blocking && m.status === "open")) owner.status = "pending";
83
+ } else if (command.type === "ask_member" || command.type === "request_help") {
84
+ if (!task) throw new Error("Questions must belong to a current task");
85
+ if (command.recipient === authority.memberId) throw new Error("A member cannot ask itself");
86
+ const question = add("question", command.recipient ?? "coordinator", command.body, command.blocking ?? true);
87
+ question.deadlineAt = now + (command.deadlineMs ?? 12e4);
88
+ receipt.requestId = question.id;
89
+ state.metrics.questionCount++;
90
+ if (question.blocking) {
91
+ state.waits.push({ requestId: question.id, taskId: task.id, memberId: member.id, attempt: authority.attempt, status: "waiting", createdAt: now, deadlineAt: question.deadlineAt });
92
+ task.waitingSince ??= now;
93
+ }
94
+ } else if (command.type === "yield_task") {
95
+ if (!task || !member) throw new Error("yield_task needs a current assignment");
96
+ if (task.status !== "running" && task.status !== "needs_info") return stale("The assignment already finished");
97
+ if (!state.messages.some((m) => m.kind === "question" && m.taskId === task.id && m.blocking && m.status === "open")) throw new Error("Yield requires an unresolved blocking request");
98
+ task.checkpoint = command.checkpoint;
99
+ task.checkpointOwner = member.id;
100
+ task.error = "Yield requested; waiting for the current turn to release its process slot";
101
+ } else if (command.type === "submit_task") {
102
+ if (!task || !member) throw new Error("submit_task needs a current assignment");
103
+ if (task.status !== "running") return stale("The assignment is not running");
104
+ if (state.messages.some((m) => m.kind === "question" && m.taskId === task.id && m.blocking && m.status === "open")) throw new Error("Cannot submit with an unanswered blocking request");
105
+ task.checkpoint = command.checkpoint;
106
+ task.checkpointOwner = member.id;
107
+ task.artifacts = command.artifacts ?? [];
108
+ add("message", "coordinator", "Task submission proposed; artifact verification is pending.");
109
+ } else {
110
+ const kind = command.type === "publish_finding" ? "finding" : command.type === "propose_contract" ? "contract" : "message";
111
+ add(kind, command.recipient ?? "coordinator", `${command.title ? `${command.title}
112
+ ` : ""}${command.body}`);
113
+ }
114
+ return done();
115
+ }
116
+ function applyMailboxProjection(target, next) {
117
+ const tasks = next.tasks.map((t) => {
118
+ const previous = target.tasks.find((old) => old.id === t.id);
119
+ return previous ? Object.assign(previous, t) : t;
120
+ });
121
+ const members = next.members.map((m) => {
122
+ const previous = target.members.find((old) => old.id === m.id);
123
+ return previous ? Object.assign(previous, m) : m;
124
+ });
125
+ Object.assign(target, next, { tasks, members });
126
+ }
127
+ function escalateMailbox(state, now, unavailable = /* @__PURE__ */ new Set()) {
128
+ const reasons = [];
129
+ const open = state.messages.filter((m) => m.kind === "question" && m.status === "open" && m.recipient !== "user");
130
+ const edges = /* @__PURE__ */ new Map();
131
+ for (const q of open.filter((q2) => q2.blocking)) edges.set(q.sender, [...edges.get(q.sender) ?? [], q.recipient]);
132
+ const reaches = (from, target, visited = /* @__PURE__ */ new Set()) => {
133
+ if (from === target) return true;
134
+ if (visited.has(from)) return false;
135
+ visited.add(from);
136
+ return (edges.get(from) ?? []).some((next) => reaches(next, target, visited));
137
+ };
138
+ for (const question of open) {
139
+ const wait = state.waits?.find((w) => w.requestId === question.id);
140
+ if (wait?.status === "escalated") continue;
141
+ const recipient = state.members.find((m) => m.id === question.recipient);
142
+ const reason = !recipient || recipient.status === "retired" || unavailable.has(question.recipient) ? "recipient unavailable" : question.deadlineAt !== void 0 && question.deadlineAt <= now ? "request deadline elapsed" : reaches(question.recipient, question.sender) ? "cyclic member wait" : void 0;
143
+ if (!reason) continue;
144
+ question.originalRecipient ??= question.recipient;
145
+ question.recipient = "coordinator";
146
+ if (wait) {
147
+ wait.status = "escalated";
148
+ wait.reason = reason;
149
+ }
150
+ reasons.push(`${question.id}: ${reason}`);
151
+ }
152
+ return reasons;
153
+ }
154
+ export {
155
+ applyMailboxCommand,
156
+ applyMailboxCommandInPlace,
157
+ applyMailboxProjection,
158
+ escalateMailbox,
159
+ mailboxCommandProblem
160
+ };
@@ -0,0 +1,84 @@
1
+ import { git, isAncestor, resolveCommit } from "./git.js";
2
+ import { memoryStore } from "./memory.js";
3
+ const empty = () => ({ entries: [], manifest: { included: [], omitted: [] } });
4
+ function memoryAuthorityForRun(run, memberId) {
5
+ const sourceTrust = run.lockdown || run.source?.trusted === false || run.source?.untrustedText || run.inheritedSourceTrust === "untrusted" ? "untrusted" : run.source && run.source.trusted !== true || run.inheritedSourceTrust === "unknown" ? "unknown" : "trusted";
6
+ return { kind: "run", runId: run.id, repo: run.repo, projectId: run.project, memberId, lockedDown: !!run.lockdown, sourceTrust };
7
+ }
8
+ async function projectMemoryForRun(run, memberId, taskId, paths, options = {}) {
9
+ if (!memberOfProject(run, options)) return empty();
10
+ return (options.store ?? memoryStore).retrieve(await projectQuery(run, memberId, taskId, paths, options), memoryAuthorityForRun(run, memberId));
11
+ }
12
+ async function projectMemoryFromManifest(run, manifest, memberId, taskId, paths, options = {}) {
13
+ if (!memberOfProject(run, options)) return { entries: [], manifest: { included: [], omitted: [...manifest.omitted, ...manifest.included.map((ref) => ({ ...ref, reason: "project membership changed" }))] } };
14
+ return (options.store ?? memoryStore).resolveSelection(await projectQuery(run, memberId, taskId, paths, options), manifest, memoryAuthorityForRun(run, memberId));
15
+ }
16
+ async function projectQuery(run, memberId, taskId, paths, options) {
17
+ const store = options.store ?? memoryStore;
18
+ const authority = memoryAuthorityForRun(run, memberId);
19
+ const head = options.head ?? run.baseRef;
20
+ const branch = options.branch ?? await runBaseBranch(run);
21
+ const entries = await store.list({ repo: run.repo, projectId: run.project, scope: "project" }, authority);
22
+ const commits = [...new Set(entries.flatMap((entry) => entry.applicability.kind === "branch" && entry.applicability.branch === branch && entry.applicability.commit ? [entry.applicability.commit] : []))].slice(0, 128);
23
+ const allowedCommits = [];
24
+ for (const commit of commits) if (commit === head || await isAncestor(run.repo, commit, head)) allowedCommits.push(commit);
25
+ return {
26
+ repo: run.repo,
27
+ projectId: run.project,
28
+ scope: "project",
29
+ runId: run.id,
30
+ memberId,
31
+ branch,
32
+ commit: head,
33
+ allowedCommits,
34
+ paths,
35
+ query: options.query ?? `${run.task}
36
+ ${taskId ?? ""}`,
37
+ maxEntries: options.maxEntries ?? 8,
38
+ maxChars: options.maxChars ?? 8e3
39
+ };
40
+ }
41
+ async function proposeProjectMemories(run, findings, taskId, head, memberId, options = {}) {
42
+ if (!memberOfProject(run, options) || run.learn === false || !findings.length) return [];
43
+ if (!/^[0-9a-f]{40,64}$/i.test(head)) throw new Error("Memory findings require a full result commit SHA");
44
+ const store = options.store ?? memoryStore;
45
+ const authority = memoryAuthorityForRun(run, memberId);
46
+ const branch = options.branch ?? run.worktrees.find((tree) => tree.name === "integrated")?.branch ?? run.worktrees.find((tree) => tree.name === "integration")?.branch ?? `${run.branchPrefix}/integrated`;
47
+ const shown = run.memoryManifest?.included.map(({ id, revision }) => ({ id, revision })) ?? [];
48
+ const created = [];
49
+ for (const [index, finding] of findings.slice(0, 32).entries()) {
50
+ const refs = [...new Map((finding.derivedFrom ?? shown).map((ref) => [`${ref.id}@${ref.revision}`, ref])).values()];
51
+ created.push(await store.propose({
52
+ scope: "project",
53
+ repo: run.repo,
54
+ projectId: run.project,
55
+ runId: run.id,
56
+ memberId,
57
+ title: finding.title,
58
+ body: finding.body,
59
+ paths: finding.paths,
60
+ applicability: { kind: "branch", branch, commit: head },
61
+ derivedFrom: refs,
62
+ sourceRefs: [`run:${run.id}`, `task:${taskId}`, `commit:${head}`, ...finding.evidenceRefs ?? []],
63
+ operationId: `${run.id}:${taskId}:${head}:${index}`
64
+ }, authority));
65
+ }
66
+ return created;
67
+ }
68
+ function memberOfProject(run, options) {
69
+ if (!run.project) return false;
70
+ if (!options.projects) return true;
71
+ return options.projects.get(run.project)?.repos.includes(run.repo) ?? false;
72
+ }
73
+ async function runBaseBranch(run) {
74
+ if (run.baseLabel && run.baseLabel !== "HEAD" && !/^[0-9a-f]{40,64}$/i.test(run.baseLabel)) return run.baseLabel.replace(/^refs\/heads\//, "");
75
+ if (await resolveCommit(run.repo).catch(() => void 0) !== run.baseRef) return void 0;
76
+ const result = await git(["symbolic-ref", "--quiet", "--short", "HEAD"], run.repo);
77
+ return result.code === 0 ? result.stdout.trim() || void 0 : void 0;
78
+ }
79
+ export {
80
+ memoryAuthorityForRun,
81
+ projectMemoryForRun,
82
+ projectMemoryFromManifest,
83
+ proposeProjectMemories
84
+ };
@@ -0,0 +1,15 @@
1
+ function dependentTaskClosure(tasks, selected) {
2
+ const included = new Set(selected);
3
+ for (let pass = 0; pass < tasks.length; pass++) {
4
+ let changed = false;
5
+ for (const task of tasks) if (!included.has(task.id) && task.dependsOn.some((id) => included.has(id))) {
6
+ included.add(task.id);
7
+ changed = true;
8
+ }
9
+ if (!changed) break;
10
+ }
11
+ return included;
12
+ }
13
+ export {
14
+ dependentTaskClosure
15
+ };