dorfl 0.0.0 → 0.1.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 (619) hide show
  1. package/dist/advance-ci-template.d.ts +73 -0
  2. package/dist/advance-ci-template.d.ts.map +1 -0
  3. package/dist/advance-ci-template.js +104 -0
  4. package/dist/advance-ci-template.js.map +1 -0
  5. package/dist/advance-classify.d.ts +132 -0
  6. package/dist/advance-classify.d.ts.map +1 -0
  7. package/dist/advance-classify.js +120 -0
  8. package/dist/advance-classify.js.map +1 -0
  9. package/dist/advance-drivers.d.ts +182 -0
  10. package/dist/advance-drivers.d.ts.map +1 -0
  11. package/dist/advance-drivers.js +231 -0
  12. package/dist/advance-drivers.js.map +1 -0
  13. package/dist/advance-isolated.d.ts +156 -0
  14. package/dist/advance-isolated.d.ts.map +1 -0
  15. package/dist/advance-isolated.js +256 -0
  16. package/dist/advance-isolated.js.map +1 -0
  17. package/dist/advance-lifecycle-template.d.ts +107 -0
  18. package/dist/advance-lifecycle-template.d.ts.map +1 -0
  19. package/dist/advance-lifecycle-template.js +668 -0
  20. package/dist/advance-lifecycle-template.js.map +1 -0
  21. package/dist/advance-loop-driver.d.ts +325 -0
  22. package/dist/advance-loop-driver.d.ts.map +1 -0
  23. package/dist/advance-loop-driver.js +437 -0
  24. package/dist/advance-loop-driver.js.map +1 -0
  25. package/dist/advance-treeless-publish.d.ts +108 -0
  26. package/dist/advance-treeless-publish.d.ts.map +1 -0
  27. package/dist/advance-treeless-publish.js +71 -0
  28. package/dist/advance-treeless-publish.js.map +1 -0
  29. package/dist/advance.d.ts +340 -0
  30. package/dist/advance.d.ts.map +1 -0
  31. package/dist/advance.js +1122 -0
  32. package/dist/advance.js.map +1 -0
  33. package/dist/advancing-lock.d.ts +294 -0
  34. package/dist/advancing-lock.d.ts.map +1 -0
  35. package/dist/advancing-lock.js +594 -0
  36. package/dist/advancing-lock.js.map +1 -0
  37. package/dist/agent-launch.d.ts +79 -0
  38. package/dist/agent-launch.d.ts.map +1 -0
  39. package/dist/agent-launch.js +61 -0
  40. package/dist/agent-launch.js.map +1 -0
  41. package/dist/agent-stop.d.ts +149 -0
  42. package/dist/agent-stop.d.ts.map +1 -0
  43. package/dist/agent-stop.js +307 -0
  44. package/dist/agent-stop.js.map +1 -0
  45. package/dist/apply-decide.d.ts +127 -0
  46. package/dist/apply-decide.d.ts.map +1 -0
  47. package/dist/apply-decide.js +176 -0
  48. package/dist/apply-decide.js.map +1 -0
  49. package/dist/apply-merge-action.d.ts +206 -0
  50. package/dist/apply-merge-action.d.ts.map +1 -0
  51. package/dist/apply-merge-action.js +307 -0
  52. package/dist/apply-merge-action.js.map +1 -0
  53. package/dist/apply-persist.d.ts +174 -0
  54. package/dist/apply-persist.d.ts.map +1 -0
  55. package/dist/apply-persist.js +359 -0
  56. package/dist/apply-persist.js.map +1 -0
  57. package/dist/arbiter.d.ts +120 -0
  58. package/dist/arbiter.d.ts.map +1 -0
  59. package/dist/arbiter.js +255 -0
  60. package/dist/arbiter.js.map +1 -0
  61. package/dist/brand.d.ts +70 -0
  62. package/dist/brand.d.ts.map +1 -0
  63. package/dist/brand.js +84 -0
  64. package/dist/brand.js.map +1 -0
  65. package/dist/buildable-body.d.ts +132 -0
  66. package/dist/buildable-body.d.ts.map +1 -0
  67. package/dist/buildable-body.js +131 -0
  68. package/dist/buildable-body.js.map +1 -0
  69. package/dist/categorise.d.ts +66 -0
  70. package/dist/categorise.d.ts.map +1 -0
  71. package/dist/categorise.js +106 -0
  72. package/dist/categorise.js.map +1 -0
  73. package/dist/claim-cas.d.ts +117 -0
  74. package/dist/claim-cas.d.ts.map +1 -0
  75. package/dist/claim-cas.js +312 -0
  76. package/dist/claim-cas.js.map +1 -0
  77. package/dist/cli-spinner.d.ts +112 -0
  78. package/dist/cli-spinner.d.ts.map +1 -0
  79. package/dist/cli-spinner.js +157 -0
  80. package/dist/cli-spinner.js.map +1 -0
  81. package/dist/cli.d.ts +11 -0
  82. package/dist/cli.d.ts.map +1 -0
  83. package/dist/cli.js +3094 -0
  84. package/dist/cli.js.map +1 -0
  85. package/dist/close-job-template.d.ts +70 -0
  86. package/dist/close-job-template.d.ts.map +1 -0
  87. package/dist/close-job-template.js +180 -0
  88. package/dist/close-job-template.js.map +1 -0
  89. package/dist/close-job.d.ts +95 -0
  90. package/dist/close-job.d.ts.map +1 -0
  91. package/dist/close-job.js +226 -0
  92. package/dist/close-job.js.map +1 -0
  93. package/dist/complete.d.ts +361 -0
  94. package/dist/complete.d.ts.map +1 -0
  95. package/dist/complete.js +885 -0
  96. package/dist/complete.js.map +1 -0
  97. package/dist/concurrency.d.ts +68 -0
  98. package/dist/concurrency.d.ts.map +1 -0
  99. package/dist/concurrency.js +112 -0
  100. package/dist/concurrency.js.map +1 -0
  101. package/dist/config-override.d.ts +76 -0
  102. package/dist/config-override.d.ts.map +1 -0
  103. package/dist/config-override.js +50 -0
  104. package/dist/config-override.js.map +1 -0
  105. package/dist/config.d.ts +668 -0
  106. package/dist/config.d.ts.map +1 -0
  107. package/dist/config.js +241 -0
  108. package/dist/config.js.map +1 -0
  109. package/dist/continue-branch.d.ts +249 -0
  110. package/dist/continue-branch.d.ts.map +1 -0
  111. package/dist/continue-branch.js +389 -0
  112. package/dist/continue-branch.js.map +1 -0
  113. package/dist/cwd-section.d.ts +186 -0
  114. package/dist/cwd-section.d.ts.map +1 -0
  115. package/dist/cwd-section.js +209 -0
  116. package/dist/cwd-section.js.map +1 -0
  117. package/dist/decision-engine.d.ts +170 -0
  118. package/dist/decision-engine.d.ts.map +1 -0
  119. package/dist/decision-engine.js +136 -0
  120. package/dist/decision-engine.js.map +1 -0
  121. package/dist/detect.d.ts +17 -0
  122. package/dist/detect.d.ts.map +1 -0
  123. package/dist/detect.js +118 -0
  124. package/dist/detect.js.map +1 -0
  125. package/dist/do-autopick.d.ts +85 -0
  126. package/dist/do-autopick.d.ts.map +1 -0
  127. package/dist/do-autopick.js +112 -0
  128. package/dist/do-autopick.js.map +1 -0
  129. package/dist/do-config.d.ts +312 -0
  130. package/dist/do-config.d.ts.map +1 -0
  131. package/dist/do-config.js +358 -0
  132. package/dist/do-config.js.map +1 -0
  133. package/dist/do-remote-auto.d.ts +75 -0
  134. package/dist/do-remote-auto.d.ts.map +1 -0
  135. package/dist/do-remote-auto.js +111 -0
  136. package/dist/do-remote-auto.js.map +1 -0
  137. package/dist/do.d.ts +621 -0
  138. package/dist/do.d.ts.map +1 -0
  139. package/dist/do.js +1882 -0
  140. package/dist/do.js.map +1 -0
  141. package/dist/drop-source.d.ts +96 -0
  142. package/dist/drop-source.d.ts.map +1 -0
  143. package/dist/drop-source.js +91 -0
  144. package/dist/drop-source.js.map +1 -0
  145. package/dist/eligibility.d.ts +46 -0
  146. package/dist/eligibility.d.ts.map +1 -0
  147. package/dist/eligibility.js +34 -0
  148. package/dist/eligibility.js.map +1 -0
  149. package/dist/env-config.d.ts +51 -0
  150. package/dist/env-config.d.ts.map +1 -0
  151. package/dist/env-config.js +272 -0
  152. package/dist/env-config.js.map +1 -0
  153. package/dist/failure-cause.d.ts +70 -0
  154. package/dist/failure-cause.d.ts.map +1 -0
  155. package/dist/failure-cause.js +126 -0
  156. package/dist/failure-cause.js.map +1 -0
  157. package/dist/format.d.ts +43 -0
  158. package/dist/format.d.ts.map +1 -0
  159. package/dist/format.js +256 -0
  160. package/dist/format.js.map +1 -0
  161. package/dist/frontmatter.d.ts +215 -0
  162. package/dist/frontmatter.d.ts.map +1 -0
  163. package/dist/frontmatter.js +345 -0
  164. package/dist/frontmatter.js.map +1 -0
  165. package/dist/gate-readiness.d.ts +84 -0
  166. package/dist/gate-readiness.d.ts.map +1 -0
  167. package/dist/gate-readiness.js +103 -0
  168. package/dist/gate-readiness.js.map +1 -0
  169. package/dist/gc.d.ts +165 -0
  170. package/dist/gc.d.ts.map +1 -0
  171. package/dist/gc.js +313 -0
  172. package/dist/gc.js.map +1 -0
  173. package/dist/gh-failure.d.ts +42 -0
  174. package/dist/gh-failure.d.ts.map +1 -0
  175. package/dist/gh-failure.js +49 -0
  176. package/dist/gh-failure.js.map +1 -0
  177. package/dist/git.d.ts +75 -0
  178. package/dist/git.d.ts.map +1 -0
  179. package/dist/git.js +130 -0
  180. package/dist/git.js.map +1 -0
  181. package/dist/github.d.ts +187 -0
  182. package/dist/github.d.ts.map +1 -0
  183. package/dist/github.js +343 -0
  184. package/dist/github.js.map +1 -0
  185. package/dist/harness.d.ts +242 -0
  186. package/dist/harness.d.ts.map +1 -0
  187. package/dist/harness.js +157 -0
  188. package/dist/harness.js.map +1 -0
  189. package/dist/identity.d.ts +167 -0
  190. package/dist/identity.d.ts.map +1 -0
  191. package/dist/identity.js +231 -0
  192. package/dist/identity.js.map +1 -0
  193. package/dist/index.d.ts +147 -0
  194. package/dist/index.d.ts.map +1 -0
  195. package/dist/index.js +76 -0
  196. package/dist/index.js.map +1 -0
  197. package/dist/install-ci-branch-protection.d.ts +147 -0
  198. package/dist/install-ci-branch-protection.d.ts.map +1 -0
  199. package/dist/install-ci-branch-protection.js +166 -0
  200. package/dist/install-ci-branch-protection.js.map +1 -0
  201. package/dist/install-ci-capabilities/advance-lifecycle.d.ts +15 -0
  202. package/dist/install-ci-capabilities/advance-lifecycle.d.ts.map +1 -0
  203. package/dist/install-ci-capabilities/advance-lifecycle.js +28 -0
  204. package/dist/install-ci-capabilities/advance-lifecycle.js.map +1 -0
  205. package/dist/install-ci-capabilities/close-job.d.ts +13 -0
  206. package/dist/install-ci-capabilities/close-job.d.ts.map +1 -0
  207. package/dist/install-ci-capabilities/close-job.js +26 -0
  208. package/dist/install-ci-capabilities/close-job.js.map +1 -0
  209. package/dist/install-ci-capabilities/example-noop.d.ts +16 -0
  210. package/dist/install-ci-capabilities/example-noop.d.ts.map +1 -0
  211. package/dist/install-ci-capabilities/example-noop.js +23 -0
  212. package/dist/install-ci-capabilities/example-noop.js.map +1 -0
  213. package/dist/install-ci-capabilities/intake.d.ts +15 -0
  214. package/dist/install-ci-capabilities/intake.d.ts.map +1 -0
  215. package/dist/install-ci-capabilities/intake.js +28 -0
  216. package/dist/install-ci-capabilities/intake.js.map +1 -0
  217. package/dist/install-ci-capabilities/verify.d.ts +14 -0
  218. package/dist/install-ci-capabilities/verify.d.ts.map +1 -0
  219. package/dist/install-ci-capabilities/verify.js +27 -0
  220. package/dist/install-ci-capabilities/verify.js.map +1 -0
  221. package/dist/install-ci-core.d.ts +446 -0
  222. package/dist/install-ci-core.d.ts.map +1 -0
  223. package/dist/install-ci-core.js +760 -0
  224. package/dist/install-ci-core.js.map +1 -0
  225. package/dist/install-ci-github.d.ts +167 -0
  226. package/dist/install-ci-github.d.ts.map +1 -0
  227. package/dist/install-ci-github.js +315 -0
  228. package/dist/install-ci-github.js.map +1 -0
  229. package/dist/install-ci.d.ts +105 -0
  230. package/dist/install-ci.d.ts.map +1 -0
  231. package/dist/install-ci.js +363 -0
  232. package/dist/install-ci.js.map +1 -0
  233. package/dist/intake-event.d.ts +88 -0
  234. package/dist/intake-event.d.ts.map +1 -0
  235. package/dist/intake-event.js +66 -0
  236. package/dist/intake-event.js.map +1 -0
  237. package/dist/intake-marker.d.ts +95 -0
  238. package/dist/intake-marker.d.ts.map +1 -0
  239. package/dist/intake-marker.js +127 -0
  240. package/dist/intake-marker.js.map +1 -0
  241. package/dist/intake-triage.d.ts +48 -0
  242. package/dist/intake-triage.d.ts.map +1 -0
  243. package/dist/intake-triage.js +95 -0
  244. package/dist/intake-triage.js.map +1 -0
  245. package/dist/intake-trigger-template.d.ts +185 -0
  246. package/dist/intake-trigger-template.d.ts.map +1 -0
  247. package/dist/intake-trigger-template.js +449 -0
  248. package/dist/intake-trigger-template.js.map +1 -0
  249. package/dist/intake.d.ts +569 -0
  250. package/dist/intake.d.ts.map +1 -0
  251. package/dist/intake.js +1628 -0
  252. package/dist/intake.js.map +1 -0
  253. package/dist/integration-core.d.ts +539 -0
  254. package/dist/integration-core.d.ts.map +1 -0
  255. package/dist/integration-core.js +2195 -0
  256. package/dist/integration-core.js.map +1 -0
  257. package/dist/integrator.d.ts +343 -0
  258. package/dist/integrator.d.ts.map +1 -0
  259. package/dist/integrator.js +400 -0
  260. package/dist/integrator.js.map +1 -0
  261. package/dist/isolation.d.ts +219 -0
  262. package/dist/isolation.d.ts.map +1 -0
  263. package/dist/isolation.js +261 -0
  264. package/dist/isolation.js.map +1 -0
  265. package/dist/issue-provider.d.ts +349 -0
  266. package/dist/issue-provider.d.ts.map +1 -0
  267. package/dist/issue-provider.js +360 -0
  268. package/dist/issue-provider.js.map +1 -0
  269. package/dist/item-lock.d.ts +626 -0
  270. package/dist/item-lock.d.ts.map +1 -0
  271. package/dist/item-lock.js +1381 -0
  272. package/dist/item-lock.js.map +1 -0
  273. package/dist/item-path.d.ts +49 -0
  274. package/dist/item-path.d.ts.map +1 -0
  275. package/dist/item-path.js +66 -0
  276. package/dist/item-path.js.map +1 -0
  277. package/dist/ledger-lint.d.ts +129 -0
  278. package/dist/ledger-lint.d.ts.map +1 -0
  279. package/dist/ledger-lint.js +249 -0
  280. package/dist/ledger-lint.js.map +1 -0
  281. package/dist/ledger-read.d.ts +357 -0
  282. package/dist/ledger-read.d.ts.map +1 -0
  283. package/dist/ledger-read.js +442 -0
  284. package/dist/ledger-read.js.map +1 -0
  285. package/dist/ledger-write.d.ts +330 -0
  286. package/dist/ledger-write.d.ts.map +1 -0
  287. package/dist/ledger-write.js +411 -0
  288. package/dist/ledger-write.js.map +1 -0
  289. package/dist/lifecycle-gather.d.ts +30 -0
  290. package/dist/lifecycle-gather.d.ts.map +1 -0
  291. package/dist/lifecycle-gather.js +205 -0
  292. package/dist/lifecycle-gather.js.map +1 -0
  293. package/dist/lifecycle-pools.d.ts +180 -0
  294. package/dist/lifecycle-pools.d.ts.map +1 -0
  295. package/dist/lifecycle-pools.js +78 -0
  296. package/dist/lifecycle-pools.js.map +1 -0
  297. package/dist/merge-question-surfacer.d.ts +166 -0
  298. package/dist/merge-question-surfacer.d.ts.map +1 -0
  299. package/dist/merge-question-surfacer.js +297 -0
  300. package/dist/merge-question-surfacer.js.map +1 -0
  301. package/dist/mint-adr.d.ts +126 -0
  302. package/dist/mint-adr.d.ts.map +1 -0
  303. package/dist/mint-adr.js +257 -0
  304. package/dist/mint-adr.js.map +1 -0
  305. package/dist/mirror-pool-scan.d.ts +125 -0
  306. package/dist/mirror-pool-scan.d.ts.map +1 -0
  307. package/dist/mirror-pool-scan.js +104 -0
  308. package/dist/mirror-pool-scan.js.map +1 -0
  309. package/dist/needs-attention.d.ts +341 -0
  310. package/dist/needs-attention.d.ts.map +1 -0
  311. package/dist/needs-attention.js +900 -0
  312. package/dist/needs-attention.js.map +1 -0
  313. package/dist/orphan-sidecar.d.ts +79 -0
  314. package/dist/orphan-sidecar.d.ts.map +1 -0
  315. package/dist/orphan-sidecar.js +71 -0
  316. package/dist/orphan-sidecar.js.map +1 -0
  317. package/dist/output.d.ts +48 -0
  318. package/dist/output.d.ts.map +1 -0
  319. package/dist/output.js +66 -0
  320. package/dist/output.js.map +1 -0
  321. package/dist/pi-harness.d.ts +179 -0
  322. package/dist/pi-harness.d.ts.map +1 -0
  323. package/dist/pi-harness.js +342 -0
  324. package/dist/pi-harness.js.map +1 -0
  325. package/dist/placement.d.ts +99 -0
  326. package/dist/placement.d.ts.map +1 -0
  327. package/dist/placement.js +67 -0
  328. package/dist/placement.js.map +1 -0
  329. package/dist/prd-to-spec.d.ts +315 -0
  330. package/dist/prd-to-spec.d.ts.map +1 -0
  331. package/dist/prd-to-spec.js +684 -0
  332. package/dist/prd-to-spec.js.map +1 -0
  333. package/dist/prepare.d.ts +121 -0
  334. package/dist/prepare.d.ts.map +1 -0
  335. package/dist/prepare.js +140 -0
  336. package/dist/prepare.js.map +1 -0
  337. package/dist/prompt.d.ts +360 -0
  338. package/dist/prompt.d.ts.map +1 -0
  339. package/dist/prompt.js +499 -0
  340. package/dist/prompt.js.map +1 -0
  341. package/dist/protocol/ADR-FORMAT.md +47 -0
  342. package/dist/protocol/CLAIM-PROTOCOL.md +217 -0
  343. package/dist/protocol/REVIEW-PROTOCOL.md +119 -0
  344. package/dist/protocol/SURFACE-PROTOCOL.md +121 -0
  345. package/dist/protocol/TASKING-PROTOCOL.md +122 -0
  346. package/dist/protocol/WORK-CONTRACT.md +276 -0
  347. package/dist/protocol/spec-template.md +71 -0
  348. package/dist/protocol/task-template.md +65 -0
  349. package/dist/readiness.d.ts +66 -0
  350. package/dist/readiness.d.ts.map +1 -0
  351. package/dist/readiness.js +36 -0
  352. package/dist/readiness.js.map +1 -0
  353. package/dist/reap-branches.d.ts +102 -0
  354. package/dist/reap-branches.d.ts.map +1 -0
  355. package/dist/reap-branches.js +149 -0
  356. package/dist/reap-branches.js.map +1 -0
  357. package/dist/recover-isolated.d.ts +72 -0
  358. package/dist/recover-isolated.d.ts.map +1 -0
  359. package/dist/recover-isolated.js +188 -0
  360. package/dist/recover-isolated.js.map +1 -0
  361. package/dist/registry.d.ts +172 -0
  362. package/dist/registry.d.ts.map +1 -0
  363. package/dist/registry.js +296 -0
  364. package/dist/registry.js.map +1 -0
  365. package/dist/repo-config.d.ts +201 -0
  366. package/dist/repo-config.d.ts.map +1 -0
  367. package/dist/repo-config.js +414 -0
  368. package/dist/repo-config.js.map +1 -0
  369. package/dist/repo-key.d.ts +20 -0
  370. package/dist/repo-key.d.ts.map +1 -0
  371. package/dist/repo-key.js +68 -0
  372. package/dist/repo-key.js.map +1 -0
  373. package/dist/repo-mirror.d.ts +177 -0
  374. package/dist/repo-mirror.d.ts.map +1 -0
  375. package/dist/repo-mirror.js +271 -0
  376. package/dist/repo-mirror.js.map +1 -0
  377. package/dist/retry-backoff.d.ts +90 -0
  378. package/dist/retry-backoff.d.ts.map +1 -0
  379. package/dist/retry-backoff.js +98 -0
  380. package/dist/retry-backoff.js.map +1 -0
  381. package/dist/review-gate.d.ts +173 -0
  382. package/dist/review-gate.d.ts.map +1 -0
  383. package/dist/review-gate.js +261 -0
  384. package/dist/review-gate.js.map +1 -0
  385. package/dist/review-verdict.d.ts +149 -0
  386. package/dist/review-verdict.d.ts.map +1 -0
  387. package/dist/review-verdict.js +332 -0
  388. package/dist/review-verdict.js.map +1 -0
  389. package/dist/run.d.ts +221 -0
  390. package/dist/run.d.ts.map +1 -0
  391. package/dist/run.js +963 -0
  392. package/dist/run.js.map +1 -0
  393. package/dist/scan.d.ts +308 -0
  394. package/dist/scan.d.ts.map +1 -0
  395. package/dist/scan.js +374 -0
  396. package/dist/scan.js.map +1 -0
  397. package/dist/select-order.d.ts +75 -0
  398. package/dist/select-order.d.ts.map +1 -0
  399. package/dist/select-order.js +108 -0
  400. package/dist/select-order.js.map +1 -0
  401. package/dist/select-priority.d.ts +188 -0
  402. package/dist/select-priority.d.ts.map +1 -0
  403. package/dist/select-priority.js +80 -0
  404. package/dist/select-priority.js.map +1 -0
  405. package/dist/select.d.ts +25 -0
  406. package/dist/select.d.ts.map +1 -0
  407. package/dist/select.js +43 -0
  408. package/dist/select.js.map +1 -0
  409. package/dist/session-path.d.ts +36 -0
  410. package/dist/session-path.d.ts.map +1 -0
  411. package/dist/session-path.js +129 -0
  412. package/dist/session-path.js.map +1 -0
  413. package/dist/sidecar-apply.d.ts +83 -0
  414. package/dist/sidecar-apply.d.ts.map +1 -0
  415. package/dist/sidecar-apply.js +111 -0
  416. package/dist/sidecar-apply.js.map +1 -0
  417. package/dist/sidecar.d.ts +245 -0
  418. package/dist/sidecar.d.ts.map +1 -0
  419. package/dist/sidecar.js +481 -0
  420. package/dist/sidecar.js.map +1 -0
  421. package/dist/slug-namespace.d.ts +204 -0
  422. package/dist/slug-namespace.d.ts.map +1 -0
  423. package/dist/slug-namespace.js +229 -0
  424. package/dist/slug-namespace.js.map +1 -0
  425. package/dist/spec-complete.d.ts +44 -0
  426. package/dist/spec-complete.d.ts.map +1 -0
  427. package/dist/spec-complete.js +69 -0
  428. package/dist/spec-complete.js.map +1 -0
  429. package/dist/start.d.ts +97 -0
  430. package/dist/start.d.ts.map +1 -0
  431. package/dist/start.js +633 -0
  432. package/dist/start.js.map +1 -0
  433. package/dist/status.d.ts +199 -0
  434. package/dist/status.d.ts.map +1 -0
  435. package/dist/status.js +228 -0
  436. package/dist/status.js.map +1 -0
  437. package/dist/surface-gate.d.ts +162 -0
  438. package/dist/surface-gate.d.ts.map +1 -0
  439. package/dist/surface-gate.js +206 -0
  440. package/dist/surface-gate.js.map +1 -0
  441. package/dist/surface-persist.d.ts +86 -0
  442. package/dist/surface-persist.d.ts.map +1 -0
  443. package/dist/surface-persist.js +129 -0
  444. package/dist/surface-persist.js.map +1 -0
  445. package/dist/tasker-review-loop.d.ts +249 -0
  446. package/dist/tasker-review-loop.d.ts.map +1 -0
  447. package/dist/tasker-review-loop.js +369 -0
  448. package/dist/tasker-review-loop.js.map +1 -0
  449. package/dist/tasking-eligibility.d.ts +74 -0
  450. package/dist/tasking-eligibility.d.ts.map +1 -0
  451. package/dist/tasking-eligibility.js +52 -0
  452. package/dist/tasking-eligibility.js.map +1 -0
  453. package/dist/tasking-lock.d.ts +111 -0
  454. package/dist/tasking-lock.d.ts.map +1 -0
  455. package/dist/tasking-lock.js +256 -0
  456. package/dist/tasking-lock.js.map +1 -0
  457. package/dist/tasking.d.ts +275 -0
  458. package/dist/tasking.d.ts.map +1 -0
  459. package/dist/tasking.js +952 -0
  460. package/dist/tasking.js.map +1 -0
  461. package/dist/triage-gate.d.ts +127 -0
  462. package/dist/triage-gate.d.ts.map +1 -0
  463. package/dist/triage-gate.js +139 -0
  464. package/dist/triage-gate.js.map +1 -0
  465. package/dist/triage-persist.d.ts +163 -0
  466. package/dist/triage-persist.d.ts.map +1 -0
  467. package/dist/triage-persist.js +387 -0
  468. package/dist/triage-persist.js.map +1 -0
  469. package/dist/verdict-json.d.ts +32 -0
  470. package/dist/verdict-json.d.ts.map +1 -0
  471. package/dist/verdict-json.js +74 -0
  472. package/dist/verdict-json.js.map +1 -0
  473. package/dist/verify-workflow-template.d.ts +60 -0
  474. package/dist/verify-workflow-template.d.ts.map +1 -0
  475. package/dist/verify-workflow-template.js +126 -0
  476. package/dist/verify-workflow-template.js.map +1 -0
  477. package/dist/verify.d.ts +60 -0
  478. package/dist/verify.d.ts.map +1 -0
  479. package/dist/verify.js +62 -0
  480. package/dist/verify.js.map +1 -0
  481. package/dist/watch-session.d.ts +112 -0
  482. package/dist/watch-session.d.ts.map +1 -0
  483. package/dist/watch-session.js +347 -0
  484. package/dist/watch-session.js.map +1 -0
  485. package/dist/work-layout.d.ts +198 -0
  486. package/dist/work-layout.d.ts.map +1 -0
  487. package/dist/work-layout.js +217 -0
  488. package/dist/work-layout.js.map +1 -0
  489. package/dist/work-on.d.ts +154 -0
  490. package/dist/work-on.d.ts.map +1 -0
  491. package/dist/work-on.js +387 -0
  492. package/dist/work-on.js.map +1 -0
  493. package/dist/workspace.d.ts +224 -0
  494. package/dist/workspace.d.ts.map +1 -0
  495. package/dist/workspace.js +325 -0
  496. package/dist/workspace.js.map +1 -0
  497. package/package.json +46 -2
  498. package/src/advance-ci-template.ts +203 -0
  499. package/src/advance-classify.ts +197 -0
  500. package/src/advance-drivers.ts +414 -0
  501. package/src/advance-isolated.ts +432 -0
  502. package/src/advance-lifecycle-template.ts +791 -0
  503. package/src/advance-loop-driver.ts +745 -0
  504. package/src/advance-treeless-publish.ts +177 -0
  505. package/src/advance.ts +1564 -0
  506. package/src/advancing-lock.ts +988 -0
  507. package/src/agent-launch.ts +137 -0
  508. package/src/agent-stop.ts +361 -0
  509. package/src/apply-decide.ts +242 -0
  510. package/src/apply-merge-action.ts +502 -0
  511. package/src/apply-persist.ts +518 -0
  512. package/src/arbiter.ts +372 -0
  513. package/src/brand.ts +111 -0
  514. package/src/buildable-body.ts +196 -0
  515. package/src/categorise.ts +158 -0
  516. package/src/claim-cas.ts +513 -0
  517. package/src/cli-spinner.ts +225 -0
  518. package/src/cli.ts +4369 -0
  519. package/src/close-job-template.ts +236 -0
  520. package/src/close-job.ts +319 -0
  521. package/src/complete.ts +1379 -0
  522. package/src/concurrency.ts +151 -0
  523. package/src/config-override.ts +116 -0
  524. package/src/config.ts +883 -0
  525. package/src/continue-branch.ts +542 -0
  526. package/src/cwd-section.ts +392 -0
  527. package/src/decision-engine.ts +272 -0
  528. package/src/detect.ts +124 -0
  529. package/src/do-autopick.ts +223 -0
  530. package/src/do-config.ts +589 -0
  531. package/src/do-remote-auto.ts +197 -0
  532. package/src/do.ts +2623 -0
  533. package/src/drop-source.ts +194 -0
  534. package/src/eligibility.ts +79 -0
  535. package/src/env-config.ts +305 -0
  536. package/src/failure-cause.ts +142 -0
  537. package/src/format.ts +313 -0
  538. package/src/frontmatter.ts +485 -0
  539. package/src/gate-readiness.ts +147 -0
  540. package/src/gc.ts +510 -0
  541. package/src/gh-failure.ts +53 -0
  542. package/src/git.ts +186 -0
  543. package/src/github.ts +468 -0
  544. package/src/harness.ts +355 -0
  545. package/src/identity.ts +322 -0
  546. package/src/index.ts +785 -0
  547. package/src/install-ci-branch-protection.ts +255 -0
  548. package/src/install-ci-capabilities/advance-lifecycle.ts +34 -0
  549. package/src/install-ci-capabilities/close-job.ts +32 -0
  550. package/src/install-ci-capabilities/example-noop.ts +24 -0
  551. package/src/install-ci-capabilities/intake.ts +34 -0
  552. package/src/install-ci-capabilities/verify.ts +33 -0
  553. package/src/install-ci-core.ts +1088 -0
  554. package/src/install-ci-github.ts +376 -0
  555. package/src/install-ci.ts +552 -0
  556. package/src/intake-event.ts +102 -0
  557. package/src/intake-marker.ts +195 -0
  558. package/src/intake-triage.ts +138 -0
  559. package/src/intake-trigger-template.ts +591 -0
  560. package/src/intake.ts +2445 -0
  561. package/src/integration-core.ts +3065 -0
  562. package/src/integrator.ts +771 -0
  563. package/src/isolation.ts +484 -0
  564. package/src/issue-provider.ts +733 -0
  565. package/src/item-lock.ts +1858 -0
  566. package/src/item-path.ts +75 -0
  567. package/src/ledger-lint.ts +332 -0
  568. package/src/ledger-read.ts +924 -0
  569. package/src/ledger-write.ts +865 -0
  570. package/src/lifecycle-gather.ts +298 -0
  571. package/src/lifecycle-pools.ts +250 -0
  572. package/src/merge-question-surfacer.ts +496 -0
  573. package/src/mint-adr.ts +362 -0
  574. package/src/mirror-pool-scan.ts +240 -0
  575. package/src/needs-attention.ts +1506 -0
  576. package/src/orphan-sidecar.ts +150 -0
  577. package/src/output.ts +89 -0
  578. package/src/pi-harness.ts +403 -0
  579. package/src/placement.ts +131 -0
  580. package/src/prd-to-spec.ts +1023 -0
  581. package/src/prepare.ts +230 -0
  582. package/src/prompt.ts +760 -0
  583. package/src/readiness.ts +98 -0
  584. package/src/reap-branches.ts +278 -0
  585. package/src/recover-isolated.ts +276 -0
  586. package/src/registry.ts +475 -0
  587. package/src/repo-config.ts +550 -0
  588. package/src/repo-key.ts +74 -0
  589. package/src/repo-mirror.ts +367 -0
  590. package/src/retry-backoff.ts +130 -0
  591. package/src/review-gate.ts +389 -0
  592. package/src/review-verdict.ts +422 -0
  593. package/src/run.ts +1430 -0
  594. package/src/scan.ts +611 -0
  595. package/src/select-order.ts +143 -0
  596. package/src/select-priority.ts +266 -0
  597. package/src/select.ts +62 -0
  598. package/src/session-path.ts +153 -0
  599. package/src/sidecar-apply.ts +216 -0
  600. package/src/sidecar.ts +700 -0
  601. package/src/slug-namespace.ts +367 -0
  602. package/src/spec-complete.ts +118 -0
  603. package/src/start.ts +974 -0
  604. package/src/status.ts +441 -0
  605. package/src/surface-gate.ts +337 -0
  606. package/src/surface-persist.ts +241 -0
  607. package/src/tasker-review-loop.ts +671 -0
  608. package/src/tasking-eligibility.ts +114 -0
  609. package/src/tasking-lock.ts +416 -0
  610. package/src/tasking.ts +1438 -0
  611. package/src/triage-gate.ts +248 -0
  612. package/src/triage-persist.ts +570 -0
  613. package/src/verdict-json.ts +73 -0
  614. package/src/verify-workflow-template.ts +159 -0
  615. package/src/verify.ts +123 -0
  616. package/src/watch-session.ts +397 -0
  617. package/src/work-layout.ts +262 -0
  618. package/src/work-on.ts +660 -0
  619. package/src/workspace.ts +502 -0
@@ -0,0 +1,1088 @@
1
+ /**
2
+ * The provider-agnostic CORE of the `install-ci` scaffolder (prd `runner-in-ci`,
3
+ * task `install-ci-core-and-github-adapter`; US #7/#10). `install-ci` is a
4
+ * human-run, one-time SCAFFOLDER (mirrors whitesmith's `src/providers/github-ci.ts`):
5
+ * it writes `.github/**` + a composite setup action + secrets so autonomous work
6
+ * can run headless in CI. The running CI job NEVER edits `.github/workflows/**`
7
+ * (US #9 — that boundary is enforced by the per-capability workflow tasks; the
8
+ * core stays agnostic to it).
9
+ *
10
+ * This module is the PROVIDER-AGNOSTIC half: the config model (`ProviderEntry` /
11
+ * `AuthMode` / `CIConfigFile`), the `models.json` builder, the secret-orchestration
12
+ * LOGIC (which secrets, dedup, prompt-or-take-from-config), the `--export-config`
13
+ * (+ `--include-secrets`) round-trip, the composite-setup-action + OAuth-refresh-
14
+ * script generators, the `--fake` snapshot mechanism (write to `.fake/` instead of
15
+ * `.github/`), and the capability-emitter REGISTRY seam. It imports NOTHING
16
+ * GitHub-specific: the thin GitHub adapter (`github-ci.ts`) plugs into the
17
+ * {@link CIProviderContext} seam, so a second provider could slot in WITHOUT
18
+ * touching this core.
19
+ *
20
+ * Reuses whitesmith's PATTERNS (the wizard prompts, the `models.json` shape, the
21
+ * config load/export, the `--fake` mechanism, the secret dedup) but NOT its label
22
+ * state-machine or issue lifecycle (out of scope). The auth/config shape carries
23
+ * NO CI-specific policy field: per ADR `ci-config-policy-and-gate-family`, CI
24
+ * policy is the SAME engine gate family resolved via the generated workflow's
25
+ * `DORFL_*` env block, NOT a new config knob minted here.
26
+ */
27
+
28
+ import {readFileSync, writeFileSync, mkdirSync, readdirSync} from 'node:fs';
29
+ import {dirname, join, relative} from 'node:path';
30
+ import {fileURLToPath} from 'node:url';
31
+ import type {HarnessAdapter} from './config.js';
32
+
33
+ // ─── Config model ──────────────────────────────────────────────────────────
34
+
35
+ /**
36
+ * The two auth modes, mirroring whitesmith:
37
+ * - `models-json` (DEFAULT): one GitHub secret per provider API key; the harness
38
+ * `models.json` is generated inline and references the env var names. No
39
+ * OAuth-refresh machinery, no `GH_PAT` — the conservative default.
40
+ * - `auth-json`: a single `PI_AUTH_JSON` secret + a `GH_PAT` for OAuth-token
41
+ * refresh + an OAuth-refresh script (the `pi-mono#2743` workaround, the SINGLE
42
+ * CI→repo mutation in the whole design). The known SHARP EDGE; default AWAY
43
+ * from it.
44
+ */
45
+ export type AuthMode = 'auth-json' | 'models-json';
46
+
47
+ /**
48
+ * Where the composite setup action gets the `dorfl` CLI from:
49
+ * - `registry` (default): `npm install -g dorfl` — the published CLI,
50
+ * for every CONSUMER repo;
51
+ * - `workspace`: build the CLI from the checked-out source (`pnpm install` +
52
+ * `pnpm -r build`) and link it onto PATH, for the SELF-HOSTING dorfl
53
+ * monorepo (which is not published under that npm name, so `registry` would
54
+ * die with `dorfl: command not found`). Auto-detected when generating
55
+ * inside the monorepo (see `installCI`), overridable by an explicit flag/config.
56
+ */
57
+ export type InstallSource = 'registry' | 'workspace';
58
+
59
+ /** A configured AI provider (the harness `models.json` shape this emits). */
60
+ export interface ProviderEntry {
61
+ /** Provider name (`anthropic` / `openai` / a custom name). */
62
+ name: string;
63
+ /** Optional custom base URL (overrides the built-in default). */
64
+ baseUrl?: string;
65
+ /** API type for a custom provider (`anthropic-messages` / `openai-completions`). */
66
+ api?: string;
67
+ /** The GitHub-secret / env var name the API key is read from at runtime. */
68
+ apiKeyEnvVar: string;
69
+ /** The model ids exposed for this provider. */
70
+ models: {id: string}[];
71
+ /** OpenAI-compat capability flags for a custom provider. */
72
+ compat?: Record<string, boolean>;
73
+ /** True for the built-in providers (`anthropic` / `openai`). */
74
+ builtin: boolean;
75
+ }
76
+
77
+ /**
78
+ * Serializable CI configuration: the non-interactive `--config <file>` INPUT and
79
+ * the `--export-config` OUTPUT. The wizard gathers exactly this; the config-file
80
+ * path reproduces the wizard's output byte-for-byte from it. When
81
+ * `--include-secrets` is used, `secrets` maps env var names to their actual API
82
+ * key values (set via the provider seam's `setSecret` on install).
83
+ */
84
+ export interface CIConfigFile {
85
+ /** The auth mode (`models-json` default). */
86
+ authMode: AuthMode;
87
+ /** The configured providers (empty for `auth-json` mode). */
88
+ providers: ProviderEntry[];
89
+ /** The default provider name. */
90
+ defaultProvider: string;
91
+ /** The default model id. */
92
+ defaultModel: string;
93
+ /** Which harness the composite setup action installs (`pi` default). */
94
+ harness?: HarnessAdapter;
95
+ /** Where to get the CLI from (`registry` default; `workspace` builds from source). */
96
+ installSource?: InstallSource;
97
+ /**
98
+ * Cap on CONCURRENT advance-lifecycle matrix legs (the `max-parallel` of the
99
+ * propose/merge matrices). Each leg spawns a FULL agent session (build + Gate-2
100
+ * review), so an unbounded fan-out over a large item set exhausts the model
101
+ * provider's API rate limit (429s that strand legs as transient-infra stuck) and
102
+ * thrashes the arbiter-main CAS. Default {@link DEFAULT_MAX_PARALLEL}. Must be a
103
+ * positive integer.
104
+ */
105
+ maxParallel?: number;
106
+ /** API key values keyed by env var name. Only present with `--include-secrets`. */
107
+ secrets?: Record<string, string>;
108
+ /**
109
+ * The project-setup escape hatch (prd `install-ci-project-provisioning`,
110
+ * task `install-ci-project-setup-hook`): a PROVIDER-NAMESPACED, OPAQUE,
111
+ * NATIVE-SYNTAX pass-through map. The key is a provider id (today only
112
+ * `github`); the value is the user's own project-toolchain snippet in that
113
+ * provider's NATIVE syntax (for GitHub: a string of Actions step YAML —
114
+ * `uses:` marketplace actions, `run:` steps). The CORE never parses or
115
+ * normalizes the snippet (only validates the outer map shape); the active
116
+ * provider's adapter splices it VERBATIM and FIRST into the generated
117
+ * composite setup action, before dorfl-install + AI-auth (US #10 of
118
+ * `install-ci-project-provisioning`). Absent/empty ⇒ the generated
119
+ * composite action is BYTE-IDENTICAL to today. Round-trips through
120
+ * `--config` / `--export-config` like every existing knob. Deliberately
121
+ * NOT a portable cross-provider step-DSL — each adapter consumes its OWN
122
+ * native shape (ADR `install-ci-project-provisioning-native-passthrough`).
123
+ */
124
+ projectSetup?: Record<string, unknown>;
125
+ }
126
+
127
+ /**
128
+ * The fully-resolved config the generators consume (the wizard / config-file path
129
+ * both produce one of these). Distinct from {@link CIConfigFile} only in that
130
+ * `authMode` + `harness` are guaranteed present and `secrets` is dropped (secrets
131
+ * are orchestrated separately, never written into a generated artifact).
132
+ */
133
+ export interface ResolvedCIConfig {
134
+ authMode: AuthMode;
135
+ providers: ProviderEntry[];
136
+ defaultProvider: string;
137
+ defaultModel: string;
138
+ harness: HarnessAdapter;
139
+ installSource: InstallSource;
140
+ /** See {@link CIConfigFile.projectSetup}. */
141
+ projectSetup?: Record<string, unknown>;
142
+ /** See {@link CIConfigFile.maxParallel}. Always resolved (default applied). */
143
+ maxParallel: number;
144
+ }
145
+
146
+ /** The default harness the composite setup action installs. */
147
+ export const DEFAULT_HARNESS: HarnessAdapter = 'pi';
148
+
149
+ /**
150
+ * The default {@link CIConfigFile.maxParallel}: cap the advance-lifecycle fan-out
151
+ * at 4 concurrent legs. A full agent session per leg, so this keeps the loop
152
+ * draining steadily rather than stampeding the model-provider rate limit + the
153
+ * arbiter CAS. Tunable via `install-ci --max-parallel <n>` / the config file.
154
+ */
155
+ export const DEFAULT_MAX_PARALLEL = 4;
156
+
157
+ /** The default install source: the published CLI via `npm install -g`. */
158
+ export const DEFAULT_INSTALL_SOURCE: InstallSource = 'registry';
159
+
160
+ // ─── The CI-provider SEAM (provider-agnostic; GitHub is the first adapter) ───
161
+
162
+ /**
163
+ * The thin CI-provider seam (whitesmith's proven `GitHubCIContext` shape,
164
+ * generalised so a second provider could slot in without touching the core). A
165
+ * provider supplies: where it is (`repo`), whether its CLI is usable
166
+ * (`ghAvailable` for GitHub), and how to set a secret (`setSecret`). Tests STUB
167
+ * this entirely — `setSecret` records to memory, `ghAvailable=false`, `repo` a
168
+ * fixture — so no network, no real `gh`, no real GitHub repo detection is touched.
169
+ *
170
+ * The historical GitHub name {@link GitHubCIContext} is kept as an alias so the
171
+ * whitesmith vocabulary the task adopts reads literally.
172
+ */
173
+ export interface CIProviderContext {
174
+ /** The target repo's working directory (where `.github/` / `.fake/` is written). */
175
+ workDir: string;
176
+ /**
177
+ * Stable id for THIS provider adapter (today only `github`). Used by the
178
+ * orchestrator to pick the active provider's payload out of
179
+ * {@link CIConfigFile.projectSetup} (the provider-namespaced opaque
180
+ * pass-through). Optional so a provider that never participates in the
181
+ * project-setup hook can omit it; the orchestrator then renders no
182
+ * fragment and the composite action is unchanged.
183
+ */
184
+ readonly providerId?: string;
185
+ /** The `owner/repo` slug, or `undefined` when not (yet) known. */
186
+ repo: string | undefined;
187
+ /** Whether the provider's CLI (`gh`) is available + authenticated. */
188
+ ghAvailable: boolean;
189
+ /** Record/set a secret on the provider. Tests record to memory. */
190
+ setSecret(name: string, value: string): Promise<void>;
191
+ /**
192
+ * OPTIONALLY set a provider repo-setting (capability-F residue: GitHub's
193
+ * `delete_branch_on_merge`). Absent on a provider that has no such setting.
194
+ * Stubbed in tests; never silently toggled (the wizard prompts).
195
+ */
196
+ setRepoSetting?(name: string, value: boolean): Promise<void>;
197
+ /**
198
+ * OPTIONALLY report whether the configured credential has REPO-ADMIN scope
199
+ * on the provider (→ can set branch protection / rulesets). Used by the
200
+ * Tier-1 branch-protection step (prd `land-time-reverify-and-parallel-
201
+ * merge-ceiling`, Story 11) to pick `auto-configure` vs `print fallback`.
202
+ * `true` ⇒ admin, `false` ⇒ explicitly NOT admin, `undefined` ⇒ unknown
203
+ * (treated as non-admin: never silently attempt a call that would 403).
204
+ * Absent on a provider that has no concept of admin scope.
205
+ */
206
+ getRepoAdminScope?(): Promise<boolean | undefined>;
207
+ /**
208
+ * OPTIONALLY configure Tier-1 branch protection on `branch` per `spec`. The
209
+ * spec body (`BranchProtectionSpec` in `install-ci-branch-protection.ts`) is
210
+ * a structural type — typed as `unknown` here to keep this core seam free of
211
+ * the branch-protection module's import. Absent on a non-GitHub provider.
212
+ * Throws on failure (caller catches + logs).
213
+ */
214
+ setBranchProtection?(branch: string, spec: unknown): Promise<void>;
215
+ /**
216
+ * OPTIONALLY render the user's project-setup payload (the value of
217
+ * {@link CIConfigFile.projectSetup}[providerId]) to the NATIVE-SYNTAX
218
+ * fragment the composite setup action splices in FIRST (before
219
+ * setup-node, dorfl-install, and AI-auth). The adapter owns the LIGHT
220
+ * structural sanity check (for GitHub: list-of-mapping-shaped steps) and
221
+ * the indentation that makes the fragment splice cleanly into the
222
+ * composite action's `steps:` body. The returned string is treated as
223
+ * OPAQUE by the core — bytes-in == bytes-out, modulo the indentation the
224
+ * adapter applies. Throws on a malformed payload (caller surfaces). An
225
+ * adapter that never participates in this hook omits the method; an
226
+ * `undefined`/missing payload short-circuits to no fragment.
227
+ */
228
+ renderProjectSetup?(payload: unknown): string;
229
+ }
230
+
231
+ /** Whitesmith's name for {@link CIProviderContext} (the seam this task adopts). */
232
+ export type GitHubCIContext = CIProviderContext;
233
+
234
+ // ─── The capability-emitter REGISTRY seam (file-orthogonality) ───────────────
235
+
236
+ /**
237
+ * One file/artifact a capability emitter produces (a workflow YAML, etc.). `path`
238
+ * is repo-relative (under the output base — `.github/` or `.fake/`).
239
+ */
240
+ export interface EmittedFile {
241
+ /** Path relative to the output base (e.g. `workflows/advance-lifecycle.yml`). */
242
+ path: string;
243
+ /** The file's full text content. */
244
+ content: string;
245
+ }
246
+
247
+ /**
248
+ * A capability emitter: a per-capability module that, given the resolved config,
249
+ * returns the workflow file(s) for its capability. The sibling capability
250
+ * tasks (advance-lifecycle, intake, close-job) each ADD one of these
251
+ * as a NEW self-registering module — NOT an edit to a shared central
252
+ * list/switch — so they stay file-orthogonal and mergeable in parallel
253
+ * (WORK-CONTRACT task-quality / `to-task` §3).
254
+ */
255
+ export interface CapabilityEmitter {
256
+ /** A stable id for the capability (e.g. `advance-lifecycle`). */
257
+ id: string;
258
+ /** Human-readable label for the wizard's capability selection. */
259
+ label: string;
260
+ /** Emit this capability's workflow file(s) for the resolved config. */
261
+ emit(config: ResolvedCIConfig): EmittedFile[];
262
+ }
263
+
264
+ /**
265
+ * The in-memory capability REGISTRY. Capability modules self-register by calling
266
+ * {@link registerCapability} at import time; {@link loadCapabilityRegistry}
267
+ * discovers + imports every module under `install-ci-capabilities/` (a DIRECTORY
268
+ * of emitters, NOT a hand-edited central list), so adding a capability is a NEW
269
+ * file, never an edit to this one.
270
+ */
271
+ const REGISTRY = new Map<string, CapabilityEmitter>();
272
+
273
+ /**
274
+ * Register a capability emitter. Idempotent per id (re-registering the same id
275
+ * replaces it — module import is idempotent). Called at import time by each
276
+ * capability module under `install-ci-capabilities/`.
277
+ */
278
+ export function registerCapability(emitter: CapabilityEmitter): void {
279
+ REGISTRY.set(emitter.id, emitter);
280
+ }
281
+
282
+ /** The currently-registered capabilities (in registration order). */
283
+ export function registeredCapabilities(): CapabilityEmitter[] {
284
+ return [...REGISTRY.values()];
285
+ }
286
+
287
+ /** Reset the registry (tests; never used in production). */
288
+ export function clearCapabilityRegistry(): void {
289
+ REGISTRY.clear();
290
+ }
291
+
292
+ /**
293
+ * Locate the `install-ci-capabilities/` directory next to this module — the
294
+ * DIRECTORY of self-registering capability emitters. Resolved relative to this
295
+ * source file so it works from both `src/` (tsx) and `dist/` (the built CLI).
296
+ * `override` short-circuits for tests / unusual layouts.
297
+ */
298
+ export function resolveCapabilitiesDir(override?: string): string {
299
+ if (override) {
300
+ return override;
301
+ }
302
+ const here = dirname(fileURLToPath(import.meta.url));
303
+ return join(here, 'install-ci-capabilities');
304
+ }
305
+
306
+ /**
307
+ * Discover + import every capability module under `install-ci-capabilities/`,
308
+ * each of which self-registers via {@link registerCapability} at import time.
309
+ * This is the file-orthogonality mechanism: a new capability is a NEW file in
310
+ * that directory, picked up here WITHOUT editing any shared list/switch. Returns
311
+ * the registry after loading. `dir` overrides the directory (tests).
312
+ */
313
+ export async function loadCapabilityRegistry(
314
+ dir?: string,
315
+ ): Promise<CapabilityEmitter[]> {
316
+ const capDir = resolveCapabilitiesDir(dir);
317
+ let entries: string[];
318
+ try {
319
+ entries = readdirSync(capDir);
320
+ } catch {
321
+ return registeredCapabilities(); // no directory yet → nothing to load
322
+ }
323
+ // Import each emitter module. Built output is `.js`; source (tsx) is `.ts`.
324
+ const modules = entries
325
+ .filter((f) => /\.(js|ts)$/.test(f) && !/\.d\.ts$/.test(f))
326
+ .sort();
327
+ for (const file of modules) {
328
+ // A path-based dynamic import works under both tsx (src) and node (dist).
329
+ await import(pathToFileUrl(join(capDir, file)));
330
+ }
331
+ return registeredCapabilities();
332
+ }
333
+
334
+ /** Convert an absolute path to a `file://` URL for a portable dynamic import. */
335
+ function pathToFileUrl(absPath: string): string {
336
+ return new URL(`file://${absPath}`).href;
337
+ }
338
+
339
+ // ─── models.json generation ──────────────────────────────────────────────────
340
+
341
+ /**
342
+ * Build the harness `models.json` object from the providers. Built-in providers
343
+ * (`anthropic`/`openai`) get `{apiKey: <ENV_VAR>}` (the harness resolves the env
344
+ * var at runtime); a custom provider carries its `baseUrl`/`api`/`models`/`compat`.
345
+ * Mirrors whitesmith's `buildModelsJson`.
346
+ */
347
+ export function buildModelsJson(providers: ProviderEntry[]): {
348
+ providers: Record<string, unknown>;
349
+ } {
350
+ const providersObj: Record<string, unknown> = {};
351
+ for (const p of providers) {
352
+ if (p.builtin) {
353
+ const entry: Record<string, unknown> = {apiKey: p.apiKeyEnvVar};
354
+ if (p.baseUrl) entry.baseUrl = p.baseUrl;
355
+ providersObj[p.name] = entry;
356
+ } else {
357
+ const entry: Record<string, unknown> = {
358
+ baseUrl: p.baseUrl,
359
+ api: p.api,
360
+ apiKey: p.apiKeyEnvVar,
361
+ models: p.models,
362
+ };
363
+ if (p.compat) entry.compat = p.compat;
364
+ providersObj[p.name] = entry;
365
+ }
366
+ }
367
+ return {providers: providersObj};
368
+ }
369
+
370
+ // ─── secret-orchestration LOGIC (which secrets, dedup) ───────────────────────
371
+
372
+ /**
373
+ * The DEDUPLICATED set of secret env-var names the config requires, in first-seen
374
+ * order. For `models-json` mode it is one per distinct provider `apiKeyEnvVar`;
375
+ * for `auth-json` mode it is the fixed `PI_AUTH_JSON` + `GH_PAT` pair (the sharp
376
+ * edge). This is the pure secret-orchestration LOGIC the adapter's setter
377
+ * consumes — which secrets, deduped — separated from any provider I/O.
378
+ */
379
+ export function requiredSecretNames(config: ResolvedCIConfig): string[] {
380
+ if (config.authMode === 'auth-json') {
381
+ return ['PI_AUTH_JSON', 'GH_PAT'];
382
+ }
383
+ const seen = new Set<string>();
384
+ const names: string[] = [];
385
+ for (const p of config.providers) {
386
+ if (seen.has(p.apiKeyEnvVar)) continue;
387
+ seen.add(p.apiKeyEnvVar);
388
+ names.push(p.apiKeyEnvVar);
389
+ }
390
+ return names;
391
+ }
392
+
393
+ /**
394
+ * The GitHub secret the propose/merge CI legs read as `GH_TOKEN` to open PRs
395
+ * under a CHOSEN identity (a PAT / App token). It is the PR-IDENTITY token, a
396
+ * distinct purpose from `auth-json` mode's `GH_PAT` (which exists only to rotate
397
+ * the OAuth `PI_AUTH_JSON` secret). The generated workflows reference it as
398
+ * `${{ secrets.DORFL_GH_TOKEN || secrets.GITHUB_TOKEN }}` so it is
399
+ * OPTIONAL: unset ⇒ the legs fall back to the built-in `GITHUB_TOKEN` (PRs are
400
+ * `github-actions[bot]` and do not trigger downstream `on: pull_request`
401
+ * workflows); set ⇒ PRs carry the token's identity and DO trigger them.
402
+ */
403
+ export const PR_IDENTITY_SECRET_NAME = 'DORFL_GH_TOKEN';
404
+
405
+ /**
406
+ * The DEDUPLICATED set of OPTIONAL secret env-var names install-ci offers but
407
+ * never requires. Today that is the single PR-identity token
408
+ * ({@link PR_IDENTITY_SECRET_NAME}). Kept separate from {@link requiredSecretNames}
409
+ * so "required" stays honestly required (an empty required key still SKIPS, but
410
+ * these are offered as explicitly optional and are never part of the required
411
+ * set the export/validation paths reason about). De-duped against the required
412
+ * names so a token already required (it never is today) is not offered twice.
413
+ */
414
+ export function optionalSecretNames(config: ResolvedCIConfig): string[] {
415
+ const required = new Set(requiredSecretNames(config));
416
+ return [PR_IDENTITY_SECRET_NAME].filter((name) => !required.has(name));
417
+ }
418
+
419
+ /**
420
+ * The `with:` block (YAML fragment, leading newline included) the AGENT-RUNNING CI
421
+ * jobs append to their `uses: ./.github/actions/dorfl-setup` line to pass
422
+ * the configured provider API key secret(s) to the setup action ONCE — the action
423
+ * forwards them to `$GITHUB_ENV` so `pi` can authenticate. Indented to attach
424
+ * under a ` - uses: ...` step line (8 spaces for `with:`, 10 for each
425
+ * entry). Each entry maps the action input (named identically to the secret) to
426
+ * `${{ secrets.<NAME> }}`.
427
+ *
428
+ * Empty string when there are no provider keys (auth-json mode, or no providers),
429
+ * so the `uses:` line is emitted bare and unchanged. This is the SINGLE place the
430
+ * provider→`with:` shape lives, shared by every capability workflow generator, so
431
+ * no template re-derives it.
432
+ */
433
+ export function providerSecretsWithBlock(config: ResolvedCIConfig): string {
434
+ const names =
435
+ config.authMode === 'models-json' ? requiredSecretNames(config) : [];
436
+ if (names.length === 0) {
437
+ return '';
438
+ }
439
+ const entries = names
440
+ .map((name) => ` ${name}: \${{ secrets.${name} }}`)
441
+ .join('\n');
442
+ return `\n with:\n${entries}`;
443
+ }
444
+
445
+ /** The outcome of orchestrating one secret through the provider seam. */
446
+ export interface SecretSetResult {
447
+ /** The secret env-var name. */
448
+ name: string;
449
+ /** `set` (recorded on the provider) / `skipped` (no value) / `failed`. */
450
+ status: 'set' | 'skipped' | 'failed';
451
+ /** The failure detail when `status === 'failed'`. */
452
+ error?: string;
453
+ }
454
+
455
+ /**
456
+ * Orchestrate the required secrets through the provider seam: for each
457
+ * DEDUPLICATED required name, take its value from `knownSecrets` (the
458
+ * config-file/`--include-secrets` path) when present, else from `prompt` (the
459
+ * interactive path), then record it via `ctx.setSecret`. An empty value is
460
+ * SKIPPED (never set blank); a `setSecret` rejection is reported as `failed`
461
+ * without aborting the rest. Mirrors whitesmith's `setOrPromptSecrets` dedup +
462
+ * prompt-or-take logic, but provider-agnostic (the seam is `ctx.setSecret`).
463
+ *
464
+ * NO real secret is touched in `--fake` mode / tests: the caller passes a STUB
465
+ * `ctx` whose `setSecret` records to memory. This function never reads/writes a
466
+ * real secrets store, `~`, or git config.
467
+ */
468
+ export async function orchestrateSecrets(options: {
469
+ ctx: CIProviderContext;
470
+ config: ResolvedCIConfig;
471
+ /** Known secret values (config-file `secrets` / `--include-secrets`). */
472
+ knownSecrets?: Record<string, string>;
473
+ /** Prompt for a missing REQUIRED secret value (interactive path). Tests stub it. */
474
+ prompt?: (name: string) => Promise<string>;
475
+ /**
476
+ * Prompt for a missing OPTIONAL secret value ({@link optionalSecretNames}) — the
477
+ * PR-identity token. Distinct from {@link orchestrateSecrets.prompt} so the UI can
478
+ * phrase it as optional (empty ⇒ cleanly skipped, the documented zero-config
479
+ * fallback). Omitted ⇒ optional secrets are taken ONLY from `knownSecrets`
480
+ * (never prompted), so the non-interactive/config-file path is unchanged.
481
+ */
482
+ optionalPrompt?: (name: string) => Promise<string>;
483
+ }): Promise<SecretSetResult[]> {
484
+ const {ctx, config, knownSecrets, prompt, optionalPrompt} = options;
485
+ const results: SecretSetResult[] = [];
486
+ const processOne = async (
487
+ name: string,
488
+ asker: ((name: string) => Promise<string>) | undefined,
489
+ ): Promise<void> => {
490
+ let value = knownSecrets?.[name];
491
+ if (value === undefined && asker) {
492
+ value = await asker(name);
493
+ }
494
+ if (!value) {
495
+ results.push({name, status: 'skipped'});
496
+ return;
497
+ }
498
+ try {
499
+ await ctx.setSecret(name, value);
500
+ results.push({name, status: 'set'});
501
+ } catch (err) {
502
+ results.push({
503
+ name,
504
+ status: 'failed',
505
+ error: err instanceof Error ? err.message : String(err),
506
+ });
507
+ }
508
+ };
509
+ for (const name of requiredSecretNames(config)) {
510
+ await processOne(name, prompt);
511
+ }
512
+ // OPTIONAL secrets (the PR-identity token) come AFTER the required set, are
513
+ // prompted only via `optionalPrompt`, and otherwise behave identically
514
+ // (empty ⇒ skipped, the zero-config fallback).
515
+ for (const name of optionalSecretNames(config)) {
516
+ await processOne(name, optionalPrompt);
517
+ }
518
+ return results;
519
+ }
520
+
521
+ // ─── config load + export ────────────────────────────────────────────────────
522
+
523
+ /** Raised for a malformed `--config <file>`. */
524
+ export class CIConfigError extends Error {}
525
+
526
+ /**
527
+ * Load + validate a `--config <file>`: the non-interactive path that skips all
528
+ * prompts. The loaded config reproduces the wizard's output byte-for-byte. A
529
+ * missing/empty required field is a loud {@link CIConfigError} (never a silent
530
+ * default), because a config file is a deliberate reproduction artifact.
531
+ */
532
+ export function loadCIConfigFile(filePath: string): CIConfigFile {
533
+ let data: CIConfigFile;
534
+ try {
535
+ data = JSON.parse(readFileSync(filePath, 'utf8')) as CIConfigFile;
536
+ } catch (err) {
537
+ throw new CIConfigError(
538
+ `could not read/parse config file ${filePath}: ` +
539
+ (err instanceof Error ? err.message : String(err)),
540
+ );
541
+ }
542
+ const authMode = data.authMode ?? 'models-json';
543
+ if (authMode !== 'models-json' && authMode !== 'auth-json') {
544
+ throw new CIConfigError(
545
+ `config file "authMode" must be "models-json" or "auth-json"`,
546
+ );
547
+ }
548
+ if (!data.defaultProvider) {
549
+ throw new CIConfigError('config file must contain "defaultProvider"');
550
+ }
551
+ if (!data.defaultModel) {
552
+ throw new CIConfigError('config file must contain "defaultModel"');
553
+ }
554
+ if (
555
+ data.installSource !== undefined &&
556
+ data.installSource !== 'registry' &&
557
+ data.installSource !== 'workspace'
558
+ ) {
559
+ throw new CIConfigError(
560
+ `config file "installSource" must be "registry" or "workspace"`,
561
+ );
562
+ }
563
+ if (data.projectSetup !== undefined) {
564
+ if (
565
+ data.projectSetup === null ||
566
+ typeof data.projectSetup !== 'object' ||
567
+ Array.isArray(data.projectSetup)
568
+ ) {
569
+ throw new CIConfigError(
570
+ 'config file "projectSetup" must be a provider-namespaced object ' +
571
+ '(e.g. {"github": "<native step YAML>"}); ' +
572
+ 'the per-provider payload is OPAQUE to the core',
573
+ );
574
+ }
575
+ }
576
+ if (authMode === 'models-json') {
577
+ if (
578
+ !data.providers ||
579
+ !Array.isArray(data.providers) ||
580
+ data.providers.length === 0
581
+ ) {
582
+ throw new CIConfigError(
583
+ 'config file (models-json) must contain a non-empty "providers" array',
584
+ );
585
+ }
586
+ }
587
+ return {
588
+ authMode,
589
+ providers: data.providers ?? [],
590
+ defaultProvider: data.defaultProvider,
591
+ defaultModel: data.defaultModel,
592
+ harness: data.harness,
593
+ installSource: data.installSource,
594
+ secrets: data.secrets,
595
+ projectSetup: data.projectSetup,
596
+ };
597
+ }
598
+
599
+ /** Resolve a loaded/gathered {@link CIConfigFile} into a {@link ResolvedCIConfig}. */
600
+ export function resolveCIConfig(file: CIConfigFile): ResolvedCIConfig {
601
+ return {
602
+ authMode: file.authMode,
603
+ providers: file.providers,
604
+ defaultProvider: file.defaultProvider,
605
+ defaultModel: file.defaultModel,
606
+ harness: file.harness ?? DEFAULT_HARNESS,
607
+ installSource: file.installSource ?? DEFAULT_INSTALL_SOURCE,
608
+ projectSetup: file.projectSetup,
609
+ maxParallel: file.maxParallel ?? DEFAULT_MAX_PARALLEL,
610
+ };
611
+ }
612
+
613
+ /**
614
+ * Serialise a config to the `--export-config` JSON text (stable 2-space indent +
615
+ * trailing newline, exactly the shape {@link loadCIConfigFile} reads back). When
616
+ * `secrets` is supplied (the `--include-secrets` path) it is included; otherwise
617
+ * the `secrets` field is omitted entirely (never an empty object).
618
+ */
619
+ export function exportCIConfig(
620
+ config: ResolvedCIConfig,
621
+ secrets?: Record<string, string>,
622
+ ): string {
623
+ const file: CIConfigFile = {
624
+ authMode: config.authMode,
625
+ providers: config.providers,
626
+ defaultProvider: config.defaultProvider,
627
+ defaultModel: config.defaultModel,
628
+ harness: config.harness,
629
+ installSource: config.installSource,
630
+ };
631
+ // Emit `maxParallel` only when it deviates from the default, so an unchanged
632
+ // config stays minimal (mirrors how projectSetup is omitted when empty).
633
+ if (config.maxParallel !== DEFAULT_MAX_PARALLEL) {
634
+ file.maxParallel = config.maxParallel;
635
+ }
636
+ if (config.projectSetup && Object.keys(config.projectSetup).length > 0) {
637
+ file.projectSetup = config.projectSetup;
638
+ }
639
+ if (secrets && Object.keys(secrets).length > 0) {
640
+ file.secrets = secrets;
641
+ }
642
+ return JSON.stringify(file, null, 2) + '\n';
643
+ }
644
+
645
+ // ─── composite setup action + OAuth-refresh script generation ────────────────
646
+
647
+ /** Indent every non-blank line of `text` by `spaces` spaces. */
648
+ function indent(text: string, spaces: number): string {
649
+ const pad = ' '.repeat(spaces);
650
+ return text
651
+ .split('\n')
652
+ .map((line) => (line.trim() === '' ? '' : pad + line))
653
+ .join('\n');
654
+ }
655
+
656
+ /**
657
+ * The install step for the configured harness (the `pi` CLI; `''` ⇒ none).
658
+ * `registry` mode installs via `npm install -g`; `workspace` mode installs via
659
+ * `pnpm add -g` so the harness lands on the pnpm global bin already on
660
+ * `$GITHUB_PATH` (mirroring whitesmith's dev mode).
661
+ */
662
+ function harnessInstallStep(
663
+ harness: HarnessAdapter,
664
+ installSource: InstallSource,
665
+ ): string {
666
+ if (harness === 'pi') {
667
+ const run =
668
+ installSource === 'workspace'
669
+ ? 'pnpm add -g @mariozechner/pi-coding-agent'
670
+ : 'npm install -g @mariozechner/pi-coding-agent';
671
+ return `
672
+ - name: Install agent harness (pi)
673
+ shell: bash
674
+ run: ${run}`;
675
+ }
676
+ return '';
677
+ }
678
+
679
+ /**
680
+ * Generate the shared COMPOSITE SETUP ACTION (`dorfl-setup`): installs
681
+ * Node + `dorfl` + the configured harness, configures git identity, and
682
+ * configures AI-provider auth. Written to
683
+ * `<base>/actions/dorfl-setup/action.yml` so a workflow can
684
+ * `uses: ./.github/actions/dorfl-setup`. The advance-loop seed template
685
+ * already references this exact action name + path (`docs/ci/README.md`).
686
+ *
687
+ * The auth step branches on the mode: `models-json` writes a generated
688
+ * `~/.pi/agent/models.json` inline (the conservative default) AND exports each
689
+ * configured provider's API key from a same-named ACTION INPUT to `$GITHUB_ENV`,
690
+ * so the agent step (`pi`) can resolve the env var `models.json` references; the
691
+ * workflow passes the secrets ONCE via `with:` on the `uses:` line (no
692
+ * per-step / per-capability provider enumeration). `auth-json` writes
693
+ * `~/.pi/agent/auth.json` from `$PI_AUTH_JSON` + runs the OAuth-refresh script
694
+ * (the sharp edge). Deterministic: the same config produces byte-identical output.
695
+ */
696
+ export function generateSetupAction(
697
+ config: ResolvedCIConfig,
698
+ /**
699
+ * OPTIONAL provider-rendered project-setup steps fragment, already indented
700
+ * to splice into the composite action's `steps:` body (4-space indent on
701
+ * the `- ` list-item lines). The CORE treats this as OPAQUE: bytes-in ==
702
+ * bytes-out (modulo the indentation the adapter already applied). When
703
+ * absent/empty, the generated action is BYTE-IDENTICAL to the no-hook
704
+ * baseline (the load-bearing absent-⇒-identical contract).
705
+ */
706
+ projectSetupSteps?: string,
707
+ ): string {
708
+ let authStep: string;
709
+ // models-json mode declares one ACTION INPUT per distinct provider key (named
710
+ // identically to the secret / env var) and exports it to `$GITHUB_ENV`, so every
711
+ // later step inherits it. auth-json mode has no provider keys (it uses auth.json).
712
+ const providerKeyNames =
713
+ config.authMode === 'models-json' ? requiredSecretNames(config) : [];
714
+ if (config.authMode === 'auth-json') {
715
+ authStep = `\
716
+ - name: Configure agent auth (auth.json)
717
+ shell: bash
718
+ run: |
719
+ if [ -z "$PI_AUTH_JSON" ]; then
720
+ echo "ERROR: PI_AUTH_JSON secret is not set" >&2; exit 1
721
+ fi
722
+ mkdir -p ~/.pi/agent
723
+ echo "$PI_AUTH_JSON" > ~/.pi/agent/auth.json
724
+ chmod 600 ~/.pi/agent/auth.json
725
+
726
+ # Workaround for https://github.com/badlogic/pi-mono/issues/2743 — the SINGLE
727
+ # CI→repo mutation in the design; needs GH_PAT to rotate PI_AUTH_JSON.
728
+ - name: Refresh OAuth token
729
+ shell: bash
730
+ run: node .github/scripts/refresh-oauth-token.mjs`;
731
+ } else {
732
+ const modelsJsonStr = JSON.stringify(
733
+ buildModelsJson(config.providers),
734
+ null,
735
+ 2,
736
+ );
737
+ // Map each provider-key INPUT into the step env, then append the non-empty
738
+ // ones to `$GITHUB_ENV` so the agent step inherits them. Values flow via env
739
+ // (never interpolated into the script body), and Actions masks the secret in
740
+ // logs. `models.json`'s `apiKey` is the env-var NAME; this puts the value there.
741
+ const exportEnvLines = providerKeyNames
742
+ .map((name) => ` ${name}: \${{ inputs.${name} }}`)
743
+ .join('\n');
744
+ const exportRunLines = providerKeyNames
745
+ .map(
746
+ (name) =>
747
+ ` if [ -n "\${${name}}" ]; then echo "${name}=\${${name}}" >> "$GITHUB_ENV"; fi`,
748
+ )
749
+ .join('\n');
750
+ const exportStep =
751
+ providerKeyNames.length > 0
752
+ ? `\n
753
+ # Surface the configured provider API key(s) to the environment so the agent
754
+ # step (\`pi\`) can resolve the env var \`models.json\` references. The workflow
755
+ # passes the secret(s) via \`with:\` on the \`uses:\` line; we forward the
756
+ # non-empty one(s) to \`$GITHUB_ENV\` (Actions masks the value in logs).
757
+ - name: Export provider API key(s) to the environment
758
+ shell: bash
759
+ env:
760
+ ${exportEnvLines}
761
+ run: |
762
+ ${exportRunLines}`
763
+ : '';
764
+ authStep = `\
765
+ - name: Configure agent models (models.json)
766
+ shell: bash
767
+ run: |
768
+ mkdir -p ~/.pi/agent
769
+ cat > ~/.pi/agent/models.json << 'MODELS_EOF'
770
+ ${indent(modelsJsonStr, 8)}
771
+ MODELS_EOF${exportStep}`;
772
+ }
773
+
774
+ const installHarness = harnessInstallStep(
775
+ config.harness,
776
+ config.installSource,
777
+ );
778
+
779
+ // The CLI-install block branches on installSource. `registry` installs the
780
+ // published CLI via `npm install -g dorfl` (the default for every
781
+ // consumer repo). `workspace` builds the CLI from the checked-out source and
782
+ // links it onto PATH — for the self-hosting dorfl monorepo, which is
783
+ // not published under that npm name. We add pnpm's global bin to $GITHUB_PATH
784
+ // so the linked `dorfl` (and the pnpm-installed harness) are on PATH in
785
+ // all subsequent steps; we always rebuild because source changes per commit.
786
+ let installSteps: string;
787
+ if (config.installSource === 'workspace') {
788
+ installSteps = `\
789
+ - name: Setup pnpm
790
+ uses: pnpm/action-setup@v4
791
+
792
+ - name: Add pnpm global bin to PATH
793
+ shell: bash
794
+ run: |
795
+ pnpm setup
796
+ echo "$HOME/.local/share/pnpm" >> "$GITHUB_PATH"
797
+
798
+ - name: Install dependencies and build dorfl
799
+ shell: bash
800
+ run: |
801
+ pnpm install
802
+ pnpm -r build
803
+ cd packages/dorfl && pnpm link --global${installHarness}`;
804
+ } else {
805
+ installSteps = `\
806
+ - name: Install dorfl
807
+ shell: bash
808
+ run: npm install -g dorfl${installHarness}`;
809
+ }
810
+
811
+ // PREFER-LOCAL RESOLVER (task install-ci-prefer-project-local-dorfl,
812
+ // PRD install-ci-project-provisioning axis C1+C3): emit a `dorfl` SHIM
813
+ // that resolves a project-local `node_modules/.bin/dorfl` FIRST (the repo's
814
+ // devDep pin, populated by the project-setup hook's `pnpm install` — C3),
815
+ // and falls back to the global bootstrap dorfl this composite action just
816
+ // installed (C1). Prepended to `$GITHUB_PATH` so EVERY downstream
817
+ // `dorfl <verb>` step in EVERY capability workflow inherits the resolver
818
+ // without each template editing its literal `dorfl` invocation (single
819
+ // shared prefix, no per-capability drift). The captured global path is
820
+ // written to a sibling file so the shim's `command -v` never recurses into
821
+ // itself once the shim dir is on PATH. Applied uniformly in both registry
822
+ // and workspace modes; the workspace-mode install path itself is untouched
823
+ // (the shim runs AFTER the install).
824
+ const resolverStep = `\
825
+
826
+ # Generated by install-ci (task install-ci-prefer-project-local-dorfl):
827
+ # a single shared shim that prefers a project-local node_modules/.bin/dorfl
828
+ # (the repo's devDep pin, C3) over the global bootstrap dorfl just installed
829
+ # (C1), so CI runs the dorfl the repo DECLARES, not a skewed global.
830
+ - name: Install dorfl resolver (prefer project-local over global)
831
+ shell: bash
832
+ run: |
833
+ global_dorfl="$(command -v dorfl || true)"
834
+ if [ -z "$global_dorfl" ]; then
835
+ echo "ERROR: dorfl bootstrap not found on PATH after install" >&2
836
+ exit 1
837
+ fi
838
+ shim_dir="\${RUNNER_TEMP:-/tmp}/dorfl-resolver"
839
+ mkdir -p "$shim_dir"
840
+ printf '%s\\n' "$global_dorfl" > "$shim_dir/global-path"
841
+ # Write the shim via printf (not a heredoc) so the shebang lands at
842
+ # column 0 — a YAML \`run: |\` block's common-indent would otherwise
843
+ # prefix every heredoc line and break the shebang.
844
+ printf '%s\\n' \\
845
+ '#!/usr/bin/env bash' \\
846
+ '# Prefer the project-pinned dorfl (devDep, populated by the' \\
847
+ "# project's dependency install) over the global bootstrap." \\
848
+ '# Generated by install-ci.' \\
849
+ 'local_bin="\${GITHUB_WORKSPACE:-$PWD}/node_modules/.bin/dorfl"' \\
850
+ 'if [ -x "$local_bin" ]; then' \\
851
+ ' exec "$local_bin" "$@"' \\
852
+ 'fi' \\
853
+ 'global_path_file="$(dirname "$0")/global-path"' \\
854
+ 'exec "$(cat "$global_path_file")" "$@"' \\
855
+ > "$shim_dir/dorfl"
856
+ chmod +x "$shim_dir/dorfl"
857
+ echo "$shim_dir" >> "$GITHUB_PATH"`;
858
+ installSteps = installSteps + resolverStep;
859
+
860
+ // One optional ACTION INPUT per provider key (models-json mode), named
861
+ // identically to the secret/env var. Optional + default '' so a workflow that
862
+ // does not pass it (or auth-json mode, which declares none) is valid; the export
863
+ // step skips an empty value. auth-json mode declares no inputs.
864
+ const inputsBlock =
865
+ providerKeyNames.length > 0
866
+ ? `inputs:
867
+ ${providerKeyNames
868
+ .map(
869
+ (name) =>
870
+ ` ${name}:
871
+ description: 'Provider API key, forwarded to the agent environment as ${name}.'
872
+ required: false
873
+ default: ''`,
874
+ )
875
+ .join('\n')}
876
+
877
+ `
878
+ : '';
879
+
880
+ // The project-setup fragment splices in FIRST, before setup-node, dorfl-install,
881
+ // and AI-auth (US #10 of `install-ci-project-provisioning`). Absent/empty ⇒ the
882
+ // emitted text below is byte-identical to the no-hook baseline.
883
+ const projectSetupBlock =
884
+ projectSetupSteps && projectSetupSteps.length > 0
885
+ ? `${projectSetupSteps.replace(/\s+$/, '')}\n\n`
886
+ : '';
887
+
888
+ return `\
889
+ name: Setup dorfl
890
+ description: Install Node.js, dorfl, the agent harness, and configure AI provider auth
891
+
892
+ ${inputsBlock}# Project-toolchain boundary (task install-ci-document-toolchain-boundary):
893
+ # This composite action provisions ONLY what dorfl + the agent harness need.
894
+ # dorfl declares engines.node '>=18' and only PINS Node 22 below for its own
895
+ # runtime — it does NOT provision the project's own toolchain (a project-
896
+ # pinned package manager, a project Node version, rust, system packages,
897
+ # project-dep install steps, ...).
898
+ # A custom or conflicting project toolchain is supported ONLY via the
899
+ # project-setup hook (sibling task install-ci-project-setup-hook); without
900
+ # that hook the conflicting case is unsupported. install-ci does NOT detect
901
+ # a likely conflict — the boundary is documentation, not detection.
902
+ runs:
903
+ using: composite
904
+ steps:
905
+ ${projectSetupBlock} - name: Setup Node.js
906
+ uses: actions/setup-node@v4
907
+ with:
908
+ node-version: '22'
909
+
910
+ - name: Configure git identity
911
+ shell: bash
912
+ run: |
913
+ git config user.name "dorfl[bot]"
914
+ git config user.email "dorfl[bot]@users.noreply.github.com"
915
+
916
+ ${installSteps}
917
+
918
+ ${authStep}
919
+ `;
920
+ }
921
+
922
+ /**
923
+ * The OAuth-refresh script emitted ONLY in `auth-json` mode (whitesmith's
924
+ * `pi-mono#2743` workaround): refreshes the Anthropic OAuth token in
925
+ * `~/.pi/agent/auth.json` before the agent runs, then writes the rotated token
926
+ * BACK via `gh secret set PI_AUTH_JSON` (the single CI→repo mutation, needing a
927
+ * `GH_PAT`). This is the documented SHARP EDGE; `models-json` mode avoids it.
928
+ */
929
+ export const REFRESH_OAUTH_SCRIPT = `\
930
+ #!/usr/bin/env node
931
+ /**
932
+ * Refresh OAuth tokens in the agent's auth.json before it runs.
933
+ *
934
+ * Workaround for https://github.com/badlogic/pi-mono/issues/2743.
935
+ * After refreshing, updates the PI_AUTH_JSON GitHub secret so the next run has
936
+ * the latest rotated refresh token (requires GH_PAT with repo scope). This is the
937
+ * known SHARP EDGE of auth-json mode; models-json mode avoids it entirely.
938
+ *
939
+ * Remove this script once the upstream fix is released.
940
+ */
941
+ import { readFileSync, writeFileSync, chmodSync } from "fs";
942
+ import { join } from "path";
943
+ import { execSync } from "child_process";
944
+
945
+ const ANTHROPIC_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
946
+ const ANTHROPIC_TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
947
+
948
+ const authPath = join(process.env.HOME, ".pi", "agent", "auth.json");
949
+ const auth = JSON.parse(readFileSync(authPath, "utf-8"));
950
+ const cred = auth.anthropic;
951
+
952
+ if (!cred || cred.type !== "oauth") {
953
+ console.log("No OAuth credentials for anthropic, skipping refresh");
954
+ process.exit(0);
955
+ }
956
+
957
+ if (Date.now() < cred.expires) {
958
+ console.log("Token still valid until", new Date(cred.expires).toISOString());
959
+ process.exit(0);
960
+ }
961
+
962
+ console.log(
963
+ "Token expired at",
964
+ new Date(cred.expires).toISOString(),
965
+ "- refreshing..."
966
+ );
967
+
968
+ const response = await fetch(ANTHROPIC_TOKEN_URL, {
969
+ method: "POST",
970
+ headers: {
971
+ "Content-Type": "application/x-www-form-urlencoded",
972
+ Accept: "application/json",
973
+ },
974
+ body: new URLSearchParams({
975
+ grant_type: "refresh_token",
976
+ client_id: ANTHROPIC_CLIENT_ID,
977
+ refresh_token: cred.refresh,
978
+ }).toString(),
979
+ signal: AbortSignal.timeout(30_000),
980
+ });
981
+
982
+ const data = await response.json();
983
+
984
+ if (!response.ok) {
985
+ console.error("Refresh failed:", response.status, JSON.stringify(data));
986
+ process.exit(1);
987
+ }
988
+
989
+ auth.anthropic = {
990
+ type: "oauth",
991
+ refresh: data.refresh_token,
992
+ access: data.access_token,
993
+ expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
994
+ };
995
+
996
+ writeFileSync(authPath, JSON.stringify(auth, null, 2));
997
+ chmodSync(authPath, 0o600);
998
+ console.log(
999
+ "Token refreshed, new expiry:",
1000
+ new Date(auth.anthropic.expires).toISOString()
1001
+ );
1002
+
1003
+ // Update the GitHub secret so the next run has the latest refresh token.
1004
+ const repo = process.env.GITHUB_REPOSITORY;
1005
+ const token = process.env.GH_PAT;
1006
+ if (repo && token) {
1007
+ try {
1008
+ execSync(\`gh secret set PI_AUTH_JSON --repo "\${repo}"\`, {
1009
+ input: JSON.stringify(auth),
1010
+ env: { ...process.env, GH_TOKEN: token },
1011
+ stdio: ["pipe", "pipe", "pipe"],
1012
+ });
1013
+ console.log("PI_AUTH_JSON secret updated");
1014
+ } catch (err) {
1015
+ console.warn("Failed to update secret (non-fatal):", err.stderr?.toString() || err.message);
1016
+ }
1017
+ } else {
1018
+ console.log("Skipping secret update (no GH_PAT or GITHUB_REPOSITORY)");
1019
+ }
1020
+ `;
1021
+
1022
+ // ─── the --fake snapshot mechanism + artifact assembly ───────────────────────
1023
+
1024
+ /**
1025
+ * The setup ARTIFACTS this task generates: the composite setup action + (in
1026
+ * `auth-json` mode) the OAuth-refresh script, plus any selected capabilities'
1027
+ * workflow files. This task emits NO capability workflow itself (those are the
1028
+ * sibling tasks); `capabilities` defaults to none.
1029
+ */
1030
+ export function buildSetupArtifacts(
1031
+ config: ResolvedCIConfig,
1032
+ capabilities: CapabilityEmitter[] = [],
1033
+ /**
1034
+ * OPTIONAL bag of provider-rendered fragments to splice into the generated
1035
+ * artifacts. `projectSetupSteps`: see {@link generateSetupAction}. Threaded
1036
+ * here so the orchestrator (`installCI`) can pre-render via the active
1037
+ * provider seam and keep this core function provider-agnostic.
1038
+ */
1039
+ options?: {projectSetupSteps?: string},
1040
+ ): EmittedFile[] {
1041
+ const files: EmittedFile[] = [
1042
+ {
1043
+ path: join('actions', 'dorfl-setup', 'action.yml'),
1044
+ content: generateSetupAction(config, options?.projectSetupSteps),
1045
+ },
1046
+ ];
1047
+ if (config.authMode === 'auth-json') {
1048
+ files.push({
1049
+ path: join('scripts', 'refresh-oauth-token.mjs'),
1050
+ content: REFRESH_OAUTH_SCRIPT,
1051
+ });
1052
+ }
1053
+ for (const cap of capabilities) {
1054
+ files.push(...cap.emit(config));
1055
+ }
1056
+ return files;
1057
+ }
1058
+
1059
+ /**
1060
+ * The output base directory name: `.fake` in `--fake` snapshot mode (a scratch
1061
+ * dir, NEVER `.github/`), else `.github`. The `--fake` mechanism: write to a
1062
+ * `.fake/` scratch dir, never the real `.github/`, and set NO real secret — so
1063
+ * the produced files are snapshot-asserted with no side effects on the repo.
1064
+ */
1065
+ export function outputBaseName(fake: boolean): string {
1066
+ return fake ? '.fake' : '.github';
1067
+ }
1068
+
1069
+ /**
1070
+ * Write the artifacts under `<workDir>/<base>/` (base = `.fake` when `fake`, else
1071
+ * `.github`). Returns the repo-relative paths actually written. Pure filesystem
1072
+ * I/O — no secrets, no git. Creates parent dirs as needed.
1073
+ */
1074
+ export function writeArtifacts(options: {
1075
+ workDir: string;
1076
+ fake: boolean;
1077
+ files: EmittedFile[];
1078
+ }): string[] {
1079
+ const base = outputBaseName(options.fake);
1080
+ const written: string[] = [];
1081
+ for (const file of options.files) {
1082
+ const abs = join(options.workDir, base, file.path);
1083
+ mkdirSync(dirname(abs), {recursive: true});
1084
+ writeFileSync(abs, file.content, 'utf8');
1085
+ written.push(relative(options.workDir, abs));
1086
+ }
1087
+ return written;
1088
+ }