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,1023 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
readdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
statSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
copyFileSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
rmdirSync,
|
|
10
|
+
} from 'node:fs';
|
|
11
|
+
import {join, relative, dirname} from 'node:path';
|
|
12
|
+
import {run, git, gitMv} from './git.js';
|
|
13
|
+
import {resolveProtocolDoc} from './prompt.js';
|
|
14
|
+
import {WORK_ROOT} from './work-layout.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The **`dorfl prd-to-spec` migration ENGINE** (spec
|
|
18
|
+
* `prd-to-spec-vocabulary-cutover-and-migration-command`, ADR
|
|
19
|
+
* `methodology-and-skills.md` §7e, user stories 4-9).
|
|
20
|
+
*
|
|
21
|
+
* This is the reusable, verb-agnostic core the thin `dorfl prd-to-spec` CLI
|
|
22
|
+
* shell drives. It migrates a repo's `work/` DATA + config + inert git refs from
|
|
23
|
+
* the legacy `prd` vocabulary to `spec`, AFTER dorfl's CODE + CONTRACT already
|
|
24
|
+
* speak `spec` (the source-part cutover — batches 1-5 + the contract task — is a
|
|
25
|
+
* hard prerequisite; this command reads the new `work-layout.ts` folder names
|
|
26
|
+
* and reuses the leak-scan proof).
|
|
27
|
+
*
|
|
28
|
+
* # Decision B — self-contained
|
|
29
|
+
*
|
|
30
|
+
* `runPrdToSpec` does three things IN ORDER (ADR §7e):
|
|
31
|
+
*
|
|
32
|
+
* 1. **Quiescence check** (refuse loudly): the tree is clean AND no per-item
|
|
33
|
+
* lock is held AND no in-progress `work/prd-*`/`work/spec-*` branch carries
|
|
34
|
+
* unlanded work. On failure it NAMES the offender and does nothing (1a).
|
|
35
|
+
* 2. **Setup contract re-sync FIRST:** copy the package's canonical
|
|
36
|
+
* `work/protocol/*` docs (the new `spec` contract) into the target repo and
|
|
37
|
+
* bump `work/protocol/VERSION` — the deterministic slice of what the `setup`
|
|
38
|
+
* SKILL does, so the migrated repo picks up the new contract in one command.
|
|
39
|
+
* 3. **Four-layer DATA migration** (deterministic — done-items ARE converted):
|
|
40
|
+
* (a) FOLDERS `work/prds/{proposed,ready,tasked,dropped}/ → work/specs/…`
|
|
41
|
+
* via `git mv`; (b) FRONTMATTER/body `prd: → spec:` + inert path/token refs
|
|
42
|
+
* across ALL items INCLUDING `work/tasks/done/` and `work/specs/tasked/`;
|
|
43
|
+
* (c) CONFIG `.dorfl.json` (`prdsLandIn → specsLandIn`, keep-case);
|
|
44
|
+
* (d) LIVE GIT REFS (inert lock-refs `refs/dorfl/lock/prd-<slug>` and
|
|
45
|
+
* work-branches `work/prd-<slug>` — the quiescence gate guarantees none are
|
|
46
|
+
* held/in-flight, so this renames only INERT refs).
|
|
47
|
+
*
|
|
48
|
+
* `--dry-run` reports exactly what each layer WOULD do, touching nothing.
|
|
49
|
+
* A second run on an already-migrated repo is a no-op (idempotent): each layer
|
|
50
|
+
* skips what is already in the `spec` shape.
|
|
51
|
+
*
|
|
52
|
+
* # Reusable-by-construction (user story 9)
|
|
53
|
+
*
|
|
54
|
+
* The layers are exported as INDEPENDENT pieces ({@link checkQuiescence},
|
|
55
|
+
* {@link resyncProtocol}, {@link migrateFolders}, {@link migrateItemContent},
|
|
56
|
+
* {@link migrateConfig}, {@link migrateRefs}, {@link keepCaseReplace},
|
|
57
|
+
* {@link scanForLeaks}) so a FUTURE vocabulary cutover reuses the STRUCTURE
|
|
58
|
+
* rather than re-deriving it — even though the verb itself stays purpose-named
|
|
59
|
+
* `prd-to-spec` (ADR §7e: no general `migrate <from> <to>`). The one place the
|
|
60
|
+
* `prd`/`spec` tokens are pinned is {@link MIGRATION}: a future cutover supplies
|
|
61
|
+
* a different {@link VocabularyMigration} and the same engine runs.
|
|
62
|
+
*
|
|
63
|
+
* # TOOLING decision — bespoke keep-case sweep, not `change-case`/`change-name`
|
|
64
|
+
*
|
|
65
|
+
* The parent spec's "Rename TOOLING" note asks to evaluate npm `change-case` /
|
|
66
|
+
* `change-name` for the keep-case sweep. Both were evaluated and DECLINED:
|
|
67
|
+
* `change-case` is a case CONVERTER (`camelCase ↔ snake_case`), not a
|
|
68
|
+
* keep-case find/replace of a substring across its case variants, and
|
|
69
|
+
* `change-name` renames FILES. The tokens this DATA migration rewrites are a
|
|
70
|
+
* small FIXED set (`prd`/`Prd`/`PRD` → `spec`/`Spec`/`SPEC`, plus the exact keys
|
|
71
|
+
* `prdsLandIn`, path prefixes `work/prds/`, `prd-`), so a dependency-free
|
|
72
|
+
* {@link keepCaseReplace} (3 case variants) is simpler, has no false-positive
|
|
73
|
+
* surface a generic tool would add, and keeps this package at its single runtime
|
|
74
|
+
* dependency. The forward+reverse {@link scanForLeaks} is the PROOF the sweep is
|
|
75
|
+
* complete regardless of the tool, so the bespoke choice carries no risk the
|
|
76
|
+
* gate would not catch. (Recorded here per the task's "record the choice"
|
|
77
|
+
* criterion — a JSDoc at the choice site.)
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
81
|
+
// The vocabulary the migration pins (a future cutover swaps this ONE value).
|
|
82
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A single vocabulary cutover the engine enacts: the retired word `from` (all
|
|
86
|
+
* three case variants) → the new word `to`. The `prd → spec` migration pins
|
|
87
|
+
* {@link MIGRATION}; a future cutover supplies a different value and reuses the
|
|
88
|
+
* same engine (user story 9). `configKeys` are the exact `.dorfl.json` keys that
|
|
89
|
+
* carry the `from` word (their VALUES are preserved — only the key renames).
|
|
90
|
+
*/
|
|
91
|
+
export interface VocabularyMigration {
|
|
92
|
+
/** The retired word, lower-case (e.g. `'prd'`). */
|
|
93
|
+
from: string;
|
|
94
|
+
/** The new word, lower-case (e.g. `'spec'`). */
|
|
95
|
+
to: string;
|
|
96
|
+
/** The retired plural folder segment under `work/` (e.g. `'prds'`). */
|
|
97
|
+
fromFolder: string;
|
|
98
|
+
/** The new plural folder segment under `work/` (e.g. `'specs'`). */
|
|
99
|
+
toFolder: string;
|
|
100
|
+
/** Exact config KEY renames (value preserved), e.g. `prdsLandIn → specsLandIn`. */
|
|
101
|
+
configKeys: ReadonlyArray<{from: string; to: string}>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** The `prd → spec` cutover this verb enacts (ADR §7e). */
|
|
105
|
+
export const MIGRATION: VocabularyMigration = {
|
|
106
|
+
from: 'prd',
|
|
107
|
+
to: 'spec',
|
|
108
|
+
fromFolder: 'prds',
|
|
109
|
+
toFolder: 'specs',
|
|
110
|
+
configKeys: [{from: 'prdsLandIn', to: 'specsLandIn'}],
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The FULL set of protocol docs `setup` propagates into a repo's
|
|
115
|
+
* `work/protocol/`. The re-sync copies EACH from the package's canonical source
|
|
116
|
+
* (via {@link resolveProtocolDoc}) so the migrated repo carries the new `spec`
|
|
117
|
+
* contract. Kept in lockstep with `skills/setup/protocol/` (the source of truth)
|
|
118
|
+
* and the `vendor-protocol` build step (which vendors these into `dist/protocol/`
|
|
119
|
+
* so the published CLI is self-contained).
|
|
120
|
+
*/
|
|
121
|
+
export const PROTOCOL_DOCS: readonly string[] = [
|
|
122
|
+
'WORK-CONTRACT.md',
|
|
123
|
+
'CLAIM-PROTOCOL.md',
|
|
124
|
+
'REVIEW-PROTOCOL.md',
|
|
125
|
+
'SURFACE-PROTOCOL.md',
|
|
126
|
+
'TASKING-PROTOCOL.md',
|
|
127
|
+
'ADR-FORMAT.md',
|
|
128
|
+
'task-template.md',
|
|
129
|
+
'spec-template.md',
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
133
|
+
// Layer: keep-case replace (the bespoke sweep — see the tooling JSDoc above).
|
|
134
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
/** Upper-case the first letter of a word (`prd → Prd`). */
|
|
137
|
+
function capitalise(word: string): string {
|
|
138
|
+
return word.length === 0 ? word : word[0].toUpperCase() + word.slice(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Keep-case replace EVERY occurrence of `from` with `to` across the three case
|
|
143
|
+
* variants — lower (`prd → spec`), Capitalised (`Prd → Spec`), and UPPER
|
|
144
|
+
* (`PRD → SPEC`) — preserving the shape of each hit. Deliberately does a plain
|
|
145
|
+
* substring replace (no word boundary) so it rewrites `prd` inside identifiers
|
|
146
|
+
* like `prdsLandIn`/`work/prds/`; callers scope it to the tokens they mean to
|
|
147
|
+
* rewrite (a whole config key, a frontmatter-field line, a path). The bespoke
|
|
148
|
+
* dependency-free sweep the tooling JSDoc justifies over `change-case`.
|
|
149
|
+
*/
|
|
150
|
+
export function keepCaseReplace(
|
|
151
|
+
text: string,
|
|
152
|
+
from: string,
|
|
153
|
+
to: string,
|
|
154
|
+
): string {
|
|
155
|
+
const variants: ReadonlyArray<[string, string]> = [
|
|
156
|
+
[from.toUpperCase(), to.toUpperCase()],
|
|
157
|
+
[capitalise(from), capitalise(to)],
|
|
158
|
+
[from.toLowerCase(), to.toLowerCase()],
|
|
159
|
+
];
|
|
160
|
+
let out = text;
|
|
161
|
+
for (const [f, t] of variants) {
|
|
162
|
+
out = out.split(f).join(t);
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
168
|
+
// Layer 1: the quiescence check (refuse loudly — ADR §7e decision 1a).
|
|
169
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
/** Why the repo is NOT quiescent (the FIRST offending clause, in check order). */
|
|
172
|
+
export interface QuiescenceViolation {
|
|
173
|
+
kind: 'dirty-tree' | 'held-lock' | 'in-progress-branch';
|
|
174
|
+
/** The offender, NAMED (a lock entry, a branch ref, or dirty-file paths). */
|
|
175
|
+
offender: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Is the repo QUIESCENT enough to migrate (ADR §7e, 1a)? Refuse if ANY of:
|
|
180
|
+
* - the working tree is DIRTY (uncommitted/untracked changes), OR
|
|
181
|
+
* - a per-item LOCK ref is held (`refs/dorfl/lock/*`), OR
|
|
182
|
+
* - an in-progress `work/<from>-*`/`work/<to>-*` work-branch exists (a branch
|
|
183
|
+
* that is NOT merged into the current tip = unlanded work in flight).
|
|
184
|
+
*
|
|
185
|
+
* Returns the FIRST violation (dirty tree dominates, then locks, then branches),
|
|
186
|
+
* naming the offender, or `undefined` when quiescent. Read-only: it inspects the
|
|
187
|
+
* LOCAL repo's refs + status (the fixture/downstream case where the migration
|
|
188
|
+
* runs in-place). The quiescence gate is what makes layer (d) safe: with no held
|
|
189
|
+
* lock and no in-flight branch, only INERT refs remain to rename.
|
|
190
|
+
*/
|
|
191
|
+
export function checkQuiescence(
|
|
192
|
+
repoPath: string,
|
|
193
|
+
migration: VocabularyMigration = MIGRATION,
|
|
194
|
+
env: NodeJS.ProcessEnv | undefined = undefined,
|
|
195
|
+
): QuiescenceViolation | undefined {
|
|
196
|
+
// 1. Dirty tree dominates — never migrate over uncommitted work.
|
|
197
|
+
const status = run('git', ['status', '--porcelain'], repoPath, {env});
|
|
198
|
+
if (status.status !== 0) {
|
|
199
|
+
return {kind: 'dirty-tree', offender: '(git status failed)'};
|
|
200
|
+
}
|
|
201
|
+
const dirty = status.stdout
|
|
202
|
+
.split('\n')
|
|
203
|
+
.map((l) => l.trimEnd())
|
|
204
|
+
.filter((l) => l !== '');
|
|
205
|
+
if (dirty.length > 0) {
|
|
206
|
+
return {
|
|
207
|
+
kind: 'dirty-tree',
|
|
208
|
+
offender: dirty.map((l) => l.slice(3).trim()).join(', '),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 2. A held per-item lock ref (`refs/dorfl/lock/<entry>`). Any such LOCAL ref
|
|
213
|
+
// means an item is claimed/in-flight — refuse, naming the entry.
|
|
214
|
+
const locks = run(
|
|
215
|
+
'git',
|
|
216
|
+
['for-each-ref', '--format=%(refname)', 'refs/dorfl/lock/'],
|
|
217
|
+
repoPath,
|
|
218
|
+
{env},
|
|
219
|
+
);
|
|
220
|
+
if (locks.status === 0) {
|
|
221
|
+
const held = locks.stdout
|
|
222
|
+
.split('\n')
|
|
223
|
+
.map((l) => l.trim())
|
|
224
|
+
.filter((l) => l.startsWith('refs/dorfl/lock/'));
|
|
225
|
+
if (held.length > 0) {
|
|
226
|
+
return {kind: 'held-lock', offender: held[0]};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 3. An in-progress `work/<from>-*` / `work/<to>-*` branch carrying UNLANDED
|
|
231
|
+
// work (a local branch not merged into HEAD). A branch already merged into
|
|
232
|
+
// the current tip is inert; an unmerged one is work in flight — refuse.
|
|
233
|
+
const branches = run(
|
|
234
|
+
'git',
|
|
235
|
+
[
|
|
236
|
+
'for-each-ref',
|
|
237
|
+
'--format=%(refname:short)',
|
|
238
|
+
`refs/heads/${WORK_ROOT}/${migration.from}-*`,
|
|
239
|
+
`refs/heads/${WORK_ROOT}/${migration.to}-*`,
|
|
240
|
+
],
|
|
241
|
+
repoPath,
|
|
242
|
+
{env},
|
|
243
|
+
);
|
|
244
|
+
if (branches.status === 0) {
|
|
245
|
+
const names = branches.stdout
|
|
246
|
+
.split('\n')
|
|
247
|
+
.map((l) => l.trim())
|
|
248
|
+
.filter((l) => l !== '');
|
|
249
|
+
for (const branch of names) {
|
|
250
|
+
// Merged into the current tip ⇒ inert (safe to rename); otherwise it
|
|
251
|
+
// carries unlanded work ⇒ refuse, naming the branch.
|
|
252
|
+
const merged = run(
|
|
253
|
+
'git',
|
|
254
|
+
['merge-base', '--is-ancestor', branch, 'HEAD'],
|
|
255
|
+
repoPath,
|
|
256
|
+
{env},
|
|
257
|
+
);
|
|
258
|
+
if (merged.status !== 0) {
|
|
259
|
+
return {kind: 'in-progress-branch', offender: branch};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
268
|
+
// Layer 2: the setup contract re-sync (the deterministic slice of `setup`).
|
|
269
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
/** One protocol doc the re-sync copied (or WOULD copy under `--dry-run`). */
|
|
272
|
+
export interface ResyncedDoc {
|
|
273
|
+
/** The doc basename (e.g. `'WORK-CONTRACT.md'`). */
|
|
274
|
+
name: string;
|
|
275
|
+
/** Repo-relative destination (`work/protocol/<name>`). */
|
|
276
|
+
dest: string;
|
|
277
|
+
/** True when the target already had byte-identical content (a no-op copy). */
|
|
278
|
+
unchanged: boolean;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** What the protocol re-sync did (or would do). */
|
|
282
|
+
export interface ResyncResult {
|
|
283
|
+
docs: ResyncedDoc[];
|
|
284
|
+
/** Repo-relative `work/protocol/VERSION` path (written unless dry-run). */
|
|
285
|
+
versionPath: string;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Re-sync the target repo's `work/protocol/*` from the package's canonical
|
|
290
|
+
* source (the deterministic part of the `setup` skill — copy the docs verbatim +
|
|
291
|
+
* bump `VERSION`). Resolves each doc's SOURCE via {@link resolveProtocolDoc} with
|
|
292
|
+
* NO `cwd`, so it reads the package-vendored (`dist/protocol/`) or dev-source
|
|
293
|
+
* (`skills/setup/protocol/`) copy — NEVER the target repo's own (old) copy. This
|
|
294
|
+
* is how the migrated repo picks up the new `spec` contract before its data is
|
|
295
|
+
* converted. Idempotent: a doc already byte-identical is reported `unchanged`.
|
|
296
|
+
*
|
|
297
|
+
* `dryRun` reports what WOULD be copied without writing.
|
|
298
|
+
*/
|
|
299
|
+
export function resyncProtocol(
|
|
300
|
+
repoPath: string,
|
|
301
|
+
options: {dryRun?: boolean} = {},
|
|
302
|
+
): ResyncResult {
|
|
303
|
+
const protocolDir = join(repoPath, WORK_ROOT, 'protocol');
|
|
304
|
+
const docs: ResyncedDoc[] = [];
|
|
305
|
+
for (const name of PROTOCOL_DOCS) {
|
|
306
|
+
// Resolve the SOURCE (package/dev), never the target's adopted copy.
|
|
307
|
+
const source = resolveProtocolDoc(name, undefined);
|
|
308
|
+
const destAbs = join(protocolDir, name);
|
|
309
|
+
const destRel = relative(repoPath, destAbs);
|
|
310
|
+
let unchanged = false;
|
|
311
|
+
if (existsSync(source) && existsSync(destAbs)) {
|
|
312
|
+
unchanged =
|
|
313
|
+
readFileSync(source, 'utf8') === readFileSync(destAbs, 'utf8');
|
|
314
|
+
}
|
|
315
|
+
if (!options.dryRun && existsSync(source)) {
|
|
316
|
+
mkdirSync(dirname(destAbs), {recursive: true});
|
|
317
|
+
copyFileSync(source, destAbs);
|
|
318
|
+
}
|
|
319
|
+
docs.push({name, dest: destRel, unchanged});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const versionAbs = join(protocolDir, 'VERSION');
|
|
323
|
+
const versionRel = relative(repoPath, versionAbs);
|
|
324
|
+
// Bump VERSION only when a doc actually CHANGED (or VERSION is absent). A
|
|
325
|
+
// re-run on an already-synced repo must stay a true no-op — otherwise the
|
|
326
|
+
// (always-fresh) `synced-at` timestamp would dirty the tree on every idempotent
|
|
327
|
+
// re-run.
|
|
328
|
+
const anyDocChanged = docs.some((d) => !d.unchanged);
|
|
329
|
+
if (!options.dryRun && (anyDocChanged || !existsSync(versionAbs))) {
|
|
330
|
+
mkdirSync(dirname(versionAbs), {recursive: true});
|
|
331
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
332
|
+
writeFileSync(
|
|
333
|
+
versionAbs,
|
|
334
|
+
[
|
|
335
|
+
`protocol-version: ${today}`,
|
|
336
|
+
'synced-from: skills/setup/protocol/',
|
|
337
|
+
`synced-at: ${new Date().toISOString()}`,
|
|
338
|
+
'source-commit: dorfl prd-to-spec',
|
|
339
|
+
'',
|
|
340
|
+
].join('\n'),
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return {docs, versionPath: versionRel};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
348
|
+
// Layer 3a: the FOLDER move (in lockstep with work-layout.ts).
|
|
349
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
350
|
+
|
|
351
|
+
/** The four lifecycle sub-folders under `work/<from>s/` the move relocates. */
|
|
352
|
+
const LIFECYCLE_SUBFOLDERS = [
|
|
353
|
+
'proposed',
|
|
354
|
+
'ready',
|
|
355
|
+
'tasked',
|
|
356
|
+
'dropped',
|
|
357
|
+
] as const;
|
|
358
|
+
|
|
359
|
+
/** One folder the move relocated (or would relocate). */
|
|
360
|
+
export interface FolderMove {
|
|
361
|
+
from: string;
|
|
362
|
+
to: string;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Move `work/<from>s/{proposed,ready,tasked,dropped}/ → work/<to>s/…`. The
|
|
367
|
+
* DESTINATION spellings match `work-layout.ts`'s post-cutover folder names in
|
|
368
|
+
* lockstep — this layer moves the DATA folders the CODE already renamed.
|
|
369
|
+
*
|
|
370
|
+
* A sub-folder with TRACKED files is `git mv`'d (renames preserve history); a
|
|
371
|
+
* sub-folder that exists on disk but has NO tracked files (an empty staging dir
|
|
372
|
+
* `setup` created — git never tracks empty dirs, so `git mv` would fatal on it)
|
|
373
|
+
* is instead removed with a plain `rmdir` so no stray `work/<from>s/` folder is
|
|
374
|
+
* left to leak (the empty dir carries no data). Only folders that EXIST are
|
|
375
|
+
* touched (an absent sub-folder is skipped), so it is idempotent AND
|
|
376
|
+
* partial-repo safe: a second run finds `work/<from>s/*` gone and does nothing.
|
|
377
|
+
* `dryRun` reports the moves without touching git or the filesystem.
|
|
378
|
+
*/
|
|
379
|
+
export function migrateFolders(
|
|
380
|
+
repoPath: string,
|
|
381
|
+
migration: VocabularyMigration = MIGRATION,
|
|
382
|
+
options: {dryRun?: boolean} = {},
|
|
383
|
+
): FolderMove[] {
|
|
384
|
+
const moves: FolderMove[] = [];
|
|
385
|
+
for (const sub of LIFECYCLE_SUBFOLDERS) {
|
|
386
|
+
const fromRel = `${WORK_ROOT}/${migration.fromFolder}/${sub}`;
|
|
387
|
+
const toRel = `${WORK_ROOT}/${migration.toFolder}/${sub}`;
|
|
388
|
+
const fromAbs = join(repoPath, fromRel);
|
|
389
|
+
if (!existsSync(fromAbs)) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
const tracked = run('git', ['ls-files', '--', fromRel], repoPath)
|
|
393
|
+
.stdout.split('\n')
|
|
394
|
+
.some((l) => l.trim() !== '');
|
|
395
|
+
if (!tracked) {
|
|
396
|
+
// An empty/untracked on-disk staging dir: nothing for git to move, but
|
|
397
|
+
// leave no stray `work/<from>s/` behind. Best-effort rmdir (only removes
|
|
398
|
+
// an empty dir; a dir with untracked files stays and is reported).
|
|
399
|
+
if (!options.dryRun) {
|
|
400
|
+
try {
|
|
401
|
+
rmdirSync(fromAbs);
|
|
402
|
+
} catch {
|
|
403
|
+
// non-empty untracked dir — leave it; the leak scan will surface it.
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
moves.push({from: fromRel, to: toRel});
|
|
409
|
+
if (!options.dryRun) {
|
|
410
|
+
gitMv(fromRel, toRel, repoPath);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// Prune a now-empty `work/<from>s/` parent so no stray folder leaks.
|
|
414
|
+
if (!options.dryRun) {
|
|
415
|
+
const parent = join(repoPath, WORK_ROOT, migration.fromFolder);
|
|
416
|
+
try {
|
|
417
|
+
rmdirSync(parent);
|
|
418
|
+
} catch {
|
|
419
|
+
// still has (untracked) content — leave it for the leak scan to surface.
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return moves;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
426
|
+
// Layer 3b: the item CONTENT rewrite (frontmatter + inert refs, incl. done/).
|
|
427
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
428
|
+
|
|
429
|
+
/** One item file whose content the rewrite changed (or would change). */
|
|
430
|
+
export interface ContentRewrite {
|
|
431
|
+
/** Repo-relative path of the item file. */
|
|
432
|
+
path: string;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Which `work/` folders carry ITEMS whose bodies/frontmatter may reference the
|
|
437
|
+
* retired word — the FULL set including terminal/history folders, because
|
|
438
|
+
* done-items ARE converted (ADR §7e: a `done/` full of dangling `prd:` refs is a
|
|
439
|
+
* broken repo, so determinism overrides the old "don't touch done" convenience).
|
|
440
|
+
* Paths are repo-relative and walked recursively. `work/specs/*` is included so
|
|
441
|
+
* items ALREADY moved by layer 3a (whose bodies may still say `work/prds/…`) are
|
|
442
|
+
* swept too. `work/protocol/` is EXCLUDED — layer 2 owns it (verbatim contract
|
|
443
|
+
* copies, not data). Each entry is a path UNDER `work/` (the `WORK_ROOT` prefix
|
|
444
|
+
* is prepended by the walker).
|
|
445
|
+
*/
|
|
446
|
+
const ITEM_CONTENT_FOLDERS: readonly string[] = [
|
|
447
|
+
'tasks/backlog',
|
|
448
|
+
'tasks/ready',
|
|
449
|
+
'tasks/done',
|
|
450
|
+
'tasks/cancelled',
|
|
451
|
+
// BOTH the post-move `specs/*` locations AND the pre-move `prds/*` locations
|
|
452
|
+
// are walked: after a real (non-dry-run) migration the items live in `specs/*`
|
|
453
|
+
// (layer 3a moved them first) and `prds/*` is empty; under `--dry-run` the
|
|
454
|
+
// folders are NOT moved, so the items are still in `prds/*` — walking both makes
|
|
455
|
+
// the content sweep (and its dry-run report) accurate in either order, and it
|
|
456
|
+
// stays idempotent (an empty folder contributes nothing).
|
|
457
|
+
'prds/proposed',
|
|
458
|
+
'prds/ready',
|
|
459
|
+
'prds/tasked',
|
|
460
|
+
'prds/dropped',
|
|
461
|
+
'specs/proposed',
|
|
462
|
+
'specs/ready',
|
|
463
|
+
'specs/tasked',
|
|
464
|
+
'specs/dropped',
|
|
465
|
+
'notes/observations',
|
|
466
|
+
'notes/ideas',
|
|
467
|
+
'notes/findings',
|
|
468
|
+
'questions',
|
|
469
|
+
];
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Recursively collect every `.md` file under a `work/`-relative folder (the entry
|
|
473
|
+
* is UNDER `work/` — e.g. `'tasks/done'` — and `WORK_ROOT` is prepended here).
|
|
474
|
+
* Returns repo-relative paths WITH the `work/` prefix.
|
|
475
|
+
*/
|
|
476
|
+
function collectMarkdown(repoPath: string, folderUnderWork: string): string[] {
|
|
477
|
+
return collectMarkdownRel(repoPath, `${WORK_ROOT}/${folderUnderWork}`);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** Recursively collect every `.md` file under a repo-relative folder path. */
|
|
481
|
+
function collectMarkdownRel(repoPath: string, folderRel: string): string[] {
|
|
482
|
+
const abs = join(repoPath, folderRel);
|
|
483
|
+
if (!existsSync(abs)) {
|
|
484
|
+
return [];
|
|
485
|
+
}
|
|
486
|
+
const out: string[] = [];
|
|
487
|
+
for (const entry of readdirSync(abs, {withFileTypes: true})) {
|
|
488
|
+
const childRel = `${folderRel}/${entry.name}`;
|
|
489
|
+
if (entry.isDirectory()) {
|
|
490
|
+
out.push(...collectMarkdownRel(repoPath, childRel));
|
|
491
|
+
} else if (entry.name.toLowerCase().endsWith('.md')) {
|
|
492
|
+
out.push(childRel);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return out;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Rewrite the retired word in ONE item's text, SCOPED to genuine STRUCTURAL
|
|
500
|
+
* refs so it never corrupts prose or an immutable provenance SLUG (a landed
|
|
501
|
+
* item's `slug: prd-to-spec-…` must survive verbatim — a slug can never be
|
|
502
|
+
* renamed):
|
|
503
|
+
*
|
|
504
|
+
* - the `prd:` frontmatter KEY at line start (`prdAfter:` too), NOT a `prd`
|
|
505
|
+
* inside a value/slug;
|
|
506
|
+
* - `work/prds/…` folder paths and a bare `prds/` folder segment;
|
|
507
|
+
* - `work/prd-<slug>` branch refs (the `work/` prefix makes it unambiguous —
|
|
508
|
+
* a bare `prd-<slug>` is NOT rewritten because it is indistinguishable from
|
|
509
|
+
* a provenance slug fragment; a genuine bare lock entry lives on a git ref,
|
|
510
|
+
* which {@link migrateRefs} handles, not in an item body);
|
|
511
|
+
* - `prd:<slug>` CLI-arg tokens (a `prd:` at a non-word boundary followed by a
|
|
512
|
+
* slug char), NOT a `prd:` embedded in a longer word.
|
|
513
|
+
*
|
|
514
|
+
* Returns the new text (unchanged when nothing matched). PURE — no IO — so it is
|
|
515
|
+
* trivially testable and the caller decides whether to write. Exported for reuse
|
|
516
|
+
* + the fixture test's determinism assertion.
|
|
517
|
+
*/
|
|
518
|
+
export function migrateItemContent(
|
|
519
|
+
text: string,
|
|
520
|
+
migration: VocabularyMigration = MIGRATION,
|
|
521
|
+
): string {
|
|
522
|
+
const from = migration.from;
|
|
523
|
+
let out = text;
|
|
524
|
+
|
|
525
|
+
// (i) The frontmatter FIELD key on its own line: `prd:` → `spec:` (keep-case),
|
|
526
|
+
// matched at line start (optionally indented) so only the KEY is rewritten,
|
|
527
|
+
// never a `prd` inside a value/slug. Also `prdAfter:` if it ever appears.
|
|
528
|
+
out = out.replace(
|
|
529
|
+
new RegExp(`^(\\s*)(${from})(After)?(\\s*:)`, gimFlags()),
|
|
530
|
+
(
|
|
531
|
+
_m,
|
|
532
|
+
indent: string,
|
|
533
|
+
word: string,
|
|
534
|
+
after: string | undefined,
|
|
535
|
+
colon: string,
|
|
536
|
+
) =>
|
|
537
|
+
indent +
|
|
538
|
+
keepCaseReplace(word, from, migration.to) +
|
|
539
|
+
(after ?? '') +
|
|
540
|
+
colon,
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
// (ii) Folder-path refs `work/prds/…` → `work/specs/…` (keep-case on the
|
|
544
|
+
// plural segment). A whole-path token, so a bare `prds/` inside a longer
|
|
545
|
+
// path is caught by the segment replace below too.
|
|
546
|
+
out = replaceToken(
|
|
547
|
+
out,
|
|
548
|
+
`${WORK_ROOT}/${migration.fromFolder}`,
|
|
549
|
+
`${WORK_ROOT}/${migration.toFolder}`,
|
|
550
|
+
);
|
|
551
|
+
out = replaceToken(out, `${migration.fromFolder}/`, `${migration.toFolder}/`);
|
|
552
|
+
|
|
553
|
+
// (iii) Inert `work/<from>-<slug>` branch refs: the `work/` prefix makes this
|
|
554
|
+
// unambiguous (never a provenance-slug fragment), so a keep-case prefix
|
|
555
|
+
// replace is safe. A BARE `<from>-<slug>` is deliberately NOT rewritten
|
|
556
|
+
// (it is indistinguishable from a slug like `prd-to-spec-…`); a genuine
|
|
557
|
+
// bare lock entry lives on a git ref (`migrateRefs`), not an item body.
|
|
558
|
+
out = replaceToken(
|
|
559
|
+
out,
|
|
560
|
+
`${WORK_ROOT}/${from}-`,
|
|
561
|
+
`${WORK_ROOT}/${migration.to}-`,
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
// (iv) `<from>:<slug>` CLI-arg tokens (`prd:my-feature`): only at a non-word
|
|
565
|
+
// boundary followed by a slug char, so a `prd:` mid-word is untouched and
|
|
566
|
+
// the frontmatter KEY (line-start, handled by (i)) is not double-hit.
|
|
567
|
+
out = out.replace(
|
|
568
|
+
new RegExp(`(^|[^A-Za-z0-9_])(${from})(:[A-Za-z0-9])`, gimFlags()),
|
|
569
|
+
(_m, pre: string, word: string, tail: string) =>
|
|
570
|
+
pre + keepCaseReplace(word, from, migration.to) + tail,
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
return out;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/** Case-insensitive, multiline, global regex flags. */
|
|
577
|
+
function gimFlags(): string {
|
|
578
|
+
return 'gim';
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Keep-case replace a fixed TOKEN wherever it appears (all three case variants),
|
|
583
|
+
* a thin wrapper over {@link keepCaseReplace} scoped to a specific substring so
|
|
584
|
+
* the intent (rewrite THIS token) is explicit at the call site.
|
|
585
|
+
*/
|
|
586
|
+
function replaceToken(text: string, from: string, to: string): string {
|
|
587
|
+
return keepCaseReplace(text, from, to);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Rewrite the retired word across EVERY item in the data folders (incl.
|
|
592
|
+
* `done/`/`tasked/` — determinism). Writes each changed file in place (unless
|
|
593
|
+
* `dryRun`), returning the list of files that changed. Idempotent: a file
|
|
594
|
+
* already in the `spec` shape produces identical text and is not listed.
|
|
595
|
+
*/
|
|
596
|
+
export function migrateAllItemContent(
|
|
597
|
+
repoPath: string,
|
|
598
|
+
migration: VocabularyMigration = MIGRATION,
|
|
599
|
+
options: {dryRun?: boolean} = {},
|
|
600
|
+
): ContentRewrite[] {
|
|
601
|
+
const changed: ContentRewrite[] = [];
|
|
602
|
+
for (const folder of ITEM_CONTENT_FOLDERS) {
|
|
603
|
+
for (const rel of collectMarkdown(repoPath, folder)) {
|
|
604
|
+
const abs = join(repoPath, rel);
|
|
605
|
+
const before = readFileSync(abs, 'utf8');
|
|
606
|
+
const after = migrateItemContent(before, migration);
|
|
607
|
+
if (after !== before) {
|
|
608
|
+
changed.push({path: rel});
|
|
609
|
+
if (!options.dryRun) {
|
|
610
|
+
writeFileSync(abs, after);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return changed;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
619
|
+
// Layer 3c: the CONFIG key rewrite (.dorfl.json).
|
|
620
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
621
|
+
|
|
622
|
+
/** One config key the rewrite renamed (or would rename). */
|
|
623
|
+
export interface ConfigRewrite {
|
|
624
|
+
from: string;
|
|
625
|
+
to: string;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Rewrite the retired config KEYS in `.dorfl.json` (`prdsLandIn → specsLandIn`),
|
|
630
|
+
* preserving each key's VALUE and the file's surrounding formatting — a TEXTUAL
|
|
631
|
+
* key rename on the raw JSON, NOT a parse+reserialise (which would drop unknown
|
|
632
|
+
* keys / reflow the file). Only renames a key that is actually present, so it is
|
|
633
|
+
* idempotent (a config already on `specsLandIn` changes nothing) and safe on a
|
|
634
|
+
* repo with no `.dorfl.json`. Returns the keys renamed. `dryRun` reports without
|
|
635
|
+
* writing.
|
|
636
|
+
*/
|
|
637
|
+
export function migrateConfig(
|
|
638
|
+
repoPath: string,
|
|
639
|
+
migration: VocabularyMigration = MIGRATION,
|
|
640
|
+
options: {dryRun?: boolean} = {},
|
|
641
|
+
): ConfigRewrite[] {
|
|
642
|
+
const configAbs = join(repoPath, '.dorfl.json');
|
|
643
|
+
if (!existsSync(configAbs)) {
|
|
644
|
+
return [];
|
|
645
|
+
}
|
|
646
|
+
const before = readFileSync(configAbs, 'utf8');
|
|
647
|
+
let after = before;
|
|
648
|
+
const renamed: ConfigRewrite[] = [];
|
|
649
|
+
for (const {from, to} of migration.configKeys) {
|
|
650
|
+
// Match the quoted JSON key exactly: `"prdsLandIn"` → `"specsLandIn"`.
|
|
651
|
+
const needle = `"${from}"`;
|
|
652
|
+
if (after.includes(needle)) {
|
|
653
|
+
after = after.split(needle).join(`"${to}"`);
|
|
654
|
+
renamed.push({from, to});
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (after !== before && !options.dryRun) {
|
|
658
|
+
writeFileSync(configAbs, after);
|
|
659
|
+
}
|
|
660
|
+
return renamed;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
664
|
+
// Layer 3d: the inert git-ref rename (lock refs + work-branches).
|
|
665
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
666
|
+
|
|
667
|
+
/** One git ref the rename relocated (or would relocate). */
|
|
668
|
+
export interface RefRename {
|
|
669
|
+
from: string;
|
|
670
|
+
to: string;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Rename INERT local git refs carrying the retired word: per-item lock refs
|
|
675
|
+
* `refs/dorfl/lock/<from>-<slug>` → `…/<to>-<slug>` and work-branches
|
|
676
|
+
* `refs/heads/work/<from>-<slug>` → `…/work/<to>-<slug>`. The {@link
|
|
677
|
+
* checkQuiescence} gate guarantees NO lock is held and NO in-flight branch
|
|
678
|
+
* exists, so every ref reaching here is inert (a leftover created before the
|
|
679
|
+
* cutover) and a plain rename is safe. Uses `git update-ref` for lock refs (they
|
|
680
|
+
* are not branches) and `git branch -m` for work-branches. Idempotent: a ref
|
|
681
|
+
* already on the `<to>-` spelling is left alone; `dryRun` reports without
|
|
682
|
+
* touching refs.
|
|
683
|
+
*/
|
|
684
|
+
export function migrateRefs(
|
|
685
|
+
repoPath: string,
|
|
686
|
+
migration: VocabularyMigration = MIGRATION,
|
|
687
|
+
options: {dryRun?: boolean; env?: NodeJS.ProcessEnv} = {},
|
|
688
|
+
): RefRename[] {
|
|
689
|
+
const env = options.env;
|
|
690
|
+
const renames: RefRename[] = [];
|
|
691
|
+
|
|
692
|
+
// Lock refs: `refs/dorfl/lock/<from>-<slug>` → `refs/dorfl/lock/<to>-<slug>`.
|
|
693
|
+
const lockPrefix = 'refs/dorfl/lock/';
|
|
694
|
+
const locks = run(
|
|
695
|
+
'git',
|
|
696
|
+
['for-each-ref', '--format=%(refname) %(objectname)', lockPrefix],
|
|
697
|
+
repoPath,
|
|
698
|
+
{env},
|
|
699
|
+
);
|
|
700
|
+
if (locks.status === 0) {
|
|
701
|
+
for (const line of locks.stdout.split('\n')) {
|
|
702
|
+
const trimmed = line.trim();
|
|
703
|
+
if (trimmed === '') continue;
|
|
704
|
+
const [ref, sha] = trimmed.split(/\s+/);
|
|
705
|
+
const entry = ref.slice(lockPrefix.length);
|
|
706
|
+
if (!entry.startsWith(`${migration.from}-`)) continue;
|
|
707
|
+
const newEntry = migration.to + entry.slice(migration.from.length);
|
|
708
|
+
const newRef = lockPrefix + newEntry;
|
|
709
|
+
renames.push({from: ref, to: newRef});
|
|
710
|
+
if (!options.dryRun) {
|
|
711
|
+
git(['update-ref', newRef, sha], repoPath, {env});
|
|
712
|
+
git(['update-ref', '-d', ref], repoPath, {env});
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Work-branches: `work/<from>-<slug>` → `work/<to>-<slug>`.
|
|
718
|
+
const branchPrefix = `${WORK_ROOT}/${migration.from}-`;
|
|
719
|
+
const branches = run(
|
|
720
|
+
'git',
|
|
721
|
+
[
|
|
722
|
+
'for-each-ref',
|
|
723
|
+
'--format=%(refname:short)',
|
|
724
|
+
`refs/heads/${branchPrefix}*`,
|
|
725
|
+
],
|
|
726
|
+
repoPath,
|
|
727
|
+
{env},
|
|
728
|
+
);
|
|
729
|
+
if (branches.status === 0) {
|
|
730
|
+
for (const line of branches.stdout.split('\n')) {
|
|
731
|
+
const branch = line.trim();
|
|
732
|
+
if (branch === '' || !branch.startsWith(branchPrefix)) continue;
|
|
733
|
+
const newBranch = `${WORK_ROOT}/${migration.to}-${branch.slice(
|
|
734
|
+
branchPrefix.length,
|
|
735
|
+
)}`;
|
|
736
|
+
renames.push({
|
|
737
|
+
from: `refs/heads/${branch}`,
|
|
738
|
+
to: `refs/heads/${newBranch}`,
|
|
739
|
+
});
|
|
740
|
+
if (!options.dryRun) {
|
|
741
|
+
git(['branch', '-m', branch, newBranch], repoPath, {env});
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
return renames;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
750
|
+
// The leak scan (the acceptance GATE over the CONVERTED data tree).
|
|
751
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
752
|
+
|
|
753
|
+
/** One leak the data-tree scan found (a survivor of the retired word). */
|
|
754
|
+
export interface DataLeak {
|
|
755
|
+
/** Repo-relative file (or ref) the leak lives in. */
|
|
756
|
+
where: string;
|
|
757
|
+
/** The offending token/line. */
|
|
758
|
+
token: string;
|
|
759
|
+
/** Which lens flagged it. */
|
|
760
|
+
lens: 'forward' | 'reverse';
|
|
761
|
+
/** A one-line explanation. */
|
|
762
|
+
why: string;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* The DATA-tree forward+reverse leak scan — the acceptance GATE for the
|
|
767
|
+
* command's OUTPUT (ADR §7e: the leak scan is exhaustive-by-construction). This
|
|
768
|
+
* is the DATA analogue of the source-part `prd-to-spec-leak-scan.test.ts`
|
|
769
|
+
* (which scans `src/`/`skills/`/`docs/`): here it walks the CONVERTED `work/`
|
|
770
|
+
* tree + `.dorfl.json` + the git refs.
|
|
771
|
+
*
|
|
772
|
+
* - **FORWARD:** fails on any surviving retired word (`prd`/`Prd`/`PRD`) in a
|
|
773
|
+
* data STRUCTURE position — a frontmatter `prd:` KEY, a `work/prds/…` folder
|
|
774
|
+
* path, a `work/prd-<slug>` branch ref, a `prd-<slug>` lock entry, a config
|
|
775
|
+
* key `prdsLandIn`, or a `prd:<slug>` token. It deliberately IGNORES the
|
|
776
|
+
* retired word in running PROSE (a body sentence, an immutable provenance
|
|
777
|
+
* slug like `prd-to-spec-…`) — those are the artifact word in narrative, not
|
|
778
|
+
* a dangling structural ref, exactly as the source-part scan's option-A cut.
|
|
779
|
+
* - **REVERSE:** fails on genuine English CORRUPTED by a blind keep-case sweep
|
|
780
|
+
* (`espec…`→`esspec…`-style mangles) — the guard that `spec`'s English
|
|
781
|
+
* collisions were not mangled.
|
|
782
|
+
*
|
|
783
|
+
* A green scan on the converted fixture is the proof the four layers left no
|
|
784
|
+
* dangling structural `prd`. Returns every leak (empty ⇒ green).
|
|
785
|
+
*/
|
|
786
|
+
export function scanForLeaks(
|
|
787
|
+
repoPath: string,
|
|
788
|
+
migration: VocabularyMigration = MIGRATION,
|
|
789
|
+
env: NodeJS.ProcessEnv | undefined = undefined,
|
|
790
|
+
): DataLeak[] {
|
|
791
|
+
const leaks: DataLeak[] = [];
|
|
792
|
+
const from = migration.from;
|
|
793
|
+
|
|
794
|
+
// STRUCTURAL forward patterns: a surviving retired word in a data position.
|
|
795
|
+
const structural: ReadonlyArray<{pattern: RegExp; why: string}> = [
|
|
796
|
+
{
|
|
797
|
+
pattern: new RegExp(`^\\s*${from}(After)?\\s*:`, 'i'),
|
|
798
|
+
why: `surviving '${from}:' frontmatter key`,
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
pattern: new RegExp(`${WORK_ROOT}/${migration.fromFolder}(/|\\b)`, 'i'),
|
|
802
|
+
why: `surviving '${WORK_ROOT}/${migration.fromFolder}/' folder path`,
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
// A bare `prds/` folder ref, but ONLY a real on-disk path shape:
|
|
806
|
+
// lowercase `prds/` followed by a lifecycle subfolder. Deliberately NO
|
|
807
|
+
// `'i'` flag + a required lifecycle segment, so a prose acronym-plural
|
|
808
|
+
// like `slices/PRDs/code` or `PRDs/ADRs` (the artifact word `PRDs` + `/`
|
|
809
|
+
// + another word) is NOT mis-flagged as a folder ref (the option-A prose
|
|
810
|
+
// exemption). The `${WORK_ROOT}/${fromFolder}` pattern above still
|
|
811
|
+
// catches a rooted `work/prds/...` path case-insensitively as the backstop.
|
|
812
|
+
pattern: new RegExp(
|
|
813
|
+
`\\b${migration.fromFolder}/(proposed|ready|tasked|dropped)\\b`,
|
|
814
|
+
),
|
|
815
|
+
why: `surviving '${migration.fromFolder}/' folder ref`,
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
pattern: new RegExp(`${WORK_ROOT}/${from}-`, 'i'),
|
|
819
|
+
why: `surviving '${WORK_ROOT}/${from}-' branch ref`,
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
pattern: new RegExp(`(^|[^A-Za-z])${from}:[A-Za-z0-9]`, 'i'),
|
|
823
|
+
why: `surviving '${from}:<slug>' arg`,
|
|
824
|
+
},
|
|
825
|
+
];
|
|
826
|
+
// Config-key survivors (the exact keys the config layer renames).
|
|
827
|
+
const configKeyPatterns = migration.configKeys.map(({from: key}) => ({
|
|
828
|
+
pattern: new RegExp(`"${key}"`),
|
|
829
|
+
why: `surviving '${key}' config key`,
|
|
830
|
+
}));
|
|
831
|
+
|
|
832
|
+
// Corrupted-English (reverse) shapes a blind sweep would have produced.
|
|
833
|
+
const corrupted: ReadonlyArray<{pattern: RegExp; why: string}> = [
|
|
834
|
+
{pattern: /esspec/i, why: 'especially → esspec… (mangle)'},
|
|
835
|
+
{pattern: /speccif/i, why: 'specify/specific → speccif… (mangle)'},
|
|
836
|
+
{pattern: /inspecc/i, why: 'inspect → inspecc… (mangle)'},
|
|
837
|
+
{pattern: /respecc/i, why: 'respect → respecc… (mangle)'},
|
|
838
|
+
];
|
|
839
|
+
|
|
840
|
+
const scanText = (
|
|
841
|
+
where: string,
|
|
842
|
+
text: string,
|
|
843
|
+
keyPatterns: typeof configKeyPatterns,
|
|
844
|
+
) => {
|
|
845
|
+
const lines = text.split('\n');
|
|
846
|
+
lines.forEach((line, idx) => {
|
|
847
|
+
for (const {pattern, why} of structural) {
|
|
848
|
+
const m = pattern.exec(line);
|
|
849
|
+
if (m) {
|
|
850
|
+
leaks.push({
|
|
851
|
+
where: `${where}:${idx + 1}`,
|
|
852
|
+
token: m[0],
|
|
853
|
+
lens: 'forward',
|
|
854
|
+
why,
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
for (const {pattern, why} of keyPatterns) {
|
|
859
|
+
const m = pattern.exec(line);
|
|
860
|
+
if (m) {
|
|
861
|
+
leaks.push({
|
|
862
|
+
where: `${where}:${idx + 1}`,
|
|
863
|
+
token: m[0],
|
|
864
|
+
lens: 'forward',
|
|
865
|
+
why,
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
for (const {pattern, why} of corrupted) {
|
|
870
|
+
const m = pattern.exec(line);
|
|
871
|
+
if (m) {
|
|
872
|
+
leaks.push({
|
|
873
|
+
where: `${where}:${idx + 1}`,
|
|
874
|
+
token: m[0],
|
|
875
|
+
lens: 'reverse',
|
|
876
|
+
why,
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
// Walk every converted data item (the FULL folder set, incl. done/tasked).
|
|
884
|
+
for (const folder of ITEM_CONTENT_FOLDERS) {
|
|
885
|
+
for (const rel of collectMarkdown(repoPath, folder)) {
|
|
886
|
+
scanText(rel, readFileSync(join(repoPath, rel), 'utf8'), []);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
// The config file (the key layer).
|
|
890
|
+
const configAbs = join(repoPath, '.dorfl.json');
|
|
891
|
+
if (existsSync(configAbs)) {
|
|
892
|
+
scanText('.dorfl.json', readFileSync(configAbs, 'utf8'), configKeyPatterns);
|
|
893
|
+
}
|
|
894
|
+
// The FOLDER TREE itself: a surviving `work/prds/` directory (empty ⇒ no
|
|
895
|
+
// file, but the folder still leaks). Report it structurally.
|
|
896
|
+
const strayFolder = join(repoPath, WORK_ROOT, migration.fromFolder);
|
|
897
|
+
if (existsSync(strayFolder)) {
|
|
898
|
+
leaks.push({
|
|
899
|
+
where: `${WORK_ROOT}/${migration.fromFolder}`,
|
|
900
|
+
token: `${WORK_ROOT}/${migration.fromFolder}`,
|
|
901
|
+
lens: 'forward',
|
|
902
|
+
why: `surviving '${WORK_ROOT}/${migration.fromFolder}/' folder`,
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
// The git refs (lock refs + branches carrying the retired word).
|
|
906
|
+
for (const spec of [
|
|
907
|
+
{
|
|
908
|
+
args: ['for-each-ref', '--format=%(refname)', 'refs/dorfl/lock/'],
|
|
909
|
+
where: 'refs/dorfl/lock',
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
args: [
|
|
913
|
+
'for-each-ref',
|
|
914
|
+
'--format=%(refname:short)',
|
|
915
|
+
`refs/heads/${WORK_ROOT}/`,
|
|
916
|
+
],
|
|
917
|
+
where: 'refs/heads',
|
|
918
|
+
},
|
|
919
|
+
]) {
|
|
920
|
+
const res = run('git', spec.args, repoPath, {env});
|
|
921
|
+
if (res.status !== 0) continue;
|
|
922
|
+
for (const line of res.stdout.split('\n')) {
|
|
923
|
+
const ref = line.trim();
|
|
924
|
+
if (ref === '') continue;
|
|
925
|
+
if (
|
|
926
|
+
new RegExp(`(^|/)${from}-`, 'i').test(ref) ||
|
|
927
|
+
new RegExp(`/${from}-`, 'i').test(ref)
|
|
928
|
+
) {
|
|
929
|
+
leaks.push({
|
|
930
|
+
where: spec.where,
|
|
931
|
+
token: ref,
|
|
932
|
+
lens: 'forward',
|
|
933
|
+
why: `surviving '${from}-' git ref`,
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
return leaks;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
943
|
+
// The orchestrator (the thin CLI shell drives THIS).
|
|
944
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
945
|
+
|
|
946
|
+
/** Options for {@link runPrdToSpec}. */
|
|
947
|
+
export interface PrdToSpecOptions {
|
|
948
|
+
/** The repo working-tree root to migrate (the in-place downstream/fixture). */
|
|
949
|
+
repoPath: string;
|
|
950
|
+
/** Report what WOULD change across all layers, touching nothing. */
|
|
951
|
+
dryRun?: boolean;
|
|
952
|
+
/** The vocabulary cutover (defaults to the `prd → spec` {@link MIGRATION}). */
|
|
953
|
+
migration?: VocabularyMigration;
|
|
954
|
+
env?: NodeJS.ProcessEnv;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/** What the migration did (or, under dry-run, would do). */
|
|
958
|
+
export interface PrdToSpecResult {
|
|
959
|
+
/** Set when the quiescence gate REFUSED — no layer ran. */
|
|
960
|
+
refused?: QuiescenceViolation;
|
|
961
|
+
/** True when this run was a dry-run (nothing was written). */
|
|
962
|
+
dryRun: boolean;
|
|
963
|
+
resync?: ResyncResult;
|
|
964
|
+
folderMoves: FolderMove[];
|
|
965
|
+
contentRewrites: ContentRewrite[];
|
|
966
|
+
configRewrites: ConfigRewrite[];
|
|
967
|
+
refRenames: RefRename[];
|
|
968
|
+
/** Leaks found by the post-migration scan (empty ⇒ green). Skipped on refuse. */
|
|
969
|
+
leaks: DataLeak[];
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Run the self-contained `prd → spec` migration end-to-end (ADR §7e, decision
|
|
974
|
+
* B): quiescence gate → setup re-sync → four-layer data migration → leak scan.
|
|
975
|
+
* On a quiescence violation it REFUSES (returns `{refused}`, runs no layer). The
|
|
976
|
+
* thin `dorfl prd-to-spec` CLI shell formats this result; the fixture test
|
|
977
|
+
* asserts each field. `dryRun` threads through every layer.
|
|
978
|
+
*/
|
|
979
|
+
export function runPrdToSpec(options: PrdToSpecOptions): PrdToSpecResult {
|
|
980
|
+
const {repoPath} = options;
|
|
981
|
+
const migration = options.migration ?? MIGRATION;
|
|
982
|
+
const dryRun = options.dryRun === true;
|
|
983
|
+
const env = options.env;
|
|
984
|
+
|
|
985
|
+
const refused = checkQuiescence(repoPath, migration, env);
|
|
986
|
+
if (refused) {
|
|
987
|
+
return {
|
|
988
|
+
refused,
|
|
989
|
+
dryRun,
|
|
990
|
+
folderMoves: [],
|
|
991
|
+
contentRewrites: [],
|
|
992
|
+
configRewrites: [],
|
|
993
|
+
refRenames: [],
|
|
994
|
+
leaks: [],
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// 2. Setup contract re-sync FIRST (decision B).
|
|
999
|
+
const resync = resyncProtocol(repoPath, {dryRun});
|
|
1000
|
+
// 3a. Folders (git mv) — before content, so the content sweep also fixes the
|
|
1001
|
+
// moved items' own `work/prds/…` body refs in their NEW location.
|
|
1002
|
+
const folderMoves = migrateFolders(repoPath, migration, {dryRun});
|
|
1003
|
+
// 3b. Item content (frontmatter + inert refs) across ALL items incl. done/.
|
|
1004
|
+
const contentRewrites = migrateAllItemContent(repoPath, migration, {dryRun});
|
|
1005
|
+
// 3c. Config key rename.
|
|
1006
|
+
const configRewrites = migrateConfig(repoPath, migration, {dryRun});
|
|
1007
|
+
// 3d. Inert git refs (lock refs + work-branches).
|
|
1008
|
+
const refRenames = migrateRefs(repoPath, migration, {dryRun, env});
|
|
1009
|
+
|
|
1010
|
+
// The acceptance GATE over the converted tree (skipped under dry-run, where
|
|
1011
|
+
// nothing was written so the tree still carries the pre-migration data).
|
|
1012
|
+
const leaks = dryRun ? [] : scanForLeaks(repoPath, migration, env);
|
|
1013
|
+
|
|
1014
|
+
return {
|
|
1015
|
+
dryRun,
|
|
1016
|
+
resync,
|
|
1017
|
+
folderMoves,
|
|
1018
|
+
contentRewrites,
|
|
1019
|
+
configRewrites,
|
|
1020
|
+
refRenames,
|
|
1021
|
+
leaks,
|
|
1022
|
+
};
|
|
1023
|
+
}
|