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

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 (284) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +122 -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/browser-7XI52A6V.js +8 -0
  17. package/public/assets/changes-EYFPASEI.js +1 -0
  18. package/public/assets/chunk-7H3ZLTKA.js +2 -0
  19. package/public/assets/chunk-L7TUCIYF.js +1 -0
  20. package/public/assets/chunk-LLMQG23J.js +163 -0
  21. package/public/assets/main-KIRM2OEK.js +1 -0
  22. package/public/assets/settings-QJZVW22Y.js +9 -0
  23. package/public/assets/styles-615EBB20.css +32 -0
  24. package/public/assets/terminal-A2FI2NAX.js +62 -0
  25. package/public/assets.json +1 -0
  26. package/public/bundle-meta.json +1 -0
  27. package/public/docs/.nojekyll +0 -0
  28. package/public/docs/accessibility.html +19 -0
  29. package/public/docs/acp-agents.html +117 -0
  30. package/public/docs/agent-containers.html +79 -0
  31. package/public/docs/api.html +1130 -0
  32. package/public/docs/app.js +342 -0
  33. package/public/docs/application-improvements.html +275 -0
  34. package/public/docs/architecture-explorer.html +19 -0
  35. package/public/docs/architecture.html +149 -0
  36. package/public/docs/auto-team.html +142 -0
  37. package/public/docs/automations.html +109 -0
  38. package/public/docs/checkpoint-review.html +37 -0
  39. package/public/docs/code-hosts.html +35 -0
  40. package/public/docs/code-intelligence.html +35 -0
  41. package/public/docs/configuration.html +346 -0
  42. package/public/docs/context-inspection.html +23 -0
  43. package/public/docs/coordinated-changes.html +28 -0
  44. package/public/docs/deferred-runs.html +25 -0
  45. package/public/docs/dependency-discovery.html +19 -0
  46. package/public/docs/documentation.html +84 -0
  47. package/public/docs/dynamic-teams-evaluation.html +174 -0
  48. package/public/docs/dynamic-teams-implementation-plan.html +882 -0
  49. package/public/docs/dynamic-teams-implementation-review-1.html +141 -0
  50. package/public/docs/dynamic-teams-plan-review-0.html +204 -0
  51. package/public/docs/extensions.html +42 -0
  52. package/public/docs/external-sources.html +96 -0
  53. package/public/docs/filesystem-policy.html +26 -0
  54. package/public/docs/filtered-tasks.html +44 -0
  55. package/public/docs/font-license.txt +92 -0
  56. package/public/docs/getting-started.html +118 -0
  57. package/public/docs/github-review-threads.html +26 -0
  58. package/public/docs/github.html +157 -0
  59. package/public/docs/help.html +69 -0
  60. package/public/docs/index.html +77 -0
  61. package/public/docs/installation.html +74 -0
  62. package/public/docs/launch-profiles.html +48 -0
  63. package/public/docs/maintenance.html +60 -0
  64. package/public/docs/memory-claims.html +31 -0
  65. package/public/docs/memory-storage.html +58 -0
  66. package/public/docs/notifications.html +39 -0
  67. package/public/docs/patterns.html +195 -0
  68. package/public/docs/performance-results.html +210 -0
  69. package/public/docs/performance.html +166 -0
  70. package/public/docs/project-conventions.html +22 -0
  71. package/public/docs/project-first-navigation-plan.html +587 -0
  72. package/public/docs/project-teams-implementation-plan.html +772 -0
  73. package/public/docs/project-teams.html +99 -0
  74. package/public/docs/provider-compatibility.html +20 -0
  75. package/public/docs/releasing.html +61 -0
  76. package/public/docs/remote-workers.html +64 -0
  77. package/public/docs/review-inbox.html +26 -0
  78. package/public/docs/run-storage.html +23 -0
  79. package/public/docs/runs.html +141 -0
  80. package/public/docs/search.json +1 -0
  81. package/public/docs/security.html +133 -0
  82. package/public/docs/shared-projects.html +31 -0
  83. package/public/docs/ssh-projects.html +60 -0
  84. package/public/docs/style.css +768 -0
  85. package/public/docs/task-reruns.html +30 -0
  86. package/public/docs/team-collaboration.html +379 -0
  87. package/public/docs/verification.html +22 -0
  88. package/public/docs/vscode.html +53 -0
  89. package/public/docs/worker-permission-verification.html +122 -0
  90. package/public/index.html +17 -0
  91. package/scripts/check-node.mjs +7 -0
  92. package/scripts/codeflow.mjs +55 -0
  93. package/scripts/pty.py +273 -0
  94. package/src/acp-connections.js +110 -0
  95. package/src/adapters/acp-presets.js +37 -0
  96. package/src/adapters/acp-types.js +0 -0
  97. package/src/adapters/acp.js +328 -0
  98. package/src/adapters/agy-auth.js +67 -0
  99. package/src/adapters/agy-isolation.js +223 -0
  100. package/src/adapters/agy.js +297 -0
  101. package/src/adapters/claude.js +220 -0
  102. package/src/adapters/codex-app-server.js +518 -0
  103. package/src/adapters/codex.js +299 -0
  104. package/src/adapters/errors.js +57 -0
  105. package/src/adapters/index.js +91 -0
  106. package/src/adapters/managed-permissions.js +37 -0
  107. package/src/adapters/model-discovery.js +100 -0
  108. package/src/adapters/process.js +418 -0
  109. package/src/adapters/team-tools.js +102 -0
  110. package/src/adapters/types.js +0 -0
  111. package/src/agent-container.js +155 -0
  112. package/src/approvals.js +121 -0
  113. package/src/architecture.js +456 -0
  114. package/src/assets.js +44 -0
  115. package/src/automation.js +901 -0
  116. package/src/certificate-renewal.js +75 -0
  117. package/src/chat-handoff.js +55 -0
  118. package/src/checkpoint-review.js +72 -0
  119. package/src/checks.js +303 -0
  120. package/src/code-host-git.js +102 -0
  121. package/src/code-hosts-api.js +99 -0
  122. package/src/code-hosts.js +482 -0
  123. package/src/code-intelligence-worker.mjs +452 -0
  124. package/src/code-intelligence.js +141 -0
  125. package/src/collab/api.js +365 -0
  126. package/src/collab/checkpoints.js +198 -0
  127. package/src/collab/connections.js +139 -0
  128. package/src/collab/host-execution.js +347 -0
  129. package/src/collab/hub-client.js +210 -0
  130. package/src/collab/link-api.js +229 -0
  131. package/src/collab/link.js +595 -0
  132. package/src/collab/model.js +496 -0
  133. package/src/collab/runner.js +1014 -0
  134. package/src/collab/store.js +1452 -0
  135. package/src/collab/terminals.js +361 -0
  136. package/src/collaboration-reviews.js +116 -0
  137. package/src/config.js +48 -0
  138. package/src/context-budget.js +14 -0
  139. package/src/context-inspection.js +204 -0
  140. package/src/context.js +154 -0
  141. package/src/conventions.js +283 -0
  142. package/src/coordinated-api.js +247 -0
  143. package/src/deferred-launch.js +82 -0
  144. package/src/deferred-runs.js +515 -0
  145. package/src/dependency-health.js +35 -0
  146. package/src/diff.js +43 -0
  147. package/src/docs-help.js +316 -0
  148. package/src/doctor.js +232 -0
  149. package/src/engine.js +4366 -0
  150. package/src/env.js +44 -0
  151. package/src/experience.js +50 -0
  152. package/src/extensions.js +144 -0
  153. package/src/filesystem-access.js +107 -0
  154. package/src/filesystem-policy.js +238 -0
  155. package/src/filtered-task-limits.js +93 -0
  156. package/src/filtered-tasks-api.js +53 -0
  157. package/src/filtered-tasks.js +576 -0
  158. package/src/fs.js +57 -0
  159. package/src/git.js +626 -0
  160. package/src/github-review-threads.js +181 -0
  161. package/src/github.js +538 -0
  162. package/src/http.js +69 -0
  163. package/src/improve-settings.js +38 -0
  164. package/src/improve.js +187 -0
  165. package/src/integrations.js +373 -0
  166. package/src/integrity.js +259 -0
  167. package/src/journey-api.js +77 -0
  168. package/src/journey-replay.js +65 -0
  169. package/src/journey-store.js +97 -0
  170. package/src/journeys.js +60 -0
  171. package/src/json.js +81 -0
  172. package/src/launch-profiles.js +292 -0
  173. package/src/learnings.js +80 -0
  174. package/src/log.js +39 -0
  175. package/src/maintenance.js +335 -0
  176. package/src/memory-analysis.js +182 -0
  177. package/src/memory-claims.js +153 -0
  178. package/src/memory-sqlite.js +236 -0
  179. package/src/memory.js +552 -0
  180. package/src/merge-validation.js +58 -0
  181. package/src/model-catalog.js +238 -0
  182. package/src/model-research.js +91 -0
  183. package/src/notes.js +176 -0
  184. package/src/notifications.js +413 -0
  185. package/src/patterns/auto-team-model.js +212 -0
  186. package/src/patterns/auto-team.js +659 -0
  187. package/src/patterns/council.js +199 -0
  188. package/src/patterns/debate.js +188 -0
  189. package/src/patterns/findings.js +141 -0
  190. package/src/patterns/index.js +19 -0
  191. package/src/patterns/loop.js +158 -0
  192. package/src/patterns/pipeline.js +125 -0
  193. package/src/patterns/prompts.js +181 -0
  194. package/src/patterns/race.js +147 -0
  195. package/src/patterns/schemas.js +40 -0
  196. package/src/patterns/solo.js +49 -0
  197. package/src/patterns/supervisor.js +235 -0
  198. package/src/patterns/task-draft.js +29 -0
  199. package/src/patterns/tdd.js +231 -0
  200. package/src/patterns/team-strategies.js +67 -0
  201. package/src/performance.js +43 -0
  202. package/src/persistence-status.js +35 -0
  203. package/src/phone-sessions.js +96 -0
  204. package/src/preview-overlay.js +383 -0
  205. package/src/preview.js +219 -0
  206. package/src/privacy.js +23 -0
  207. package/src/project-report.js +22 -0
  208. package/src/project-team-model.js +178 -0
  209. package/src/project-team-policy.js +42 -0
  210. package/src/project-team-store.js +213 -0
  211. package/src/project-teams-api.js +36 -0
  212. package/src/project-teams.js +551 -0
  213. package/src/projects.js +286 -0
  214. package/src/provider-eligibility.js +9 -0
  215. package/src/readiness.js +98 -0
  216. package/src/remote-tls.js +66 -0
  217. package/src/remote-workers/bundles.js +198 -0
  218. package/src/remote-workers/client.js +332 -0
  219. package/src/remote-workers/durability.js +37 -0
  220. package/src/remote-workers/protocol.js +68 -0
  221. package/src/remote-workers/stdio.js +138 -0
  222. package/src/remote-workers/worker.js +304 -0
  223. package/src/remote.js +204 -0
  224. package/src/repo-config.js +201 -0
  225. package/src/repo-coordination.js +732 -0
  226. package/src/repo-tree.js +106 -0
  227. package/src/retention-policy.js +466 -0
  228. package/src/review-inbox.js +38 -0
  229. package/src/routing.js +49 -0
  230. package/src/run-analysis.js +111 -0
  231. package/src/run-cold-store.js +231 -0
  232. package/src/run-environment.js +15 -0
  233. package/src/run-history.js +91 -0
  234. package/src/run-log.js +160 -0
  235. package/src/run-permissions.js +110 -0
  236. package/src/run-projection.js +75 -0
  237. package/src/run-recovery.js +53 -0
  238. package/src/run-status.js +54 -0
  239. package/src/serve.js +138 -0
  240. package/src/server.js +2738 -0
  241. package/src/shared-access.js +103 -0
  242. package/src/shared-accounts.js +301 -0
  243. package/src/shared-api.js +239 -0
  244. package/src/shell.js +346 -0
  245. package/src/ship.js +344 -0
  246. package/src/slots.js +74 -0
  247. package/src/snapshot.js +388 -0
  248. package/src/source-context.js +17 -0
  249. package/src/sse-writer.js +70 -0
  250. package/src/ssh-projects/bootstrap.mjs +95 -0
  251. package/src/ssh-projects/config.js +45 -0
  252. package/src/ssh-projects/gateway.js +143 -0
  253. package/src/ssh-projects/manager.js +320 -0
  254. package/src/ssh-projects/team-autostart.mjs +93 -0
  255. package/src/ssh-projects/team-remove.mjs +69 -0
  256. package/src/ssh-projects/team-status.mjs +39 -0
  257. package/src/ssh-projects/transport.js +116 -0
  258. package/src/stats.js +139 -0
  259. package/src/store.js +544 -0
  260. package/src/stream.js +91 -0
  261. package/src/sweeps.js +241 -0
  262. package/src/tailscale.js +101 -0
  263. package/src/task-draft.js +23 -0
  264. package/src/team-evaluation.js +242 -0
  265. package/src/team-mailbox.js +160 -0
  266. package/src/team-memory.js +84 -0
  267. package/src/team-rerun-graph.js +15 -0
  268. package/src/team-reruns.js +130 -0
  269. package/src/team-scheduler.js +121 -0
  270. package/src/team-template-store.js +98 -0
  271. package/src/team-templates.js +117 -0
  272. package/src/team-tool-client.mjs +79 -0
  273. package/src/team-tools.js +176 -0
  274. package/src/teams.js +0 -0
  275. package/src/templates.js +62 -0
  276. package/src/transcript.js +134 -0
  277. package/src/verification-integrity.js +27 -0
  278. package/src/verification.js +183 -0
  279. package/src/watch.js +195 -0
  280. package/src/work-queue.js +47 -0
  281. package/src/workflow-control.js +83 -0
  282. package/src/workspace-api.js +207 -0
  283. package/src/workspace-changes.js +61 -0
  284. package/src/workspace.js +146 -0
@@ -0,0 +1,199 @@
1
+ import { blocks } from "../engine.js";
2
+ import { defaultBranch, mergeBase } from "../github.js";
3
+ import { checkIssues, checkMark, CONFIRMATION, REVIEW_SHAPE, reproRule, severityOf } from "./findings.js";
4
+ import { COUNCIL_SCHEMA, REVIEW_SCHEMA } from "./schemas.js";
5
+ import { checkEvidence, fence, jsonInstruction, LETTERS, preamble, proofEvidence, proofLine, READ_ONLY, section } from "./prompts.js";
6
+ const SEVERITY = { high: 0, medium: 1, low: 2 };
7
+ const councilPattern = {
8
+ id: "council",
9
+ name: "Review council",
10
+ summary: "Several agents review the same changes on their own; a chair merges what they found.",
11
+ description: "Use to review a pull request or a branch before merging: different models catch different problems, code-flow runs the tests reviewers show bugs with, and the worst confirmed problems rank first. Read-only \u2014 nothing is changed.",
12
+ roles: [
13
+ { id: "reviewers", label: "Reviewers", description: "Each reviews the changes on its own, read-only", multiple: true, default: ["claude", "codex"] },
14
+ { id: "chair", label: "Chair", description: "Merges the reviews into one, noting where reviewers agree", default: ["claude"] }
15
+ ],
16
+ options: [],
17
+ async run(ctx) {
18
+ const reviewers = ctx.participants("reviewers");
19
+ if (!reviewers.length) throw new Error("Pick at least one reviewer");
20
+ const wt = await ctx.worktree("review");
21
+ const base = await ctx.remember("review-base", "base", async () => {
22
+ if (ctx.run.reviewBase) return ctx.run.reviewBase;
23
+ const main = await defaultBranch(ctx.run.repo);
24
+ return main ? await mergeBase(ctx.run.repo, main, ctx.run.baseRef) ?? null : null;
25
+ });
26
+ const diff = base && base !== ctx.run.baseRef ? await ctx.diff(wt, base) : "(no changes)";
27
+ const scope = diff === "(no changes)" ? section("What to review", "There's no diff against the default branch, so review the code the task is about.") : section("Changes under review", fence(diff, "diff"));
28
+ if (diff === "(no changes)") ctx.log("No diff against the default branch; reviewing the code the task points at");
29
+ let evidence = "";
30
+ let proof = null;
31
+ if (ctx.proves && base && diff !== "(no changes)") {
32
+ const check = await ctx.check(wt, "Check the changes");
33
+ proof = check?.ok ? await ctx.prove(wt, { from: base }) : null;
34
+ evidence = checkEvidence(ctx, check, 3e3) + proofEvidence(proof);
35
+ }
36
+ const reviews = await ctx.parallel(reviewers, async ({ agent, settings, identity }, i) => {
37
+ const label = LETTERS[i] ?? String(i + 1);
38
+ const outcome = await ctx.decide({
39
+ title: `Review ${label}`,
40
+ schema: REVIEW_SCHEMA,
41
+ what: "review",
42
+ role: "reviewers",
43
+ agent,
44
+ settings,
45
+ identity,
46
+ mode: "read",
47
+ cwd: wt,
48
+ prompt: preamble("reviewer", agent, `${READ_ONLY} Other reviewers are reviewing the same changes independently.`) + section("Task", ctx.task) + scope + evidence + section(
49
+ "Deliverable",
50
+ "Look for bugs, security problems, missing or weak tests, and anything that doesn't do what the task or description says. Read the surrounding code where you need to. Report real problems only \u2014 not style preferences \u2014 with the file and a concrete fix."
51
+ ) + reproRule(ctx) + jsonInstruction(REVIEW_SHAPE)
52
+ }).catch((err) => {
53
+ if (ctx.signal.aborted) throw err;
54
+ ctx.log(`Reviewer ${label} (${agent}) didn't finish: ${err.message}`, "warn");
55
+ return void 0;
56
+ });
57
+ return outcome ? { label, agent, review: outcome.value } : void 0;
58
+ });
59
+ const done = reviews.filter((r) => !!r);
60
+ if (!done.length) throw new Error("No reviewer finished");
61
+ const checked = [];
62
+ for (const r of done) checked.push(await checkIssues(ctx, wt, r.review.issues, `Reviewer ${r.label}'s test`));
63
+ let merged;
64
+ if (done.length === 1) {
65
+ const only = done[0];
66
+ merged = {
67
+ verdict: only.review.verdict,
68
+ summary: "",
69
+ findings: only.review.issues.map((i) => ({ ...i, severity: severityOf(i.severity), raised_by: [only.label] }))
70
+ };
71
+ } else {
72
+ const chair = ctx.agent("chair");
73
+ const letters = done.map((r) => r.label);
74
+ const { value } = await ctx.decide({
75
+ title: "Merge the reviews",
76
+ schema: COUNCIL_SCHEMA,
77
+ what: "merged review",
78
+ role: "chair",
79
+ agent: chair,
80
+ mode: "read",
81
+ cwd: wt,
82
+ validate: (v) => {
83
+ const bad = v.findings.flatMap((f) => f.raised_by).find((l) => !letters.includes(l));
84
+ return bad ? `raised_by must only use reviewer letters ${letters.join(", ")} (got "${bad}")` : void 0;
85
+ },
86
+ prompt: preamble("chair of a review council", chair, READ_ONLY) + section("Task", ctx.task) + section(
87
+ "Reviews",
88
+ done.map((r, n) => {
89
+ const issues = r.review.issues.map((issue, i) => ({ ...issue, checked_by_code_flow: checkNote(issue.severity, checked[n][i]) }));
90
+ return `### Reviewer ${r.label}: ${r.review.verdict}
91
+ ${fence(JSON.stringify(issues, null, 2), "json")}`;
92
+ }).join("\n\n")
93
+ ) + section(
94
+ "Deliverable",
95
+ "Merge these into one review. Combine findings that describe the same problem and list every reviewer who raised it. `checked_by_code_flow` says what code-flow found running a finding's `repro` on the code: a high-severity finding whose test passes is a false alarm, and one without a test is unconfirmed. Check doubtful findings against the code and drop the ones that are wrong. Keep each finding's `repro` as it is (the confirmed one, if several reviewers gave one); use \"\" fields when none did. Request changes if a confirmed high-severity problem or a real medium one remains. Write a short summary for the author."
96
+ ) + jsonInstruction(
97
+ '{"verdict": "approve" | "changes_requested", "summary": "...", "findings": [{"severity": "high|medium|low", "file": "path", "problem": "...", "fix": "...", "raised_by": ["A", "B"], "repro": {"path": "", "contents": "", "command": ""}}]}'
98
+ )
99
+ });
100
+ merged = value;
101
+ }
102
+ const agentOf = Object.fromEntries(done.map((r) => [r.label, r.agent]));
103
+ const checks = await checkIssues(ctx, wt, merged.findings, "Merged finding's test");
104
+ const { raised, added } = restoreConfirmed(done, checked, merged, checks);
105
+ const restored = [...raised.values(), ...added];
106
+ if (restored.length) {
107
+ ctx.log(
108
+ `The chair left out or rated lower ${restored.length} finding${restored.length === 1 ? "" : "s"} a test confirms; code-flow put ${restored.length === 1 ? "it" : "them"} back as raised: ` + restored.map((r) => `${r.finding.severity} "${r.finding.problem}" (Reviewer ${r.label}: ${r.why})`).join("; "),
109
+ "warn"
110
+ );
111
+ }
112
+ const note = (r) => `from Reviewer ${r.label}, whose test confirms it: ${r.why}`;
113
+ const findings = [
114
+ ...merged.findings.map((f, i) => {
115
+ const back = raised.get(i);
116
+ return {
117
+ severity: back?.finding.severity ?? severityOf(f.severity),
118
+ file: f.file,
119
+ problem: f.problem,
120
+ fix: f.fix,
121
+ raisedBy: [...new Set(f.raised_by.map((l) => agentOf[l]).filter(Boolean))],
122
+ // Rated low, the chair's copy wasn't run (a suggestion): the reviewer's test result stands.
123
+ check: back?.finding.check ?? checks[i],
124
+ ...back ? { restored: note(back) } : {}
125
+ };
126
+ }),
127
+ ...added.map((r) => ({ ...r.finding, raisedBy: [agentOf[r.label]], restored: note(r) }))
128
+ ].sort(
129
+ (a, b) => SEVERITY[a.severity] - SEVERITY[b.severity] || CONFIRMATION[a.check.status] - CONFIRMATION[b.check.status] || b.raisedBy.length - a.raisedBy.length
130
+ );
131
+ ctx.learn(findings.filter((f) => f.check?.status !== "refuted"));
132
+ const blocking = findings.filter((f) => blocks(f.severity, f.check?.status ?? "unconfirmed"));
133
+ const verdict = blocking.length ? "changes_requested" : "approve";
134
+ if (verdict !== merged.verdict) {
135
+ ctx.log(
136
+ `The chair ${merged.verdict === "approve" ? "approved" : "requested changes"}, but ` + (blocking.length ? `${blocking.length} finding${blocking.length === 1 ? "" : "s"} block${blocking.length === 1 ? "s" : ""}` : "no finding blocks") + ` once code-flow has run their tests, so the review says ${verdict === "approve" ? "Approve" : "Changes requested"}`,
137
+ "warn"
138
+ );
139
+ }
140
+ const markdown = renderReview(verdict, merged.summary, findings, done.map((r) => r.agent), proof);
141
+ ctx.setReview({
142
+ verdict,
143
+ summary: merged.summary,
144
+ findings,
145
+ reviewers: done.map((r) => ({ agent: r.agent, verdict: r.review.verdict, issues: r.review.issues.length })),
146
+ proof: proof ?? void 0,
147
+ markdown
148
+ });
149
+ if (proof) ctx.setFacts({ proof: proof.status });
150
+ ctx.log(`Council: ${verdict === "approve" ? "approve" : "changes requested"} with ${findings.length} finding(s) from ${done.length} reviewer(s)`);
151
+ return markdown;
152
+ }
153
+ };
154
+ function restoreConfirmed(done, checked, merged, checks) {
155
+ const raised = /* @__PURE__ */ new Map();
156
+ const added = [];
157
+ const standsFor = /* @__PURE__ */ new Map();
158
+ done.forEach(
159
+ (r, n) => r.review.issues.forEach((issue, i) => {
160
+ const check = checked[n][i];
161
+ if (check.status !== "confirmed") return;
162
+ const severity = severityOf(issue.severity);
163
+ const rank = (j) => SEVERITY[raised.get(j)?.finding.severity ?? severityOf(merged.findings[j].severity)];
164
+ const best = merged.findings.map((f, j) => ({ f, j })).filter(({ j }) => !standsFor.get(j)?.has(r.label)).filter(({ f, j }) => f.file === issue.file && (sameRepro(f.repro, issue.repro) || f.raised_by.includes(r.label) && checks[j].status === "confirmed")).sort((a, b) => rank(a.j) - rank(b.j))[0];
165
+ if (best) standsFor.set(best.j, (standsFor.get(best.j) ?? /* @__PURE__ */ new Set()).add(r.label));
166
+ if (best && rank(best.j) <= SEVERITY[severity]) return;
167
+ const finding = { severity, file: issue.file, problem: issue.problem, fix: issue.fix, check };
168
+ if (best) raised.set(best.j, { label: r.label, why: `the chair rated it ${severityOf(best.f.severity)}`, finding });
169
+ else added.push({ label: r.label, why: "the chair left it out", finding });
170
+ })
171
+ );
172
+ return { raised, added };
173
+ }
174
+ function sameRepro(a, b) {
175
+ if (!a || !b) return false;
176
+ const [path, command] = [(a.path ?? "").trim(), (a.command ?? "").trim()];
177
+ return !!(path || command) && path === (b.path ?? "").trim() && (a.contents ?? "") === (b.contents ?? "") && command === (b.command ?? "").trim();
178
+ }
179
+ function checkNote(severity, check) {
180
+ if (check.status === "confirmed") return `confirmed: \`${check.command}\` fails with its test on this code`;
181
+ if (check.status === "refuted") return `false alarm: \`${check.command}\` passes with its test on this code`;
182
+ if (severityOf(severity) === "low") return "a suggestion: not checked";
183
+ return check.status === "unverified" ? `couldn't run its test: ${check.detail}` : `unconfirmed: ${check.detail}`;
184
+ }
185
+ function renderReview(verdict, summary, findings, reviewers, proof = null) {
186
+ const head = `**Review council: ${verdict === "approve" ? "Approve" : "Changes requested"}** \u2014 ${reviewers.length} reviewer${reviewers.length === 1 ? "" : "s"} (${reviewers.join(", ")})`;
187
+ const list = findings.length ? findings.map(
188
+ (f, i) => `${i + 1}. **${f.severity}** \`${f.file || "general"}\` \u2014 ${f.problem}
189
+ *Fix:* ${f.fix}
190
+ *Raised by:* ${f.raisedBy.join(", ")}` + (f.check ? `
191
+ *Checked by code-flow:* ${checkMark(f)}` : "") + (f.restored ? `
192
+ *Restored by code-flow:* ${f.restored}` : "")
193
+ ).join("\n") : "No problems found.";
194
+ return [head, proofLine(proof), summary.trim() ? section("Summary", summary) : "", section("Findings", list)].join("\n");
195
+ }
196
+ export {
197
+ councilPattern,
198
+ renderReview
199
+ };
@@ -0,0 +1,188 @@
1
+ import { getAdapter } from "../adapters/index.js";
2
+ import { describeIssues } from "../integrity.js";
3
+ import { approves, builderReply, checkReview, findingsForBuilder, findingsLine, findingsSection, REVIEW_SHAPE, reproRule } from "./findings.js";
4
+ import { REVIEW_SCHEMA } from "./schemas.js";
5
+ import { checkEvidence, fence, fixUntilGreen, jsonInstruction, LETTERS, planCheckpoint, preamble, proofEvidence, proofLine, proofRule, proveOrAsk, READ_ONLY, section, WRITE } from "./prompts.js";
6
+ const debatePattern = {
7
+ id: "debate",
8
+ name: "Debate \u2192 Synthesize \u2192 Build",
9
+ summary: "Agents propose designs independently, critique each other, then one plan is synthesized and built.",
10
+ description: "Use for design-heavy or ambiguous work (architecture, APIs, refactors) where independent perspectives beat a single plan.",
11
+ roles: [
12
+ {
13
+ id: "debaters",
14
+ label: "Debaters",
15
+ description: "Each proposes and defends a design",
16
+ multiple: true,
17
+ default: ["claude", "codex"]
18
+ },
19
+ { id: "synthesizer", label: "Synthesizer", description: "Merges the debate into one plan, ideally a vendor that isn't debating", default: ["claude"] },
20
+ {
21
+ id: "builder",
22
+ label: "Builder",
23
+ description: "Implements the final plan (leave empty for design only)",
24
+ optional: true,
25
+ default: ["codex"]
26
+ },
27
+ {
28
+ id: "reviewer",
29
+ label: "Reviewer",
30
+ description: "Reviews the build against the agreed plan (optional)",
31
+ optional: true,
32
+ default: ["claude"]
33
+ }
34
+ ],
35
+ options: [
36
+ { id: "rounds", label: "Critique rounds", type: "number", default: 1, min: 0, max: 3 },
37
+ { id: "reviewRounds", label: "Max review rounds", type: "number", default: 2, min: 1, max: 5 }
38
+ ],
39
+ async run(ctx) {
40
+ const debaters = ctx.participants("debaters");
41
+ if (debaters.length < 2) throw new Error("A debate needs at least two debaters");
42
+ const rounds = ctx.option("rounds", 1);
43
+ const synthesizer = ctx.agent("synthesizer");
44
+ if (debaters.some((d) => d.agent === synthesizer)) ctx.log(`${synthesizer} both debates and synthesizes; a synthesizer from another vendor is fairer`, "warn");
45
+ const designWt = await ctx.worktree("design");
46
+ const label = (i) => `Proposal ${LETTERS[i] ?? String(i + 1)}`;
47
+ let positions = await ctx.parallel(
48
+ debaters,
49
+ ({ agent, settings, identity }, i) => ctx.ask({
50
+ title: `Proposal \u2014 ${getAdapter(agent).displayName}`,
51
+ role: "debaters",
52
+ agent,
53
+ settings,
54
+ identity,
55
+ mode: "read",
56
+ cwd: designWt,
57
+ prompt: preamble("debater", agent, `${READ_ONLY} Other agents are proposing designs independently. Yours will be ${label(i)}.`) + section("Task", ctx.task) + section(
58
+ "Deliverable",
59
+ "Study the codebase and propose a design: approach, key changes, trade-offs, and the risks you see. Under 500 words."
60
+ )
61
+ })
62
+ );
63
+ for (let round = 1; round <= rounds; round++) {
64
+ const snapshot = positions;
65
+ positions = await ctx.parallel(snapshot, (own, i) => {
66
+ const others = snapshot.map((p, j) => j === i ? "" : section(label(j), p.finalText)).join("");
67
+ return ctx.ask({
68
+ title: `Critique round ${round} \u2014 ${getAdapter(own.agent).displayName}`,
69
+ role: "debaters",
70
+ agent: own.agent,
71
+ settings: debaters[i].settings,
72
+ identity: debaters[i].identity,
73
+ mode: "read",
74
+ cwd: designWt,
75
+ resume: own,
76
+ prompt: `Here are the other agents' current proposals (yours is ${label(i)}).` + others + section(
77
+ "Deliverable",
78
+ "Critique them honestly: what is better than yours, what is wrong or risky. Then give your revised proposal. Concede points where they are right; hold your ground where you have evidence."
79
+ ),
80
+ // Its own conversation gone, a new one needs its earlier proposal too.
81
+ fresh: preamble("debater", own.agent, `${READ_ONLY} Other agents are proposing designs too.`) + section("Task", ctx.task) + section(`Your proposal so far (${label(i)})`, own.finalText) + section("The other agents' current proposals", others) + section(
82
+ "Deliverable",
83
+ "Critique them honestly: what is better than yours, what is wrong or risky. Then give your revised proposal. Concede points where they are right; hold your ground where you have evidence."
84
+ )
85
+ });
86
+ });
87
+ }
88
+ let plan = await ctx.ask({
89
+ title: "Synthesize plan",
90
+ role: "synthesizer",
91
+ agent: synthesizer,
92
+ mode: "read",
93
+ cwd: designWt,
94
+ prompt: preamble("synthesizer", synthesizer, `${READ_ONLY} The debaters are anonymous: weigh each proposal on its merits.`) + section("Task", ctx.task) + positions.map((p, i) => section(`Final position: ${label(i)}`, p.finalText)).join("") + section(
95
+ "Deliverable",
96
+ "Merge these into a single implementation plan. List each point of disagreement and which side you chose and why. End with concrete steps and verification commands."
97
+ )
98
+ });
99
+ const [builder] = ctx.agents("builder");
100
+ if (!builder) {
101
+ return section("Synthesized plan", plan.finalText);
102
+ }
103
+ plan = await planCheckpoint(ctx, { agent: synthesizer, role: "synthesizer", wt: designWt, plan, what: "plan" });
104
+ const buildWt = await ctx.worktree("build");
105
+ const build = await ctx.ask({
106
+ title: "Implement synthesized plan",
107
+ role: "builder",
108
+ agent: builder,
109
+ mode: "write",
110
+ cwd: buildWt,
111
+ prompt: preamble("builder", builder, WRITE) + section("Task", ctx.task) + section("Agreed plan", plan.finalText) + proofRule(ctx) + section("Deliverable", "Implement the plan, run the relevant tests, then summarize the changes.")
112
+ });
113
+ await ctx.commit(buildWt, `${builder}: implement synthesized plan`);
114
+ let fixed = await fixUntilGreen(ctx, { agent: builder, role: "builder", wt: buildWt, session: build, attempts: 2 });
115
+ const proved = await proveOrAsk(ctx, { agent: builder, role: "builder", wt: buildWt, check: fixed.check, session: fixed.session ?? build, attempts: 2, ask: true });
116
+ fixed = { check: proved.check, session: proved.session ?? fixed.session };
117
+ let check = fixed.check;
118
+ const [reviewer] = ctx.agents("reviewer");
119
+ const reviewRounds = ctx.option("reviewRounds", 2);
120
+ let verdict;
121
+ let findings = [];
122
+ let review;
123
+ let proof = proved.proof;
124
+ let session = fixed.session ?? build;
125
+ let reply;
126
+ let reviewed = 0;
127
+ let unreviewed = false;
128
+ for (let round = 1; reviewer && (!check || check.ok) && round <= reviewRounds; round++) {
129
+ const testChanges = await ctx.testChanges(buildWt);
130
+ const decided = await ctx.decide({
131
+ title: round === 1 ? "Review the build" : `Review the build (round ${round})`,
132
+ schema: REVIEW_SCHEMA,
133
+ what: "review verdict",
134
+ role: "reviewer",
135
+ agent: reviewer,
136
+ mode: "read",
137
+ cwd: buildWt,
138
+ resume: review,
139
+ prompt: (review ? "The builder addressed your previous review. Here is the updated diff.\n" : "") + builderReply(reply) + preamble("reviewer", reviewer, `${READ_ONLY} You did not write this code; review it critically.`) + section("Task", ctx.task) + // A reviewer without its earlier session hasn't seen the plan.
140
+ (round === 1 || !review?.sessionId ? section("Agreed plan", plan.finalText) : "") + section("Diff against base", fence(await ctx.diff(buildWt), "diff")) + checkEvidence(ctx, check) + proofEvidence(proof) + (testChanges.length ? section("Changes to tests (found by code-flow)", `Raise each as a medium issue unless the task requires it:
141
+ ${describeIssues(testChanges)}`) : "") + section("Deliverable", "Check that the build does what the plan and task say, and look for bugs, missed edge cases and missing tests. Only raise real problems.") + reproRule(ctx) + jsonInstruction(REVIEW_SHAPE)
142
+ });
143
+ review = decided.outcome;
144
+ verdict = decided.value;
145
+ reviewed = round;
146
+ unreviewed = false;
147
+ findings = await checkReview(ctx, buildWt, { label: `Review round ${round}`, reviewer, round, review: verdict });
148
+ if (approves(findings) || round === reviewRounds) break;
149
+ session = await ctx.ask({
150
+ title: round === 1 ? "Address review" : `Address review (round ${round})`,
151
+ role: "builder",
152
+ agent: builder,
153
+ mode: "write",
154
+ cwd: buildWt,
155
+ resume: session,
156
+ prompt: findingsForBuilder(reviewer, findings)
157
+ });
158
+ reply = session.finalText;
159
+ await ctx.commit(buildWt, round === 1 ? `${builder}: address review` : `${builder}: address review round ${round}`);
160
+ unreviewed = true;
161
+ fixed = await fixUntilGreen(ctx, { agent: builder, role: "builder", wt: buildWt, session, attempts: 2 });
162
+ session = fixed.session ?? session;
163
+ check = fixed.check;
164
+ if (check && !check.ok) {
165
+ ctx.log("The check still fails after 2 fix attempts; stopping before the next review", "warn");
166
+ break;
167
+ }
168
+ proof = check?.ok ? await ctx.prove(buildWt) : null;
169
+ }
170
+ const approved = !!verdict && approves(findings);
171
+ await ctx.artifact("Result", buildWt);
172
+ if (check) ctx.setVerdict(check.ok ? "passed" : "failing");
173
+ if (verdict) ctx.setFacts({ approved, reviewRounds: reviewed });
174
+ ctx.setFacts({ proof: ctx.proofOf(buildWt)?.status });
175
+ return [
176
+ `**Built**${check ? check.ok ? `, and \`${check.command}\` passes` : `, but \`${check.command}\` still fails` : ""} on branch \`${buildWt.branch}\``,
177
+ proofLine(ctx.proofOf(buildWt)),
178
+ verdict ? `Review by ${reviewer}: ${approved ? `approved${reviewed > 1 ? ` in round ${reviewed}` : ""}` : unreviewed ? `${findings.filter((f) => f.blocking).length} blocking finding(s) sent back to the builder, whose fixes weren't re-reviewed: the check fails since` : `not approved after ${reviewed} round${reviewed === 1 ? "" : "s"}`}.` : "",
179
+ verdict ? findingsLine(findings) : "",
180
+ verdict && !approved && !unreviewed ? findingsSection("Open review findings", findings) : "",
181
+ section("Synthesized plan", plan.finalText),
182
+ section("Builder summary", build.finalText)
183
+ ].join("\n");
184
+ }
185
+ };
186
+ export {
187
+ debatePattern
188
+ };
@@ -0,0 +1,141 @@
1
+ import { getAdapter } from "../adapters/index.js";
2
+ import { blocks } from "../engine.js";
3
+ import { fence, section } from "./prompts.js";
4
+ const REVIEW_SHAPE = '{"verdict": "approve" | "changes_requested", "issues": [{"severity": "high|medium|low", "file": "path", "problem": "...", "fix": "...", "repro": {"path": "test file to add, or \\"\\"", "contents": "the whole test file, or \\"\\"", "command": "plain test command that runs it, or \\"\\""}}]}';
5
+ const MAX_REPROS = 6;
6
+ const SEVERITIES = ["high", "medium", "low"];
7
+ function reproRule(ctx) {
8
+ const check = ctx.hasCheck ? ctx.run.loop.check : void 0;
9
+ return section(
10
+ "Show it (code-flow runs your tests)",
11
+ "Severity decides what blocks approval: high and medium issues block, low ones are suggestions. A high-severity issue says the code has a bug, so show it with a `repro`: a test that fails on this code because of the bug. Give the test file's path, named like the project's tests, and its whole contents" + (check ? `. code-flow adds it to a copy of this code and runs \`${check}\` there, or the plain test command you give instead (one that runs just your test, say).` : ", and the plain test command that runs it (e.g. `pytest tests/test_bug.py`): this run has no check of its own.") + " A command alone can run tests that are already there. A high-severity issue blocks only if its test fails. If the test passes, the issue is a false alarm and doesn't block; without a repro it's marked unconfirmed and doesn't block either, so rate a problem no test can show medium. A repro on a medium issue is welcome too: if it passes, that issue doesn't block. Use \"\" for every `repro` field when there's none."
12
+ );
13
+ }
14
+ async function checkReview(ctx, wt, opts) {
15
+ const issues = opts.review.issues ?? [];
16
+ const checks = await checkIssues(ctx, wt, issues, "Reviewer's test");
17
+ const findings = issues.map((issue, i) => {
18
+ const severity = severityOf(issue.severity);
19
+ const check = checks[i];
20
+ return { severity, file: issue.file ?? "", problem: issue.problem, fix: issue.fix ?? "", check, blocking: blocks(severity, check.status) };
21
+ });
22
+ ctx.setFindings(wt, { reviewer: opts.reviewer, round: opts.round, verdict: opts.review.verdict, findings, head: await ctx.head(wt), at: Date.now() });
23
+ const blocking = findings.filter((f) => f.blocking);
24
+ ctx.log(`${opts.label}: ${opts.review.verdict} (${findings.length} issues${findings.length ? `; ${tally(findings)}` : ""})`);
25
+ if (blocking.length) ctx.learn(findings.filter((f) => f.check.status !== "refuted"));
26
+ return findings;
27
+ }
28
+ async function checkIssues(ctx, wt, issues, title) {
29
+ const checks = [];
30
+ let ran = 0;
31
+ for (const issue of issues) {
32
+ if (severityOf(issue.severity) === "low") checks.push({ status: "unconfirmed", detail: "a suggestion: not checked" });
33
+ else if (!hasRepro(issue.repro)) checks.push({ status: "unconfirmed", detail: "no test came with it" });
34
+ else if (ran >= MAX_REPROS) checks.push({ status: "unverified", detail: `not run: code-flow runs at most ${MAX_REPROS} tests per review` });
35
+ else {
36
+ ran++;
37
+ checks.push(await ctx.reproduce(wt, issue.repro, `${title}: ${short(issue.problem)}`));
38
+ }
39
+ }
40
+ return checks;
41
+ }
42
+ function severityOf(severity) {
43
+ return SEVERITIES.find((s) => s === severity) ?? "medium";
44
+ }
45
+ function approves(findings) {
46
+ return !findings.some((f) => f.blocking);
47
+ }
48
+ function findingsForBuilder(reviewer, findings) {
49
+ const confirmed = findings.filter((f) => f.check.status === "confirmed" && f.severity !== "low");
50
+ const open = findings.filter((f) => f.check.status !== "confirmed" && f.check.status !== "refuted" && f.severity !== "low");
51
+ const falseAlarms = findings.filter((f) => f.check.status === "refuted" && f.severity !== "low");
52
+ const suggestions = findings.filter((f) => f.severity === "low");
53
+ const item = (f, i) => `${i + 1}. **${f.severity}** \`${f.file || "general"}\` \u2014 ${f.problem}
54
+ Suggested fix: ${f.fix || "(none given)"}`;
55
+ return `The ${getAdapter(reviewer).displayName} reviewer's findings, checked by code-flow. Fix what blocks, re-run the tests, then summarize what you fixed and anything you dispute.` + (confirmed.length ? section(
56
+ "Confirmed: the reviewer's test fails on your code (these block)",
57
+ confirmed.map(
58
+ (f, i) => item(f, i) + (f.check.path && f.check.contents ? `
59
+ The test, \`${f.check.path}\`:
60
+ ${fence(f.check.contents)}` : "") + `
61
+ \`${f.check.command}\` fails:
62
+ ${fence(f.check.detail)}`
63
+ ).join("\n") + "\n\nMake each of these tests pass by fixing the code, and add the reviewer's test (or one like it) to your change. If a test itself is wrong, say why instead."
64
+ ) : "") + (open.length ? section(
65
+ "Not shown by a test",
66
+ open.map((f, i) => `${item(f, i)}
67
+ ${openNote(f)}`).join("\n") + "\n\nFix these if you agree. If you don't, change nothing for it and say why, with evidence (a test you ran, the code that already handles it): the reviewer sees your answer."
68
+ ) : "") + (falseAlarms.length ? section("False alarms: the reviewer's own test passes (no change needed)", falseAlarms.map((f, i) => item(f, i)).join("\n")) : "") + (suggestions.length ? section("Suggestions (low severity, optional)", suggestions.map((f, i) => item(f, i)).join("\n")) : "");
69
+ }
70
+ function builderReply(text) {
71
+ if (!text?.trim()) return "";
72
+ return section(
73
+ "The builder's answer to your review",
74
+ `${text.trim()}
75
+
76
+ It may dispute findings no test shows. Drop the ones it shows are wrong; keep a disputed bug only with a repro that fails.`
77
+ );
78
+ }
79
+ function findingsSection(title, findings) {
80
+ if (!findings.length) return "";
81
+ return section(
82
+ title,
83
+ findings.map((f) => `- **${f.severity}** \`${f.file || "general"}\` \u2014 ${f.problem} \u2014 ${checkMark(f)}${f.blocking ? " \xB7 blocks" : ""}`).join("\n")
84
+ );
85
+ }
86
+ function findingsLine(findings) {
87
+ const unconfirmed = findings.filter((f) => f.severity === "high" && f.check.status === "unconfirmed").length;
88
+ const falseAlarms = findings.filter((f) => f.check.status === "refuted" && f.severity !== "low").length;
89
+ const parts = [
90
+ unconfirmed ? `\u26A0 ${unconfirmed} unconfirmed high-severity claim${unconfirmed === 1 ? "" : "s"} (no test shows it; not blocking)` : "",
91
+ falseAlarms ? `${falseAlarms} false alarm${falseAlarms === 1 ? "" : "s"} (the reviewer's own test passes)` : ""
92
+ ].filter(Boolean);
93
+ return parts.length ? `Review findings: ${parts.join("; ")}.` : "";
94
+ }
95
+ function checkMark(f) {
96
+ const check = f.check;
97
+ if (!check) return "not checked";
98
+ if (check.status === "confirmed") return "\u2713 confirmed: its test fails";
99
+ if (check.status === "refuted") return "\u2717 not confirmed: its test passes (a false alarm)";
100
+ if (f.severity === "low") return "suggestion";
101
+ if (check.status === "unverified") return `couldn't run its test (${check.detail})`;
102
+ return f.severity === "high" ? "\u26A0 unconfirmed: no test shows it" : "no test";
103
+ }
104
+ const CONFIRMATION = { confirmed: 0, unverified: 1, unconfirmed: 2, refuted: 3 };
105
+ function hasRepro(repro) {
106
+ return !!repro && (!!repro.path?.trim() || !!repro.command?.trim());
107
+ }
108
+ function openNote(f) {
109
+ if (f.check.status === "unverified") return `code-flow couldn't run the reviewer's test: ${f.check.detail}. It blocks on the reviewer's word.`;
110
+ if (f.severity === "high") return `Unconfirmed: ${f.check.detail}. A bug claim nobody has shown; it doesn't block.`;
111
+ return "No test with it; it blocks on the reviewer's word.";
112
+ }
113
+ function tally(findings) {
114
+ const count = (pred) => findings.filter(pred).length;
115
+ const parts = [
116
+ [count((f) => f.blocking), "blocking"],
117
+ [count((f) => f.check.status === "confirmed"), "confirmed by a failing test"],
118
+ [count((f) => f.check.status === "refuted"), "false alarm (its test passes)"],
119
+ [count((f) => f.severity === "high" && f.check.status === "unconfirmed"), "unconfirmed high-severity claim"]
120
+ ];
121
+ return parts.map(([n, what]) => `${n} ${what}`).filter((p) => !p.startsWith("0 ")).join(", ") || "none blocking";
122
+ }
123
+ function short(text) {
124
+ const line = text.split("\n")[0].trim();
125
+ return line.length > 60 ? `${line.slice(0, 59)}\u2026` : line;
126
+ }
127
+ export {
128
+ CONFIRMATION,
129
+ REVIEW_SHAPE,
130
+ approves,
131
+ builderReply,
132
+ checkIssues,
133
+ checkMark,
134
+ checkReview,
135
+ findingsForBuilder,
136
+ findingsLine,
137
+ findingsSection,
138
+ hasRepro,
139
+ reproRule,
140
+ severityOf
141
+ };
@@ -0,0 +1,19 @@
1
+ import { pipelinePattern } from "./pipeline.js";
2
+ import { racePattern } from "./race.js";
3
+ import { debatePattern } from "./debate.js";
4
+ import { supervisorPattern } from "./supervisor.js";
5
+ import { tddPattern } from "./tdd.js";
6
+ import { loopPattern } from "./loop.js";
7
+ import { councilPattern } from "./council.js";
8
+ import { soloPattern } from "./solo.js";
9
+ import { autoTeamPattern } from "./auto-team.js";
10
+ import { taskDraftPattern } from "./task-draft.js";
11
+ const patterns = [autoTeamPattern, pipelinePattern, loopPattern, racePattern, debatePattern, supervisorPattern, tddPattern, councilPattern, soloPattern];
12
+ function findPattern(id) {
13
+ if (id === taskDraftPattern.id) return taskDraftPattern;
14
+ return patterns.find((p) => p.id === id);
15
+ }
16
+ export {
17
+ findPattern,
18
+ patterns
19
+ };