@evomap/evolver-core 2.0.0-beta.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 (373) hide show
  1. package/dist/algo/antiDistill.d.ts +35 -0
  2. package/dist/algo/antiDistill.js +167 -0
  3. package/dist/algo/bans.d.ts +6 -0
  4. package/dist/algo/bans.js +33 -0
  5. package/dist/algo/candidateAssembly.d.ts +65 -0
  6. package/dist/algo/candidateAssembly.js +196 -0
  7. package/dist/algo/capabilityCandidates.d.ts +38 -0
  8. package/dist/algo/capabilityCandidates.js +131 -0
  9. package/dist/algo/confidence.d.ts +106 -0
  10. package/dist/algo/confidence.js +196 -0
  11. package/dist/algo/cycleEngine.d.ts +143 -0
  12. package/dist/algo/cycleEngine.js +417 -0
  13. package/dist/algo/cycleFailureClassifier.d.ts +52 -0
  14. package/dist/algo/cycleFailureClassifier.js +104 -0
  15. package/dist/algo/epigenetics.d.ts +14 -0
  16. package/dist/algo/epigenetics.js +41 -0
  17. package/dist/algo/evolutionEvent.d.ts +23 -0
  18. package/dist/algo/evolutionEvent.js +24 -0
  19. package/dist/algo/exploration.d.ts +34 -0
  20. package/dist/algo/exploration.js +55 -0
  21. package/dist/algo/geneHealth.d.ts +24 -0
  22. package/dist/algo/geneHealth.js +16 -0
  23. package/dist/algo/geneIntake.d.ts +37 -0
  24. package/dist/algo/geneIntake.js +100 -0
  25. package/dist/algo/genePromotion.d.ts +48 -0
  26. package/dist/algo/genePromotion.js +53 -0
  27. package/dist/algo/geneSelection.d.ts +128 -0
  28. package/dist/algo/geneSelection.js +222 -0
  29. package/dist/algo/index.d.ts +18 -0
  30. package/dist/algo/index.js +18 -0
  31. package/dist/algo/mutation.d.ts +12 -0
  32. package/dist/algo/mutation.js +22 -0
  33. package/dist/algo/orchestrator.d.ts +65 -0
  34. package/dist/algo/orchestrator.js +63 -0
  35. package/dist/algo/solidify.d.ts +43 -0
  36. package/dist/algo/solidify.js +63 -0
  37. package/dist/algo/strategyPresets.d.ts +32 -0
  38. package/dist/algo/strategyPresets.js +61 -0
  39. package/dist/assetstore/index.d.ts +8 -0
  40. package/dist/assetstore/index.js +8 -0
  41. package/dist/assetstore/learningHistory.d.ts +25 -0
  42. package/dist/assetstore/learningHistory.js +38 -0
  43. package/dist/assetstore/localJsonl.d.ts +34 -0
  44. package/dist/assetstore/localJsonl.js +182 -0
  45. package/dist/assetstore/pendingSignals.d.ts +22 -0
  46. package/dist/assetstore/pendingSignals.js +171 -0
  47. package/dist/assetstore/provenance.d.ts +39 -0
  48. package/dist/assetstore/provenance.js +109 -0
  49. package/dist/assetstore/provider.d.ts +48 -0
  50. package/dist/assetstore/provider.js +36 -0
  51. package/dist/assetstore/remoteStub.d.ts +32 -0
  52. package/dist/assetstore/remoteStub.js +36 -0
  53. package/dist/assetstore/reviewFilter.d.ts +20 -0
  54. package/dist/assetstore/reviewFilter.js +44 -0
  55. package/dist/assetstore/reviewLedger.d.ts +55 -0
  56. package/dist/assetstore/reviewLedger.js +121 -0
  57. package/dist/benchmark/antiGeneBenchmark.d.ts +79 -0
  58. package/dist/benchmark/antiGeneBenchmark.js +241 -0
  59. package/dist/benchmark/antiGeneRollout.d.ts +80 -0
  60. package/dist/benchmark/antiGeneRollout.js +256 -0
  61. package/dist/benchmark/benchmark.d.ts +32 -0
  62. package/dist/benchmark/benchmark.js +26 -0
  63. package/dist/benchmark/evolutionThesisSolver.d.ts +25 -0
  64. package/dist/benchmark/evolutionThesisSolver.js +41 -0
  65. package/dist/benchmark/index.d.ts +5 -0
  66. package/dist/benchmark/index.js +5 -0
  67. package/dist/benchmark/thesis.d.ts +85 -0
  68. package/dist/benchmark/thesis.js +150 -0
  69. package/dist/bootstrap/envFingerprint.d.ts +45 -0
  70. package/dist/bootstrap/envFingerprint.js +72 -0
  71. package/dist/bootstrap/index.d.ts +2 -0
  72. package/dist/bootstrap/index.js +2 -0
  73. package/dist/bootstrap/selfEvolve.d.ts +36 -0
  74. package/dist/bootstrap/selfEvolve.js +26 -0
  75. package/dist/cycle/cycleTimeline.d.ts +35 -0
  76. package/dist/cycle/cycleTimeline.js +74 -0
  77. package/dist/cycle/index.d.ts +2 -0
  78. package/dist/cycle/index.js +2 -0
  79. package/dist/cycle/stateMachine.d.ts +6 -0
  80. package/dist/cycle/stateMachine.js +25 -0
  81. package/dist/daemon/cycleContext.d.ts +12 -0
  82. package/dist/daemon/cycleContext.js +13 -0
  83. package/dist/daemon/drain.d.ts +11 -0
  84. package/dist/daemon/drain.js +16 -0
  85. package/dist/daemon/guards.d.ts +87 -0
  86. package/dist/daemon/guards.js +142 -0
  87. package/dist/daemon/idempotency.d.ts +11 -0
  88. package/dist/daemon/idempotency.js +36 -0
  89. package/dist/daemon/idleScheduler.d.ts +55 -0
  90. package/dist/daemon/idleScheduler.js +181 -0
  91. package/dist/daemon/index.d.ts +8 -0
  92. package/dist/daemon/index.js +8 -0
  93. package/dist/daemon/stepRunner.d.ts +12 -0
  94. package/dist/daemon/stepRunner.js +31 -0
  95. package/dist/daemon/unhandledRejectionWindow.d.ts +21 -0
  96. package/dist/daemon/unhandledRejectionWindow.js +52 -0
  97. package/dist/daemon/watchdog.d.ts +11 -0
  98. package/dist/daemon/watchdog.js +14 -0
  99. package/dist/events/eventSchema.d.ts +132 -0
  100. package/dist/events/eventSchema.js +30 -0
  101. package/dist/events/eventStore.d.ts +32 -0
  102. package/dist/events/eventStore.js +137 -0
  103. package/dist/events/ingest.d.ts +34 -0
  104. package/dist/events/ingest.js +61 -0
  105. package/dist/events/paths.d.ts +22 -0
  106. package/dist/events/paths.js +44 -0
  107. package/dist/events/projectors.d.ts +9 -0
  108. package/dist/events/projectors.js +11 -0
  109. package/dist/events/public.d.ts +14 -0
  110. package/dist/events/public.js +8 -0
  111. package/dist/events/replayer.d.ts +26 -0
  112. package/dist/events/replayer.js +67 -0
  113. package/dist/events/reports.d.ts +90 -0
  114. package/dist/events/reports.js +244 -0
  115. package/dist/events/retention.d.ts +70 -0
  116. package/dist/events/retention.js +177 -0
  117. package/dist/events/sink.d.ts +5 -0
  118. package/dist/events/sink.js +1 -0
  119. package/dist/exec/autoExec.d.ts +137 -0
  120. package/dist/exec/autoExec.js +221 -0
  121. package/dist/exec/autonomousCycle.d.ts +48 -0
  122. package/dist/exec/autonomousCycle.js +78 -0
  123. package/dist/exec/claudeBridge.d.ts +112 -0
  124. package/dist/exec/claudeBridge.js +219 -0
  125. package/dist/exec/index.d.ts +9 -0
  126. package/dist/exec/index.js +11 -0
  127. package/dist/exec/openPrRegistry.d.ts +60 -0
  128. package/dist/exec/openPrRegistry.js +130 -0
  129. package/dist/exec/policy/blastRadius.d.ts +31 -0
  130. package/dist/exec/policy/blastRadius.js +67 -0
  131. package/dist/exec/policy/constraints.d.ts +26 -0
  132. package/dist/exec/policy/constraints.js +62 -0
  133. package/dist/exec/policy/destructive.d.ts +45 -0
  134. package/dist/exec/policy/destructive.js +108 -0
  135. package/dist/exec/policy/failureMode.d.ts +33 -0
  136. package/dist/exec/policy/failureMode.js +49 -0
  137. package/dist/exec/policy/index.d.ts +24 -0
  138. package/dist/exec/policy/index.js +30 -0
  139. package/dist/exec/policy/protectedPaths.d.ts +17 -0
  140. package/dist/exec/policy/protectedPaths.js +65 -0
  141. package/dist/exec/policyCheck.d.ts +2 -0
  142. package/dist/exec/policyCheck.js +7 -0
  143. package/dist/exec/prompt.d.ts +34 -0
  144. package/dist/exec/prompt.js +108 -0
  145. package/dist/exec/proofOfWork.d.ts +9 -0
  146. package/dist/exec/proofOfWork.js +11 -0
  147. package/dist/exec/runnerRegistry.d.ts +123 -0
  148. package/dist/exec/runnerRegistry.js +237 -0
  149. package/dist/exec/selfPr.d.ts +83 -0
  150. package/dist/exec/selfPr.js +104 -0
  151. package/dist/exec/selfPrObfuscation.d.ts +21 -0
  152. package/dist/exec/selfPrObfuscation.js +85 -0
  153. package/dist/hooks/hooks.d.ts +117 -0
  154. package/dist/hooks/hooks.js +89 -0
  155. package/dist/hooks/index.d.ts +1 -0
  156. package/dist/hooks/index.js +1 -0
  157. package/dist/hub/assetCallLog.d.ts +43 -0
  158. package/dist/hub/assetCallLog.js +72 -0
  159. package/dist/hub/bindings.d.ts +28 -0
  160. package/dist/hub/bindings.js +90 -0
  161. package/dist/hub/capability.d.ts +278 -0
  162. package/dist/hub/capability.js +1 -0
  163. package/dist/hub/conversationDistiller.d.ts +88 -0
  164. package/dist/hub/conversationDistiller.js +264 -0
  165. package/dist/hub/fake.d.ts +45 -0
  166. package/dist/hub/fake.js +69 -0
  167. package/dist/hub/hubReview.d.ts +91 -0
  168. package/dist/hub/hubReview.js +106 -0
  169. package/dist/hub/index.d.ts +10 -0
  170. package/dist/hub/index.js +10 -0
  171. package/dist/hub/ingest.d.ts +15 -0
  172. package/dist/hub/ingest.js +15 -0
  173. package/dist/hub/questionGenerator.d.ts +59 -0
  174. package/dist/hub/questionGenerator.js +406 -0
  175. package/dist/hub/reuseDecision.d.ts +87 -0
  176. package/dist/hub/reuseDecision.js +127 -0
  177. package/dist/hub/sanitize.d.ts +52 -0
  178. package/dist/hub/sanitize.js +321 -0
  179. package/dist/index.d.ts +27 -0
  180. package/dist/index.js +27 -0
  181. package/dist/mailbox/catalog.d.ts +17 -0
  182. package/dist/mailbox/catalog.js +53 -0
  183. package/dist/mailbox/daemon.d.ts +48 -0
  184. package/dist/mailbox/daemon.js +61 -0
  185. package/dist/mailbox/dispatch.d.ts +46 -0
  186. package/dist/mailbox/dispatch.js +62 -0
  187. package/dist/mailbox/envelope.d.ts +43 -0
  188. package/dist/mailbox/envelope.js +52 -0
  189. package/dist/mailbox/index.d.ts +8 -0
  190. package/dist/mailbox/index.js +8 -0
  191. package/dist/mailbox/ipcServer.d.ts +46 -0
  192. package/dist/mailbox/ipcServer.js +168 -0
  193. package/dist/mailbox/requestReply.d.ts +40 -0
  194. package/dist/mailbox/requestReply.js +77 -0
  195. package/dist/mailbox/store.d.ts +74 -0
  196. package/dist/mailbox/store.js +308 -0
  197. package/dist/mailbox/wake.d.ts +45 -0
  198. package/dist/mailbox/wake.js +60 -0
  199. package/dist/material/boundary.d.ts +7 -0
  200. package/dist/material/boundary.js +14 -0
  201. package/dist/material/consumer.d.ts +21 -0
  202. package/dist/material/consumer.js +52 -0
  203. package/dist/material/emit.d.ts +55 -0
  204. package/dist/material/emit.js +52 -0
  205. package/dist/material/factory.d.ts +27 -0
  206. package/dist/material/factory.js +25 -0
  207. package/dist/material/index.d.ts +8 -0
  208. package/dist/material/index.js +8 -0
  209. package/dist/material/materialStore.d.ts +24 -0
  210. package/dist/material/materialStore.js +76 -0
  211. package/dist/material/sampling.d.ts +21 -0
  212. package/dist/material/sampling.js +39 -0
  213. package/dist/material/sources.d.ts +25 -0
  214. package/dist/material/sources.js +33 -0
  215. package/dist/material/watermark.d.ts +21 -0
  216. package/dist/material/watermark.js +76 -0
  217. package/dist/observers/cursorRewriteObserver.d.ts +46 -0
  218. package/dist/observers/cursorRewriteObserver.js +103 -0
  219. package/dist/observers/distillObserver.d.ts +31 -0
  220. package/dist/observers/distillObserver.js +93 -0
  221. package/dist/observers/index.d.ts +5 -0
  222. package/dist/observers/index.js +5 -0
  223. package/dist/observers/observerBus.d.ts +44 -0
  224. package/dist/observers/observerBus.js +73 -0
  225. package/dist/observers/reflectionObserver.d.ts +31 -0
  226. package/dist/observers/reflectionObserver.js +124 -0
  227. package/dist/observers/valueDigestObserver.d.ts +50 -0
  228. package/dist/observers/valueDigestObserver.js +70 -0
  229. package/dist/ops/cleanup.d.ts +23 -0
  230. package/dist/ops/cleanup.js +68 -0
  231. package/dist/ops/health.d.ts +37 -0
  232. package/dist/ops/health.js +96 -0
  233. package/dist/ops/index.d.ts +7 -0
  234. package/dist/ops/index.js +7 -0
  235. package/dist/ops/recall.d.ts +65 -0
  236. package/dist/ops/recall.js +152 -0
  237. package/dist/ops/reuseOutcomes.d.ts +68 -0
  238. package/dist/ops/reuseOutcomes.js +126 -0
  239. package/dist/ops/savingsCore.d.ts +47 -0
  240. package/dist/ops/savingsCore.js +112 -0
  241. package/dist/ops/selfUpdate.d.ts +106 -0
  242. package/dist/ops/selfUpdate.js +294 -0
  243. package/dist/ops/valueLedger.d.ts +205 -0
  244. package/dist/ops/valueLedger.js +238 -0
  245. package/dist/ops/valueOutreach.d.ts +70 -0
  246. package/dist/ops/valueOutreach.js +244 -0
  247. package/dist/personality/drift.d.ts +45 -0
  248. package/dist/personality/drift.js +106 -0
  249. package/dist/personality/events.d.ts +25 -0
  250. package/dist/personality/events.js +37 -0
  251. package/dist/personality/evolveOps.d.ts +20 -0
  252. package/dist/personality/evolveOps.js +93 -0
  253. package/dist/personality/index.d.ts +11 -0
  254. package/dist/personality/index.js +11 -0
  255. package/dist/personality/mutate.d.ts +21 -0
  256. package/dist/personality/mutate.js +39 -0
  257. package/dist/personality/pivot.d.ts +23 -0
  258. package/dist/personality/pivot.js +22 -0
  259. package/dist/personality/prompt.d.ts +6 -0
  260. package/dist/personality/prompt.js +62 -0
  261. package/dist/personality/riskGate.d.ts +42 -0
  262. package/dist/personality/riskGate.js +80 -0
  263. package/dist/personality/schema.d.ts +255 -0
  264. package/dist/personality/schema.js +119 -0
  265. package/dist/personality/select.d.ts +46 -0
  266. package/dist/personality/select.js +71 -0
  267. package/dist/personality/stats.d.ts +46 -0
  268. package/dist/personality/stats.js +82 -0
  269. package/dist/personality/store.d.ts +35 -0
  270. package/dist/personality/store.js +92 -0
  271. package/dist/schema/common.d.ts +28 -0
  272. package/dist/schema/common.js +17 -0
  273. package/dist/schema/index.d.ts +5 -0
  274. package/dist/schema/index.js +5 -0
  275. package/dist/schema/material.d.ts +201 -0
  276. package/dist/schema/material.js +50 -0
  277. package/dist/schema/problem.d.ts +160 -0
  278. package/dist/schema/problem.js +45 -0
  279. package/dist/schema/proofOfWork.d.ts +97 -0
  280. package/dist/schema/proofOfWork.js +9 -0
  281. package/dist/schema/signal.d.ts +41 -0
  282. package/dist/schema/signal.js +19 -0
  283. package/dist/shadow/index.d.ts +6 -0
  284. package/dist/shadow/index.js +6 -0
  285. package/dist/shadow/jsonlSink.d.ts +17 -0
  286. package/dist/shadow/jsonlSink.js +27 -0
  287. package/dist/shadow/redact.d.ts +11 -0
  288. package/dist/shadow/redact.js +18 -0
  289. package/dist/shadow/shadowHub.d.ts +12 -0
  290. package/dist/shadow/shadowHub.js +42 -0
  291. package/dist/shadow/shadowMailbox.d.ts +17 -0
  292. package/dist/shadow/shadowMailbox.js +25 -0
  293. package/dist/shadow/shadowStore.d.ts +18 -0
  294. package/dist/shadow/shadowStore.js +55 -0
  295. package/dist/shadow/sink.d.ts +37 -0
  296. package/dist/shadow/sink.js +18 -0
  297. package/dist/signals/cycleHistoryFromEvents.d.ts +11 -0
  298. package/dist/signals/cycleHistoryFromEvents.js +96 -0
  299. package/dist/signals/expand.d.ts +34 -0
  300. package/dist/signals/expand.js +125 -0
  301. package/dist/signals/extractor.d.ts +25 -0
  302. package/dist/signals/extractor.js +57 -0
  303. package/dist/signals/index.d.ts +6 -0
  304. package/dist/signals/index.js +6 -0
  305. package/dist/signals/metaSignals.d.ts +57 -0
  306. package/dist/signals/metaSignals.js +158 -0
  307. package/dist/signals/signalGate.d.ts +15 -0
  308. package/dist/signals/signalGate.js +40 -0
  309. package/dist/signals/traceSignals.d.ts +44 -0
  310. package/dist/signals/traceSignals.js +123 -0
  311. package/dist/signatures/classifier.d.ts +16 -0
  312. package/dist/signatures/classifier.js +29 -0
  313. package/dist/signatures/index.d.ts +2 -0
  314. package/dist/signatures/index.js +2 -0
  315. package/dist/signatures/signatures.d.ts +22 -0
  316. package/dist/signatures/signatures.js +42 -0
  317. package/dist/strategy/experiment.d.ts +48 -0
  318. package/dist/strategy/experiment.js +63 -0
  319. package/dist/strategy/index.d.ts +2 -0
  320. package/dist/strategy/index.js +2 -0
  321. package/dist/strategy/strategyPoint.d.ts +30 -0
  322. package/dist/strategy/strategyPoint.js +23 -0
  323. package/dist/trace/index.d.ts +2 -0
  324. package/dist/trace/index.js +2 -0
  325. package/dist/trace/trajectory.d.ts +82 -0
  326. package/dist/trace/trajectory.js +117 -0
  327. package/dist/trace/trajectoryExport.d.ts +237 -0
  328. package/dist/trace/trajectoryExport.js +1818 -0
  329. package/dist/trigger/budget.d.ts +16 -0
  330. package/dist/trigger/budget.js +20 -0
  331. package/dist/trigger/engine.d.ts +12 -0
  332. package/dist/trigger/engine.js +23 -0
  333. package/dist/trigger/index.d.ts +6 -0
  334. package/dist/trigger/index.js +6 -0
  335. package/dist/trigger/problemBuilder.d.ts +24 -0
  336. package/dist/trigger/problemBuilder.js +32 -0
  337. package/dist/trigger/trigger.d.ts +22 -0
  338. package/dist/trigger/trigger.js +27 -0
  339. package/dist/trigger/valueModel.d.ts +26 -0
  340. package/dist/trigger/valueModel.js +25 -0
  341. package/dist/trigger/wfq.d.ts +13 -0
  342. package/dist/trigger/wfq.js +25 -0
  343. package/dist/util/fileLock.d.ts +24 -0
  344. package/dist/util/fileLock.js +227 -0
  345. package/dist/util/index.d.ts +2 -0
  346. package/dist/util/index.js +2 -0
  347. package/dist/util/version.d.ts +12 -0
  348. package/dist/util/version.js +51 -0
  349. package/dist/verify/index.d.ts +4 -0
  350. package/dist/verify/index.js +4 -0
  351. package/dist/verify/rollback.d.ts +22 -0
  352. package/dist/verify/rollback.js +33 -0
  353. package/dist/verify/sandboxRunner.d.ts +39 -0
  354. package/dist/verify/sandboxRunner.js +116 -0
  355. package/dist/verify/sandboxedValidation.d.ts +35 -0
  356. package/dist/verify/sandboxedValidation.js +56 -0
  357. package/dist/verify/validation.d.ts +41 -0
  358. package/dist/verify/validation.js +85 -0
  359. package/dist/wire/canonicalize.golden.d.ts +23 -0
  360. package/dist/wire/canonicalize.golden.js +46 -0
  361. package/dist/wire/geneHints.d.ts +64 -0
  362. package/dist/wire/geneHints.js +100 -0
  363. package/dist/wire/index.d.ts +91 -0
  364. package/dist/wire/index.js +3 -0
  365. package/dist/wire/schemaGate.d.ts +11 -0
  366. package/dist/wire/schemaGate.js +49 -0
  367. package/dist/workflow/dsl.d.ts +54 -0
  368. package/dist/workflow/dsl.js +15 -0
  369. package/dist/workflow/engine.d.ts +30 -0
  370. package/dist/workflow/engine.js +94 -0
  371. package/dist/workflow/index.d.ts +2 -0
  372. package/dist/workflow/index.js +2 -0
  373. package/package.json +29 -0
@@ -0,0 +1,123 @@
1
+ export declare const DEFAULT_TIMEOUT_MS = 600000;
2
+ export interface AgentRunContext {
3
+ cwd: string;
4
+ timeoutMs?: number;
5
+ /** Environment for the spawned agent. The bridge passes a scrubbed env here (see scrubAgentEnv); undefined → inherit. */
6
+ env?: NodeJS.ProcessEnv;
7
+ }
8
+ export interface AgentRunResult {
9
+ ok: boolean;
10
+ output: string;
11
+ error?: string;
12
+ }
13
+ /** Run a coding agent against a working directory with the given instruction. */
14
+ export type AgentRunner = (prompt: string, ctx: AgentRunContext) => Promise<AgentRunResult>;
15
+ /** Thrown when permission bypass is requested without bounding the agent's tools (would be an unbounded autonomous agent). */
16
+ export declare class UnboundedSkipPermissionsError extends Error {
17
+ constructor();
18
+ }
19
+ /** Thrown when Cursor skipPermissions is requested before the runner can enforce per-run permissions. */
20
+ export declare class UnsupportedCursorSkipPermissionsError extends Error {
21
+ constructor();
22
+ }
23
+ /** Thrown when Cursor's built-in runner is requested on Windows before its launcher path is run-verified. */
24
+ export declare class UnsupportedCursorWindowsRunnerError extends Error {
25
+ constructor();
26
+ }
27
+ export declare function assertCursorRunnerPlatformSupported(platform?: NodeJS.Platform): void;
28
+ /**
29
+ * Make a bare command name spawnable shell-free on Windows. `spawn(shell:false)` cannot execute an npm CLI
30
+ * shim (a `.cmd`/`.bat`), and routing through a shell would expose the prompt arg to cmd.exe quoting
31
+ * (injection). npm shims are node wrappers, so we resolve the bare name on PATH and, when it's a node shim,
32
+ * run `node <entry.js>` directly (shell-free, args passed safely). A native `.exe` (claude) resolves to itself.
33
+ * No-op on POSIX and for any command that is already a path or has an extension. Surfaced by codex on Windows
34
+ * (codex is `codex.cmd`, while claude is `claude.exe`), #66.
35
+ */
36
+ export declare function resolveSpawnCommand(cmd: string, args: readonly string[], env?: NodeJS.ProcessEnv): {
37
+ cmd: string;
38
+ args: string[];
39
+ };
40
+ /**
41
+ * Promise wrapper over spawn (shell:false). Optionally writes `input` to stdin; resolves with stdout/exit.
42
+ * On timeout the WHOLE process group is killed, not just the direct child (finding #39.5): an agent spawns
43
+ * tool subprocesses (grandchildren) that would otherwise orphan and leak. On POSIX we spawn detached (the
44
+ * child becomes its own group leader) and SIGKILL the group via the negative pid; Windows falls back to a
45
+ * direct kill (different process-group semantics).
46
+ */
47
+ export declare function spawnCapture(cmd: string, args: readonly string[], opts: {
48
+ cwd: string;
49
+ timeoutMs: number;
50
+ input?: string;
51
+ env?: NodeJS.ProcessEnv;
52
+ }): Promise<{
53
+ code: number | null;
54
+ stdout: string;
55
+ stderr: string;
56
+ }>;
57
+ /** Options for a built-in headless runner (claude / codex share the shape and the skip⇒bounded invariant). */
58
+ export interface AgentRunnerOptions {
59
+ /** Bypass permission prompts so the agent can edit autonomously (required for unattended use). Default off.
60
+ * MUST be paired with a non-empty allowedTools (enforced) — bypassing prompts without bounding tools
61
+ * would be an unbounded autonomous agent. */
62
+ skipPermissions?: boolean;
63
+ /** Constrain the agent to these tools (e.g. ['Read', 'Edit', 'Write']) — the safety counterpart to
64
+ * skipPermissions: bypass prompts but bound what the agent can do. */
65
+ allowedTools?: readonly string[];
66
+ /** Pin a model (e.g. 'claude-sonnet-4-6'). */
67
+ model?: string;
68
+ }
69
+ /** @deprecated use AgentRunnerOptions — kept for back-compat (#91 item 6 rename). */
70
+ export type ClaudeRunnerOptions = AgentRunnerOptions;
71
+ /** @deprecated use AgentRunnerOptions — codex shares the exact option shape. */
72
+ export type CodexRunnerOptions = AgentRunnerOptions;
73
+ /**
74
+ * Build the `claude -p` argv for the given options (pure — testable without spawning).
75
+ * Safety invariant: skipPermissions (bypassing prompts) is only allowed together with a non-empty
76
+ * allowedTools — otherwise it would be an unattended agent with full tools and no gate; refuse loudly.
77
+ */
78
+ export declare function claudeRunnerArgs(opts?: AgentRunnerOptions): string[];
79
+ /**
80
+ * Build a headless `claude -p` agent runner. Prompt fed via stdin (no shell, no argv length limit). For
81
+ * unattended evolution set { skipPermissions: true, allowedTools: ['Read','Edit','Write'] } — bypass the
82
+ * permission prompts but bound the agent to file edits. Validated end to end against a real agent.
83
+ */
84
+ export declare function makeClaudeHeadlessRunner(opts?: AgentRunnerOptions): AgentRunner;
85
+ /** Default agent runner: conservative `claude -p --output-format text` (no permission bypass; opt in via makeClaudeHeadlessRunner). */
86
+ export declare const claudeHeadlessRunner: AgentRunner;
87
+ /**
88
+ * Build the `codex exec` argv (pure). Verified live against codex-cli 0.137.0:
89
+ * - sandboxed default → `exec --sandbox workspace-write`: edits the workspace non-interactively (read-only,
90
+ * the codex default, cannot write). The wrapper's worktree + allowedRoots are the outer containment.
91
+ * - skipPermissions (bounded) → `exec --dangerously-bypass-approvals-and-sandbox`: full bypass, intended for
92
+ * an already-externally-sandboxed run (our throwaway worktree). The `skip⇒bounded` invariant is the explicit
93
+ * acknowledgement guard, same shape as claude.
94
+ */
95
+ export declare function codexRunnerArgs(opts?: AgentRunnerOptions): string[];
96
+ /** Headless `codex exec` runner. Working root pinned with `--cd`; prompt is the trailing positional arg (shell:false). */
97
+ export declare function makeCodexHeadlessRunner(opts?: AgentRunnerOptions): AgentRunner;
98
+ /**
99
+ * Build the `cursor-agent` argv (pure). Ground-truth from `cursor-agent --help` (#66): base `-p --output-format
100
+ * text` (headless, write+shell access). `--model` is a real flag. skipPermissions is rejected until Cursor has a
101
+ * verified per-run allowlist/sandbox mapping; allowedTools is not emitted because cursor has no per-tool allowlist.
102
+ */
103
+ export declare function cursorRunnerArgs(opts?: AgentRunnerOptions): string[];
104
+ /**
105
+ * Headless `cursor-agent` runner. Prompt passed as the trailing positional arg (shell:false, no injection risk;
106
+ * docs show `cursor-agent -p "<prompt>"`). cwd is set via spawn. SCAFFOLD — run-verify against a real
107
+ * cursor-agent before autonomous use (see the block comment above for what is doc-confirmed vs unverified).
108
+ */
109
+ export declare function makeCursorHeadlessRunner(opts?: AgentRunnerOptions, platform?: NodeJS.Platform): AgentRunner;
110
+ /** A built-in coding-agent harness (#66). cursor is a SCAFFOLD — its runner is unverified (see cursorRunnerArgs). */
111
+ export type RunnerName = 'claude' | 'codex' | 'cursor';
112
+ /** A harness runner: how to launch it + which env auth prefixes it (and ONLY it) may keep (#66). */
113
+ export interface AgentRunnerSpec {
114
+ name: RunnerName;
115
+ makeRunner: (opts?: AgentRunnerOptions) => AgentRunner;
116
+ /** Per-runner auth env — only this runner's keys reach it (claude never gets OPENAI_, codex never gets ANTHROPIC_). */
117
+ envAllow: {
118
+ prefixes: readonly string[];
119
+ keys?: readonly string[];
120
+ };
121
+ }
122
+ /** Resolve a runner spec by name (default 'claude' — byte-identical to the pre-registry behavior). */
123
+ export declare function getRunnerSpec(name?: RunnerName): AgentRunnerSpec;
@@ -0,0 +1,237 @@
1
+ // The runner layer of the exec bridge (#66, split out per #91 item 6): how to spawn a coding-agent CLI
2
+ // shell-free, the per-runner argv builders + headless factories, and the runner registry that maps a runner
3
+ // name → { factory, env-auth allowlist }. claudeBridge.ts (the bridge orchestration) imports from here; this
4
+ // module never imports back from claudeBridge, so the dependency is one-directional (no cycle). Pure/seam-able:
5
+ // nothing here spawns a real agent in tests except through spawnCapture, which the bridge injects fakes around.
6
+ import { spawn } from 'node:child_process';
7
+ import { join as joinPath, delimiter as pathDelimiter } from 'node:path';
8
+ import { readFileSync, existsSync } from 'node:fs';
9
+ export const DEFAULT_TIMEOUT_MS = 600_000;
10
+ /** Thrown when permission bypass is requested without bounding the agent's tools (would be an unbounded autonomous agent). */
11
+ export class UnboundedSkipPermissionsError extends Error {
12
+ constructor() {
13
+ super("skipPermissions requires a non-empty allowedTools — refusing to bypass permission prompts without bounding the agent (e.g. allowedTools: ['Read','Edit','Write'])");
14
+ this.name = 'UnboundedSkipPermissionsError';
15
+ }
16
+ }
17
+ /** Thrown when Cursor skipPermissions is requested before the runner can enforce per-run permissions. */
18
+ export class UnsupportedCursorSkipPermissionsError extends Error {
19
+ constructor() {
20
+ super('cursor runner does not support skipPermissions yet: cursor-agent has no verified per-run allowlist or sandbox mapping, so --force --trust is refused');
21
+ this.name = 'UnsupportedCursorSkipPermissionsError';
22
+ }
23
+ }
24
+ /** Thrown when Cursor's built-in runner is requested on Windows before its launcher path is run-verified. */
25
+ export class UnsupportedCursorWindowsRunnerError extends Error {
26
+ constructor() {
27
+ super('cursor runner on Windows is not yet supported/run-verified due to cursor-agent shim spawn issue; use claude/codex or an injected cursor runner until the Windows launcher is run-verified');
28
+ this.name = 'UnsupportedCursorWindowsRunnerError';
29
+ }
30
+ }
31
+ export function assertCursorRunnerPlatformSupported(platform = process.platform) {
32
+ if (platform === 'win32')
33
+ throw new UnsupportedCursorWindowsRunnerError();
34
+ }
35
+ /**
36
+ * Make a bare command name spawnable shell-free on Windows. `spawn(shell:false)` cannot execute an npm CLI
37
+ * shim (a `.cmd`/`.bat`), and routing through a shell would expose the prompt arg to cmd.exe quoting
38
+ * (injection). npm shims are node wrappers, so we resolve the bare name on PATH and, when it's a node shim,
39
+ * run `node <entry.js>` directly (shell-free, args passed safely). A native `.exe` (claude) resolves to itself.
40
+ * No-op on POSIX and for any command that is already a path or has an extension. Surfaced by codex on Windows
41
+ * (codex is `codex.cmd`, while claude is `claude.exe`), #66.
42
+ */
43
+ export function resolveSpawnCommand(cmd, args, env = process.env) {
44
+ if (process.platform !== 'win32' || /[\\/]/.test(cmd) || /\.[a-z0-9]+$/i.test(cmd))
45
+ return { cmd, args: [...args] };
46
+ for (const dir of (env['PATH'] ?? '').split(pathDelimiter).filter(Boolean)) {
47
+ for (const ext of ['.exe', '.cmd', '.bat']) {
48
+ const full = joinPath(dir, cmd + ext);
49
+ if (!existsSync(full))
50
+ continue;
51
+ if (ext === '.exe')
52
+ return { cmd: full, args: [...args] };
53
+ try {
54
+ const shim = readFileSync(full, 'utf8');
55
+ const rel = shim.match(/%~?dp0%?\\([^"\s]+\.js)/i)?.[1]; // npm node-shim: node "%dp0%\...\entry.js" %*
56
+ if (rel) {
57
+ const js = joinPath(dir, rel);
58
+ if (existsSync(js))
59
+ return { cmd: process.execPath, args: [js, ...args] };
60
+ }
61
+ }
62
+ catch { /* unreadable shim — keep searching */ }
63
+ // A .cmd/.bat whose node entry we can't extract is NOT runnable shell-free — DON'T return it (that would
64
+ // ENOENT and also stop the search). Keep scanning later PATH dirs/exts for a real .exe or resolvable shim.
65
+ }
66
+ }
67
+ return { cmd, args: [...args] };
68
+ }
69
+ /**
70
+ * Promise wrapper over spawn (shell:false). Optionally writes `input` to stdin; resolves with stdout/exit.
71
+ * On timeout the WHOLE process group is killed, not just the direct child (finding #39.5): an agent spawns
72
+ * tool subprocesses (grandchildren) that would otherwise orphan and leak. On POSIX we spawn detached (the
73
+ * child becomes its own group leader) and SIGKILL the group via the negative pid; Windows falls back to a
74
+ * direct kill (different process-group semantics).
75
+ */
76
+ export function spawnCapture(cmd, args, opts) {
77
+ return new Promise((resolve, reject) => {
78
+ const detached = process.platform !== 'win32';
79
+ const r = resolveSpawnCommand(cmd, args, opts.env);
80
+ const child = spawn(r.cmd, r.args, { cwd: opts.cwd, shell: false, detached, ...(opts.env ? { env: opts.env } : {}) });
81
+ let stdout = '';
82
+ let stderr = '';
83
+ const killTree = () => {
84
+ if (detached && typeof child.pid === 'number') {
85
+ try {
86
+ process.kill(-child.pid, 'SIGKILL');
87
+ return;
88
+ }
89
+ catch { /* group gone; fall back */ }
90
+ }
91
+ child.kill('SIGKILL');
92
+ };
93
+ const timer = setTimeout(killTree, opts.timeoutMs);
94
+ child.stdout?.on('data', (d) => { stdout += d.toString(); });
95
+ child.stderr?.on('data', (d) => { stderr += d.toString(); });
96
+ child.on('error', (e) => { clearTimeout(timer); reject(e); });
97
+ child.on('close', (code) => { clearTimeout(timer); resolve({ code, stdout, stderr }); });
98
+ if (opts.input !== undefined) {
99
+ child.stdin?.write(opts.input);
100
+ child.stdin?.end();
101
+ }
102
+ });
103
+ }
104
+ /**
105
+ * Build the `claude -p` argv for the given options (pure — testable without spawning).
106
+ * Safety invariant: skipPermissions (bypassing prompts) is only allowed together with a non-empty
107
+ * allowedTools — otherwise it would be an unattended agent with full tools and no gate; refuse loudly.
108
+ */
109
+ export function claudeRunnerArgs(opts = {}) {
110
+ const bounded = !!(opts.allowedTools && opts.allowedTools.length > 0);
111
+ if (opts.skipPermissions && !bounded)
112
+ throw new UnboundedSkipPermissionsError();
113
+ const args = ['-p', '--output-format', 'text'];
114
+ if (opts.skipPermissions)
115
+ args.push('--dangerously-skip-permissions');
116
+ if (opts.allowedTools && opts.allowedTools.length > 0)
117
+ args.push('--allowedTools', ...opts.allowedTools);
118
+ if (opts.model)
119
+ args.push('--model', opts.model);
120
+ return args;
121
+ }
122
+ /**
123
+ * Build a headless `claude -p` agent runner. Prompt fed via stdin (no shell, no argv length limit). For
124
+ * unattended evolution set { skipPermissions: true, allowedTools: ['Read','Edit','Write'] } — bypass the
125
+ * permission prompts but bound the agent to file edits. Validated end to end against a real agent.
126
+ */
127
+ export function makeClaudeHeadlessRunner(opts = {}) {
128
+ const args = claudeRunnerArgs(opts);
129
+ return async (prompt, ctx) => {
130
+ try {
131
+ const r = await spawnCapture('claude', args, { cwd: ctx.cwd, timeoutMs: ctx.timeoutMs ?? DEFAULT_TIMEOUT_MS, input: prompt, ...(ctx.env ? { env: ctx.env } : {}) });
132
+ return r.code === 0 ? { ok: true, output: r.stdout } : { ok: false, output: r.stdout, error: r.stderr || `exit ${r.code}` };
133
+ }
134
+ catch (e) {
135
+ return { ok: false, output: '', error: e instanceof Error ? e.message : String(e) };
136
+ }
137
+ };
138
+ }
139
+ /** Default agent runner: conservative `claude -p --output-format text` (no permission bypass; opt in via makeClaudeHeadlessRunner). */
140
+ export const claudeHeadlessRunner = makeClaudeHeadlessRunner();
141
+ // --- Codex runner (#66 multi-harness) ---
142
+ /**
143
+ * Build the `codex exec` argv (pure). Verified live against codex-cli 0.137.0:
144
+ * - sandboxed default → `exec --sandbox workspace-write`: edits the workspace non-interactively (read-only,
145
+ * the codex default, cannot write). The wrapper's worktree + allowedRoots are the outer containment.
146
+ * - skipPermissions (bounded) → `exec --dangerously-bypass-approvals-and-sandbox`: full bypass, intended for
147
+ * an already-externally-sandboxed run (our throwaway worktree). The `skip⇒bounded` invariant is the explicit
148
+ * acknowledgement guard, same shape as claude.
149
+ */
150
+ export function codexRunnerArgs(opts = {}) {
151
+ const bounded = !!(opts.allowedTools && opts.allowedTools.length > 0);
152
+ if (opts.skipPermissions && !bounded)
153
+ throw new UnboundedSkipPermissionsError(); // same invariant as claude skip⇒allowedTools
154
+ const args = ['exec'];
155
+ // codex bounds via sandbox mode (not a per-tool allowlist): workspace-write is the safe autonomous default;
156
+ // the explicit bypass removes the inner sandbox for a run the wrapper already isolates.
157
+ args.push('--sandbox', opts.skipPermissions ? 'danger-full-access' : 'workspace-write');
158
+ if (opts.skipPermissions)
159
+ args.push('--dangerously-bypass-approvals-and-sandbox');
160
+ if (opts.model)
161
+ args.push('--model', opts.model);
162
+ return args;
163
+ }
164
+ /** Headless `codex exec` runner. Working root pinned with `--cd`; prompt is the trailing positional arg (shell:false). */
165
+ export function makeCodexHeadlessRunner(opts = {}) {
166
+ const args = codexRunnerArgs(opts);
167
+ return async (prompt, ctx) => {
168
+ try {
169
+ const r = await spawnCapture('codex', [...args, '--cd', ctx.cwd, prompt], { cwd: ctx.cwd, timeoutMs: ctx.timeoutMs ?? DEFAULT_TIMEOUT_MS, ...(ctx.env ? { env: ctx.env } : {}) });
170
+ return r.code === 0 ? { ok: true, output: r.stdout } : { ok: false, output: r.stdout, error: r.stderr || `exit ${r.code}` };
171
+ }
172
+ catch (e) {
173
+ return { ok: false, output: '', error: e instanceof Error ? e.message : String(e) };
174
+ }
175
+ };
176
+ }
177
+ // --- Cursor runner (#66 multi-harness) ---
178
+ //
179
+ // Flags below are GROUND-TRUTH from `cursor-agent --help` (CLI 2026.06.15), not guesses. Confirmed surface:
180
+ // - headless: `-p` / `--print` — "Has access to all tools, including write and shell". So `-p` ALONE can edit;
181
+ // output via `--output-format text|json|stream-json` (text default, only with --print).
182
+ // - autonomy flags: `-f`/`--force` (alias `--yolo`) = auto-allow commands unless explicitly denied; `--trust`
183
+ // = trust the workspace without prompting (headless only). Together they make `-p` truly non-interactive —
184
+ // WITHOUT them headless can block on a command-approval / workspace-trust prompt (the forum "-p hangs" reports).
185
+ // - `--model <model>` exists (e.g. gpt-5, sonnet-4, sonnet-4-thinking); `--list-models` enumerates.
186
+ // - auth: `CURSOR_API_KEY` or `--api-key` (the spec's envAllow prefix is CURSOR_).
187
+ // - cursor has its OWN `--sandbox enabled|disabled` and `-w/--worktree`; we still wrap with our git worktree.
188
+ // STILL NOT run-verified end to end (needs an authed cursor-agent; claude/codex were each run-verified). Two known
189
+ // gaps surfaced while testing: (1) on Windows `cursor-agent.cmd` is a powershell shim, NOT an npm node-shim, so
190
+ // resolveSpawnCommand cannot rewrite it; the built-in runner fail-fasts there instead of surfacing ENOENT;
191
+ // (2) the installed launcher's version-discovery regex rejects the real version dir name (Cursor's own bug).
192
+ //
193
+ // SAFETY: `-p --force --trust` auto-approves shell+write with no verified per-run allowlist/sandbox mapping.
194
+ // Until Cursor can really map agentOptions into per-run permissions, skipPermissions is refused outright. The
195
+ // wrapper worktree still contains default cursor runs, but it is not a permissions/sandbox substitute for skip.
196
+ // Safe default keeps skip OFF (CURSOR_DEFAULT_AGENT_OPTIONS), so the wiring is exercised by tests with fakes.
197
+ /**
198
+ * Build the `cursor-agent` argv (pure). Ground-truth from `cursor-agent --help` (#66): base `-p --output-format
199
+ * text` (headless, write+shell access). `--model` is a real flag. skipPermissions is rejected until Cursor has a
200
+ * verified per-run allowlist/sandbox mapping; allowedTools is not emitted because cursor has no per-tool allowlist.
201
+ */
202
+ export function cursorRunnerArgs(opts = {}) {
203
+ if (opts.skipPermissions)
204
+ throw new UnsupportedCursorSkipPermissionsError();
205
+ const args = ['-p', '--output-format', 'text'];
206
+ if (opts.model)
207
+ args.push('--model', opts.model);
208
+ return args;
209
+ }
210
+ /**
211
+ * Headless `cursor-agent` runner. Prompt passed as the trailing positional arg (shell:false, no injection risk;
212
+ * docs show `cursor-agent -p "<prompt>"`). cwd is set via spawn. SCAFFOLD — run-verify against a real
213
+ * cursor-agent before autonomous use (see the block comment above for what is doc-confirmed vs unverified).
214
+ */
215
+ export function makeCursorHeadlessRunner(opts = {}, platform = process.platform) {
216
+ const args = cursorRunnerArgs(opts);
217
+ return async (prompt, ctx) => {
218
+ assertCursorRunnerPlatformSupported(platform);
219
+ try {
220
+ const r = await spawnCapture('cursor-agent', [...args, prompt], { cwd: ctx.cwd, timeoutMs: ctx.timeoutMs ?? DEFAULT_TIMEOUT_MS, ...(ctx.env ? { env: ctx.env } : {}) });
221
+ return r.code === 0 ? { ok: true, output: r.stdout } : { ok: false, output: r.stdout, error: r.stderr || `exit ${r.code}` };
222
+ }
223
+ catch (e) {
224
+ return { ok: false, output: '', error: e instanceof Error ? e.message : String(e) };
225
+ }
226
+ };
227
+ }
228
+ const RUNNER_SPECS = {
229
+ claude: { name: 'claude', makeRunner: makeClaudeHeadlessRunner, envAllow: { prefixes: ['ANTHROPIC_', 'CLAUDE_'] } },
230
+ codex: { name: 'codex', makeRunner: makeCodexHeadlessRunner, envAllow: { prefixes: ['OPENAI_', 'CODEX_'] } },
231
+ // cursor keeps only its OWN auth env (CURSOR_); like every runner it never inherits another's vendor key.
232
+ cursor: { name: 'cursor', makeRunner: makeCursorHeadlessRunner, envAllow: { prefixes: ['CURSOR_'] } },
233
+ };
234
+ /** Resolve a runner spec by name (default 'claude' — byte-identical to the pre-registry behavior). */
235
+ export function getRunnerSpec(name = 'claude') {
236
+ return RUNNER_SPECS[name];
237
+ }
@@ -0,0 +1,83 @@
1
+ import { type ReadManifest } from './selfPrObfuscation.js';
2
+ export interface SelfPrGates {
3
+ /** Minimum outcome score. Default 0.9. */
4
+ minScore: number;
5
+ /** Minimum consecutive-success streak. Default 2. */
6
+ minStreak: number;
7
+ /** Maximum changed files. Default 5. */
8
+ maxFiles: number;
9
+ /** Maximum changed lines. Default 200. */
10
+ maxLines: number;
11
+ /** Cooldown between self-PRs (ms). Default 24h. */
12
+ cooldownMs: number;
13
+ }
14
+ export declare const DEFAULT_SELF_PR_GATES: SelfPrGates;
15
+ export interface SelfPrEvalInput {
16
+ /** Master opt-in. Default-off: when false/undefined the PR is never created. */
17
+ enabled: boolean;
18
+ /** Deny-by-default repo allowlist. Empty = deny everything (the safe default). */
19
+ allowedRoots: readonly string[];
20
+ repoRoot: string;
21
+ outcome: {
22
+ status: string;
23
+ score: number;
24
+ };
25
+ successStreak?: number;
26
+ changedFiles: readonly string[];
27
+ blastLines?: number;
28
+ /** Dedup/cooldown state (supplied from a SelfPrState store). */
29
+ lastPrAt?: string | null;
30
+ recentDiffHashes?: readonly string[];
31
+ diffHash?: string;
32
+ now?: number;
33
+ }
34
+ export interface SelfPrDecision {
35
+ eligible: boolean;
36
+ reason: string;
37
+ }
38
+ /**
39
+ * Decide whether a self-PR may be created. Pure — combines every gate and returns the FIRST failing reason, so
40
+ * the default (disabled, empty allowlist) is always a clean refusal that runs nothing.
41
+ */
42
+ export declare function evaluateSelfPr(input: SelfPrEvalInput, gates?: SelfPrGates): SelfPrDecision;
43
+ /** Run a `gh`/`git` subcommand. Inject a fake in tests; the default would shell out (never invoked unless eligible). */
44
+ export type GhRunner = (args: readonly string[], cwd: string) => Promise<{
45
+ ok: boolean;
46
+ stdout: string;
47
+ stderr?: string;
48
+ }>;
49
+ /** Scan text for secrets before any push. Return leaked:true to BLOCK. Inject e.g. the hub sanitize check. */
50
+ export type LeakScan = (text: string) => {
51
+ leaked: boolean;
52
+ detail?: string;
53
+ };
54
+ export interface CreateSelfPrDeps {
55
+ gh: GhRunner;
56
+ leakScan?: LeakScan;
57
+ /**
58
+ * Injectable reader for `public.manifest.json` (the obfuscate guard). Tests pass a fake so they don't depend
59
+ * on a real manifest on disk. When omitted, defaults to reading `<repoRoot>/public.manifest.json`. A missing /
60
+ * unreadable / invalid manifest is FAIL-CLOSED: every changed file is treated as obfuscated → the PR is blocked.
61
+ */
62
+ readManifest?: ReadManifest;
63
+ }
64
+ export interface CreateSelfPrContent {
65
+ branch: string;
66
+ title: string;
67
+ body: string;
68
+ /** The unified diff (scanned for leaks before pushing). */
69
+ diff: string;
70
+ base?: string;
71
+ }
72
+ export interface CreateSelfPrResult {
73
+ created: boolean;
74
+ reason: string;
75
+ url?: string;
76
+ }
77
+ /**
78
+ * Create a DRAFT pull request for an eligible self-mutation. Re-checks eligibility (so it can't be bypassed),
79
+ * blocks any change touching an OBFUSCATED file (fail-closed manifest), leak-scans the diff + body, then stages
80
+ * ONLY the agent's changed files (never `git add -A`), commits, pushes the branch and opens the PR with
81
+ * `gh pr create --draft`. ALWAYS draft, NEVER merges. Any gh failure is returned, not thrown.
82
+ */
83
+ export declare function createDraftSelfPr(evalInput: SelfPrEvalInput, content: CreateSelfPrContent, deps: CreateSelfPrDeps, gates?: SelfPrGates): Promise<CreateSelfPrResult>;
@@ -0,0 +1,104 @@
1
+ // Self-PR (ported + hardened from v1 gep/selfPR.js). Turns a high-confidence self-mutation into a DRAFT pull
2
+ // request on an allowlisted repo, so a human reviews and merges. This touches a REAL repo, so it is wrapped in
3
+ // the same deny-by-default discipline as the exec bridge and is OFF unless explicitly opted in:
4
+ // 1. enabled === true (default off) 4. always --draft, NEVER auto-merge
5
+ // 2. repo within allowedRoots (empty = deny all) 5. confidence gates (score/streak/blast)
6
+ // 3. leak scan of the diff before any push 6. cooldown + diff dedup
7
+ // evaluateSelfPr is PURE (decision only); the `gh` call is an injected GhRunner seam so a test never creates a
8
+ // real PR. Nothing here runs unless the caller has wired enabled:true AND a non-empty allowedRoots.
9
+ import { resolve as resolvePath, sep } from 'node:path';
10
+ import { defaultReadManifest, isObfuscatedFile } from './selfPrObfuscation.js';
11
+ export const DEFAULT_SELF_PR_GATES = { minScore: 0.9, minStreak: 2, maxFiles: 5, maxLines: 200, cooldownMs: 24 * 60 * 60 * 1000 };
12
+ /** Whether `child` is the same as or nested under `root` (both resolved absolute). */
13
+ function isWithinRoot(child, root) {
14
+ const c = resolvePath(child);
15
+ const r = resolvePath(root);
16
+ return c === r || c.startsWith(r.endsWith(sep) ? r : r + sep);
17
+ }
18
+ /**
19
+ * Decide whether a self-PR may be created. Pure — combines every gate and returns the FIRST failing reason, so
20
+ * the default (disabled, empty allowlist) is always a clean refusal that runs nothing.
21
+ */
22
+ export function evaluateSelfPr(input, gates = DEFAULT_SELF_PR_GATES) {
23
+ if (input.enabled !== true)
24
+ return { eligible: false, reason: 'disabled' };
25
+ if (!input.allowedRoots || input.allowedRoots.length === 0)
26
+ return { eligible: false, reason: 'no_allowed_roots' };
27
+ if (!input.allowedRoots.some((root) => isWithinRoot(input.repoRoot, root)))
28
+ return { eligible: false, reason: 'repo_not_allowlisted' };
29
+ if (input.outcome?.status !== 'success')
30
+ return { eligible: false, reason: 'not_successful' };
31
+ if ((Number(input.outcome.score) || 0) < gates.minScore)
32
+ return { eligible: false, reason: 'score_below_min' };
33
+ if ((input.successStreak ?? 0) < gates.minStreak)
34
+ return { eligible: false, reason: 'streak_below_min' };
35
+ const files = (input.changedFiles ?? []).filter(Boolean);
36
+ if (files.length === 0)
37
+ return { eligible: false, reason: 'no_changes' };
38
+ if (files.length > gates.maxFiles)
39
+ return { eligible: false, reason: 'too_many_files' };
40
+ if ((input.blastLines ?? 0) > gates.maxLines)
41
+ return { eligible: false, reason: 'too_many_lines' };
42
+ if (input.lastPrAt) {
43
+ const elapsed = (input.now ?? Date.now()) - new Date(input.lastPrAt).getTime();
44
+ if (Number.isFinite(elapsed) && elapsed < gates.cooldownMs)
45
+ return { eligible: false, reason: 'cooldown' };
46
+ }
47
+ if (input.diffHash && (input.recentDiffHashes ?? []).includes(input.diffHash))
48
+ return { eligible: false, reason: 'duplicate_diff' };
49
+ return { eligible: true, reason: 'eligible' };
50
+ }
51
+ /**
52
+ * Create a DRAFT pull request for an eligible self-mutation. Re-checks eligibility (so it can't be bypassed),
53
+ * blocks any change touching an OBFUSCATED file (fail-closed manifest), leak-scans the diff + body, then stages
54
+ * ONLY the agent's changed files (never `git add -A`), commits, pushes the branch and opens the PR with
55
+ * `gh pr create --draft`. ALWAYS draft, NEVER merges. Any gh failure is returned, not thrown.
56
+ */
57
+ export async function createDraftSelfPr(evalInput, content, deps, gates = DEFAULT_SELF_PR_GATES) {
58
+ const decision = evaluateSelfPr(evalInput, gates);
59
+ if (!decision.eligible)
60
+ return { created: false, reason: decision.reason };
61
+ // The set of files the agent actually changed this cycle (from the exec `git diff --name-only`). We stage
62
+ // EXACTLY these, never `git add -A` (which would sweep every stray/uncommitted change in the working tree
63
+ // into the self-PR). evaluateSelfPr already guarantees this is non-empty (the `no_changes` gate), but re-guard
64
+ // here so an empty list is an explicit no-op self-PR rather than a tree-sweeping commit.
65
+ const changedFiles = (evalInput.changedFiles ?? []).filter(Boolean);
66
+ if (changedFiles.length === 0)
67
+ return { created: false, reason: 'no_changes' };
68
+ // Obfuscation-leak guard: any changed file that ships OBFUSCATED on the public repo (per public.manifest.json)
69
+ // would land as raw source there. Block before we create anything. FAIL-CLOSED: a missing/unreadable/invalid
70
+ // manifest treats every file as obfuscated, so the guard never silently lets a leaking file through.
71
+ const readManifest = deps.readManifest ?? defaultReadManifest(evalInput.repoRoot);
72
+ for (const file of changedFiles) {
73
+ if (isObfuscatedFile(file, readManifest))
74
+ return { created: false, reason: `obfuscated_file_blocked: ${file}` };
75
+ }
76
+ if (deps.leakScan) {
77
+ for (const text of [content.diff, content.body]) {
78
+ const scan = deps.leakScan(text);
79
+ if (scan.leaked)
80
+ return { created: false, reason: `leak_blocked${scan.detail ? `: ${scan.detail}` : ''}` };
81
+ }
82
+ }
83
+ const cwd = evalInput.repoRoot;
84
+ const run = async (args) => deps.gh(args, cwd);
85
+ const checkout = await run(['checkout', '-b', content.branch]);
86
+ if (!checkout.ok)
87
+ return { created: false, reason: `git_checkout_failed: ${checkout.stderr ?? ''}`.trim() };
88
+ // Stage ONLY the agent's changed files (the `--` terminator stops any path being read as an option). NOT -A.
89
+ const add = await run(['add', '--', ...changedFiles]);
90
+ if (!add.ok)
91
+ return { created: false, reason: 'git_add_failed' };
92
+ const commit = await run(['commit', '-m', content.title]);
93
+ if (!commit.ok)
94
+ return { created: false, reason: 'git_commit_failed' };
95
+ const push = await run(['push', '-u', 'origin', content.branch]);
96
+ if (!push.ok)
97
+ return { created: false, reason: `git_push_failed: ${push.stderr ?? ''}`.trim() };
98
+ // ALWAYS --draft; never any merge/auto-merge flag.
99
+ const prArgs = ['pr', 'create', '--draft', '--title', content.title, '--body', content.body, ...(content.base ? ['--base', content.base] : [])];
100
+ const pr = await run(prArgs);
101
+ if (!pr.ok)
102
+ return { created: false, reason: `gh_pr_create_failed: ${pr.stderr ?? ''}`.trim() };
103
+ return { created: true, reason: 'created', url: pr.stdout.trim() };
104
+ }
@@ -0,0 +1,21 @@
1
+ /** Reads the raw manifest text. Return null when absent/unreadable (treated as fail-closed). Inject in tests. */
2
+ export type ReadManifest = () => string | null;
3
+ /** Normalize a path for comparison: backslash→slash, strip leading `./`, trim. Mirrors v1 normalizeRel. */
4
+ export declare function normalizeManifestPath(filePath: string): string;
5
+ /** Default manifest reader: reads `<repoRoot>/public.manifest.json`, returning null on any failure. */
6
+ export declare function defaultReadManifest(repoRoot: string): ReadManifest;
7
+ /** Test/maintenance hook: drop any cached load for a reader (or all readers) so the load path re-runs. */
8
+ export declare function resetObfuscatedCache(reader?: ReadManifest): void;
9
+ /**
10
+ * Load the obfuscated-file set from the manifest. Returns a normalized Set, or null when the manifest is
11
+ * missing / unreadable / structurally invalid (NOT an `obfuscate` array of literal string paths). null is the
12
+ * fail-closed signal: the caller must then reject all files. Result is cached per reader.
13
+ */
14
+ export declare function loadObfuscatedFiles(readManifest: ReadManifest): Set<string> | null;
15
+ /**
16
+ * Is `changedFile` one of the obfuscated files? FAIL-CLOSED: when the manifest can't be loaded (null) every
17
+ * file is treated as obfuscated. Matching is on the normalized relative path (exact, segment-aware) — the
18
+ * manifest holds literal file paths, so a similarly-named non-obfuscated file (e.g. "src/secret_helper.ts"
19
+ * vs an obfuscated "src/secret.ts") is NOT a false positive.
20
+ */
21
+ export declare function isObfuscatedFile(changedFile: string, readManifest: ReadManifest): boolean;
@@ -0,0 +1,85 @@
1
+ // Obfuscation-leak guard for self-PRs (ported from v1 src/gep/selfPR.js loadObfuscatedFromManifest /
2
+ // isPublicNonObfuscated, the `public.manifest.json` obfuscate slice). Some files ship OBFUSCATED on the public
3
+ // repo: a self-PR that touches one of those would land RAW SOURCE there, leaking implementation detail. This
4
+ // module reads `public.manifest.json`'s `obfuscate` array (literal string paths) and exposes a segment-aware
5
+ // membership check.
6
+ //
7
+ // FAIL-CLOSED (matches v1): when the manifest is MISSING / unreadable / invalid, loadObfuscatedFiles returns
8
+ // null and isObfuscatedFile treats EVERY file as obfuscated (rejects), so a missing manifest can never silently
9
+ // let a potentially-leaking file through. The manifest read is INJECTABLE so tests are hermetic and never depend
10
+ // on a real public.manifest.json on disk. Pure + injectable; the cache is resettable. Hub-agnostic (no economy).
11
+ import { readFileSync } from 'node:fs';
12
+ import { join as joinPath } from 'node:path';
13
+ /** Normalize a path for comparison: backslash→slash, strip leading `./`, trim. Mirrors v1 normalizeRel. */
14
+ export function normalizeManifestPath(filePath) {
15
+ return String(filePath ?? '')
16
+ .replace(/\\/g, '/')
17
+ .replace(/^\.\/+/, '')
18
+ .trim();
19
+ }
20
+ /** Default manifest reader: reads `<repoRoot>/public.manifest.json`, returning null on any failure. */
21
+ export function defaultReadManifest(repoRoot) {
22
+ return () => {
23
+ try {
24
+ return readFileSync(joinPath(repoRoot, 'public.manifest.json'), 'utf8');
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ };
30
+ }
31
+ // Resettable cache keyed by the reader identity, so distinct readers (e.g. per test) don't bleed into each other.
32
+ // `null` = a load was attempted and FAILED (fail-closed); a Set = the loaded obfuscate paths.
33
+ const cache = new WeakMap();
34
+ /** Test/maintenance hook: drop any cached load for a reader (or all readers) so the load path re-runs. */
35
+ export function resetObfuscatedCache(reader) {
36
+ if (reader)
37
+ cache.delete(reader);
38
+ // A WeakMap can't be cleared wholesale; callers that need a clean slate pass a fresh reader instead.
39
+ }
40
+ /**
41
+ * Load the obfuscated-file set from the manifest. Returns a normalized Set, or null when the manifest is
42
+ * missing / unreadable / structurally invalid (NOT an `obfuscate` array of literal string paths). null is the
43
+ * fail-closed signal: the caller must then reject all files. Result is cached per reader.
44
+ */
45
+ export function loadObfuscatedFiles(readManifest) {
46
+ if (cache.has(readManifest))
47
+ return cache.get(readManifest) ?? null;
48
+ let result;
49
+ try {
50
+ const raw = readManifest();
51
+ if (raw === null)
52
+ throw new Error('public.manifest.json not found / unreadable');
53
+ const manifest = JSON.parse(raw);
54
+ if (!Array.isArray(manifest.obfuscate)) {
55
+ throw new Error('public.manifest.json missing `obfuscate` array');
56
+ }
57
+ // Reject globs / non-strings: a glob would silently miss exact-path matches and reintroduce drift.
58
+ for (const f of manifest.obfuscate) {
59
+ if (typeof f !== 'string' || /[*?[\]]/.test(f)) {
60
+ throw new Error(`public.manifest.json \`obfuscate\` must contain literal paths, got: ${JSON.stringify(f)}`);
61
+ }
62
+ }
63
+ result = new Set(manifest.obfuscate.map((f) => normalizeManifestPath(f)).filter(Boolean));
64
+ }
65
+ catch {
66
+ result = null; // fail-closed: an unreadable/invalid manifest rejects everything
67
+ }
68
+ cache.set(readManifest, result);
69
+ return result;
70
+ }
71
+ /**
72
+ * Is `changedFile` one of the obfuscated files? FAIL-CLOSED: when the manifest can't be loaded (null) every
73
+ * file is treated as obfuscated. Matching is on the normalized relative path (exact, segment-aware) — the
74
+ * manifest holds literal file paths, so a similarly-named non-obfuscated file (e.g. "src/secret_helper.ts"
75
+ * vs an obfuscated "src/secret.ts") is NOT a false positive.
76
+ */
77
+ export function isObfuscatedFile(changedFile, readManifest) {
78
+ const obfuscated = loadObfuscatedFiles(readManifest);
79
+ if (obfuscated === null)
80
+ return true; // fail-closed
81
+ const rel = normalizeManifestPath(changedFile);
82
+ if (!rel)
83
+ return false;
84
+ return obfuscated.has(rel);
85
+ }