@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,207 @@
1
+ import { isAgentId } from "./adapters/index.js";
2
+ import { open, readFile } from "node:fs/promises";
3
+ import { join, resolve, sep } from "node:path";
4
+ import { assertWorktree, repoRoot, resolveCommit, git, worktreeGit, WorktreeMismatchError } from "./git.js";
5
+ import { readJson, HttpError } from "./http.js";
6
+ import { inspectReadiness, inTemporaryWorktree } from "./readiness.js";
7
+ import { applyCheckpoint, checkpoints, readEditableFile, saveEditableFile, searchCode, WorkspaceConflictError } from "./workspace.js";
8
+ import { detectVerificationProfile, renderVerificationReport, runVerification, validateVerificationProfile } from "./verification.js";
9
+ import { runCheck } from "./checks.js";
10
+ import { redactExport } from "./privacy.js";
11
+ import { CODEFLOW_HOME } from "./config.js";
12
+ import { writeHealth } from "./store.js";
13
+ import { mergeFileDraft, checkpointHunks, applyCheckpointHunks } from "./workspace-changes.js";
14
+ import { verificationIntegrity, VerificationIntegrityError } from "./verification-integrity.js";
15
+ import { inspectContext, ContextInspectionError } from "./context-inspection.js";
16
+ import { applyCommitRevert, compareCheckpoint, previewCommitRevert } from "./checkpoint-review.js";
17
+ import { searchSemanticSymbols, findCodeReferences } from "./code-intelligence.js";
18
+ import { inspectArchitecture } from "./architecture.js";
19
+ async function ownedCheckout(repo, tree) {
20
+ try {
21
+ return await assertWorktree(repo, tree);
22
+ } catch (error) {
23
+ if (error instanceof WorktreeMismatchError) throw new HttpError(409, error.message);
24
+ throw error;
25
+ }
26
+ }
27
+ async function diagnosticTail(path) {
28
+ const file = await open(path, "r").catch(() => void 0);
29
+ if (!file) return "";
30
+ try {
31
+ const info = await file.stat();
32
+ if (!info.isFile()) return "";
33
+ const bytes = Math.min(info.size, 32e3);
34
+ const buffer = Buffer.alloc(bytes);
35
+ const { bytesRead } = await file.read(buffer, 0, bytes, Math.max(0, info.size - bytes));
36
+ return buffer.subarray(0, bytesRead).toString("utf8");
37
+ } finally {
38
+ await file.close();
39
+ }
40
+ }
41
+ async function handleWorkspaceApi(req, url, parts, manager, signal) {
42
+ if (!["readiness", "verification", "workspace", "diagnostics", "v1"].includes(parts[1] ?? "")) return void 0;
43
+ const body = req.method === "POST" ? await readJson(req) : {};
44
+ const selected = body.repo ?? url.searchParams.get("repo");
45
+ const absolute = typeof selected === "string" && selected.startsWith("/") ? resolve(selected) : void 0;
46
+ const owned = absolute ? manager.list().flatMap((run) => run.worktrees.map((tree) => ({ run, tree }))).find(({ tree }) => absolute === resolve(tree.path) || absolute.startsWith(resolve(tree.path) + sep)) : void 0;
47
+ const repo = owned?.tree.path ?? (absolute ? await repoRoot(absolute).catch(() => {
48
+ throw new HttpError(400, "Choose an existing Git repository");
49
+ }) : void 0);
50
+ const requireRepo = () => {
51
+ if (!repo) throw new HttpError(400, "Choose a repository");
52
+ return repo;
53
+ };
54
+ const readRepo = () => owned ? worktreeGit(owned.run.repo, owned.tree.path) : Promise.resolve(requireRepo());
55
+ const writeRepo = () => owned ? ownedCheckout(owned.run.repo, owned.tree) : Promise.resolve(requireRepo());
56
+ const boundedCommand = (value) => {
57
+ if (value === void 0) return void 0;
58
+ if (typeof value !== "string" || value.length > 4e3 || value.includes("\0")) throw new HttpError(400, "Commands must be text under 4,000 characters");
59
+ return value;
60
+ };
61
+ if (parts[1] === "v1" && parts[2] === "capabilities" && req.method === "GET") return { status: 200, body: { version: 1, features: ["readiness", "verification", "file-editing", "code-search", "dependencies", "checkpoints", "diagnostics", "run-export", "cursor-history", "shared-projects", "ssh-projects", "ssh-workers", "coordinated-changes", "code-hosts", "github-review-threads", "filesystem-policies", "filtered-tasks", "provider-workflow-extensions", "team-collaboration"], authentication: "Bearer CODEFLOW_TOKEN", api: "/api", docs: "docs/api.md" } };
62
+ if (parts[1] === "readiness" && req.method === "POST") {
63
+ const boundedId = (value) => typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9_:/.-]{0,127}$/.test(value);
64
+ const provider = (value) => boundedId(value) && (value === "auto" || isAgentId(value));
65
+ const providerList = (value) => Array.isArray(value) && value.length <= 128 && value.every(provider);
66
+ if (body.pattern !== void 0 && !boundedId(body.pattern)) throw new HttpError(400, "Invalid workflow pattern");
67
+ if (body.providers !== void 0 && !providerList(body.providers)) throw new HttpError(400, "Invalid readiness providers");
68
+ if (body.roles !== void 0 && (!body.roles || typeof body.roles !== "object" || Array.isArray(body.roles) || Object.keys(body.roles).length > 64 || !Object.entries(body.roles).every(([role, value]) => boundedId(role) && (typeof value === "string" ? provider(value) : providerList(value))))) throw new HttpError(400, "Invalid readiness roles");
69
+ if (body.execute === true) manager.noteActivity();
70
+ return { status: 200, body: await inspectReadiness({ repo: owned?.run.repo ?? requireRepo(), baseRef: await resolveCommit(await readRepo(), typeof body.baseRef === "string" && body.baseRef ? body.baseRef : "HEAD"), setup: boundedCommand(body.setup), check: boundedCommand(body.check), pattern: body.pattern, roles: body.roles, providers: body.providers, execute: body.execute === true, signal }) };
71
+ }
72
+ if (parts[1] === "verification") {
73
+ if (req.method === "GET" && parts[2] === "profile") return { status: 200, body: await detectVerificationProfile(requireRepo(), url.searchParams.get("preset") || "standard") };
74
+ if (req.method === "POST" && !parts[2]) {
75
+ const selectedRepo = requireRepo();
76
+ const profile = body.profile ? validateVerificationProfile(body.profile) : await detectVerificationProfile(selectedRepo, body.preset ?? "standard");
77
+ const originalRepo = owned?.run.repo ?? selectedRepo;
78
+ const at = await readRepo();
79
+ const ref = await resolveCommit(at, typeof body.baseRef === "string" && body.baseRef ? body.baseRef : "HEAD");
80
+ const setup = boundedCommand(body.setup) ?? profile.setup;
81
+ const run = async (path, baseline2) => {
82
+ const integrity = await verificationIntegrity(await assertWorktree(originalRepo, { path }), signal);
83
+ const report = await runVerification(path, profile, { signal, repeats: body.repeats, baseline: baseline2, execute: async (check, options) => {
84
+ await integrity.assert(`before gate ${check.id}`);
85
+ await assertWorktree(originalRepo, { path });
86
+ const result = await runCheck(path, check.command, options);
87
+ await integrity.assert(`gate ${check.id}`);
88
+ return result;
89
+ } });
90
+ await integrity.assert("completion");
91
+ return report;
92
+ };
93
+ manager.noteActivity();
94
+ let baseline;
95
+ const prepare = async (path) => {
96
+ const integrity = await verificationIntegrity(await assertWorktree(originalRepo, { path }), signal);
97
+ if (setup) {
98
+ await assertWorktree(originalRepo, { path });
99
+ const result = await runCheck(path, setup, { timeoutMs: 12e4, signal });
100
+ await integrity.assert("setup");
101
+ if (!result.ok) throw new HttpError(422, `Verification setup failed: ${result.output.slice(-2e3)}`);
102
+ }
103
+ };
104
+ try {
105
+ if (typeof body.baseline === "string" && body.baseline) {
106
+ const base = await resolveCommit(at, body.baseline);
107
+ baseline = await inTemporaryWorktree(originalRepo, base, async (path) => {
108
+ await prepare(path);
109
+ const report2 = await run(path);
110
+ return Object.fromEntries(report2.checks.filter((check) => check.attempts.length).map((check) => [check.check.id, check.attempts[0]]));
111
+ }, signal);
112
+ }
113
+ const report = await inTemporaryWorktree(originalRepo, ref, async (path) => {
114
+ await prepare(path);
115
+ return run(path, baseline);
116
+ }, signal);
117
+ return { status: 200, body: redactExport({ ...report, repo: selectedRepo, commit: ref, markdown: renderVerificationReport(report) }) };
118
+ } catch (error) {
119
+ if (error instanceof VerificationIntegrityError || error instanceof WorktreeMismatchError) throw new HttpError(409, error.message);
120
+ throw error;
121
+ }
122
+ }
123
+ }
124
+ if (parts[1] === "workspace") {
125
+ const selectedRepo = requireRepo();
126
+ if (parts[2] === "compare-checkpoint" && req.method === "POST") {
127
+ if (typeof body.from !== "string" || typeof body.to !== "string") throw new HttpError(400, "Choose two commits to compare");
128
+ return { status: 200, body: await compareCheckpoint(await readRepo(), body, signal) };
129
+ }
130
+ if (["preview-revert", "apply-revert"].includes(parts[2] ?? "") && req.method === "POST") {
131
+ const run = typeof body.runId === "string" ? manager.get(body.runId) : void 0;
132
+ const tree = run?.worktrees.find((tree2) => tree2.path === selectedRepo);
133
+ if (!run || !tree) throw new HttpError(409, "Choose an owned run worktree before reverting a commit");
134
+ if (typeof body.commit !== "string" || typeof body.expectedHead !== "string") throw new HttpError(400, "Choose a commit and refresh the current HEAD");
135
+ try {
136
+ return { status: 200, body: await manager.withIdleRepository(selectedRepo, async () => {
137
+ const at = await ownedCheckout(run.repo, tree);
138
+ return parts[2] === "preview-revert" ? previewCommitRevert(at, body, signal, run.repo) : applyCommitRevert(at, body, signal, run.repo);
139
+ }, { kind: "checkpoint-revert", owner: run.id }) };
140
+ } catch (error) {
141
+ if (error instanceof WorkspaceConflictError) throw new HttpError(409, error.message);
142
+ throw error;
143
+ }
144
+ }
145
+ if (parts[2] === "merge-file" && req.method === "POST") return { status: 200, body: await mergeFileDraft(await readRepo(), body.path, body.base, body.draft) };
146
+ if (["checkpoint-hunks", "apply-hunks"].includes(parts[2] ?? "") && req.method === "POST") {
147
+ const run = typeof body.runId === "string" ? manager.get(body.runId) : void 0;
148
+ const tree = run?.worktrees.find((tree2) => tree2.path === selectedRepo);
149
+ if (!run || !tree || run.status === "running") throw new HttpError(409, "Choose an idle run worktree before applying checkpoint hunks");
150
+ try {
151
+ return { status: 200, body: await manager.withIdleRepository(selectedRepo, async () => {
152
+ const at = await ownedCheckout(run.repo, tree);
153
+ return parts[2] === "checkpoint-hunks" ? checkpointHunks(at, body) : applyCheckpointHunks(at, body);
154
+ }, { kind: "checkpoint-hunks", owner: run.id }) };
155
+ } catch (error) {
156
+ if (error instanceof WorkspaceConflictError) throw new HttpError(409, error.message);
157
+ throw error;
158
+ }
159
+ }
160
+ if (parts[2] === "file") {
161
+ const path = typeof body.path === "string" ? body.path : url.searchParams.get("path") ?? "";
162
+ if (req.method === "GET") return { status: 200, body: await readEditableFile(await readRepo(), path) };
163
+ if (req.method === "POST") {
164
+ const busy = manager.list().find((run) => run.status === "running" && (run.repo === selectedRepo || run.worktrees.some((tree) => tree.path === selectedRepo)));
165
+ if (busy) throw new HttpError(409, "Finish or stop the active run before editing its workspace");
166
+ try {
167
+ return { status: 200, body: await manager.withIdleRepository(selectedRepo, async () => saveEditableFile(await writeRepo(), path, body.text, body.expectedHash), { kind: "file-edit", owner: "workspace" }) };
168
+ } catch (error) {
169
+ if (error instanceof WorkspaceConflictError) throw new HttpError(409, error.message);
170
+ throw error;
171
+ }
172
+ }
173
+ }
174
+ if (parts[2] === "search" && req.method === "GET") return { status: 200, body: url.searchParams.get("mode") === "semantic" ? await searchSemanticSymbols(selectedRepo, url.searchParams.get("q") ?? "", signal) : await searchCode(await readRepo(), url.searchParams.get("q") ?? "", url.searchParams.get("mode") === "symbols" ? "symbols" : "text") };
175
+ if (parts[2] === "references" && req.method === "GET") return { status: 200, body: await findCodeReferences(selectedRepo, { path: url.searchParams.get("path") ?? "", line: Number(url.searchParams.get("line")), column: Number(url.searchParams.get("column")) }, signal) };
176
+ if (parts[2] === "dependencies" && req.method === "GET") return { status: 200, body: await inspectArchitecture(selectedRepo, signal) };
177
+ if (parts[2] === "checkpoints" && req.method === "GET") {
178
+ const at = await readRepo();
179
+ return { status: 200, body: { head: await resolveCommit(at), checkpoints: await checkpoints(at, url.searchParams.get("ref") || "HEAD") } };
180
+ }
181
+ if (parts[2] === "apply-checkpoint" && req.method === "POST") {
182
+ const run = typeof body.runId === "string" ? manager.get(body.runId) : void 0;
183
+ const tree = run?.worktrees.find((tree2) => tree2.path === selectedRepo);
184
+ if (!run || !tree || run.status === "running") throw new HttpError(409, "Choose an idle run worktree before applying checkpoint files");
185
+ if (!Array.isArray(body.paths) || body.paths.some((path) => typeof path !== "string")) throw new HttpError(400, "Choose repository paths");
186
+ return { status: 200, body: await manager.withIdleRepository(selectedRepo, async () => applyCheckpoint(await ownedCheckout(run.repo, tree), { source: String(body.source ?? ""), paths: body.paths, expectedHead: String(body.expectedHead ?? "") }), { kind: "checkpoint-files", owner: run.id }) };
187
+ }
188
+ if (parts[2] === "context" && req.method === "POST") {
189
+ try {
190
+ return { status: 200, body: await inspectContext({ repo: selectedRepo, baseRef: typeof body.baseRef === "string" ? body.baseRef : void 0, paths: body.paths, attachments: body.attachments, signal }) };
191
+ } catch (error) {
192
+ if (error instanceof ContextInspectionError) throw new HttpError(error.status, error.message);
193
+ throw error;
194
+ }
195
+ }
196
+ }
197
+ if (parts[1] === "diagnostics" && req.method === "GET") {
198
+ const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
199
+ const log = await diagnosticTail(join(CODEFLOW_HOME, "server.log"));
200
+ const runs = manager.list();
201
+ return { status: 200, body: redactExport({ version: 1, application: packageJson.version, node: process.version, platform: process.platform, at: Date.now(), busy: manager.busy, activity: manager.inspectRepositoryActivity(), writes: writeHealth, runs: { total: runs.length, active: runs.filter((run) => run.status === "running").length, interrupted: runs.filter((run) => run.status === "interrupted").length }, recentErrors: log.slice(-32e3), capabilities: { workspace: true, verificationProfiles: true }, git: repo ? await git(["--version"], repo).then((result) => result.stdout.trim()) : void 0 }) };
202
+ }
203
+ throw new HttpError(404, "Unknown workspace operation");
204
+ }
205
+ export {
206
+ handleWorkspaceApi
207
+ };
@@ -0,0 +1,61 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { git, resolveCommit, withRepoLock } from "./git.js";
6
+ import { normalizeRepoPath } from "./repo-tree.js";
7
+ import { readEditableFile, WorkspaceConflictError } from "./workspace.js";
8
+ const digest = (text) => createHash("sha256").update(text).digest("hex");
9
+ const MAX_TEXT = 512 * 1024;
10
+ async function mergeFileDraft(repo, path, base, draft) {
11
+ if ([base, draft].some((value) => typeof value !== "string" || Buffer.byteLength(value) > MAX_TEXT || value.includes("\0"))) throw new Error("Merge drafts must be text under 512 KB");
12
+ const current = await readEditableFile(repo, path);
13
+ const folder = await mkdtemp(join(tmpdir(), "codeflow-file-merge-"));
14
+ try {
15
+ await Promise.all([["current", current.text], ["base", base], ["draft", draft]].map(([name, text]) => writeFile(join(folder, name), text, { mode: 384, flag: "wx" })));
16
+ const merged = await git(["merge-file", "--stdout", "--diff3", "-L", "Current file", "-L", "Loaded base", "-L", "Your draft", "current", "base", "draft"], folder, { timeoutMs: 1e4 });
17
+ if (merged.code < 0 || merged.code > 127 || merged.timedOut || merged.aborted || merged.stderr.trim()) throw new Error(merged.stderr.trim() || "Cannot merge the file drafts");
18
+ return { current, text: merged.stdout, conflicts: merged.code, clean: merged.code === 0 };
19
+ } finally {
20
+ await rm(folder, { recursive: true, force: true });
21
+ }
22
+ }
23
+ async function checkpointHunks(repo, input) {
24
+ const path = normalizeRepoPath(input.path);
25
+ if (!path || path.split("/").some((part) => part.toLowerCase() === ".git")) throw new Error("Choose a regular repository file outside .git");
26
+ await readEditableFile(repo, path);
27
+ const [head, source] = await Promise.all([resolveCommit(repo), resolveCommit(repo, input.source)]);
28
+ const modes = await Promise.all([head, source].map((ref) => git(["--literal-pathspecs", "ls-tree", "-z", ref, "--", path], repo)));
29
+ if (modes.some((result) => result.code || !/^100(?:644|755) blob /.test(result.stdout))) throw new Error("Hunks support existing regular text files. Apply added, deleted, renamed or binary files as complete files instead.");
30
+ const diff = await git(["--literal-pathspecs", "diff", "--no-ext-diff", "--no-textconv", "--no-color", "--unified=3", head, source, "--", path], repo);
31
+ if (diff.code) throw new Error(diff.stderr.trim() || "Cannot compare the checkpoint file");
32
+ if (diff.stdout.length > 2 * 1024 * 1024 || /^(?:Binary files |GIT binary patch)/m.test(diff.stdout)) throw new Error("The patch is binary or too large for selective hunks");
33
+ const first = diff.stdout.search(/^@@ /m);
34
+ if (first < 0) return { head, source, path, patchHeader: "", hunks: [] };
35
+ const patchHeader = diff.stdout.slice(0, first).replace(/^index .*\n/m, "");
36
+ const chunks = diff.stdout.slice(first).split(/(?=^@@ )/m).filter(Boolean);
37
+ if (chunks.length > 200) throw new Error("This file has too many hunks; apply the complete file instead");
38
+ return { head, source, path, patchHeader, hunks: chunks.map((patch) => ({ id: digest(`${head}\0${source}\0${path}\0${patch}`), header: patch.split("\n")[0], patch })) };
39
+ }
40
+ async function applyCheckpointHunks(repo, input) {
41
+ if (!Array.isArray(input.hunks) || input.hunks.length < 1 || input.hunks.length > 200 || input.hunks.some((id) => typeof id !== "string" || !/^[a-f0-9]{64}$/.test(id)) || new Set(input.hunks).size !== input.hunks.length) throw new Error("Choose 1\u2013200 distinct checkpoint hunks");
42
+ return withRepoLock(typeof repo === "string" ? repo : repo.path, async () => {
43
+ if (await resolveCommit(repo) !== input.expectedHead) throw new WorkspaceConflictError(await resolveCommit(repo));
44
+ const status = await git(["status", "--porcelain"], repo);
45
+ if (status.code || status.stdout.trim()) throw new Error("Commit or save existing changes before applying checkpoint hunks");
46
+ const preview = await checkpointHunks(repo, input);
47
+ const chosen = preview.hunks.filter((hunk) => input.hunks.includes(hunk.id));
48
+ if (chosen.length !== input.hunks.length) throw new Error("The checkpoint hunks changed. Refresh the comparison before applying");
49
+ const patch = preview.patchHeader + chosen.map((hunk) => hunk.patch).join("");
50
+ const checked = await git(["apply", "--check", "--whitespace=nowarn", "-"], repo, { input: patch });
51
+ if (checked.code) throw new Error(checked.stderr.trim() || "The selected hunks cannot be applied safely");
52
+ const applied = await git(["apply", "--whitespace=nowarn", "-"], repo, { input: patch });
53
+ if (applied.code) throw new Error(applied.stderr.trim() || "Cannot apply the selected hunks");
54
+ return { head: preview.head, source: preview.source, path: preview.path, hunks: input.hunks, staged: false };
55
+ });
56
+ }
57
+ export {
58
+ applyCheckpointHunks,
59
+ checkpointHunks,
60
+ mergeFileDraft
61
+ };
@@ -0,0 +1,146 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { lstat, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
3
+ import { dirname, join, posix, sep } from "node:path";
4
+ import { git, repoRoot, resolveCommit, withRepoLock } from "./git.js";
5
+ import { normalizeRepoPath } from "./repo-tree.js";
6
+ import { assertProjectFileAccess } from "./filesystem-policy.js";
7
+ const MAX_EDIT_BYTES = 512 * 1024;
8
+ class WorkspaceConflictError extends Error {
9
+ constructor(currentHash) {
10
+ super("This file changed since it was loaded. Reload and compare your draft before saving.");
11
+ this.currentHash = currentHash;
12
+ }
13
+ }
14
+ const hash = (text) => createHash("sha256").update(text).digest("hex");
15
+ const folderOf = (repo) => typeof repo === "string" ? repo : repo.path;
16
+ const rootOf = (repo) => typeof repo === "string" ? repoRoot(repo) : Promise.resolve(repo.path);
17
+ async function editablePath(repo, path) {
18
+ const root = await realpath(await rootOf(repo));
19
+ const rel = normalizeRepoPath(path);
20
+ if (!rel || rel.split("/").some((part) => part.toLowerCase() === ".git")) throw new Error("Choose a regular repository file outside .git");
21
+ let current = root;
22
+ for (const part of rel.split("/")) {
23
+ current = join(current, part);
24
+ const info2 = await lstat(current);
25
+ if (info2.isSymbolicLink()) throw new Error("Editing symbolic links is refused; open the regular file instead");
26
+ }
27
+ const canonical = await realpath(current);
28
+ if (!canonical.startsWith(root + sep)) throw new Error("The file leaves the repository");
29
+ const info = await lstat(canonical);
30
+ if (!info.isFile() || info.size > MAX_EDIT_BYTES) throw new Error("Edit a regular text file under 512 KB");
31
+ return { root, path: rel, absolute: canonical };
32
+ }
33
+ async function readEditableFile(repo, path) {
34
+ await assertProjectFileAccess(folderOf(repo), path, "read");
35
+ const file = await editablePath(repo, path);
36
+ const text = await readFile(file.absolute, "utf8");
37
+ if (text.includes("\0")) throw new Error("Binary files cannot be edited as text");
38
+ await assertProjectFileAccess(folderOf(repo), path, "read");
39
+ return { path: file.path, text, hash: hash(text), bytes: Buffer.byteLength(text) };
40
+ }
41
+ async function saveEditableFile(repo, path, text, expectedHash) {
42
+ await assertProjectFileAccess(folderOf(repo), path, "write");
43
+ if (typeof text !== "string" || Buffer.byteLength(text) > MAX_EDIT_BYTES || text.includes("\0")) throw new Error("The file must be text under 512 KB without binary NUL characters");
44
+ if (!/^[a-f0-9]{64}$/.test(expectedHash)) throw new Error("Saving needs the hash of the loaded version");
45
+ return withRepoLock(await rootOf(repo), async () => {
46
+ const file = await editablePath(repo, path);
47
+ const before = await readEditableFile(repo, path);
48
+ if (before.hash !== expectedHash) throw new WorkspaceConflictError(before.hash);
49
+ const temp = join(dirname(file.absolute), `.codeflow-edit-${randomBytes(8).toString("hex")}`);
50
+ const mode = (await lstat(file.absolute)).mode & 511;
51
+ try {
52
+ await writeFile(temp, text, { flag: "wx", mode });
53
+ const current = await readEditableFile(repo, path);
54
+ if (current.hash !== expectedHash) throw new WorkspaceConflictError(current.hash);
55
+ await assertProjectFileAccess(folderOf(repo), path, "write");
56
+ await rename(temp, file.absolute);
57
+ return { path: file.path, hash: hash(text), bytes: Buffer.byteLength(text), savedAt: Date.now() };
58
+ } finally {
59
+ await unlink(temp).catch(() => void 0);
60
+ }
61
+ });
62
+ }
63
+ const definition = /^\s*(?:(?:export|default|public|private|protected|static|async|declare|abstract)\s+)*(?:(function|class|interface|type|enum|def|struct|fn|trait)\s+([\w$]+)|(?:const|let|var)\s+([\w$]+)\s*=)/;
64
+ async function searchCode(repo, query, mode = "text") {
65
+ const root = await rootOf(repo);
66
+ const at = typeof repo === "string" ? root : repo;
67
+ const listing = await git(["-c", "core.fsmonitor=false", "ls-files", "-z", "--cached", "--others", "--exclude-standard"], at);
68
+ if (listing.code !== 0) throw new Error("Cannot list repository files");
69
+ const files = [...new Set(listing.stdout.split("\0").filter(Boolean))];
70
+ const needle = query.trim().toLowerCase();
71
+ if (!needle && mode === "text") return { hits: [], truncated: false, scanned: 0 };
72
+ if (query.length > 300) throw new Error("Search is limited to 300 characters");
73
+ const hits = [];
74
+ let scanned = 0, bytes = 0, truncated = false;
75
+ for (const path of files) {
76
+ if (scanned >= 2e3 || bytes > 8 * 1024 * 1024 || hits.length >= 200) {
77
+ truncated = true;
78
+ break;
79
+ }
80
+ if (/(^|\/)(?:node_modules|vendor|dist|build|\.git)\//.test(path)) continue;
81
+ const file = await readEditableFile(at, path).catch(() => void 0);
82
+ if (!file) continue;
83
+ scanned++;
84
+ bytes += file.bytes;
85
+ const lines = file.text.split("\n");
86
+ for (let index = 0; index < lines.length && hits.length < 200; index++) {
87
+ const text = lines[index];
88
+ const match = mode === "symbols" ? definition.exec(text) : null;
89
+ const symbol = match?.[2] ?? match?.[3];
90
+ if (mode === "symbols" ? symbol && (!needle || symbol.toLowerCase().includes(needle)) : text.toLowerCase().includes(needle)) hits.push({ path, line: index + 1, text: text.slice(0, 500), ...symbol ? { symbol, kind: match?.[1] ?? "variable" } : {} });
91
+ }
92
+ }
93
+ return { hits, scanned, truncated: truncated || hits.length >= 200 };
94
+ }
95
+ async function dependencyGraph(repo) {
96
+ const root = await rootOf(repo);
97
+ const at = typeof repo === "string" ? root : repo;
98
+ const listed = await git(["ls-files", "-z"], at);
99
+ if (listed.code !== 0) throw new Error("Cannot list repository files");
100
+ const all = new Set(listed.stdout.split("\0").filter((path) => /\.[cm]?[jt]sx?$/.test(path)));
101
+ const nodes = [...all].slice(0, 500), edges = [];
102
+ for (const path of nodes) {
103
+ const file = await readEditableFile(at, path).catch(() => void 0);
104
+ if (!file) continue;
105
+ for (const match of file.text.matchAll(/(?:\b(?:import|export)\s+(?:[^;\n]*?\s+from\s*)?|\b(?:require|import)\s*\()\s*["']([^"']+)["']/g)) {
106
+ const specifier = match[1];
107
+ if (!specifier.startsWith(".")) continue;
108
+ const base = posix.normalize(posix.join(posix.dirname(path), specifier));
109
+ const stem = base.replace(/\.[cm]?js$/, "");
110
+ const target = [base, `${stem}.ts`, `${stem}.tsx`, `${stem}.js`, `${base}/index.ts`, `${base}/index.tsx`, `${base}/index.js`].find((candidate) => all.has(candidate));
111
+ if (target) edges.push({ from: path, to: target, specifier });
112
+ }
113
+ }
114
+ return { nodes, edges, truncated: all.size > nodes.length, language: "JavaScript/TypeScript relative imports" };
115
+ }
116
+ async function checkpoints(repo, ref = "HEAD") {
117
+ const commit = await resolveCommit(repo, ref);
118
+ const log = await git(["log", "--max-count=50", "--format=%H%x00%at%x00%s", commit], repo);
119
+ if (log.code !== 0) throw new Error("Cannot read checkpoints");
120
+ return log.stdout.trim().split("\n").filter(Boolean).map((line) => {
121
+ const [sha, at, subject] = line.split("\0");
122
+ return { sha, at: Number(at) * 1e3, subject };
123
+ });
124
+ }
125
+ async function applyCheckpoint(repo, input) {
126
+ return withRepoLock(folderOf(repo), async () => {
127
+ const head = await resolveCommit(repo);
128
+ if (head !== input.expectedHead) throw new WorkspaceConflictError(head);
129
+ if ((await git(["status", "--porcelain"], repo)).stdout.trim()) throw new Error("Commit or save existing changes before applying a checkpoint");
130
+ const source = await resolveCommit(repo, input.source);
131
+ const paths = [...new Set(input.paths.map(normalizeRepoPath))];
132
+ if (!paths.length || paths.length > 100 || paths.some((path) => !path || path.split("/").some((part) => part.toLowerCase() === ".git"))) throw new Error("Choose 1\u2013100 repository paths");
133
+ const applied = await git(["--literal-pathspecs", "restore", `--source=${source}`, "--worktree", "--", ...paths], repo);
134
+ if (applied.code !== 0) throw new Error(applied.stderr.trim() || "Could not apply the selected paths");
135
+ return { head, source, paths, staged: false };
136
+ });
137
+ }
138
+ export {
139
+ WorkspaceConflictError,
140
+ applyCheckpoint,
141
+ checkpoints,
142
+ dependencyGraph,
143
+ readEditableFile,
144
+ saveEditableFile,
145
+ searchCode
146
+ };