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.
- package/dist/advance-ci-template.d.ts +73 -0
- package/dist/advance-ci-template.d.ts.map +1 -0
- package/dist/advance-ci-template.js +104 -0
- package/dist/advance-ci-template.js.map +1 -0
- package/dist/advance-classify.d.ts +132 -0
- package/dist/advance-classify.d.ts.map +1 -0
- package/dist/advance-classify.js +120 -0
- package/dist/advance-classify.js.map +1 -0
- package/dist/advance-drivers.d.ts +182 -0
- package/dist/advance-drivers.d.ts.map +1 -0
- package/dist/advance-drivers.js +231 -0
- package/dist/advance-drivers.js.map +1 -0
- package/dist/advance-isolated.d.ts +156 -0
- package/dist/advance-isolated.d.ts.map +1 -0
- package/dist/advance-isolated.js +256 -0
- package/dist/advance-isolated.js.map +1 -0
- package/dist/advance-lifecycle-template.d.ts +107 -0
- package/dist/advance-lifecycle-template.d.ts.map +1 -0
- package/dist/advance-lifecycle-template.js +668 -0
- package/dist/advance-lifecycle-template.js.map +1 -0
- package/dist/advance-loop-driver.d.ts +325 -0
- package/dist/advance-loop-driver.d.ts.map +1 -0
- package/dist/advance-loop-driver.js +437 -0
- package/dist/advance-loop-driver.js.map +1 -0
- package/dist/advance-treeless-publish.d.ts +108 -0
- package/dist/advance-treeless-publish.d.ts.map +1 -0
- package/dist/advance-treeless-publish.js +71 -0
- package/dist/advance-treeless-publish.js.map +1 -0
- package/dist/advance.d.ts +340 -0
- package/dist/advance.d.ts.map +1 -0
- package/dist/advance.js +1122 -0
- package/dist/advance.js.map +1 -0
- package/dist/advancing-lock.d.ts +294 -0
- package/dist/advancing-lock.d.ts.map +1 -0
- package/dist/advancing-lock.js +594 -0
- package/dist/advancing-lock.js.map +1 -0
- package/dist/agent-launch.d.ts +79 -0
- package/dist/agent-launch.d.ts.map +1 -0
- package/dist/agent-launch.js +61 -0
- package/dist/agent-launch.js.map +1 -0
- package/dist/agent-stop.d.ts +149 -0
- package/dist/agent-stop.d.ts.map +1 -0
- package/dist/agent-stop.js +307 -0
- package/dist/agent-stop.js.map +1 -0
- package/dist/apply-decide.d.ts +127 -0
- package/dist/apply-decide.d.ts.map +1 -0
- package/dist/apply-decide.js +176 -0
- package/dist/apply-decide.js.map +1 -0
- package/dist/apply-merge-action.d.ts +206 -0
- package/dist/apply-merge-action.d.ts.map +1 -0
- package/dist/apply-merge-action.js +307 -0
- package/dist/apply-merge-action.js.map +1 -0
- package/dist/apply-persist.d.ts +174 -0
- package/dist/apply-persist.d.ts.map +1 -0
- package/dist/apply-persist.js +359 -0
- package/dist/apply-persist.js.map +1 -0
- package/dist/arbiter.d.ts +120 -0
- package/dist/arbiter.d.ts.map +1 -0
- package/dist/arbiter.js +255 -0
- package/dist/arbiter.js.map +1 -0
- package/dist/brand.d.ts +70 -0
- package/dist/brand.d.ts.map +1 -0
- package/dist/brand.js +84 -0
- package/dist/brand.js.map +1 -0
- package/dist/buildable-body.d.ts +132 -0
- package/dist/buildable-body.d.ts.map +1 -0
- package/dist/buildable-body.js +131 -0
- package/dist/buildable-body.js.map +1 -0
- package/dist/categorise.d.ts +66 -0
- package/dist/categorise.d.ts.map +1 -0
- package/dist/categorise.js +106 -0
- package/dist/categorise.js.map +1 -0
- package/dist/claim-cas.d.ts +117 -0
- package/dist/claim-cas.d.ts.map +1 -0
- package/dist/claim-cas.js +312 -0
- package/dist/claim-cas.js.map +1 -0
- package/dist/cli-spinner.d.ts +112 -0
- package/dist/cli-spinner.d.ts.map +1 -0
- package/dist/cli-spinner.js +157 -0
- package/dist/cli-spinner.js.map +1 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3094 -0
- package/dist/cli.js.map +1 -0
- package/dist/close-job-template.d.ts +70 -0
- package/dist/close-job-template.d.ts.map +1 -0
- package/dist/close-job-template.js +180 -0
- package/dist/close-job-template.js.map +1 -0
- package/dist/close-job.d.ts +95 -0
- package/dist/close-job.d.ts.map +1 -0
- package/dist/close-job.js +226 -0
- package/dist/close-job.js.map +1 -0
- package/dist/complete.d.ts +361 -0
- package/dist/complete.d.ts.map +1 -0
- package/dist/complete.js +885 -0
- package/dist/complete.js.map +1 -0
- package/dist/concurrency.d.ts +68 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +112 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/config-override.d.ts +76 -0
- package/dist/config-override.d.ts.map +1 -0
- package/dist/config-override.js +50 -0
- package/dist/config-override.js.map +1 -0
- package/dist/config.d.ts +668 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +241 -0
- package/dist/config.js.map +1 -0
- package/dist/continue-branch.d.ts +249 -0
- package/dist/continue-branch.d.ts.map +1 -0
- package/dist/continue-branch.js +389 -0
- package/dist/continue-branch.js.map +1 -0
- package/dist/cwd-section.d.ts +186 -0
- package/dist/cwd-section.d.ts.map +1 -0
- package/dist/cwd-section.js +209 -0
- package/dist/cwd-section.js.map +1 -0
- package/dist/decision-engine.d.ts +170 -0
- package/dist/decision-engine.d.ts.map +1 -0
- package/dist/decision-engine.js +136 -0
- package/dist/decision-engine.js.map +1 -0
- package/dist/detect.d.ts +17 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +118 -0
- package/dist/detect.js.map +1 -0
- package/dist/do-autopick.d.ts +85 -0
- package/dist/do-autopick.d.ts.map +1 -0
- package/dist/do-autopick.js +112 -0
- package/dist/do-autopick.js.map +1 -0
- package/dist/do-config.d.ts +312 -0
- package/dist/do-config.d.ts.map +1 -0
- package/dist/do-config.js +358 -0
- package/dist/do-config.js.map +1 -0
- package/dist/do-remote-auto.d.ts +75 -0
- package/dist/do-remote-auto.d.ts.map +1 -0
- package/dist/do-remote-auto.js +111 -0
- package/dist/do-remote-auto.js.map +1 -0
- package/dist/do.d.ts +621 -0
- package/dist/do.d.ts.map +1 -0
- package/dist/do.js +1882 -0
- package/dist/do.js.map +1 -0
- package/dist/drop-source.d.ts +96 -0
- package/dist/drop-source.d.ts.map +1 -0
- package/dist/drop-source.js +91 -0
- package/dist/drop-source.js.map +1 -0
- package/dist/eligibility.d.ts +46 -0
- package/dist/eligibility.d.ts.map +1 -0
- package/dist/eligibility.js +34 -0
- package/dist/eligibility.js.map +1 -0
- package/dist/env-config.d.ts +51 -0
- package/dist/env-config.d.ts.map +1 -0
- package/dist/env-config.js +272 -0
- package/dist/env-config.js.map +1 -0
- package/dist/failure-cause.d.ts +70 -0
- package/dist/failure-cause.d.ts.map +1 -0
- package/dist/failure-cause.js +126 -0
- package/dist/failure-cause.js.map +1 -0
- package/dist/format.d.ts +43 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +256 -0
- package/dist/format.js.map +1 -0
- package/dist/frontmatter.d.ts +215 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +345 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/gate-readiness.d.ts +84 -0
- package/dist/gate-readiness.d.ts.map +1 -0
- package/dist/gate-readiness.js +103 -0
- package/dist/gate-readiness.js.map +1 -0
- package/dist/gc.d.ts +165 -0
- package/dist/gc.d.ts.map +1 -0
- package/dist/gc.js +313 -0
- package/dist/gc.js.map +1 -0
- package/dist/gh-failure.d.ts +42 -0
- package/dist/gh-failure.d.ts.map +1 -0
- package/dist/gh-failure.js +49 -0
- package/dist/gh-failure.js.map +1 -0
- package/dist/git.d.ts +75 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +130 -0
- package/dist/git.js.map +1 -0
- package/dist/github.d.ts +187 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js +343 -0
- package/dist/github.js.map +1 -0
- package/dist/harness.d.ts +242 -0
- package/dist/harness.d.ts.map +1 -0
- package/dist/harness.js +157 -0
- package/dist/harness.js.map +1 -0
- package/dist/identity.d.ts +167 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +231 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/install-ci-branch-protection.d.ts +147 -0
- package/dist/install-ci-branch-protection.d.ts.map +1 -0
- package/dist/install-ci-branch-protection.js +166 -0
- package/dist/install-ci-branch-protection.js.map +1 -0
- package/dist/install-ci-capabilities/advance-lifecycle.d.ts +15 -0
- package/dist/install-ci-capabilities/advance-lifecycle.d.ts.map +1 -0
- package/dist/install-ci-capabilities/advance-lifecycle.js +28 -0
- package/dist/install-ci-capabilities/advance-lifecycle.js.map +1 -0
- package/dist/install-ci-capabilities/close-job.d.ts +13 -0
- package/dist/install-ci-capabilities/close-job.d.ts.map +1 -0
- package/dist/install-ci-capabilities/close-job.js +26 -0
- package/dist/install-ci-capabilities/close-job.js.map +1 -0
- package/dist/install-ci-capabilities/example-noop.d.ts +16 -0
- package/dist/install-ci-capabilities/example-noop.d.ts.map +1 -0
- package/dist/install-ci-capabilities/example-noop.js +23 -0
- package/dist/install-ci-capabilities/example-noop.js.map +1 -0
- package/dist/install-ci-capabilities/intake.d.ts +15 -0
- package/dist/install-ci-capabilities/intake.d.ts.map +1 -0
- package/dist/install-ci-capabilities/intake.js +28 -0
- package/dist/install-ci-capabilities/intake.js.map +1 -0
- package/dist/install-ci-capabilities/verify.d.ts +14 -0
- package/dist/install-ci-capabilities/verify.d.ts.map +1 -0
- package/dist/install-ci-capabilities/verify.js +27 -0
- package/dist/install-ci-capabilities/verify.js.map +1 -0
- package/dist/install-ci-core.d.ts +446 -0
- package/dist/install-ci-core.d.ts.map +1 -0
- package/dist/install-ci-core.js +760 -0
- package/dist/install-ci-core.js.map +1 -0
- package/dist/install-ci-github.d.ts +167 -0
- package/dist/install-ci-github.d.ts.map +1 -0
- package/dist/install-ci-github.js +315 -0
- package/dist/install-ci-github.js.map +1 -0
- package/dist/install-ci.d.ts +105 -0
- package/dist/install-ci.d.ts.map +1 -0
- package/dist/install-ci.js +363 -0
- package/dist/install-ci.js.map +1 -0
- package/dist/intake-event.d.ts +88 -0
- package/dist/intake-event.d.ts.map +1 -0
- package/dist/intake-event.js +66 -0
- package/dist/intake-event.js.map +1 -0
- package/dist/intake-marker.d.ts +95 -0
- package/dist/intake-marker.d.ts.map +1 -0
- package/dist/intake-marker.js +127 -0
- package/dist/intake-marker.js.map +1 -0
- package/dist/intake-triage.d.ts +48 -0
- package/dist/intake-triage.d.ts.map +1 -0
- package/dist/intake-triage.js +95 -0
- package/dist/intake-triage.js.map +1 -0
- package/dist/intake-trigger-template.d.ts +185 -0
- package/dist/intake-trigger-template.d.ts.map +1 -0
- package/dist/intake-trigger-template.js +449 -0
- package/dist/intake-trigger-template.js.map +1 -0
- package/dist/intake.d.ts +569 -0
- package/dist/intake.d.ts.map +1 -0
- package/dist/intake.js +1628 -0
- package/dist/intake.js.map +1 -0
- package/dist/integration-core.d.ts +539 -0
- package/dist/integration-core.d.ts.map +1 -0
- package/dist/integration-core.js +2195 -0
- package/dist/integration-core.js.map +1 -0
- package/dist/integrator.d.ts +343 -0
- package/dist/integrator.d.ts.map +1 -0
- package/dist/integrator.js +400 -0
- package/dist/integrator.js.map +1 -0
- package/dist/isolation.d.ts +219 -0
- package/dist/isolation.d.ts.map +1 -0
- package/dist/isolation.js +261 -0
- package/dist/isolation.js.map +1 -0
- package/dist/issue-provider.d.ts +349 -0
- package/dist/issue-provider.d.ts.map +1 -0
- package/dist/issue-provider.js +360 -0
- package/dist/issue-provider.js.map +1 -0
- package/dist/item-lock.d.ts +626 -0
- package/dist/item-lock.d.ts.map +1 -0
- package/dist/item-lock.js +1381 -0
- package/dist/item-lock.js.map +1 -0
- package/dist/item-path.d.ts +49 -0
- package/dist/item-path.d.ts.map +1 -0
- package/dist/item-path.js +66 -0
- package/dist/item-path.js.map +1 -0
- package/dist/ledger-lint.d.ts +129 -0
- package/dist/ledger-lint.d.ts.map +1 -0
- package/dist/ledger-lint.js +249 -0
- package/dist/ledger-lint.js.map +1 -0
- package/dist/ledger-read.d.ts +357 -0
- package/dist/ledger-read.d.ts.map +1 -0
- package/dist/ledger-read.js +442 -0
- package/dist/ledger-read.js.map +1 -0
- package/dist/ledger-write.d.ts +330 -0
- package/dist/ledger-write.d.ts.map +1 -0
- package/dist/ledger-write.js +411 -0
- package/dist/ledger-write.js.map +1 -0
- package/dist/lifecycle-gather.d.ts +30 -0
- package/dist/lifecycle-gather.d.ts.map +1 -0
- package/dist/lifecycle-gather.js +205 -0
- package/dist/lifecycle-gather.js.map +1 -0
- package/dist/lifecycle-pools.d.ts +180 -0
- package/dist/lifecycle-pools.d.ts.map +1 -0
- package/dist/lifecycle-pools.js +78 -0
- package/dist/lifecycle-pools.js.map +1 -0
- package/dist/merge-question-surfacer.d.ts +166 -0
- package/dist/merge-question-surfacer.d.ts.map +1 -0
- package/dist/merge-question-surfacer.js +297 -0
- package/dist/merge-question-surfacer.js.map +1 -0
- package/dist/mint-adr.d.ts +126 -0
- package/dist/mint-adr.d.ts.map +1 -0
- package/dist/mint-adr.js +257 -0
- package/dist/mint-adr.js.map +1 -0
- package/dist/mirror-pool-scan.d.ts +125 -0
- package/dist/mirror-pool-scan.d.ts.map +1 -0
- package/dist/mirror-pool-scan.js +104 -0
- package/dist/mirror-pool-scan.js.map +1 -0
- package/dist/needs-attention.d.ts +341 -0
- package/dist/needs-attention.d.ts.map +1 -0
- package/dist/needs-attention.js +900 -0
- package/dist/needs-attention.js.map +1 -0
- package/dist/orphan-sidecar.d.ts +79 -0
- package/dist/orphan-sidecar.d.ts.map +1 -0
- package/dist/orphan-sidecar.js +71 -0
- package/dist/orphan-sidecar.js.map +1 -0
- package/dist/output.d.ts +48 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +66 -0
- package/dist/output.js.map +1 -0
- package/dist/pi-harness.d.ts +179 -0
- package/dist/pi-harness.d.ts.map +1 -0
- package/dist/pi-harness.js +342 -0
- package/dist/pi-harness.js.map +1 -0
- package/dist/placement.d.ts +99 -0
- package/dist/placement.d.ts.map +1 -0
- package/dist/placement.js +67 -0
- package/dist/placement.js.map +1 -0
- package/dist/prd-to-spec.d.ts +315 -0
- package/dist/prd-to-spec.d.ts.map +1 -0
- package/dist/prd-to-spec.js +684 -0
- package/dist/prd-to-spec.js.map +1 -0
- package/dist/prepare.d.ts +121 -0
- package/dist/prepare.d.ts.map +1 -0
- package/dist/prepare.js +140 -0
- package/dist/prepare.js.map +1 -0
- package/dist/prompt.d.ts +360 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +499 -0
- package/dist/prompt.js.map +1 -0
- package/dist/protocol/ADR-FORMAT.md +47 -0
- package/dist/protocol/CLAIM-PROTOCOL.md +217 -0
- package/dist/protocol/REVIEW-PROTOCOL.md +119 -0
- package/dist/protocol/SURFACE-PROTOCOL.md +121 -0
- package/dist/protocol/TASKING-PROTOCOL.md +122 -0
- package/dist/protocol/WORK-CONTRACT.md +276 -0
- package/dist/protocol/spec-template.md +71 -0
- package/dist/protocol/task-template.md +65 -0
- package/dist/readiness.d.ts +66 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +36 -0
- package/dist/readiness.js.map +1 -0
- package/dist/reap-branches.d.ts +102 -0
- package/dist/reap-branches.d.ts.map +1 -0
- package/dist/reap-branches.js +149 -0
- package/dist/reap-branches.js.map +1 -0
- package/dist/recover-isolated.d.ts +72 -0
- package/dist/recover-isolated.d.ts.map +1 -0
- package/dist/recover-isolated.js +188 -0
- package/dist/recover-isolated.js.map +1 -0
- package/dist/registry.d.ts +172 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +296 -0
- package/dist/registry.js.map +1 -0
- package/dist/repo-config.d.ts +201 -0
- package/dist/repo-config.d.ts.map +1 -0
- package/dist/repo-config.js +414 -0
- package/dist/repo-config.js.map +1 -0
- package/dist/repo-key.d.ts +20 -0
- package/dist/repo-key.d.ts.map +1 -0
- package/dist/repo-key.js +68 -0
- package/dist/repo-key.js.map +1 -0
- package/dist/repo-mirror.d.ts +177 -0
- package/dist/repo-mirror.d.ts.map +1 -0
- package/dist/repo-mirror.js +271 -0
- package/dist/repo-mirror.js.map +1 -0
- package/dist/retry-backoff.d.ts +90 -0
- package/dist/retry-backoff.d.ts.map +1 -0
- package/dist/retry-backoff.js +98 -0
- package/dist/retry-backoff.js.map +1 -0
- package/dist/review-gate.d.ts +173 -0
- package/dist/review-gate.d.ts.map +1 -0
- package/dist/review-gate.js +261 -0
- package/dist/review-gate.js.map +1 -0
- package/dist/review-verdict.d.ts +149 -0
- package/dist/review-verdict.d.ts.map +1 -0
- package/dist/review-verdict.js +332 -0
- package/dist/review-verdict.js.map +1 -0
- package/dist/run.d.ts +221 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +963 -0
- package/dist/run.js.map +1 -0
- package/dist/scan.d.ts +308 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +374 -0
- package/dist/scan.js.map +1 -0
- package/dist/select-order.d.ts +75 -0
- package/dist/select-order.d.ts.map +1 -0
- package/dist/select-order.js +108 -0
- package/dist/select-order.js.map +1 -0
- package/dist/select-priority.d.ts +188 -0
- package/dist/select-priority.d.ts.map +1 -0
- package/dist/select-priority.js +80 -0
- package/dist/select-priority.js.map +1 -0
- package/dist/select.d.ts +25 -0
- package/dist/select.d.ts.map +1 -0
- package/dist/select.js +43 -0
- package/dist/select.js.map +1 -0
- package/dist/session-path.d.ts +36 -0
- package/dist/session-path.d.ts.map +1 -0
- package/dist/session-path.js +129 -0
- package/dist/session-path.js.map +1 -0
- package/dist/sidecar-apply.d.ts +83 -0
- package/dist/sidecar-apply.d.ts.map +1 -0
- package/dist/sidecar-apply.js +111 -0
- package/dist/sidecar-apply.js.map +1 -0
- package/dist/sidecar.d.ts +245 -0
- package/dist/sidecar.d.ts.map +1 -0
- package/dist/sidecar.js +481 -0
- package/dist/sidecar.js.map +1 -0
- package/dist/slug-namespace.d.ts +204 -0
- package/dist/slug-namespace.d.ts.map +1 -0
- package/dist/slug-namespace.js +229 -0
- package/dist/slug-namespace.js.map +1 -0
- package/dist/spec-complete.d.ts +44 -0
- package/dist/spec-complete.d.ts.map +1 -0
- package/dist/spec-complete.js +69 -0
- package/dist/spec-complete.js.map +1 -0
- package/dist/start.d.ts +97 -0
- package/dist/start.d.ts.map +1 -0
- package/dist/start.js +633 -0
- package/dist/start.js.map +1 -0
- package/dist/status.d.ts +199 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +228 -0
- package/dist/status.js.map +1 -0
- package/dist/surface-gate.d.ts +162 -0
- package/dist/surface-gate.d.ts.map +1 -0
- package/dist/surface-gate.js +206 -0
- package/dist/surface-gate.js.map +1 -0
- package/dist/surface-persist.d.ts +86 -0
- package/dist/surface-persist.d.ts.map +1 -0
- package/dist/surface-persist.js +129 -0
- package/dist/surface-persist.js.map +1 -0
- package/dist/tasker-review-loop.d.ts +249 -0
- package/dist/tasker-review-loop.d.ts.map +1 -0
- package/dist/tasker-review-loop.js +369 -0
- package/dist/tasker-review-loop.js.map +1 -0
- package/dist/tasking-eligibility.d.ts +74 -0
- package/dist/tasking-eligibility.d.ts.map +1 -0
- package/dist/tasking-eligibility.js +52 -0
- package/dist/tasking-eligibility.js.map +1 -0
- package/dist/tasking-lock.d.ts +111 -0
- package/dist/tasking-lock.d.ts.map +1 -0
- package/dist/tasking-lock.js +256 -0
- package/dist/tasking-lock.js.map +1 -0
- package/dist/tasking.d.ts +275 -0
- package/dist/tasking.d.ts.map +1 -0
- package/dist/tasking.js +952 -0
- package/dist/tasking.js.map +1 -0
- package/dist/triage-gate.d.ts +127 -0
- package/dist/triage-gate.d.ts.map +1 -0
- package/dist/triage-gate.js +139 -0
- package/dist/triage-gate.js.map +1 -0
- package/dist/triage-persist.d.ts +163 -0
- package/dist/triage-persist.d.ts.map +1 -0
- package/dist/triage-persist.js +387 -0
- package/dist/triage-persist.js.map +1 -0
- package/dist/verdict-json.d.ts +32 -0
- package/dist/verdict-json.d.ts.map +1 -0
- package/dist/verdict-json.js +74 -0
- package/dist/verdict-json.js.map +1 -0
- package/dist/verify-workflow-template.d.ts +60 -0
- package/dist/verify-workflow-template.d.ts.map +1 -0
- package/dist/verify-workflow-template.js +126 -0
- package/dist/verify-workflow-template.js.map +1 -0
- package/dist/verify.d.ts +60 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +62 -0
- package/dist/verify.js.map +1 -0
- package/dist/watch-session.d.ts +112 -0
- package/dist/watch-session.d.ts.map +1 -0
- package/dist/watch-session.js +347 -0
- package/dist/watch-session.js.map +1 -0
- package/dist/work-layout.d.ts +198 -0
- package/dist/work-layout.d.ts.map +1 -0
- package/dist/work-layout.js +217 -0
- package/dist/work-layout.js.map +1 -0
- package/dist/work-on.d.ts +154 -0
- package/dist/work-on.d.ts.map +1 -0
- package/dist/work-on.js +387 -0
- package/dist/work-on.js.map +1 -0
- package/dist/workspace.d.ts +224 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +325 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +46 -2
- package/src/advance-ci-template.ts +203 -0
- package/src/advance-classify.ts +197 -0
- package/src/advance-drivers.ts +414 -0
- package/src/advance-isolated.ts +432 -0
- package/src/advance-lifecycle-template.ts +791 -0
- package/src/advance-loop-driver.ts +745 -0
- package/src/advance-treeless-publish.ts +177 -0
- package/src/advance.ts +1564 -0
- package/src/advancing-lock.ts +988 -0
- package/src/agent-launch.ts +137 -0
- package/src/agent-stop.ts +361 -0
- package/src/apply-decide.ts +242 -0
- package/src/apply-merge-action.ts +502 -0
- package/src/apply-persist.ts +518 -0
- package/src/arbiter.ts +372 -0
- package/src/brand.ts +111 -0
- package/src/buildable-body.ts +196 -0
- package/src/categorise.ts +158 -0
- package/src/claim-cas.ts +513 -0
- package/src/cli-spinner.ts +225 -0
- package/src/cli.ts +4369 -0
- package/src/close-job-template.ts +236 -0
- package/src/close-job.ts +319 -0
- package/src/complete.ts +1379 -0
- package/src/concurrency.ts +151 -0
- package/src/config-override.ts +116 -0
- package/src/config.ts +883 -0
- package/src/continue-branch.ts +542 -0
- package/src/cwd-section.ts +392 -0
- package/src/decision-engine.ts +272 -0
- package/src/detect.ts +124 -0
- package/src/do-autopick.ts +223 -0
- package/src/do-config.ts +589 -0
- package/src/do-remote-auto.ts +197 -0
- package/src/do.ts +2623 -0
- package/src/drop-source.ts +194 -0
- package/src/eligibility.ts +79 -0
- package/src/env-config.ts +305 -0
- package/src/failure-cause.ts +142 -0
- package/src/format.ts +313 -0
- package/src/frontmatter.ts +485 -0
- package/src/gate-readiness.ts +147 -0
- package/src/gc.ts +510 -0
- package/src/gh-failure.ts +53 -0
- package/src/git.ts +186 -0
- package/src/github.ts +468 -0
- package/src/harness.ts +355 -0
- package/src/identity.ts +322 -0
- package/src/index.ts +785 -0
- package/src/install-ci-branch-protection.ts +255 -0
- package/src/install-ci-capabilities/advance-lifecycle.ts +34 -0
- package/src/install-ci-capabilities/close-job.ts +32 -0
- package/src/install-ci-capabilities/example-noop.ts +24 -0
- package/src/install-ci-capabilities/intake.ts +34 -0
- package/src/install-ci-capabilities/verify.ts +33 -0
- package/src/install-ci-core.ts +1088 -0
- package/src/install-ci-github.ts +376 -0
- package/src/install-ci.ts +552 -0
- package/src/intake-event.ts +102 -0
- package/src/intake-marker.ts +195 -0
- package/src/intake-triage.ts +138 -0
- package/src/intake-trigger-template.ts +591 -0
- package/src/intake.ts +2445 -0
- package/src/integration-core.ts +3065 -0
- package/src/integrator.ts +771 -0
- package/src/isolation.ts +484 -0
- package/src/issue-provider.ts +733 -0
- package/src/item-lock.ts +1858 -0
- package/src/item-path.ts +75 -0
- package/src/ledger-lint.ts +332 -0
- package/src/ledger-read.ts +924 -0
- package/src/ledger-write.ts +865 -0
- package/src/lifecycle-gather.ts +298 -0
- package/src/lifecycle-pools.ts +250 -0
- package/src/merge-question-surfacer.ts +496 -0
- package/src/mint-adr.ts +362 -0
- package/src/mirror-pool-scan.ts +240 -0
- package/src/needs-attention.ts +1506 -0
- package/src/orphan-sidecar.ts +150 -0
- package/src/output.ts +89 -0
- package/src/pi-harness.ts +403 -0
- package/src/placement.ts +131 -0
- package/src/prd-to-spec.ts +1023 -0
- package/src/prepare.ts +230 -0
- package/src/prompt.ts +760 -0
- package/src/readiness.ts +98 -0
- package/src/reap-branches.ts +278 -0
- package/src/recover-isolated.ts +276 -0
- package/src/registry.ts +475 -0
- package/src/repo-config.ts +550 -0
- package/src/repo-key.ts +74 -0
- package/src/repo-mirror.ts +367 -0
- package/src/retry-backoff.ts +130 -0
- package/src/review-gate.ts +389 -0
- package/src/review-verdict.ts +422 -0
- package/src/run.ts +1430 -0
- package/src/scan.ts +611 -0
- package/src/select-order.ts +143 -0
- package/src/select-priority.ts +266 -0
- package/src/select.ts +62 -0
- package/src/session-path.ts +153 -0
- package/src/sidecar-apply.ts +216 -0
- package/src/sidecar.ts +700 -0
- package/src/slug-namespace.ts +367 -0
- package/src/spec-complete.ts +118 -0
- package/src/start.ts +974 -0
- package/src/status.ts +441 -0
- package/src/surface-gate.ts +337 -0
- package/src/surface-persist.ts +241 -0
- package/src/tasker-review-loop.ts +671 -0
- package/src/tasking-eligibility.ts +114 -0
- package/src/tasking-lock.ts +416 -0
- package/src/tasking.ts +1438 -0
- package/src/triage-gate.ts +248 -0
- package/src/triage-persist.ts +570 -0
- package/src/verdict-json.ts +73 -0
- package/src/verify-workflow-template.ts +159 -0
- package/src/verify.ts +123 -0
- package/src/watch-session.ts +397 -0
- package/src/work-layout.ts +262 -0
- package/src/work-on.ts +660 -0
- 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
|
+
}
|