@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,235 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { createServer } from "node:http";
3
+ const text = (maxLength = 6e3) => ({ type: "string", minLength: 1, maxLength });
4
+ const strings = (maxItems = 20) => ({ type: "array", maxItems, items: text(1e3) });
5
+ const operationId = text(160);
6
+ function tool(name, description, properties, required = []) {
7
+ return { name, description, inputSchema: { type: "object", properties, required, additionalProperties: false }, annotations: { readOnlyHint: name === "read_inbox" || name === "read_memory", destructiveHint: false, idempotentHint: true, openWorldHint: false } };
8
+ }
9
+ const TEAM_TOOLS = [
10
+ tool("send_message", "Send a fact to a teammate. Returns after persistence; delivery may be live or at their next turn. Keep operationId stable on retries.", { operationId, recipient: text(40), body: text(), evidence: strings(16) }, ["operationId", "recipient", "body"]),
11
+ tool("ask_member", "Ask a teammate or user. Returns a request ID immediately, never waits for an answer. If blocked, checkpoint and yield through your structured outcome.", { operationId, recipient: text(40), body: text(), blocking: { type: "boolean" }, deadlineMs: { type: "integer", minimum: 1e3, maximum: 36e5 }, evidence: strings(16) }, ["operationId", "recipient", "body"]),
12
+ tool("reply_request", "Answer an addressed request with evidence. This is agent data and cannot authorize user-only actions.", { operationId, requestId: text(200), body: text(), evidence: strings(16) }, ["operationId", "requestId", "body", "evidence"]),
13
+ tool("yield_task", "Persist a checkpoint and mark this task waiting. Then finish the current turn with a structured needs_info outcome so its process slot is released.", { operationId, checkpoint: text(12e3) }, ["operationId", "checkpoint"]),
14
+ tool("publish_finding", "Publish a team finding with source evidence; this does not promote project memory.", { operationId, title: text(200), body: text(), evidence: strings(16), paths: strings() }, ["operationId", "title", "body", "evidence"]),
15
+ tool("propose_contract", "Propose a team contract for coordinator validation. A proposal does not change user requirements or grant permissions.", { operationId, title: text(200), body: text(), evidence: strings(16) }, ["operationId", "body", "evidence"]),
16
+ tool("submit_task", "Record a submission checkpoint and artifacts. Finish with your structured outcome; only the engine's checks and independent review can complete the task.", { operationId, checkpoint: text(12e3), artifacts: strings() }, ["operationId", "checkpoint"]),
17
+ tool("request_help", "Ask the coordinator to resolve a blocker. Returns immediately; checkpoint and yield when no safe independent work remains.", { operationId, recipient: text(40), body: text(), evidence: strings(16) }, ["operationId", "body"]),
18
+ tool("read_inbox", "Read only messages addressed to this member or the team. An inbox read is not an answer or a claim of comprehension.", { afterId: text(200), limit: { type: "integer", minimum: 1, maximum: 50 } }),
19
+ tool("read_memory", "Read bounded memory selected for this member's current run and task. Private memory is restricted to this member; project entries retain provenance and applicability.", { scope: { enum: ["private", "team", "project"] }, query: text(1e3), limit: { type: "integer", minimum: 1, maximum: 20 } }, ["scope"]),
20
+ tool("write_memory", "Save private notes or propose team notes with run-derived provenance. Team proposals stay unaccepted; publish_finding shares immediate findings. This cannot create accepted project conventions or export private notes.", { operationId, scope: { enum: ["private", "team"] }, title: text(200), body: text(12e3), tags: strings(), paths: strings() }, ["operationId", "scope", "title", "body"]),
21
+ tool("propose_project_memory", "Propose a reusable project lesson for later review. Acceptance, trust, and repository applicability remain controlled by the server and user.", { operationId, title: text(200), body: text(12e3), tags: strings(), paths: strings() }, ["operationId", "title", "body"])
22
+ ];
23
+ const definitions = new Map(TEAM_TOOLS.map((value) => [value.name, value]));
24
+ const MAX_BODY = 64 * 1024;
25
+ const MAX_RESPONSE = 128 * 1024;
26
+ const MAX_TTL = 25 * 60 * 6e4;
27
+ function teamMcpConfig(connection) {
28
+ if (!/^http:\/\/127\.0\.0\.1:\d+\/v1\/runs\/[^/]+\/members\/[^/]+\/attempts\/[^/]+$/.test(connection.endpoint) || !/^[\w-]{43}$/.test(connection.token) || !Number.isFinite(connection.expiresAt) || connection.expiresAt <= Date.now()) throw new Error("Invalid member tool connection");
29
+ return { serverUrl: `${connection.endpoint}/mcp`, headers: { Authorization: `Bearer ${connection.token}` }, tools: Object.fromEntries(TEAM_TOOLS.map((tool2) => [tool2.name, { eager: true }])) };
30
+ }
31
+ function validateTeamToolRequest(value) {
32
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid tool request");
33
+ const input = value;
34
+ if (Object.keys(input).some((key) => key !== "name" && key !== "arguments")) throw new Error("Unexpected tool request field");
35
+ const definition = definitions.get(input.name);
36
+ if (!definition) throw new Error("Unknown team tool");
37
+ const args = input.arguments ?? {};
38
+ if (!args || typeof args !== "object" || Array.isArray(args)) throw new Error("Tool arguments must be an object");
39
+ const schema = definition.inputSchema;
40
+ const fields = schema.properties;
41
+ for (const key of Object.keys(args)) if (!Object.hasOwn(fields, key)) throw new Error(`Unexpected argument: ${key}`);
42
+ for (const key of schema.required) if (!Object.hasOwn(args, key)) throw new Error(`Missing argument: ${key}`);
43
+ for (const [key, value2] of Object.entries(args)) {
44
+ const field = fields[key];
45
+ if (field.enum && !field.enum.includes(value2)) throw new Error(`Invalid argument: ${key}`);
46
+ if (field.type === "string" && (typeof value2 !== "string" || !value2.trim() || value2.length > field.maxLength)) throw new Error(`Invalid argument: ${key}`);
47
+ if (field.type === "boolean" && typeof value2 !== "boolean") throw new Error(`Invalid argument: ${key}`);
48
+ if (field.type === "integer" && (typeof value2 !== "number" || !Number.isSafeInteger(value2) || value2 < field.minimum || value2 > field.maximum)) throw new Error(`Invalid argument: ${key}`);
49
+ if (field.type === "array" && (!Array.isArray(value2) || value2.length > field.maxItems || value2.some((item) => typeof item !== "string" || !item.trim() || item.length > field.items.maxLength))) throw new Error(`Invalid argument: ${key}`);
50
+ }
51
+ return { name: definition.name, arguments: args };
52
+ }
53
+ class TeamToolBridge {
54
+ server;
55
+ listening;
56
+ grants = /* @__PURE__ */ new Map();
57
+ async issue(options) {
58
+ const { identity } = options;
59
+ if (!identity.runId || !identity.memberId || !identity.attemptId) throw new Error("A team capability requires a run, member and attempt");
60
+ if (options.signal?.aborted) throw new Error("Cannot issue a team capability for a stopped turn");
61
+ const base = await this.listen();
62
+ if (options.signal?.aborted) throw new Error("Cannot issue a team capability for a stopped turn");
63
+ const token = randomBytes(32).toString("base64url");
64
+ const key = digest(token);
65
+ const path = `/v1/runs/${encodeURIComponent(identity.runId)}/members/${encodeURIComponent(identity.memberId)}/attempts/${encodeURIComponent(identity.attemptId)}`;
66
+ const ttl = typeof options.ttlMs === "number" && Number.isFinite(options.ttlMs) ? Math.max(1, Math.min(MAX_TTL, options.ttlMs)) : 30 * 6e4;
67
+ const expiresAt = Date.now() + ttl;
68
+ this.grants.set(key, { identity: Object.freeze({ ...identity }), path, expiresAt, handler: options.handler, isActive: options.isActive, pending: 0 });
69
+ let timer;
70
+ const revoke = () => {
71
+ this.grants.delete(key);
72
+ clearTimeout(timer);
73
+ options.signal?.removeEventListener("abort", revoke);
74
+ };
75
+ timer = setTimeout(revoke, ttl);
76
+ timer.unref();
77
+ options.signal?.addEventListener("abort", revoke, { once: true });
78
+ return { connection: { endpoint: base + path, token, expiresAt }, revoke };
79
+ }
80
+ async close() {
81
+ this.grants.clear();
82
+ const server = this.server;
83
+ this.server = void 0;
84
+ this.listening = void 0;
85
+ if (server) await new Promise((resolve) => {
86
+ server.close(() => resolve());
87
+ server.closeAllConnections();
88
+ });
89
+ }
90
+ listen() {
91
+ if (this.listening) return this.listening;
92
+ const server = createServer((req, res) => {
93
+ void this.handle(req, res);
94
+ });
95
+ this.server = server;
96
+ server.requestTimeout = 1e4;
97
+ server.headersTimeout = 1e4;
98
+ server.maxHeadersCount = 30;
99
+ this.listening = new Promise((resolve, reject) => {
100
+ server.once("error", (error) => {
101
+ this.listening = void 0;
102
+ this.server = void 0;
103
+ reject(error);
104
+ });
105
+ server.listen(0, "127.0.0.1", () => {
106
+ server.unref();
107
+ const address = server.address();
108
+ if (!address || typeof address === "string") {
109
+ reject(new Error("Could not start team bridge"));
110
+ return;
111
+ }
112
+ resolve(`http://127.0.0.1:${address.port}`);
113
+ });
114
+ });
115
+ return this.listening;
116
+ }
117
+ async handle(req, res) {
118
+ const reply = (status, body) => {
119
+ if (res.destroyed || res.writableEnded) return;
120
+ const json = JSON.stringify(body ?? null);
121
+ res.writeHead(status, { "content-type": "application/json", "cache-control": "no-store", "x-content-type-options": "nosniff" });
122
+ res.end(json.length <= MAX_RESPONSE ? json : JSON.stringify({ error: "Tool response exceeded its context limit" }));
123
+ };
124
+ if (req.headers.origin) {
125
+ reply(403, { error: "Forbidden team bridge request" });
126
+ return;
127
+ }
128
+ const auth = req.headers.authorization;
129
+ const token = typeof auth === "string" && /^Bearer [A-Za-z0-9_-]{43}$/.test(auth) ? auth.slice(7) : "";
130
+ const grant = token ? this.grants.get(digest(token)) : void 0;
131
+ const mcp = !!grant && req.url === `${grant.path}/mcp`;
132
+ if (!grant || req.url !== grant.path && !mcp || grant.expiresAt <= Date.now() || grant.isActive?.() === false) {
133
+ reply(403, { error: "Invalid or expired member capability" });
134
+ return;
135
+ }
136
+ if (mcp && req.method === "GET") {
137
+ res.writeHead(405, { Allow: "POST" });
138
+ res.end();
139
+ return;
140
+ }
141
+ if (req.method !== "POST" || !/^application\/json(?:\s*;|$)/i.test(req.headers["content-type"] ?? "")) {
142
+ reply(403, { error: "Forbidden team bridge request" });
143
+ return;
144
+ }
145
+ if (grant.pending >= 4) {
146
+ reply(429, { error: "Too many active member commands" });
147
+ return;
148
+ }
149
+ grant.pending++;
150
+ let rpcId = null, toolCall = false;
151
+ const success = (value) => reply(200, mcp ? { jsonrpc: "2.0", id: rpcId, result: value } : value);
152
+ try {
153
+ const chunks = [];
154
+ let size = 0;
155
+ for await (const chunk of req) {
156
+ size += chunk.length;
157
+ if (size > MAX_BODY) {
158
+ reply(413, { error: "Team command is too large" });
159
+ return;
160
+ }
161
+ chunks.push(chunk);
162
+ }
163
+ if (this.grants.get(digest(token)) !== grant || grant.expiresAt <= Date.now() || grant.isActive?.() === false) {
164
+ reply(403, { error: "Invalid or expired member capability" });
165
+ return;
166
+ }
167
+ let body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
168
+ if (mcp) {
169
+ if (!body || typeof body !== "object" || Array.isArray(body) || body.jsonrpc !== "2.0" || typeof body.method !== "string" || body.id !== void 0 && typeof body.id !== "string" && !(typeof body.id === "number" && Number.isFinite(body.id))) {
170
+ reply(400, { jsonrpc: "2.0", id: null, error: { code: -32600, message: "Invalid request" } });
171
+ return;
172
+ }
173
+ if (body.id === void 0) {
174
+ res.writeHead(202);
175
+ res.end();
176
+ return;
177
+ }
178
+ rpcId = body.id;
179
+ if (body.method === "initialize") {
180
+ grant.initialized = true;
181
+ success({ protocolVersion: ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"].includes(body.params?.protocolVersion) ? body.params.protocolVersion : "2025-03-26", capabilities: { tools: {}, resources: {} }, serverInfo: { name: "code-flow-team", version: "1.0.0" } });
182
+ return;
183
+ }
184
+ if (!grant.initialized) throw new Error("Initialize the member bridge first");
185
+ if (body.method === "resources/list") {
186
+ success({ resources: [] });
187
+ return;
188
+ }
189
+ if (body.method === "resources/templates/list") {
190
+ success({ resourceTemplates: [] });
191
+ return;
192
+ }
193
+ if (body.method === "ping") {
194
+ success({});
195
+ return;
196
+ }
197
+ if (body.method === "tools/call") {
198
+ toolCall = true;
199
+ body = { name: body.params?.name, arguments: body.params?.arguments ?? {} };
200
+ } else if (body.method === "tools/list") {
201
+ success({ tools: TEAM_TOOLS });
202
+ return;
203
+ } else {
204
+ reply(200, { jsonrpc: "2.0", id: rpcId, error: { code: -32601, message: "Method not found" } });
205
+ return;
206
+ }
207
+ }
208
+ if (body?.method === "tools/list" && Object.keys(body).length === 1) {
209
+ success({ tools: TEAM_TOOLS });
210
+ return;
211
+ }
212
+ const request = validateTeamToolRequest(body);
213
+ const value = await grant.handler(request, grant.identity);
214
+ success(mcp ? { content: [{ type: "text", text: JSON.stringify(value ?? null) }] } : { result: value ?? null });
215
+ } catch (error) {
216
+ const message = error instanceof Error ? error.message : "Team command failed";
217
+ const safe = message.replaceAll(token, "[redacted]").slice(0, 1e3);
218
+ if (mcp && toolCall) success({ isError: true, content: [{ type: "text", text: safe }] });
219
+ else reply(message.includes("no longer active") ? 403 : 400, mcp ? { jsonrpc: "2.0", id: rpcId, error: { code: -32603, message: safe } } : { error: safe });
220
+ } finally {
221
+ grant.pending--;
222
+ }
223
+ }
224
+ }
225
+ function digest(token) {
226
+ return createHash("sha256").update(token).digest("hex");
227
+ }
228
+ const teamToolBridge = new TeamToolBridge();
229
+ export {
230
+ TEAM_TOOLS,
231
+ TeamToolBridge,
232
+ teamMcpConfig,
233
+ teamToolBridge,
234
+ validateTeamToolRequest
235
+ };
package/src/teams.js ADDED
File without changes
@@ -0,0 +1,62 @@
1
+ const templates = [
2
+ {
3
+ id: "fix-issue",
4
+ name: "Fix an issue",
5
+ description: "Plan, build and review a fix for a GitHub issue; the PR closes it.",
6
+ pattern: "pipeline",
7
+ needs: "issue",
8
+ task: "Fix issue #{number}. Add a test that fails without the fix."
9
+ },
10
+ {
11
+ id: "fix-ci",
12
+ name: "Fix failing CI",
13
+ description: "Hand a PR's failing checks and their logs to an agent; push the fix to the PR.",
14
+ pattern: "pipeline",
15
+ needs: "pr",
16
+ ciLogs: true,
17
+ task: "Make CI pass on pull request #{number}. The failing checks and their logs are below. Fix the cause; don't disable, skip or weaken tests."
18
+ },
19
+ {
20
+ id: "review-pr",
21
+ name: "Review a PR as a council",
22
+ description: "Several agents review the PR independently; a chair merges their findings. You choose whether to post it.",
23
+ pattern: "council",
24
+ needs: "pr",
25
+ task: "Review pull request #{number}. Look for bugs, security problems, missing tests, and anything that doesn't match its description."
26
+ },
27
+ {
28
+ id: "upgrade-dependency",
29
+ name: "Upgrade a dependency",
30
+ description: "Bump a package, read its changelog for breaking changes, and keep the tests green.",
31
+ pattern: "pipeline",
32
+ task: "Upgrade PACKAGE to its latest version. Read its changelog for breaking changes, update every call site, and keep the tests passing."
33
+ },
34
+ {
35
+ id: "sweep-issues",
36
+ name: "Fix open issues",
37
+ description: "List the open issues, pick the ones to fix, and start one Plan \u2192 Build \u2192 Review run each. Results wait in your inbox.",
38
+ pattern: "pipeline",
39
+ sweep: "issues",
40
+ task: "Fix the GitHub issue below. Add a test that fails without the fix."
41
+ },
42
+ {
43
+ id: "sweep-dependabot",
44
+ name: "Fix Dependabot PRs",
45
+ description: "Every Dependabot PR whose CI fails gets a run that fixes it; approving pushes the fix to the PR. Green ones are left for you to merge.",
46
+ pattern: "pipeline",
47
+ sweep: "dependabot",
48
+ ciLogs: true,
49
+ task: "Make CI pass on the Dependabot pull request below by adapting the code to the new version. Don't pin it back, and don't disable, skip or weaken tests."
50
+ },
51
+ {
52
+ id: "sweep-alerts",
53
+ name: "Fix security alerts",
54
+ description: "Dependabot alerts (one run per package) and code scanning alerts (one per alert). Leaked secrets are listed for you to rotate; agents never get them.",
55
+ pattern: "pipeline",
56
+ sweep: "alerts",
57
+ task: "Fix the security alert below. Fix the cause rather than suppressing the alert, and keep the tests passing."
58
+ }
59
+ ];
60
+ export {
61
+ templates
62
+ };
@@ -0,0 +1,134 @@
1
+ import { constants } from "node:fs";
2
+ import { open } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { RUNS_DIR, waitForRunWrites } from "./store.js";
5
+ const TRANSCRIPT_LIMITS = { events: 500, bytes: 1024 * 1024, recordBytes: 64 * 1024 * 1024, scanBytes: 16 * 1024 * 1024, fieldChars: 32 * 1024 };
6
+ class TranscriptCursorError extends Error {
7
+ }
8
+ function transcriptPreview(event) {
9
+ const shorten = (value) => value.length > TRANSCRIPT_LIMITS.fieldChars ? value.slice(0, TRANSCRIPT_LIMITS.fieldChars) + "\n[Preview shortened; the complete event is in the run export.]" : value;
10
+ if (event.type === "log") return { ...event, text: shorten(event.text) };
11
+ if (event.type !== "agent") return event;
12
+ return { ...event, event: Object.fromEntries(Object.entries(event.event).map(([key, value]) => [key, typeof value === "string" ? shorten(value) : value])) };
13
+ }
14
+ async function* reverseLines(file, end) {
15
+ let position = end, pending = [], pendingBytes = 0;
16
+ while (position > 0) {
17
+ const size = Math.min(position, 64 * 1024);
18
+ position -= size;
19
+ const chunk = Buffer.allocUnsafe(size);
20
+ const { bytesRead } = await file.read(chunk, 0, size, position);
21
+ if (bytesRead !== size) throw new TranscriptCursorError("Transcript changed; reload its latest page");
22
+ let right = size;
23
+ for (let index = size - 1; index >= 0; index--) {
24
+ if (chunk[index] !== 10) continue;
25
+ const part = chunk.subarray(index + 1, right);
26
+ if (part.length + pendingBytes > TRANSCRIPT_LIMITS.recordBytes) throw new Error("Transcript record exceeds the 64 MiB bound");
27
+ if (part.length || pendingBytes) {
28
+ const text = pending.length ? Buffer.concat([part, ...pending.reverse()], part.length + pendingBytes).toString("utf8") : part.toString("utf8");
29
+ yield { text, offset: position + index + 1 };
30
+ } else {
31
+ while (index > 0 && chunk[index - 1] === 10) index--;
32
+ yield { text: "", offset: position === 0 && index === 0 ? 0 : position + index + 1 };
33
+ }
34
+ pending = [];
35
+ pendingBytes = 0;
36
+ right = index;
37
+ }
38
+ if (right) {
39
+ const part = chunk.subarray(0, right);
40
+ pending.push(part);
41
+ pendingBytes += part.length;
42
+ }
43
+ if (pendingBytes > TRANSCRIPT_LIMITS.recordBytes) throw new Error("Transcript record exceeds the 64 MiB bound");
44
+ }
45
+ if (pendingBytes) yield { text: Buffer.concat(pending.reverse(), pendingBytes).toString("utf8"), offset: 0 };
46
+ }
47
+ async function transcriptPage(id, input = {}, reader = reverseLines) {
48
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,199}$/.test(id)) throw new TranscriptCursorError("Invalid run ID");
49
+ const limit = input.limit ?? TRANSCRIPT_LIMITS.events;
50
+ if (!Number.isInteger(limit) || limit < 1 || limit > TRANSCRIPT_LIMITS.events) throw new TranscriptCursorError("Invalid transcript page size");
51
+ await waitForRunWrites(id);
52
+ input.signal?.throwIfAborted();
53
+ const file = await open(join(RUNS_DIR, `${id}.jsonl`), constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
54
+ try {
55
+ const info = await file.stat({ bigint: true });
56
+ if (!info.isFile()) throw new Error("Transcript must be a regular file");
57
+ let end = Number(info.size);
58
+ if (input.before) {
59
+ let cursor;
60
+ try {
61
+ if (input.before.length > 1024) throw new Error();
62
+ cursor = JSON.parse(Buffer.from(input.before, "base64url").toString());
63
+ } catch {
64
+ throw new TranscriptCursorError("Invalid transcript cursor");
65
+ }
66
+ if (!cursor || cursor.run !== id || cursor.dev !== String(info.dev) || cursor.ino !== String(info.ino) || !Number.isSafeInteger(cursor.offset) || cursor.offset < 0 || cursor.offset > end) throw new TranscriptCursorError("Transcript changed; reload its latest page");
67
+ end = cursor.offset;
68
+ if (end) {
69
+ const byte = Buffer.alloc(1);
70
+ await file.read(byte, 0, 1, end - 1);
71
+ if (byte[0] !== 10) throw new TranscriptCursorError("Invalid transcript boundary");
72
+ }
73
+ }
74
+ const events = [];
75
+ let bytes = 0, offset = end, latestSeq = 0, resumed = input.after === 0;
76
+ for await (const line of reader(file, end)) {
77
+ input.signal?.throwIfAborted();
78
+ const skip = () => {
79
+ offset = line.offset;
80
+ return end - offset >= TRANSCRIPT_LIMITS.scanBytes;
81
+ };
82
+ if (!line.text.trim()) {
83
+ if (skip()) break;
84
+ continue;
85
+ }
86
+ let event;
87
+ try {
88
+ event = JSON.parse(line.text);
89
+ } catch {
90
+ if (skip()) break;
91
+ continue;
92
+ }
93
+ if (!event || event.runId !== id || !Number.isSafeInteger(event.seq)) {
94
+ if (skip()) break;
95
+ continue;
96
+ }
97
+ latestSeq = Math.max(latestSeq, event.seq);
98
+ if (input.after !== void 0 && event.seq <= input.after) {
99
+ resumed = true;
100
+ break;
101
+ }
102
+ if ((event.type === "agent" || event.type === "log") && (!input.step || event.type === "agent" && event.stepId === input.step)) {
103
+ if (event.type === "agent" && (!event.event || typeof event.event !== "object") || event.type === "log" && typeof event.text !== "string") {
104
+ if (skip()) break;
105
+ continue;
106
+ }
107
+ let preview = transcriptPreview(event), size = Buffer.byteLength(JSON.stringify(preview));
108
+ if (size > TRANSCRIPT_LIMITS.bytes) {
109
+ const text = "[Preview omitted because this event exceeds the display limit; the complete event is in the run export.]";
110
+ const ts = Number.isFinite(event.ts) ? event.ts : 0;
111
+ preview = event.type === "agent" ? { type: "agent", runId: id, stepId: typeof event.stepId === "string" ? event.stepId.slice(0, 200) : "", seq: event.seq, ts, event: { kind: "status", text } } : { type: "log", runId: id, seq: event.seq, ts, level: "info", text };
112
+ size = Buffer.byteLength(JSON.stringify(preview));
113
+ }
114
+ if (events.length >= limit || bytes + size > TRANSCRIPT_LIMITS.bytes) break;
115
+ events.push(preview);
116
+ bytes += size;
117
+ }
118
+ offset = line.offset;
119
+ if (end - offset >= TRANSCRIPT_LIMITS.scanBytes) break;
120
+ }
121
+ const after = await file.stat({ bigint: true });
122
+ if (after.size < info.size) throw new TranscriptCursorError("Transcript changed; reload its latest page");
123
+ const before = offset > 0 ? Buffer.from(JSON.stringify({ run: id, dev: String(info.dev), ino: String(info.ino), offset })).toString("base64url") : void 0;
124
+ return { events: events.reverse(), before, latestSeq, bytes, scannedBytes: end - offset, resumed: input.after !== void 0 && input.after <= latestSeq && (resumed || !before) };
125
+ } finally {
126
+ await file.close();
127
+ }
128
+ }
129
+ export {
130
+ TRANSCRIPT_LIMITS,
131
+ TranscriptCursorError,
132
+ transcriptPage,
133
+ transcriptPreview
134
+ };
@@ -0,0 +1,27 @@
1
+ import { git } from "./git.js";
2
+ class VerificationIntegrityError extends Error {
3
+ }
4
+ async function verificationIntegrity(repo, signal) {
5
+ const options = { signal, timeoutMs: 1e4 };
6
+ const initial = await git(["rev-parse", "--verify", "HEAD^{commit}"], repo, options);
7
+ const tree = await git(["rev-parse", "--verify", "HEAD^{tree}"], repo, options);
8
+ if (initial.code || tree.code) throw new VerificationIntegrityError("Cannot pin the verification checkout commit");
9
+ const assert = async (stage) => {
10
+ if (signal?.aborted) throw new VerificationIntegrityError("Verification was cancelled");
11
+ const [head, index, unstaged, flags] = await Promise.all([
12
+ git(["rev-parse", "--verify", "HEAD^{commit}"], repo, options),
13
+ git(["write-tree"], repo, options),
14
+ git(["diff", "--no-ext-diff", "--no-textconv", "--ignore-submodules=none", "--quiet", "--exit-code", "--"], repo, options),
15
+ git(["ls-files", "-v", "-z"], repo, options)
16
+ ]);
17
+ if (signal?.aborted) throw new VerificationIntegrityError("Verification was cancelled");
18
+ const hiddenFiles = flags.stdout.split("\0").some((file) => /^[a-zS] /.test(file));
19
+ if (head.code || index.code || unstaged.code || flags.code || hiddenFiles || head.stdout.trim() !== initial.stdout.trim() || index.stdout.trim() !== tree.stdout.trim()) throw new VerificationIntegrityError(`Verification ${stage} changed tracked files, the staged tree or the checkout commit, or hid tracked files from comparison. The original commit was not verified.`);
20
+ };
21
+ await assert("starting checkout");
22
+ return { head: initial.stdout.trim(), assert };
23
+ }
24
+ export {
25
+ VerificationIntegrityError,
26
+ verificationIntegrity
27
+ };
@@ -0,0 +1,183 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { cantRunReason, detectCheckCommand, failureExcerpt, failureFingerprint, runCheck } from "./checks.js";
5
+ const KINDS = /* @__PURE__ */ new Set(["build", "typecheck", "lint", "unit", "integration", "browser", "mutation", "visual", "accessibility", "custom"]);
6
+ const DEFAULT_CHECK_TIMEOUT = 5 * 6e4;
7
+ const MAX_CHECK_TIMEOUT = 30 * 6e4;
8
+ const MAX_SUITE_TIMEOUT = 60 * 6e4;
9
+ function validateVerificationProfile(value) {
10
+ if (!value || typeof value !== "object") throw new Error("A verification profile is required");
11
+ const p = value;
12
+ if (typeof p.name !== "string" || !p.name.trim() || p.name.length > 120) throw new Error("Verification profile needs a name of at most 120 characters");
13
+ if (!Array.isArray(p.checks) || p.checks.length > 12) throw new Error("Verification profiles support at most 12 checks");
14
+ const ids = /* @__PURE__ */ new Set();
15
+ const checks = p.checks.map((raw) => {
16
+ if (!raw || typeof raw !== "object") throw new Error("Invalid verification check");
17
+ const c = raw;
18
+ if (typeof c.id !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(c.id) || ids.has(c.id)) throw new Error("Verification check IDs must be unique and contain letters, digits, underscores or hyphens");
19
+ ids.add(c.id);
20
+ if (!KINDS.has(c.kind)) throw new Error(`Unknown verification kind for ${c.id}`);
21
+ if (typeof c.command !== "string" || !c.command.trim() || c.command.length > 4e3 || c.command.includes("\0")) throw new Error(`Invalid command for ${c.id}`);
22
+ const timeoutMs = c.timeoutMs ?? DEFAULT_CHECK_TIMEOUT;
23
+ if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs) || timeoutMs < 1 || timeoutMs > MAX_CHECK_TIMEOUT) throw new Error(`Check timeout must be between 1 ms and ${MAX_CHECK_TIMEOUT} ms`);
24
+ if (c.required !== void 0 && typeof c.required !== "boolean") throw new Error(`Invalid required flag for ${c.id}`);
25
+ return { id: c.id, ...typeof c.title === "string" && c.title.trim() ? { title: c.title.trim().slice(0, 120) } : {}, kind: c.kind, command: c.command.trim(), required: c.required !== false, timeoutMs };
26
+ });
27
+ let requirements;
28
+ if (p.requirements !== void 0) {
29
+ if (!Array.isArray(p.requirements) || p.requirements.length > 48) throw new Error("Verification supports at most 48 requirement mappings");
30
+ const requirementIds = /* @__PURE__ */ new Set();
31
+ requirements = p.requirements.map((raw) => {
32
+ if (!raw || typeof raw !== "object") throw new Error("Invalid requirement mapping");
33
+ const requirement = raw;
34
+ if (typeof requirement.id !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(requirement.id) || requirementIds.has(requirement.id) || typeof requirement.description !== "string" || !requirement.description.trim() || requirement.description.length > 4e3 || !Array.isArray(requirement.checks) || requirement.checks.some((id) => typeof id !== "string" || !ids.has(id)) || new Set(requirement.checks).size !== requirement.checks.length) throw new Error("Requirements need unique IDs, descriptions, and references to configured checks");
35
+ requirementIds.add(requirement.id);
36
+ return { id: requirement.id, description: requirement.description.trim(), checks: [...requirement.checks] };
37
+ });
38
+ }
39
+ return { name: p.name.trim(), checks, ...requirements ? { requirements } : {}, ...typeof p.source === "string" ? { source: p.source.slice(0, 500) } : {}, ...typeof p.setup === "string" && p.setup.trim() ? { setup: p.setup.trim().slice(0, 4e3) } : {} };
40
+ }
41
+ async function detectVerificationProfile(repo, preset = "standard") {
42
+ if (!["lightweight", "standard", "thorough"].includes(preset)) throw new Error("Unknown verification preset");
43
+ const include = preset === "lightweight" ? ["typecheck", "lint", "unit"] : preset === "standard" ? ["build", "typecheck", "lint", "unit"] : ["build", "typecheck", "lint", "unit", "integration", "browser", "mutation", "visual", "accessibility"];
44
+ const has = (path) => existsSync(join(repo, path));
45
+ const checks = [];
46
+ if (has("package.json")) {
47
+ let pkg;
48
+ try {
49
+ pkg = JSON.parse(await readFile(join(repo, "package.json"), "utf8"));
50
+ } catch {
51
+ throw new Error("Cannot detect verification: package.json is not valid JSON");
52
+ }
53
+ const runner = has("pnpm-lock.yaml") ? "pnpm run" : has("yarn.lock") ? "yarn run" : has("bun.lock") || has("bun.lockb") ? "bun run" : "npm run";
54
+ const scripts = [["build", ["build"]], ["typecheck", ["typecheck", "check:types"]], ["lint", ["lint"]], ["unit", ["test:unit", "test"]], ["integration", ["test:integration"]], ["browser", ["test:e2e", "test:browser"]], ["mutation", ["test:mutation", "mutation"]], ["visual", ["test:visual"]], ["accessibility", ["test:a11y", "test:accessibility"]]];
55
+ for (const [kind, candidates] of scripts) {
56
+ if (!include.includes(kind)) continue;
57
+ const script = candidates.find((name) => typeof pkg.scripts?.[name] === "string" && String(pkg.scripts[name]).trim() && !/no test specified/i.test(String(pkg.scripts[name])));
58
+ if (script) checks.push({ id: kind, kind, command: `${runner} ${script}`, required: true, timeoutMs: DEFAULT_CHECK_TIMEOUT });
59
+ }
60
+ if (checks.length) return { name: preset, checks, source: "package.json" };
61
+ }
62
+ const detected = await detectCheckCommand(repo);
63
+ if (detected) checks.push({ id: "unit", kind: "unit", command: detected.command, required: true, timeoutMs: DEFAULT_CHECK_TIMEOUT });
64
+ return { name: preset, checks, source: detected?.source, setup: detected?.setup };
65
+ }
66
+ function diagnoseChecks(attempts, baseline) {
67
+ if (!attempts.length) return { diagnosis: "failed", reason: "No check attempt completed" };
68
+ if (attempts.some((a) => a.ok) && attempts.some((a) => !a.ok)) return { diagnosis: "flaky", reason: "The same command both passed and failed on this revision" };
69
+ if (attempts.every((a) => a.ok)) return { diagnosis: "passed" };
70
+ const reason = attempts.map(cantRunReason).find(Boolean);
71
+ if (reason) return { diagnosis: "environment", reason };
72
+ if (baseline?.ok) return { diagnosis: "regression", reason: "This check passed on the baseline revision" };
73
+ if (baseline && !baseline.ok && attempts.every((a) => failureFingerprint(a) === failureFingerprint(baseline))) return { diagnosis: "pre-existing", reason: "The baseline reports the same failure; this check still requires attention" };
74
+ return { diagnosis: "failed" };
75
+ }
76
+ function resultState(checks, skipped = []) {
77
+ if (skipped.some((c) => c.check.required)) return "blocked";
78
+ const required = checks.filter((c) => c.check.required);
79
+ if (required.some((c) => c.diagnosis === "environment")) return "blocked";
80
+ if (required.some((c) => c.diagnosis !== "passed")) return "needs-changes";
81
+ return required.length ? "verified" : "unchecked";
82
+ }
83
+ async function runVerification(cwd, input, opts = {}) {
84
+ const profile = validateVerificationProfile(input);
85
+ const repeats = opts.repeats ?? 1;
86
+ if (!Number.isInteger(repeats) || repeats < 1 || repeats > 3) throw new Error("Verification repeats must be between 1 and 3");
87
+ const timeoutMs = opts.timeoutMs ?? 15 * 6e4;
88
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 1 || timeoutMs > MAX_SUITE_TIMEOUT) throw new Error("Verification deadline must be between 1 ms and one hour");
89
+ const startedAt = Date.now();
90
+ const checks = [];
91
+ const skipped = [];
92
+ for (const check of profile.checks) {
93
+ if (opts.signal?.aborted || Date.now() - startedAt >= timeoutMs) {
94
+ skipped.push({ check, reason: opts.signal?.aborted ? "Verification was cancelled" : "The verification deadline was reached" });
95
+ continue;
96
+ }
97
+ const attempts = [];
98
+ for (let i = 0; i < repeats; i++) {
99
+ const remaining = timeoutMs - (Date.now() - startedAt);
100
+ if (opts.signal?.aborted || remaining <= 0) break;
101
+ const execution = { timeoutMs: Math.min(check.timeoutMs, remaining), signal: opts.signal, container: opts.container };
102
+ attempts.push(await (opts.execute ? opts.execute(check, execution) : runCheck(cwd, check.command, execution)));
103
+ }
104
+ const baseline = opts.baseline?.[check.id];
105
+ const diagnosis = diagnoseChecks(attempts, baseline);
106
+ if (["unit", "integration", "browser"].includes(check.kind) && attempts.some((attempt) => attempt.tests === 0)) {
107
+ diagnosis.diagnosis = "failed";
108
+ diagnosis.reason = "The test runner reported zero executed tests";
109
+ }
110
+ checks.push({ check, attempts, baseline, ...diagnosis });
111
+ if (attempts.length < repeats || opts.signal?.aborted) skipped.push({ check, reason: opts.signal?.aborted ? "Verification was cancelled" : "Repeated verification did not complete before the deadline" });
112
+ }
113
+ const requirements = (opts.requirements ?? profile.requirements)?.map((r) => {
114
+ const missing = r.checks.filter((id) => !checks.some((c) => c.check.id === id && c.diagnosis === "passed"));
115
+ return { id: r.id, description: r.description, covered: r.checks.length > 0 && !missing.length, missing };
116
+ });
117
+ let state = resultState(checks, skipped);
118
+ if (state === "verified" && requirements?.some((r) => !r.covered)) state = "needs-changes";
119
+ return {
120
+ version: 1,
121
+ profile: profile.name,
122
+ state,
123
+ startedAt,
124
+ endedAt: Date.now(),
125
+ checks,
126
+ skipped,
127
+ requirements,
128
+ ...opts.changedFiles ? { risk: assessChangeRisk(opts.changedFiles), review: reviewCoverage(opts.changedFiles.map((f) => f.path), opts.reviewedFiles ?? []) } : {}
129
+ };
130
+ }
131
+ function assessChangeRisk(files) {
132
+ const reasons = /* @__PURE__ */ new Set();
133
+ const sensitiveFiles = [];
134
+ for (const f of files) {
135
+ const path = f.path.replaceAll("\\", "/");
136
+ let sensitive = false;
137
+ for (const [pattern, reason] of [
138
+ [/(^|\/)(migrations?|schema)(\/|\.)|\.sql$/i, "Database or schema changes require migration review"],
139
+ [/(^|[\/_-])(auth|permissions?|security|secrets?|credentials?|crypto)([\/_.-]|$)/i, "Authentication, permissions or sensitive data handling changed"],
140
+ [/(^|\/)(\.github\/workflows|Dockerfile|\.env)|(^|\/)(deploy|infra|terraform)(\/|\.)/i, "Execution or deployment configuration changed"]
141
+ ]) if (pattern.test(path)) {
142
+ reasons.add(reason);
143
+ sensitive = true;
144
+ }
145
+ if (sensitive) sensitiveFiles.push(f.path);
146
+ }
147
+ const lines = files.reduce((n, f) => n + (f.additions ?? 0) + (f.deletions ?? 0), 0);
148
+ if (files.length > 20 || lines > 1e3) reasons.add("Large change surface needs broader verification");
149
+ return { level: sensitiveFiles.length ? "high" : reasons.size || files.length > 5 ? "medium" : "low", reasons: [...reasons], sensitiveFiles };
150
+ }
151
+ function reviewCoverage(changed, reviewed) {
152
+ const normalize = (p) => p.replaceAll("\\", "/").replace(/^\.\//, "");
153
+ const files = [...new Set(changed.map(normalize))];
154
+ const seen = new Set(reviewed.map(normalize));
155
+ const unreviewed = files.filter((f) => !seen.has(f));
156
+ return { changed: files.length, reviewed: files.length - unreviewed.length, percent: files.length ? Math.round(100 * (files.length - unreviewed.length) / files.length) : 100, unreviewed };
157
+ }
158
+ function renderVerificationReport(report) {
159
+ const cell = (s) => s.replaceAll("|", "\\|").replaceAll("\n", " ");
160
+ const parts = [`# Verification: ${report.state}`, "", `Profile: ${cell(report.profile)} \xB7 Duration: ${((report.endedAt - report.startedAt) / 1e3).toFixed(1)}s`, "", "| Check | Required | Result | Attempts |", "| --- | --- | --- | --- |"];
161
+ for (const c of report.checks) parts.push(`| ${cell(c.check.title ?? c.check.id)} | ${c.check.required ? "yes" : "no"} | ${c.diagnosis} | ${c.attempts.length} |`);
162
+ for (const s of report.skipped) parts.push(`| ${cell(s.check.id)} | ${s.check.required ? "yes" : "no"} | skipped: ${cell(s.reason)} | 0 |`);
163
+ for (const c of report.checks.filter((c2) => c2.diagnosis !== "passed")) {
164
+ const output = failureExcerpt(c.attempts.find((a) => !a.ok)?.output ?? c.reason ?? "No output");
165
+ const fence = "`".repeat(Math.max(3, ...[...output.matchAll(/`+/g)].map((m) => m[0].length + 1)));
166
+ parts.push("", `## ${cell(c.check.id)}: ${c.diagnosis}`, "", c.reason ?? "The check did not pass.", "", fence, output, fence);
167
+ }
168
+ if (report.risk) parts.push("", `Change risk: ${report.risk.level}`, ...report.risk.reasons.map((r) => `- ${r}`));
169
+ if (report.review) parts.push("", `Review coverage: ${report.review.reviewed}/${report.review.changed} files (${report.review.percent}%)`, ...report.review.unreviewed.map((p) => `- Unreviewed: ${p}`));
170
+ if (report.requirements) parts.push("", "Requirements:", ...report.requirements.map((r) => `- ${r.covered ? "[x]" : "[ ]"} ${r.id}: ${r.description}${r.missing.length ? ` (missing: ${r.missing.join(", ")})` : ""}`));
171
+ return `${parts.join("\n")}
172
+ `;
173
+ }
174
+ export {
175
+ assessChangeRisk,
176
+ detectVerificationProfile,
177
+ diagnoseChecks,
178
+ renderVerificationReport,
179
+ resultState,
180
+ reviewCoverage,
181
+ runVerification,
182
+ validateVerificationProfile
183
+ };