@mirnoorata/codexa 0.2.0

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 (364) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +634 -0
  3. package/dist/artifacts.d.ts +2 -0
  4. package/dist/artifacts.js +375 -0
  5. package/dist/artifacts.js.map +1 -0
  6. package/dist/autonomy.d.ts +17 -0
  7. package/dist/autonomy.js +124 -0
  8. package/dist/autonomy.js.map +1 -0
  9. package/dist/autoverify/policy.d.ts +5 -0
  10. package/dist/autoverify/policy.js +18 -0
  11. package/dist/autoverify/policy.js.map +1 -0
  12. package/dist/autoverify.d.ts +45 -0
  13. package/dist/autoverify.js +1041 -0
  14. package/dist/autoverify.js.map +1 -0
  15. package/dist/cache-lock.d.ts +16 -0
  16. package/dist/cache-lock.js +181 -0
  17. package/dist/cache-lock.js.map +1 -0
  18. package/dist/cli/hooks.d.ts +5 -0
  19. package/dist/cli/hooks.js +264 -0
  20. package/dist/cli/hooks.js.map +1 -0
  21. package/dist/cli.d.ts +2 -0
  22. package/dist/cli.js +1034 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/codex-contract.d.ts +2 -0
  25. package/dist/codex-contract.js +78 -0
  26. package/dist/codex-contract.js.map +1 -0
  27. package/dist/command.d.ts +34 -0
  28. package/dist/command.js +162 -0
  29. package/dist/command.js.map +1 -0
  30. package/dist/doctor.d.ts +112 -0
  31. package/dist/doctor.js +518 -0
  32. package/dist/doctor.js.map +1 -0
  33. package/dist/eval/baseline.d.ts +7 -0
  34. package/dist/eval/baseline.js +146 -0
  35. package/dist/eval/baseline.js.map +1 -0
  36. package/dist/eval/historical.d.ts +4 -0
  37. package/dist/eval/historical.js +663 -0
  38. package/dist/eval/historical.js.map +1 -0
  39. package/dist/eval/render.d.ts +2 -0
  40. package/dist/eval/render.js +53 -0
  41. package/dist/eval/render.js.map +1 -0
  42. package/dist/eval/scoring.d.ts +21 -0
  43. package/dist/eval/scoring.js +618 -0
  44. package/dist/eval/scoring.js.map +1 -0
  45. package/dist/eval/synthetic.d.ts +36 -0
  46. package/dist/eval/synthetic.js +107 -0
  47. package/dist/eval/synthetic.js.map +1 -0
  48. package/dist/eval/types.d.ts +36 -0
  49. package/dist/eval/types.js +2 -0
  50. package/dist/eval/types.js.map +1 -0
  51. package/dist/eval.d.ts +140 -0
  52. package/dist/eval.js +551 -0
  53. package/dist/eval.js.map +1 -0
  54. package/dist/git.d.ts +17 -0
  55. package/dist/git.js +189 -0
  56. package/dist/git.js.map +1 -0
  57. package/dist/github-release.d.ts +47 -0
  58. package/dist/github-release.js +610 -0
  59. package/dist/github-release.js.map +1 -0
  60. package/dist/github-sync.d.ts +68 -0
  61. package/dist/github-sync.js +345 -0
  62. package/dist/github-sync.js.map +1 -0
  63. package/dist/graph.d.ts +10 -0
  64. package/dist/graph.js +665 -0
  65. package/dist/graph.js.map +1 -0
  66. package/dist/indexer/aliases.d.ts +2 -0
  67. package/dist/indexer/aliases.js +190 -0
  68. package/dist/indexer/aliases.js.map +1 -0
  69. package/dist/indexer/artifact-writing.d.ts +3 -0
  70. package/dist/indexer/artifact-writing.js +79 -0
  71. package/dist/indexer/artifact-writing.js.map +1 -0
  72. package/dist/indexer/discovery.d.ts +2 -0
  73. package/dist/indexer/discovery.js +5 -0
  74. package/dist/indexer/discovery.js.map +1 -0
  75. package/dist/indexer/external-facts.d.ts +6 -0
  76. package/dist/indexer/external-facts.js +45 -0
  77. package/dist/indexer/external-facts.js.map +1 -0
  78. package/dist/indexer/freshness.d.ts +8 -0
  79. package/dist/indexer/freshness.js +56 -0
  80. package/dist/indexer/freshness.js.map +1 -0
  81. package/dist/indexer/graph-stage.d.ts +2 -0
  82. package/dist/indexer/graph-stage.js +21 -0
  83. package/dist/indexer/graph-stage.js.map +1 -0
  84. package/dist/indexer/parsing.d.ts +30 -0
  85. package/dist/indexer/parsing.js +177 -0
  86. package/dist/indexer/parsing.js.map +1 -0
  87. package/dist/indexer/pipeline.d.ts +5 -0
  88. package/dist/indexer/pipeline.js +8 -0
  89. package/dist/indexer/pipeline.js.map +1 -0
  90. package/dist/indexer/ranking.d.ts +4 -0
  91. package/dist/indexer/ranking.js +134 -0
  92. package/dist/indexer/ranking.js.map +1 -0
  93. package/dist/indexer.d.ts +13 -0
  94. package/dist/indexer.js +395 -0
  95. package/dist/indexer.js.map +1 -0
  96. package/dist/init.d.ts +24 -0
  97. package/dist/init.js +566 -0
  98. package/dist/init.js.map +1 -0
  99. package/dist/language.d.ts +8 -0
  100. package/dist/language.js +123 -0
  101. package/dist/language.js.map +1 -0
  102. package/dist/live-index.d.ts +68 -0
  103. package/dist/live-index.js +215 -0
  104. package/dist/live-index.js.map +1 -0
  105. package/dist/lsp/assist.d.ts +44 -0
  106. package/dist/lsp/assist.js +331 -0
  107. package/dist/lsp/assist.js.map +1 -0
  108. package/dist/lsp/client.d.ts +59 -0
  109. package/dist/lsp/client.js +208 -0
  110. package/dist/lsp/client.js.map +1 -0
  111. package/dist/mcp/compaction.d.ts +15 -0
  112. package/dist/mcp/compaction.js +1249 -0
  113. package/dist/mcp/compaction.js.map +1 -0
  114. package/dist/mcp/envelope.d.ts +44 -0
  115. package/dist/mcp/envelope.js +425 -0
  116. package/dist/mcp/envelope.js.map +1 -0
  117. package/dist/mcp/prompts.d.ts +2 -0
  118. package/dist/mcp/prompts.js +109 -0
  119. package/dist/mcp/prompts.js.map +1 -0
  120. package/dist/mcp/resources.d.ts +2 -0
  121. package/dist/mcp/resources.js +132 -0
  122. package/dist/mcp/resources.js.map +1 -0
  123. package/dist/mcp/runtime.d.ts +15 -0
  124. package/dist/mcp/runtime.js +122 -0
  125. package/dist/mcp/runtime.js.map +1 -0
  126. package/dist/mcp/session-memory.d.ts +3 -0
  127. package/dist/mcp/session-memory.js +61 -0
  128. package/dist/mcp/session-memory.js.map +1 -0
  129. package/dist/mcp/tool-registry.d.ts +269 -0
  130. package/dist/mcp/tool-registry.js +284 -0
  131. package/dist/mcp/tool-registry.js.map +1 -0
  132. package/dist/mcp/tools.d.ts +53 -0
  133. package/dist/mcp/tools.js +372 -0
  134. package/dist/mcp/tools.js.map +1 -0
  135. package/dist/mcp-repo-root.d.ts +16 -0
  136. package/dist/mcp-repo-root.js +322 -0
  137. package/dist/mcp-repo-root.js.map +1 -0
  138. package/dist/mcp-tool-catalog.d.ts +2 -0
  139. package/dist/mcp-tool-catalog.js +2 -0
  140. package/dist/mcp-tool-catalog.js.map +1 -0
  141. package/dist/mcp.d.ts +11 -0
  142. package/dist/mcp.js +332 -0
  143. package/dist/mcp.js.map +1 -0
  144. package/dist/outcome-ranking.d.ts +5 -0
  145. package/dist/outcome-ranking.js +115 -0
  146. package/dist/outcome-ranking.js.map +1 -0
  147. package/dist/parser/context.d.ts +28 -0
  148. package/dist/parser/context.js +2 -0
  149. package/dist/parser/context.js.map +1 -0
  150. package/dist/parser/ecma.d.ts +5 -0
  151. package/dist/parser/ecma.js +388 -0
  152. package/dist/parser/ecma.js.map +1 -0
  153. package/dist/parser/facts.d.ts +12 -0
  154. package/dist/parser/facts.js +137 -0
  155. package/dist/parser/facts.js.map +1 -0
  156. package/dist/parser/json.d.ts +3 -0
  157. package/dist/parser/json.js +318 -0
  158. package/dist/parser/json.js.map +1 -0
  159. package/dist/parser/markdown.d.ts +3 -0
  160. package/dist/parser/markdown.js +180 -0
  161. package/dist/parser/markdown.js.map +1 -0
  162. package/dist/parser/nodes.d.ts +5 -0
  163. package/dist/parser/nodes.js +75 -0
  164. package/dist/parser/nodes.js.map +1 -0
  165. package/dist/parser/python.d.ts +2 -0
  166. package/dist/parser/python.js +307 -0
  167. package/dist/parser/python.js.map +1 -0
  168. package/dist/parser/references.d.ts +3 -0
  169. package/dist/parser/references.js +204 -0
  170. package/dist/parser/references.js.map +1 -0
  171. package/dist/parser/risks.d.ts +4 -0
  172. package/dist/parser/risks.js +62 -0
  173. package/dist/parser/risks.js.map +1 -0
  174. package/dist/parser/routes.d.ts +5 -0
  175. package/dist/parser/routes.js +97 -0
  176. package/dist/parser/routes.js.map +1 -0
  177. package/dist/parser/shallow.d.ts +3 -0
  178. package/dist/parser/shallow.js +545 -0
  179. package/dist/parser/shallow.js.map +1 -0
  180. package/dist/parser/source.d.ts +4 -0
  181. package/dist/parser/source.js +127 -0
  182. package/dist/parser/source.js.map +1 -0
  183. package/dist/parser.d.ts +2 -0
  184. package/dist/parser.js +2 -0
  185. package/dist/parser.js.map +1 -0
  186. package/dist/placeholder-signals.d.ts +15 -0
  187. package/dist/placeholder-signals.js +511 -0
  188. package/dist/placeholder-signals.js.map +1 -0
  189. package/dist/post-edit-outcomes.d.ts +167 -0
  190. package/dist/post-edit-outcomes.js +484 -0
  191. package/dist/post-edit-outcomes.js.map +1 -0
  192. package/dist/queries.d.ts +12 -0
  193. package/dist/queries.js +13 -0
  194. package/dist/queries.js.map +1 -0
  195. package/dist/query/change-plan.d.ts +48 -0
  196. package/dist/query/change-plan.js +858 -0
  197. package/dist/query/change-plan.js.map +1 -0
  198. package/dist/query/compact-data.d.ts +25 -0
  199. package/dist/query/compact-data.js +74 -0
  200. package/dist/query/compact-data.js.map +1 -0
  201. package/dist/query/context.d.ts +5 -0
  202. package/dist/query/context.js +1162 -0
  203. package/dist/query/context.js.map +1 -0
  204. package/dist/query/diff.d.ts +5 -0
  205. package/dist/query/diff.js +111 -0
  206. package/dist/query/diff.js.map +1 -0
  207. package/dist/query/edge-evidence.d.ts +3 -0
  208. package/dist/query/edge-evidence.js +36 -0
  209. package/dist/query/edge-evidence.js.map +1 -0
  210. package/dist/query/formatting.d.ts +14 -0
  211. package/dist/query/formatting.js +67 -0
  212. package/dist/query/formatting.js.map +1 -0
  213. package/dist/query/graph-traversal.d.ts +22 -0
  214. package/dist/query/graph-traversal.js +218 -0
  215. package/dist/query/graph-traversal.js.map +1 -0
  216. package/dist/query/graph.d.ts +14 -0
  217. package/dist/query/graph.js +102 -0
  218. package/dist/query/graph.js.map +1 -0
  219. package/dist/query/impact.d.ts +28 -0
  220. package/dist/query/impact.js +568 -0
  221. package/dist/query/impact.js.map +1 -0
  222. package/dist/query/inspection.d.ts +9 -0
  223. package/dist/query/inspection.js +290 -0
  224. package/dist/query/inspection.js.map +1 -0
  225. package/dist/query/next-tools.d.ts +3 -0
  226. package/dist/query/next-tools.js +25 -0
  227. package/dist/query/next-tools.js.map +1 -0
  228. package/dist/query/placeholders.d.ts +24 -0
  229. package/dist/query/placeholders.js +121 -0
  230. package/dist/query/placeholders.js.map +1 -0
  231. package/dist/query/post-edit/decision.d.ts +49 -0
  232. package/dist/query/post-edit/decision.js +130 -0
  233. package/dist/query/post-edit/decision.js.map +1 -0
  234. package/dist/query/post-edit/dirty-scope.d.ts +16 -0
  235. package/dist/query/post-edit/dirty-scope.js +21 -0
  236. package/dist/query/post-edit/dirty-scope.js.map +1 -0
  237. package/dist/query/post-edit/next-actions.d.ts +22 -0
  238. package/dist/query/post-edit/next-actions.js +44 -0
  239. package/dist/query/post-edit/next-actions.js.map +1 -0
  240. package/dist/query/post-edit/snapshot-contract.d.ts +8 -0
  241. package/dist/query/post-edit/snapshot-contract.js +111 -0
  242. package/dist/query/post-edit/snapshot-contract.js.map +1 -0
  243. package/dist/query/post-edit.d.ts +5 -0
  244. package/dist/query/post-edit.js +1108 -0
  245. package/dist/query/post-edit.js.map +1 -0
  246. package/dist/query/quality.d.ts +43 -0
  247. package/dist/query/quality.js +134 -0
  248. package/dist/query/quality.js.map +1 -0
  249. package/dist/query/raw-search.d.ts +23 -0
  250. package/dist/query/raw-search.js +147 -0
  251. package/dist/query/raw-search.js.map +1 -0
  252. package/dist/query/runtime.d.ts +11 -0
  253. package/dist/query/runtime.js +79 -0
  254. package/dist/query/runtime.js.map +1 -0
  255. package/dist/query/search.d.ts +25 -0
  256. package/dist/query/search.js +429 -0
  257. package/dist/query/search.js.map +1 -0
  258. package/dist/query/session-memory.d.ts +3 -0
  259. package/dist/query/session-memory.js +108 -0
  260. package/dist/query/session-memory.js.map +1 -0
  261. package/dist/query/session.d.ts +41 -0
  262. package/dist/query/session.js +90 -0
  263. package/dist/query/session.js.map +1 -0
  264. package/dist/query/targets.d.ts +25 -0
  265. package/dist/query/targets.js +97 -0
  266. package/dist/query/targets.js.map +1 -0
  267. package/dist/query/test-commands.d.ts +10 -0
  268. package/dist/query/test-commands.js +110 -0
  269. package/dist/query/test-commands.js.map +1 -0
  270. package/dist/query/test-plan.d.ts +6 -0
  271. package/dist/query/test-plan.js +104 -0
  272. package/dist/query/test-plan.js.map +1 -0
  273. package/dist/query/tests.d.ts +48 -0
  274. package/dist/query/tests.js +444 -0
  275. package/dist/query/tests.js.map +1 -0
  276. package/dist/query/verification/shell.d.ts +20 -0
  277. package/dist/query/verification/shell.js +164 -0
  278. package/dist/query/verification/shell.js.map +1 -0
  279. package/dist/query/verification.d.ts +47 -0
  280. package/dist/query/verification.js +1123 -0
  281. package/dist/query/verification.js.map +1 -0
  282. package/dist/query/workflow.d.ts +17 -0
  283. package/dist/query/workflow.js +252 -0
  284. package/dist/query/workflow.js.map +1 -0
  285. package/dist/query/workspace-guidance.d.ts +26 -0
  286. package/dist/query/workspace-guidance.js +214 -0
  287. package/dist/query/workspace-guidance.js.map +1 -0
  288. package/dist/query/worktree-state.d.ts +22 -0
  289. package/dist/query/worktree-state.js +32 -0
  290. package/dist/query/worktree-state.js.map +1 -0
  291. package/dist/query/worktree.d.ts +16 -0
  292. package/dist/query/worktree.js +194 -0
  293. package/dist/query/worktree.js.map +1 -0
  294. package/dist/query-data.d.ts +4 -0
  295. package/dist/query-data.js +112 -0
  296. package/dist/query-data.js.map +1 -0
  297. package/dist/repo-files.d.ts +24 -0
  298. package/dist/repo-files.js +105 -0
  299. package/dist/repo-files.js.map +1 -0
  300. package/dist/resolver.d.ts +9 -0
  301. package/dist/resolver.js +555 -0
  302. package/dist/resolver.js.map +1 -0
  303. package/dist/retrieval.d.ts +46 -0
  304. package/dist/retrieval.js +783 -0
  305. package/dist/retrieval.js.map +1 -0
  306. package/dist/risk-ingest.d.ts +16 -0
  307. package/dist/risk-ingest.js +458 -0
  308. package/dist/risk-ingest.js.map +1 -0
  309. package/dist/rules.d.ts +10 -0
  310. package/dist/rules.js +107 -0
  311. package/dist/rules.js.map +1 -0
  312. package/dist/semantic/python.d.ts +9 -0
  313. package/dist/semantic/python.js +817 -0
  314. package/dist/semantic/python.js.map +1 -0
  315. package/dist/semantic/typescript.d.ts +10 -0
  316. package/dist/semantic/typescript.js +714 -0
  317. package/dist/semantic/typescript.js.map +1 -0
  318. package/dist/semantic-retrieval.d.ts +53 -0
  319. package/dist/semantic-retrieval.js +673 -0
  320. package/dist/semantic-retrieval.js.map +1 -0
  321. package/dist/session-memory/derivation.d.ts +6 -0
  322. package/dist/session-memory/derivation.js +400 -0
  323. package/dist/session-memory/derivation.js.map +1 -0
  324. package/dist/session-memory/event-log.d.ts +23 -0
  325. package/dist/session-memory/event-log.js +126 -0
  326. package/dist/session-memory/event-log.js.map +1 -0
  327. package/dist/session-memory/formatting.d.ts +7 -0
  328. package/dist/session-memory/formatting.js +86 -0
  329. package/dist/session-memory/formatting.js.map +1 -0
  330. package/dist/session-memory/model.d.ts +94 -0
  331. package/dist/session-memory/model.js +17 -0
  332. package/dist/session-memory/model.js.map +1 -0
  333. package/dist/session-memory/runtime.d.ts +24 -0
  334. package/dist/session-memory/runtime.js +289 -0
  335. package/dist/session-memory/runtime.js.map +1 -0
  336. package/dist/session-memory/store.d.ts +27 -0
  337. package/dist/session-memory/store.js +447 -0
  338. package/dist/session-memory/store.js.map +1 -0
  339. package/dist/session-memory.d.ts +1 -0
  340. package/dist/session-memory.js +2 -0
  341. package/dist/session-memory.js.map +1 -0
  342. package/dist/static-analysis.d.ts +36 -0
  343. package/dist/static-analysis.js +505 -0
  344. package/dist/static-analysis.js.map +1 -0
  345. package/dist/symbol-report-ingest.d.ts +8 -0
  346. package/dist/symbol-report-ingest.js +504 -0
  347. package/dist/symbol-report-ingest.js.map +1 -0
  348. package/dist/task-snapshots.d.ts +41 -0
  349. package/dist/task-snapshots.js +430 -0
  350. package/dist/task-snapshots.js.map +1 -0
  351. package/dist/types.d.ts +848 -0
  352. package/dist/types.js +12 -0
  353. package/dist/types.js.map +1 -0
  354. package/dist/util.d.ts +11 -0
  355. package/dist/util.js +63 -0
  356. package/dist/util.js.map +1 -0
  357. package/dist/version.d.ts +1 -0
  358. package/dist/version.js +5 -0
  359. package/dist/version.js.map +1 -0
  360. package/package.json +81 -0
  361. package/plugins/codexa/.codex-plugin/plugin.json +38 -0
  362. package/plugins/codexa/.mcp.json +20 -0
  363. package/plugins/codexa/scripts/codexa-mcp.js +100 -0
  364. package/plugins/codexa/skills/codexa/SKILL.md +48 -0
@@ -0,0 +1,663 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { buildIndex } from "../indexer.js";
8
+ import { changePlanQuery, contextPackQuery, focusBriefQuery, impactQuery, postEditReviewQuery, taskBriefQuery, workflowPathQuery } from "../queries.js";
9
+ const HISTORICAL_PACK_SCHEMA_VERSION = 1;
10
+ export async function historicalFixtureScenarios(seed, options) {
11
+ const fixture = await createHistoricalFixtureRepo(seed);
12
+ return await Promise.all(fixture.tasks.map((task) => historicalScenario(fixture.repoRoot, options, task, "historical-fixture", undefined, fixture.repoRoot)));
13
+ }
14
+ export async function externalHistoricalTaskPackScenarios(repoRoot, options, taskPackPath) {
15
+ const tasks = loadExternalHistoricalTaskPack(repoRoot, taskPackPath);
16
+ return await Promise.all(tasks.map((task) => historicalScenario(repoRoot, options, task, "historical-task-pack", taskPackPath)));
17
+ }
18
+ async function historicalScenario(baseRepoRoot, options, task, suite, taskPackPath, sharedCleanupRoot) {
19
+ const prepared = await prepareHistoricalScenarioRepo(baseRepoRoot, task, sharedCleanupRoot);
20
+ const baselineCommand = task.baselineCommands?.[0];
21
+ const expectedCalls = task.expectedCodexaCalls ?? [task.tool];
22
+ const oracle = {
23
+ expectedFiles: task.expectedReadFirst,
24
+ expectedChangedFiles: task.expectedChangedFiles,
25
+ expectedTests: task.expectedTests,
26
+ forbiddenFiles: task.forbiddenFiles,
27
+ topFiles: task.expectedReadFirst.slice(0, 1),
28
+ knownTraps: task.knownTraps,
29
+ expectedCodexaCalls: expectedCalls,
30
+ maxTextChars: task.maxContextChars,
31
+ maxFalsePositiveFiles: task.maxFalsePositiveFiles ?? 2,
32
+ minFileRecall: task.minFileRecall ?? 0.66,
33
+ minChangedFileRecall: task.minChangedFileRecall ?? (task.expectedChangedFiles?.length ? 1 : undefined),
34
+ minTestRecall: task.minTestRecall ?? (task.expectedTests?.length ? 0.5 : undefined),
35
+ minFilePrecisionAtK: task.minFilePrecisionAtK ?? 0.4,
36
+ maxSelectedToBaselineRatio: baselineCommand ? 2 : undefined
37
+ };
38
+ return {
39
+ id: `${suite}-${task.id}`,
40
+ suite,
41
+ description: task.description ?? task.task,
42
+ repoRoot: prepared.repoRoot,
43
+ baselineCommand,
44
+ baselineCommands: task.baselineCommands,
45
+ codexa: async () => runHistoricalTask(prepared.repoRoot, options, task, expectedCalls),
46
+ oracle,
47
+ privatePack: Boolean(taskPackPath),
48
+ taskPackPath,
49
+ cleanupRepoRoots: prepared.cleanupRepoRoots
50
+ };
51
+ }
52
+ async function runHistoricalTask(repoRoot, options, task, expectedCalls) {
53
+ const dirtyBackups = [];
54
+ const calls = [];
55
+ const call = async (name, run) => {
56
+ calls.push(name);
57
+ return await run();
58
+ };
59
+ try {
60
+ if (task.dirtyPatch?.length) {
61
+ dirtyBackups.push(...backupHistoricalDirtyPaths(repoRoot, task.dirtyPatch));
62
+ await applyHistoricalSetupPatches(repoRoot, task.dirtyPatch);
63
+ }
64
+ let result;
65
+ if (task.tool === "impact") {
66
+ result = await call("impact", () => impactQuery(repoRoot, { file: task.files?.[0], symbol: task.symbols?.[0], changeType: "behavior" }, options));
67
+ }
68
+ else if (task.tool === "workflow_path") {
69
+ result = await call("workflow_path", () => workflowPathQuery(repoRoot, { query: task.task, file: task.files?.[0], symbol: task.symbols?.[0], limit: 10 }, options));
70
+ }
71
+ else if (task.tool === "context_pack") {
72
+ result = await call("context_pack", () => contextPackQuery(repoRoot, { task: task.task, files: task.files, symbols: task.symbols, diff: Boolean(task.dirtyPatch?.length), tokenBudget: 2200, limit: 10 }, options));
73
+ }
74
+ else if (task.tool === "focus_brief") {
75
+ result = await call("focus_brief", () => focusBriefQuery(repoRoot, { task: task.task, diff: false, tokenBudget: 1600, limit: 8 }, options));
76
+ }
77
+ else if (task.tool === "change_plan") {
78
+ result = await call("change_plan", () => changePlanQuery(repoRoot, { task: task.task, files: task.files, symbols: task.symbols, changeType: "api", diff: false, tokenBudget: 2200, limit: 8 }, options));
79
+ }
80
+ else if (task.tool === "post_edit_review") {
81
+ result = await call("change_plan", () => changePlanQuery(repoRoot, {
82
+ task: task.task,
83
+ files: task.files,
84
+ symbols: task.symbols,
85
+ changeType: "api",
86
+ diff: false,
87
+ tokenBudget: 1800,
88
+ limit: 8,
89
+ saveSnapshot: true,
90
+ taskId: `historical-${task.id}-${randomUUID()}`
91
+ }, options));
92
+ const editFile = task.expectedChangedFiles?.[0] ?? task.files?.[0];
93
+ const editPath = editFile ? path.join(repoRoot, editFile) : undefined;
94
+ const originalContent = editPath ? readFileSync(editPath, "utf8") : undefined;
95
+ if (editPath && originalContent !== undefined) {
96
+ await writeFile(editPath, `${originalContent}\n// historical edit marker\n`, "utf8");
97
+ }
98
+ try {
99
+ result = await call("post_edit_review", () => postEditReviewQuery(repoRoot, { taskId: undefined, ranTests: [], tokenBudget: 1800, limit: 8 }, options));
100
+ }
101
+ finally {
102
+ if (editPath && originalContent !== undefined) {
103
+ await writeFile(editPath, originalContent, "utf8");
104
+ }
105
+ }
106
+ }
107
+ else {
108
+ result = await call("task_brief", () => taskBriefQuery(repoRoot, { task: task.task, files: task.files, symbols: task.symbols, diff: false, tokenBudget: 2200, limit: 10 }, options));
109
+ }
110
+ return {
111
+ ...result,
112
+ data: {
113
+ ...(result.data && typeof result.data === "object" ? result.data : {}),
114
+ historicalTaskSuite: task.suite,
115
+ repoFixture: task.repoFixture,
116
+ setupPatchFiles: task.setupPatch?.map((entry) => entry.path) ?? [],
117
+ dirtyPatchFiles: task.dirtyPatch?.map((entry) => entry.path) ?? [],
118
+ callTrace: calls,
119
+ expectedCallTrace: expectedCalls
120
+ }
121
+ };
122
+ }
123
+ finally {
124
+ await restoreHistoricalDirtyPatches(repoRoot, dirtyBackups);
125
+ }
126
+ }
127
+ async function prepareHistoricalScenarioRepo(baseRepoRoot, task, sharedCleanupRoot) {
128
+ if (!task.setupPatch || task.setupPatch.length === 0) {
129
+ return {
130
+ repoRoot: baseRepoRoot,
131
+ cleanupRepoRoots: sharedCleanupRoot ? [sharedCleanupRoot] : []
132
+ };
133
+ }
134
+ const parent = await mkdtemp(path.join(os.tmpdir(), `codexa-historical-setup-${task.id}-`));
135
+ const repoRoot = path.join(parent, "repo");
136
+ try {
137
+ execFileSync("git", ["clone", "--quiet", "--no-hardlinks", baseRepoRoot, repoRoot], {
138
+ stdio: ["ignore", "pipe", "pipe"],
139
+ timeout: 20_000,
140
+ maxBuffer: 2 * 1024 * 1024
141
+ });
142
+ execFileSync("git", ["config", "user.name", "Codexa Eval"], { cwd: repoRoot, stdio: "ignore" });
143
+ execFileSync("git", ["config", "user.email", "codexa-eval@example.invalid"], { cwd: repoRoot, stdio: "ignore" });
144
+ await applyHistoricalSetupPatches(repoRoot, task.setupPatch);
145
+ execFileSync("git", ["add", "."], { cwd: repoRoot, stdio: "ignore" });
146
+ try {
147
+ execFileSync("git", ["diff", "--cached", "--quiet"], { cwd: repoRoot, stdio: "ignore" });
148
+ }
149
+ catch {
150
+ execFileSync("git", ["commit", "-m", `historical setup ${task.id}`], { cwd: repoRoot, stdio: "ignore" });
151
+ }
152
+ await buildIndex({ repoRoot, writeArtifacts: true });
153
+ return {
154
+ repoRoot,
155
+ cleanupRepoRoots: [parent, ...(sharedCleanupRoot ? [sharedCleanupRoot] : [])]
156
+ };
157
+ }
158
+ catch (error) {
159
+ await rm(parent, { recursive: true, force: true });
160
+ throw error;
161
+ }
162
+ }
163
+ async function applyHistoricalSetupPatches(repoRoot, patches) {
164
+ for (const patch of patches) {
165
+ const absolute = path.join(repoRoot, patch.path);
166
+ let text = patch.content;
167
+ if (text === undefined) {
168
+ try {
169
+ text = readFileSync(absolute, "utf8");
170
+ }
171
+ catch (error) {
172
+ if (patch.append !== undefined) {
173
+ text = "";
174
+ }
175
+ else {
176
+ throw new Error(`setupPatch target does not exist: ${patch.path}; ${error instanceof Error ? error.message : String(error)}`);
177
+ }
178
+ }
179
+ }
180
+ for (const replacement of patch.replace ?? []) {
181
+ if (!text.includes(replacement.from)) {
182
+ throw new Error(`setupPatch ${patch.path} replacement string was not found`);
183
+ }
184
+ text = text.split(replacement.from).join(replacement.to);
185
+ }
186
+ if (patch.append !== undefined) {
187
+ text = `${text}${patch.append}`;
188
+ }
189
+ await mkdir(path.dirname(absolute), { recursive: true });
190
+ await writeFile(absolute, text, "utf8");
191
+ }
192
+ }
193
+ function backupHistoricalDirtyPaths(repoRoot, patches) {
194
+ const backups = [];
195
+ const seen = new Set();
196
+ for (const patch of patches) {
197
+ if (seen.has(patch.path)) {
198
+ continue;
199
+ }
200
+ seen.add(patch.path);
201
+ const absolute = path.join(repoRoot, patch.path);
202
+ try {
203
+ backups.push({ path: patch.path, existed: true, content: readFileSync(absolute, "utf8") });
204
+ }
205
+ catch {
206
+ backups.push({ path: patch.path, existed: false });
207
+ }
208
+ }
209
+ return backups;
210
+ }
211
+ async function restoreHistoricalDirtyPatches(repoRoot, backups) {
212
+ for (const backup of backups.reverse()) {
213
+ const absolute = path.join(repoRoot, backup.path);
214
+ if (backup.existed) {
215
+ await mkdir(path.dirname(absolute), { recursive: true });
216
+ await writeFile(absolute, backup.content ?? "", "utf8");
217
+ }
218
+ else {
219
+ await rm(absolute, { force: true });
220
+ }
221
+ }
222
+ }
223
+ function loadExternalHistoricalTaskPack(repoRoot, taskPackPath) {
224
+ const resolved = path.resolve(taskPackPath);
225
+ if (!existsSync(resolved)) {
226
+ throw new Error(`historical task pack does not exist: ${resolved}`);
227
+ }
228
+ const parsed = JSON.parse(readFileSync(resolved, "utf8"));
229
+ if (!parsed || typeof parsed !== "object") {
230
+ throw new Error("historical task pack must be a JSON object");
231
+ }
232
+ const pack = parsed;
233
+ if (pack.schemaVersion !== HISTORICAL_PACK_SCHEMA_VERSION) {
234
+ throw new Error(`historical task pack schemaVersion must be ${HISTORICAL_PACK_SCHEMA_VERSION}`);
235
+ }
236
+ if (typeof pack.packId !== "string" || !pack.packId.trim()) {
237
+ throw new Error("historical task pack requires packId");
238
+ }
239
+ if (typeof pack.repoCommit !== "string" || !pack.repoCommit.trim()) {
240
+ throw new Error("historical task pack requires repoCommit so tasks are anchored to a snapshot");
241
+ }
242
+ const repoCommit = pack.repoCommit.trim();
243
+ const currentCommit = currentGitCommit(repoRoot);
244
+ if (currentCommit !== repoCommit) {
245
+ throw new Error(`historical task pack repoCommit ${repoCommit} does not match target repo HEAD ${currentCommit}`);
246
+ }
247
+ if (!Array.isArray(pack.tasks) || pack.tasks.length === 0) {
248
+ throw new Error("historical task pack requires at least one task");
249
+ }
250
+ const tasks = pack.tasks.map((entry, index) => parseHistoricalTask(entry, `tasks[${index}]`));
251
+ validateTaskCoverage(tasks);
252
+ return tasks;
253
+ }
254
+ function parseHistoricalTask(value, label) {
255
+ if (!value || typeof value !== "object") {
256
+ throw new Error(`${label} must be an object`);
257
+ }
258
+ const record = value;
259
+ const id = requiredString(record.id, `${label}.id`);
260
+ const task = requiredString(record.task, `${label}.task`);
261
+ const tool = requiredTool(record.tool, `${label}.tool`);
262
+ const baselineCommands = optionalCommandList(record.baselineCommands, `${label}.baselineCommands`);
263
+ return {
264
+ id,
265
+ suite: optionalString(record.suite),
266
+ task,
267
+ description: optionalString(record.description),
268
+ tool,
269
+ repoFixture: optionalString(record.repoFixture),
270
+ setupPatch: optionalSetupPatchList(record.setupPatch, `${label}.setupPatch`),
271
+ dirtyPatch: optionalSetupPatchList(record.dirtyPatch, `${label}.dirtyPatch`),
272
+ files: optionalRepoPathList(record.files, `${label}.files`),
273
+ symbols: optionalStringList(record.symbols, `${label}.symbols`),
274
+ expectedReadFirst: requiredRepoPathList(record.expectedReadFirst, `${label}.expectedReadFirst`),
275
+ expectedChangedFiles: optionalRepoPathList(record.expectedChangedFiles, `${label}.expectedChangedFiles`),
276
+ expectedTests: optionalRepoPathList(record.expectedTests, `${label}.expectedTests`),
277
+ knownTraps: optionalStringList(record.knownTraps, `${label}.knownTraps`),
278
+ forbiddenFiles: optionalRepoPathList(record.forbiddenFiles, `${label}.forbiddenFiles`),
279
+ baselineCommands,
280
+ expectedCodexaCalls: optionalStringList(record.expectedCodexaCalls, `${label}.expectedCodexaCalls`),
281
+ maxContextChars: optionalNumber(record.maxContextChars, `${label}.maxContextChars`),
282
+ maxFalsePositiveFiles: optionalNumber(record.maxFalsePositiveFiles, `${label}.maxFalsePositiveFiles`),
283
+ minFileRecall: optionalNumber(record.minFileRecall, `${label}.minFileRecall`),
284
+ minChangedFileRecall: optionalNumber(record.minChangedFileRecall, `${label}.minChangedFileRecall`),
285
+ minTestRecall: optionalNumber(record.minTestRecall, `${label}.minTestRecall`),
286
+ minFilePrecisionAtK: optionalNumber(record.minFilePrecisionAtK, `${label}.minFilePrecisionAtK`)
287
+ };
288
+ }
289
+ function validateTaskCoverage(tasks) {
290
+ const tools = new Set(tasks.map((task) => task.tool));
291
+ if (tasks.length < 1) {
292
+ throw new Error("historical task pack must include tasks");
293
+ }
294
+ if (tasks.length >= 5) {
295
+ for (const tool of ["task_brief", "focus_brief", "workflow_path", "change_plan", "post_edit_review"]) {
296
+ if (!tools.has(tool)) {
297
+ throw new Error(`historical task pack with five or more tasks must include ${tool}`);
298
+ }
299
+ }
300
+ }
301
+ }
302
+ async function createHistoricalFixtureRepo(seed) {
303
+ const token = alphaToken(seeded(seed), 8);
304
+ const camel = `${token[0].toUpperCase()}${token.slice(1)}`;
305
+ const repoRoot = await mkdtemp(path.join(os.tmpdir(), `codexa-historical-${token}-`));
306
+ execFileSync("git", ["init"], { cwd: repoRoot, stdio: "ignore" });
307
+ execFileSync("git", ["config", "user.name", "Codexa Eval"], { cwd: repoRoot, stdio: "ignore" });
308
+ execFileSync("git", ["config", "user.email", "codexa-eval@example.invalid"], { cwd: repoRoot, stdio: "ignore" });
309
+ await mkdir(path.join(repoRoot, "src/ui"), { recursive: true });
310
+ await mkdir(path.join(repoRoot, "src/backend"), { recursive: true });
311
+ await mkdir(path.join(repoRoot, "tests"), { recursive: true });
312
+ await mkdir(path.join(repoRoot, "manifests"), { recursive: true });
313
+ await writeFile(path.join(repoRoot, "package.json"), JSON.stringify({ scripts: { test: "vitest run" } }, null, 2), "utf8");
314
+ await writeFile(path.join(repoRoot, "pyproject.toml"), `[tool.pytest.ini_options]\ntestpaths = ["tests"]\n`, "utf8");
315
+ await writeFile(path.join(repoRoot, "src/shared.ts"), `export function normalize${camel}(value: string) {\n return value.trim().toLowerCase()\n}\n`, "utf8");
316
+ await writeFile(path.join(repoRoot, "src/feature.ts"), `import { normalize${camel} } from "./shared"\nexport function render${camel}(value: string) {\n return normalize${camel}(value)\n}\n`, "utf8");
317
+ await writeFile(path.join(repoRoot, "src/feature.test.ts"), `import { normalize${camel} } from "./shared"\ntest("${token}", () => expect(normalize${camel}(" A ")).toBe("a"))\n`, "utf8");
318
+ await writeFile(path.join(repoRoot, "src/shared_decoy.ts"), `export function normalize${camel}Decoy(value: string) {\n return value\n}\n`, "utf8");
319
+ await writeFile(path.join(repoRoot, "src/ui/use_polling.ts"), `import { fetch${camel}Queue } from "./api_client"\nexport function use${camel}Polling() {\n return fetch${camel}Queue()\n}\n`, "utf8");
320
+ await writeFile(path.join(repoRoot, "src/ui/api_client.ts"), `export function fetch${camel}Queue() {\n return fetch("/api/${token}/queue")\n}\n`, "utf8");
321
+ await writeFile(path.join(repoRoot, "src/ui/use_polling.test.ts"), `import { use${camel}Polling } from "./use_polling"\ntest("${token}-polling", () => expect(use${camel}Polling).toBeTruthy())\n`, "utf8");
322
+ await writeFile(path.join(repoRoot, "src/backend/store.py"), `def load_${token}_queue():\n return []\n`, "utf8");
323
+ await writeFile(path.join(repoRoot, "src/backend/routes.py"), `from .store import load_${token}_queue\n\n@router.get("/api/${token}/queue")\ndef ${token}_queue():\n return load_${token}_queue()\n`, "utf8");
324
+ await writeFile(path.join(repoRoot, "src/backend/helpers.py"), `def prepare_${token}(value):\n return value.strip().casefold()\n`, "utf8");
325
+ await writeFile(path.join(repoRoot, "src/backend/app.py"), `from .helpers import prepare_${token}\n\n@router.post("/api/${token}/prepare")\ndef prepare_route(value):\n return prepare_${token}(value)\n`, "utf8");
326
+ await writeFile(path.join(repoRoot, "tests/test_backend.py"), `from src.backend.app import prepare_route\n\ndef test_prepare_route():\n assert prepare_route(" A ") == "a"\n`, "utf8");
327
+ await writeFile(path.join(repoRoot, "tests/test_queue.py"), `from src.backend.routes import ${token}_queue\n\ndef test_queue_route():\n assert ${token}_queue() == []\n`, "utf8");
328
+ await writeFile(path.join(repoRoot, `manifests/${token}.json`), JSON.stringify({ nodes: [{ type_id: `${token}.node`, adapter_key: `${token}.adapter` }] }, null, 2), "utf8");
329
+ await writeFile(path.join(repoRoot, "src/backend/adapter.py"), `NODE_TYPE = "${token}.node"\ndef run_${token}_adapter():\n return NODE_TYPE\n`, "utf8");
330
+ await writeFile(path.join(repoRoot, "tests/test_adapter.py"), `from src.backend.adapter import run_${token}_adapter\n\ndef test_adapter():\n assert run_${token}_adapter() == "${token}.node"\n`, "utf8");
331
+ execFileSync("git", ["add", "."], { cwd: repoRoot, stdio: "ignore" });
332
+ execFileSync("git", ["commit", "-m", "historical benchmark fixture"], { cwd: repoRoot, stdio: "ignore" });
333
+ await buildIndex({ repoRoot, writeArtifacts: true });
334
+ const tasks = [
335
+ {
336
+ id: "exact-shared-api",
337
+ suite: "typescript-api",
338
+ repoFixture: "seeded-typescript-python-service",
339
+ task: `Refactor normalize${camel} API without missing consumers`,
340
+ tool: "change_plan",
341
+ files: ["src/shared.ts"],
342
+ expectedReadFirst: ["src/shared.ts", "src/feature.test.ts", "src/feature.ts"],
343
+ expectedChangedFiles: ["src/shared.ts"],
344
+ expectedTests: ["src/feature.test.ts"],
345
+ forbiddenFiles: ["src/shared_decoy.ts"],
346
+ knownTraps: ["similarly named decoy helper"],
347
+ baselineCommands: [["rg", "-n", `normalize${camel}`, "."]],
348
+ expectedCodexaCalls: ["change_plan"],
349
+ maxFalsePositiveFiles: 1
350
+ },
351
+ {
352
+ id: "broad-workflow",
353
+ suite: "workflow",
354
+ repoFixture: "seeded-typescript-python-service",
355
+ task: `${token} queue polling from UI to backend route store and tests`,
356
+ tool: "workflow_path",
357
+ expectedReadFirst: ["src/backend/routes.py", "src/ui/api_client.ts", "src/backend/store.py", "src/ui/use_polling.ts"],
358
+ expectedTests: ["src/ui/use_polling.test.ts", "tests/test_queue.py"],
359
+ baselineCommands: [["rg", "-n", `${token}|queue|polling|/api/${token}/queue`, "."]],
360
+ expectedCodexaCalls: ["workflow_path"],
361
+ maxFalsePositiveFiles: 0,
362
+ minFileRecall: 1,
363
+ minTestRecall: 0.5
364
+ },
365
+ {
366
+ id: "python-route-helper",
367
+ suite: "python-api",
368
+ repoFixture: "seeded-typescript-python-service",
369
+ task: `Change prepare_${token} helper and keep route tests in scope`,
370
+ tool: "impact",
371
+ files: ["src/backend/helpers.py"],
372
+ expectedReadFirst: ["src/backend/helpers.py", "src/backend/app.py"],
373
+ expectedTests: ["tests/test_backend.py"],
374
+ baselineCommands: [["rg", "-n", `prepare_${token}`, "."]],
375
+ expectedCodexaCalls: ["impact"],
376
+ maxFalsePositiveFiles: 1
377
+ },
378
+ {
379
+ id: "manifest-adapter",
380
+ suite: "manifest-adapter",
381
+ repoFixture: "seeded-typescript-python-service",
382
+ task: `Change ${token}.node manifest behavior and find adapter tests`,
383
+ tool: "context_pack",
384
+ files: [`manifests/${token}.json`],
385
+ expectedReadFirst: [`manifests/${token}.json`, "src/backend/adapter.py"],
386
+ expectedTests: ["tests/test_adapter.py"],
387
+ baselineCommands: [["rg", "-n", `${token}.node`, "."]],
388
+ expectedCodexaCalls: ["context_pack"],
389
+ maxFalsePositiveFiles: 0,
390
+ minFileRecall: 1,
391
+ minTestRecall: 0
392
+ },
393
+ {
394
+ id: "post-edit-drift",
395
+ suite: "post-edit",
396
+ repoFixture: "seeded-typescript-python-service",
397
+ task: `Edit normalize${camel} and verify planned drift before finishing`,
398
+ tool: "post_edit_review",
399
+ files: ["src/shared.ts"],
400
+ expectedReadFirst: ["src/shared.ts", "src/feature.test.ts", "src/feature.ts"],
401
+ expectedChangedFiles: ["src/shared.ts"],
402
+ expectedTests: ["src/feature.test.ts"],
403
+ forbiddenFiles: ["src/shared_decoy.ts"],
404
+ baselineCommands: [["rg", "-n", `normalize${camel}`, "."]],
405
+ expectedCodexaCalls: ["change_plan", "post_edit_review"],
406
+ maxFalsePositiveFiles: 1,
407
+ maxContextChars: 7200
408
+ },
409
+ {
410
+ id: "target-led-broad-dirty",
411
+ suite: "usage-derived",
412
+ repoFixture: "seeded-typescript-python-service",
413
+ task: `Change normalize${camel} while unrelated queue, route, and manifest edits are already dirty`,
414
+ tool: "context_pack",
415
+ files: ["src/shared.ts"],
416
+ dirtyPatch: [
417
+ { path: "src/backend/routes.py", append: `\n# dirty ${token} route note\n` },
418
+ { path: "src/backend/store.py", append: `\n# dirty ${token} store note\n` },
419
+ { path: "src/backend/helpers.py", append: `\n# dirty ${token} helper note\n` },
420
+ { path: "src/backend/app.py", append: `\n# dirty ${token} app note\n` },
421
+ { path: "src/backend/adapter.py", append: `\n# dirty ${token} adapter note\n` },
422
+ { path: "src/ui/use_polling.ts", append: `\n// dirty ${token} polling note\n` },
423
+ { path: "src/ui/api_client.ts", append: `\n// dirty ${token} api note\n` },
424
+ { path: "tests/test_backend.py", append: `\n# dirty ${token} backend test note\n` },
425
+ { path: "tests/test_queue.py", append: `\n# dirty ${token} queue test note\n` },
426
+ { path: "tests/test_adapter.py", append: `\n# dirty ${token} adapter test note\n` },
427
+ { path: `manifests/${token}.json`, replace: [{ from: `"adapter_key": "${token}.adapter"`, to: `"adapter_key": "${token}.adapter.dirty"` }] }
428
+ ],
429
+ expectedReadFirst: ["src/shared.ts", "src/feature.test.ts", "src/feature.ts"],
430
+ expectedTests: ["src/feature.test.ts"],
431
+ forbiddenFiles: ["src/shared_decoy.ts"],
432
+ knownTraps: ["broad unrelated dirty tree should stay diff context, not read-first focus"],
433
+ baselineCommands: [["rg", "-n", `normalize${camel}|dirty ${token}`, "."]],
434
+ expectedCodexaCalls: ["context_pack"],
435
+ maxFalsePositiveFiles: 2,
436
+ minFileRecall: 1,
437
+ minTestRecall: 1,
438
+ minFilePrecisionAtK: 0.6
439
+ }
440
+ ];
441
+ return { repoRoot, tasks };
442
+ }
443
+ function requiredString(value, label) {
444
+ if (typeof value !== "string" || !value.trim()) {
445
+ throw new Error(`${label} must be a non-empty string`);
446
+ }
447
+ return value;
448
+ }
449
+ function optionalString(value) {
450
+ return typeof value === "string" && value.trim() ? value : undefined;
451
+ }
452
+ function requiredTool(value, label) {
453
+ const tool = requiredString(value, label);
454
+ if (!["task_brief", "context_pack", "focus_brief", "impact", "workflow_path", "change_plan", "post_edit_review"].includes(tool)) {
455
+ throw new Error(`${label} is not a supported historical Codexa tool`);
456
+ }
457
+ return tool;
458
+ }
459
+ function requiredStringList(value, label) {
460
+ const values = optionalStringList(value, label);
461
+ if (!values || values.length === 0) {
462
+ throw new Error(`${label} must include at least one string`);
463
+ }
464
+ return values;
465
+ }
466
+ function optionalStringList(value, label) {
467
+ if (value === undefined) {
468
+ return undefined;
469
+ }
470
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || !entry.trim())) {
471
+ throw new Error(`${label} must be a string array`);
472
+ }
473
+ return value;
474
+ }
475
+ function requiredRepoPathList(value, label) {
476
+ return validateRepoPathList(requiredStringList(value, label), label);
477
+ }
478
+ function optionalRepoPathList(value, label) {
479
+ const values = optionalStringList(value, label);
480
+ return values ? validateRepoPathList(values, label) : undefined;
481
+ }
482
+ function validateRepoPathList(values, label) {
483
+ for (const value of values) {
484
+ if (isUnsafeRepoRelativePath(value)) {
485
+ throw new Error(`${label} contains unsafe repo-relative path: ${value}`);
486
+ }
487
+ }
488
+ return values;
489
+ }
490
+ function optionalNumber(value, label) {
491
+ if (value === undefined) {
492
+ return undefined;
493
+ }
494
+ if (typeof value !== "number" || !Number.isFinite(value)) {
495
+ throw new Error(`${label} must be a finite number`);
496
+ }
497
+ return value;
498
+ }
499
+ function optionalCommandList(value, label) {
500
+ if (value === undefined) {
501
+ return undefined;
502
+ }
503
+ if (!Array.isArray(value)) {
504
+ throw new Error(`${label} must be an array of command arrays`);
505
+ }
506
+ return value.map((entry, index) => {
507
+ const command = requiredStringList(entry, `${label}[${index}]`);
508
+ assertAllowedHistoricalBaseline(command, `${label}[${index}]`);
509
+ return command;
510
+ });
511
+ }
512
+ function optionalSetupPatchList(value, label) {
513
+ if (value === undefined) {
514
+ return undefined;
515
+ }
516
+ if (!Array.isArray(value)) {
517
+ throw new Error(`${label} must be an array of setup patch objects`);
518
+ }
519
+ return value.map((entry, index) => parseSetupPatch(entry, `${label}[${index}]`));
520
+ }
521
+ function parseSetupPatch(value, label) {
522
+ if (!value || typeof value !== "object") {
523
+ throw new Error(`${label} must be an object`);
524
+ }
525
+ const record = value;
526
+ const patchPath = requiredString(record.path, `${label}.path`);
527
+ validateRepoPathList([patchPath], `${label}.path`);
528
+ const content = record.content === undefined ? undefined : requiredPatchText(record.content, `${label}.content`);
529
+ const append = record.append === undefined ? undefined : requiredPatchText(record.append, `${label}.append`);
530
+ const replace = optionalReplacementList(record.replace, `${label}.replace`);
531
+ if (content === undefined && append === undefined && (!replace || replace.length === 0)) {
532
+ throw new Error(`${label} must include content, append, or replace`);
533
+ }
534
+ return { path: patchPath, content, append, replace };
535
+ }
536
+ function requiredPatchText(value, label) {
537
+ if (typeof value !== "string") {
538
+ throw new Error(`${label} must be a string`);
539
+ }
540
+ return value;
541
+ }
542
+ function optionalReplacementList(value, label) {
543
+ if (value === undefined) {
544
+ return undefined;
545
+ }
546
+ if (!Array.isArray(value)) {
547
+ throw new Error(`${label} must be an array of replacement objects`);
548
+ }
549
+ return value.map((entry, index) => {
550
+ if (!entry || typeof entry !== "object") {
551
+ throw new Error(`${label}[${index}] must be an object`);
552
+ }
553
+ const record = entry;
554
+ return {
555
+ from: requiredString(record.from, `${label}[${index}].from`),
556
+ to: requiredPatchText(record.to, `${label}[${index}].to`)
557
+ };
558
+ });
559
+ }
560
+ function assertAllowedHistoricalBaseline(command, label) {
561
+ const executable = command[0];
562
+ for (const arg of command) {
563
+ if (isUnsafeBaselineArgument(arg)) {
564
+ throw new Error(`${label} contains unsafe baseline argument: ${arg}`);
565
+ }
566
+ }
567
+ if (executable === "rg" && isAllowedRipgrepBaseline(command.slice(1))) {
568
+ return;
569
+ }
570
+ if (isAllowedGitStatusBaseline(command) || isAllowedGitGrepBaseline(command)) {
571
+ return;
572
+ }
573
+ throw new Error(`${label} uses unsupported baseline executable: ${executable}`);
574
+ }
575
+ function currentGitCommit(repoRoot) {
576
+ try {
577
+ return execFileSync("git", ["rev-parse", "HEAD"], {
578
+ cwd: repoRoot,
579
+ encoding: "utf8",
580
+ timeout: 10_000,
581
+ maxBuffer: 1024 * 1024,
582
+ stdio: ["ignore", "pipe", "pipe"]
583
+ }).trim();
584
+ }
585
+ catch (error) {
586
+ throw new Error(`historical task pack target repo must be a git repository with a HEAD commit: ${error instanceof Error ? error.message : String(error)}`);
587
+ }
588
+ }
589
+ function isAllowedGitStatusBaseline(command) {
590
+ return command[0] === "git" && command[1] === "status" && command.length === 3 && (command[2] === "--short" || command[2] === "--porcelain");
591
+ }
592
+ function isAllowedGitGrepBaseline(command) {
593
+ if (command[0] !== "git" || command[1] !== "grep") {
594
+ return false;
595
+ }
596
+ const allowedFlags = new Set(["-n", "--line-number", "-E", "-F", "-e", "-m", "--"]);
597
+ for (let i = 2; i < command.length; i += 1) {
598
+ const arg = command[i];
599
+ if (command[i - 1] === "-e" || command[i - 1] === "-m") {
600
+ continue;
601
+ }
602
+ if (arg.startsWith("-") && !allowedFlags.has(arg)) {
603
+ return false;
604
+ }
605
+ }
606
+ return true;
607
+ }
608
+ function isAllowedRipgrepBaseline(args) {
609
+ let pattern;
610
+ for (let index = 0; index < args.length; index += 1) {
611
+ const arg = args[index];
612
+ if (arg === "-n" || arg === "--line-number" || arg === "--") {
613
+ continue;
614
+ }
615
+ if (arg === "-e") {
616
+ if (!args[index + 1]) {
617
+ return false;
618
+ }
619
+ pattern = args[index + 1];
620
+ index += 1;
621
+ continue;
622
+ }
623
+ if (arg.startsWith("-")) {
624
+ return false;
625
+ }
626
+ pattern ??= arg;
627
+ }
628
+ return Boolean(pattern);
629
+ }
630
+ function isUnsafeRepoRelativePath(value) {
631
+ if (path.isAbsolute(value) || value === "." || value === "") {
632
+ return true;
633
+ }
634
+ return value.split(/[\\/]+/).some((part) => part === ".." || part === "");
635
+ }
636
+ function isUnsafeBaselineArgument(value) {
637
+ if (value === ".") {
638
+ return false;
639
+ }
640
+ return path.isAbsolute(value) || value === ".codex" || value.includes(".codex/") || value.includes(".codex\\") || value.includes("../") || value.includes("..\\");
641
+ }
642
+ function seeded(seed) {
643
+ let state = 2166136261;
644
+ for (let i = 0; i < seed.length; i += 1) {
645
+ state ^= seed.charCodeAt(i);
646
+ state = Math.imul(state, 16777619);
647
+ }
648
+ return () => {
649
+ state = Math.imul(state ^ (state >>> 15), 2246822507);
650
+ state = Math.imul(state ^ (state >>> 13), 3266489909);
651
+ state ^= state >>> 16;
652
+ return (state >>> 0) / 0xffffffff;
653
+ };
654
+ }
655
+ function alphaToken(rng, length) {
656
+ const letters = "abcdefghijklmnopqrstuvwxyz";
657
+ let value = "";
658
+ for (let i = 0; i < length; i += 1) {
659
+ value += letters[Math.floor(rng() * letters.length) % letters.length];
660
+ }
661
+ return value;
662
+ }
663
+ //# sourceMappingURL=historical.js.map