dorfl 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advance-ci-template.d.ts +3 -3
- package/dist/advance-ci-template.js +1 -1
- package/dist/advance-ci-template.js.map +1 -1
- package/dist/advance-classify.d.ts +5 -5
- package/dist/advance-classify.d.ts.map +1 -1
- package/dist/advance-classify.js +4 -4
- package/dist/advance-drivers.d.ts +8 -8
- package/dist/advance-drivers.d.ts.map +1 -1
- package/dist/advance-drivers.js +20 -8
- package/dist/advance-drivers.js.map +1 -1
- package/dist/advance-isolated.d.ts +3 -3
- package/dist/advance-isolated.d.ts.map +1 -1
- package/dist/advance-isolated.js +1 -1
- package/dist/advance-lifecycle-template.d.ts +2 -2
- package/dist/advance-lifecycle-template.d.ts.map +1 -1
- package/dist/advance-lifecycle-template.js +85 -13
- package/dist/advance-lifecycle-template.js.map +1 -1
- package/dist/advance-loop-driver.d.ts +3 -3
- package/dist/advance-loop-driver.d.ts.map +1 -1
- package/dist/advance-loop-driver.js +1 -1
- package/dist/advance-treeless-publish.d.ts +24 -1
- package/dist/advance-treeless-publish.d.ts.map +1 -1
- package/dist/advance-treeless-publish.js +41 -0
- package/dist/advance-treeless-publish.js.map +1 -1
- package/dist/advance.d.ts +69 -17
- package/dist/advance.d.ts.map +1 -1
- package/dist/advance.js +409 -102
- package/dist/advance.js.map +1 -1
- package/dist/advancing-lock.d.ts +31 -3
- package/dist/advancing-lock.d.ts.map +1 -1
- package/dist/advancing-lock.js +55 -5
- package/dist/advancing-lock.js.map +1 -1
- package/dist/agent-launch.d.ts +12 -0
- package/dist/agent-launch.d.ts.map +1 -1
- package/dist/agent-launch.js +22 -12
- package/dist/agent-launch.js.map +1 -1
- package/dist/agent-stop.d.ts +40 -2
- package/dist/agent-stop.d.ts.map +1 -1
- package/dist/agent-stop.js +30 -2
- package/dist/agent-stop.js.map +1 -1
- package/dist/apply-decide.d.ts +19 -5
- package/dist/apply-decide.d.ts.map +1 -1
- package/dist/apply-decide.js +38 -9
- package/dist/apply-decide.js.map +1 -1
- package/dist/apply-merge-action.d.ts +20 -8
- package/dist/apply-merge-action.d.ts.map +1 -1
- package/dist/apply-merge-action.js +44 -9
- package/dist/apply-merge-action.js.map +1 -1
- package/dist/apply-persist.d.ts +62 -31
- package/dist/apply-persist.d.ts.map +1 -1
- package/dist/apply-persist.js +173 -44
- package/dist/apply-persist.js.map +1 -1
- package/dist/apply-stuck-action.d.ts +151 -0
- package/dist/apply-stuck-action.d.ts.map +1 -0
- package/dist/apply-stuck-action.js +125 -0
- package/dist/apply-stuck-action.js.map +1 -0
- package/dist/brand.d.ts +12 -1
- package/dist/brand.d.ts.map +1 -1
- package/dist/brand.js +2 -1
- package/dist/brand.js.map +1 -1
- package/dist/buildable-body.d.ts +14 -14
- package/dist/buildable-body.js +9 -9
- package/dist/claim-cas.d.ts +3 -3
- package/dist/claim-cas.js +1 -1
- package/dist/claim-cas.js.map +1 -1
- package/dist/cli-spinner.d.ts +1 -1
- package/dist/cli-spinner.js +1 -1
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +385 -128
- package/dist/cli.js.map +1 -1
- package/dist/close-job-template.d.ts +3 -3
- package/dist/close-job-template.js +9 -9
- package/dist/close-job.d.ts +8 -8
- package/dist/close-job.js +25 -25
- package/dist/close-job.js.map +1 -1
- package/dist/complete.d.ts +9 -6
- package/dist/complete.d.ts.map +1 -1
- package/dist/complete.js +99 -45
- package/dist/complete.js.map +1 -1
- package/dist/concurrency.d.ts +1 -1
- package/dist/concurrency.js +1 -1
- package/dist/config.d.ts +86 -40
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +61 -9
- package/dist/config.js.map +1 -1
- package/dist/continue-branch.d.ts +1 -1
- package/dist/continue-branch.d.ts.map +1 -1
- package/dist/continue-branch.js +23 -1
- package/dist/continue-branch.js.map +1 -1
- package/dist/cwd-section.js +1 -1
- package/dist/cwd-section.js.map +1 -1
- package/dist/decision-engine.d.ts +39 -11
- package/dist/decision-engine.d.ts.map +1 -1
- package/dist/decision-engine.js +10 -6
- package/dist/decision-engine.js.map +1 -1
- package/dist/do-autopick.d.ts +6 -6
- package/dist/do-autopick.d.ts.map +1 -1
- package/dist/do-autopick.js +16 -6
- package/dist/do-autopick.js.map +1 -1
- package/dist/do-config.d.ts +4 -4
- package/dist/do-config.js +3 -3
- package/dist/do-config.js.map +1 -1
- package/dist/do-remote-auto.d.ts +2 -2
- package/dist/do-remote-auto.js +1 -1
- package/dist/do.d.ts +74 -81
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +439 -50
- package/dist/do.js.map +1 -1
- package/dist/drop-source.d.ts +2 -2
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +19 -11
- package/dist/env-config.js.map +1 -1
- package/dist/failure-cause.d.ts +3 -2
- package/dist/failure-cause.d.ts.map +1 -1
- package/dist/failure-cause.js +28 -1
- package/dist/failure-cause.js.map +1 -1
- package/dist/format.d.ts +6 -6
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +13 -30
- package/dist/format.js.map +1 -1
- package/dist/frontmatter.d.ts +28 -15
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +38 -24
- package/dist/frontmatter.js.map +1 -1
- package/dist/gc.d.ts +65 -6
- package/dist/gc.d.ts.map +1 -1
- package/dist/gc.js +126 -9
- package/dist/gc.js.map +1 -1
- package/dist/github.d.ts +14 -0
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +73 -0
- package/dist/github.js.map +1 -1
- package/dist/harness.d.ts +25 -0
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/install-ci-branch-protection.d.ts +133 -39
- package/dist/install-ci-branch-protection.d.ts.map +1 -1
- package/dist/install-ci-branch-protection.js +191 -49
- package/dist/install-ci-branch-protection.js.map +1 -1
- package/dist/install-ci-capabilities/advance-lifecycle.d.ts +1 -1
- package/dist/install-ci-capabilities/advance-lifecycle.js +1 -1
- package/dist/install-ci-capabilities/close-job.d.ts +1 -1
- package/dist/install-ci-capabilities/close-job.js +1 -1
- package/dist/install-ci-capabilities/example-noop.d.ts +1 -1
- package/dist/install-ci-capabilities/example-noop.js +1 -1
- package/dist/install-ci-capabilities/intake.d.ts +2 -2
- package/dist/install-ci-capabilities/intake.js +2 -2
- package/dist/install-ci-capabilities/verify.d.ts +1 -1
- package/dist/install-ci-capabilities/verify.js +1 -1
- package/dist/install-ci-core.d.ts +24 -4
- package/dist/install-ci-core.d.ts.map +1 -1
- package/dist/install-ci-core.js +12 -5
- package/dist/install-ci-core.js.map +1 -1
- package/dist/install-ci-github.d.ts +33 -1
- package/dist/install-ci-github.d.ts.map +1 -1
- package/dist/install-ci-github.js +74 -1
- package/dist/install-ci-github.js.map +1 -1
- package/dist/install-ci.d.ts +1 -1
- package/dist/install-ci.js +4 -4
- package/dist/install-ci.js.map +1 -1
- package/dist/install-skills.d.ts +123 -0
- package/dist/install-skills.d.ts.map +1 -0
- package/dist/install-skills.js +100 -0
- package/dist/install-skills.js.map +1 -0
- package/dist/intake-event.d.ts +6 -6
- package/dist/intake-event.js +6 -6
- package/dist/intake-marker.d.ts +3 -3
- package/dist/intake-marker.d.ts.map +1 -1
- package/dist/intake-triage.d.ts +1 -1
- package/dist/intake-triage.js +2 -2
- package/dist/intake-trigger-template.d.ts +8 -8
- package/dist/intake-trigger-template.d.ts.map +1 -1
- package/dist/intake-trigger-template.js +17 -17
- package/dist/intake-trigger-template.js.map +1 -1
- package/dist/intake.d.ts +65 -52
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +92 -76
- package/dist/intake.js.map +1 -1
- package/dist/integration-core.d.ts +27 -24
- package/dist/integration-core.d.ts.map +1 -1
- package/dist/integration-core.js +191 -77
- package/dist/integration-core.js.map +1 -1
- package/dist/integrator.d.ts +1 -1
- package/dist/integrator.d.ts.map +1 -1
- package/dist/integrator.js +19 -3
- package/dist/integrator.js.map +1 -1
- package/dist/isolation.d.ts +4 -4
- package/dist/isolation.d.ts.map +1 -1
- package/dist/isolation.js +9 -0
- package/dist/isolation.js.map +1 -1
- package/dist/issue-provider.d.ts +4 -4
- package/dist/issue-provider.js +1 -1
- package/dist/item-lock.d.ts +216 -149
- package/dist/item-lock.d.ts.map +1 -1
- package/dist/item-lock.js +345 -270
- package/dist/item-lock.js.map +1 -1
- package/dist/item-path.d.ts +2 -2
- package/dist/item-path.js +2 -2
- package/dist/ledger-lint.d.ts +9 -9
- package/dist/ledger-lint.js +9 -9
- package/dist/ledger-read.d.ts +57 -57
- package/dist/ledger-read.d.ts.map +1 -1
- package/dist/ledger-read.js +24 -24
- package/dist/ledger-read.js.map +1 -1
- package/dist/ledger-write.d.ts +33 -28
- package/dist/ledger-write.d.ts.map +1 -1
- package/dist/ledger-write.js +100 -122
- package/dist/ledger-write.js.map +1 -1
- package/dist/lifecycle-gather.d.ts +18 -1
- package/dist/lifecycle-gather.d.ts.map +1 -1
- package/dist/lifecycle-gather.js +23 -17
- package/dist/lifecycle-gather.js.map +1 -1
- package/dist/lifecycle-pools.d.ts +44 -7
- package/dist/lifecycle-pools.d.ts.map +1 -1
- package/dist/lifecycle-pools.js +27 -7
- package/dist/lifecycle-pools.js.map +1 -1
- package/dist/merge-question-surfacer.d.ts +18 -4
- package/dist/merge-question-surfacer.d.ts.map +1 -1
- package/dist/merge-question-surfacer.js +20 -5
- package/dist/merge-question-surfacer.js.map +1 -1
- package/dist/migrate-stuck-locks.d.ts +129 -0
- package/dist/migrate-stuck-locks.d.ts.map +1 -0
- package/dist/migrate-stuck-locks.js +355 -0
- package/dist/migrate-stuck-locks.js.map +1 -0
- package/dist/mint-adr.js +4 -4
- package/dist/mirror-pool-scan.d.ts +2 -2
- package/dist/mirror-pool-scan.js +3 -3
- package/dist/mirror-pool-scan.js.map +1 -1
- package/dist/needs-attention.d.ts +304 -27
- package/dist/needs-attention.d.ts.map +1 -1
- package/dist/needs-attention.js +542 -67
- package/dist/needs-attention.js.map +1 -1
- package/dist/orphan-sidecar.d.ts +10 -6
- package/dist/orphan-sidecar.d.ts.map +1 -1
- package/dist/orphan-sidecar.js +35 -2
- package/dist/orphan-sidecar.js.map +1 -1
- package/dist/pi-harness.d.ts +16 -0
- package/dist/pi-harness.d.ts.map +1 -1
- package/dist/pi-harness.js +82 -2
- package/dist/pi-harness.js.map +1 -1
- package/dist/placement.d.ts +8 -8
- package/dist/placement.js +3 -3
- package/dist/prd-to-spec.d.ts.map +1 -1
- package/dist/prd-to-spec.js +9 -5
- package/dist/prd-to-spec.js.map +1 -1
- package/dist/prompt.d.ts +16 -19
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +17 -19
- package/dist/prompt.js.map +1 -1
- package/dist/protocol/CLAIM-PROTOCOL.md +17 -10
- package/dist/protocol/REVIEW-PROTOCOL.md +4 -1
- package/dist/protocol/SURFACE-PROTOCOL.md +16 -2
- package/dist/protocol/TASKING-PROTOCOL.md +3 -1
- package/dist/protocol/WORK-CONTRACT.md +22 -18
- package/dist/protocol/task-template.md +1 -1
- package/dist/readiness.d.ts +1 -1
- package/dist/reap-branches.d.ts +12 -9
- package/dist/reap-branches.d.ts.map +1 -1
- package/dist/reap-branches.js +25 -7
- package/dist/reap-branches.js.map +1 -1
- package/dist/recover-isolated.d.ts +12 -0
- package/dist/recover-isolated.d.ts.map +1 -1
- package/dist/recover-isolated.js +6 -1
- package/dist/recover-isolated.js.map +1 -1
- package/dist/repo-config.d.ts +23 -2
- package/dist/repo-config.d.ts.map +1 -1
- package/dist/repo-config.js +64 -18
- package/dist/repo-config.js.map +1 -1
- package/dist/repo-mirror.d.ts.map +1 -1
- package/dist/repo-mirror.js +18 -2
- package/dist/repo-mirror.js.map +1 -1
- package/dist/review-gate.d.ts +3 -3
- package/dist/review-gate.d.ts.map +1 -1
- package/dist/review-gate.js +11 -10
- package/dist/review-gate.js.map +1 -1
- package/dist/review-verdict.d.ts +2 -2
- package/dist/review-verdict.d.ts.map +1 -1
- package/dist/review-verdict.js +2 -2
- package/dist/review-verdict.js.map +1 -1
- package/dist/run.d.ts +2 -2
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +58 -23
- package/dist/run.js.map +1 -1
- package/dist/scan.d.ts +26 -17
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +29 -15
- package/dist/scan.js.map +1 -1
- package/dist/select-order.d.ts +1 -1
- package/dist/select-priority.d.ts +14 -14
- package/dist/select-priority.d.ts.map +1 -1
- package/dist/select-priority.js +5 -5
- package/dist/select-priority.js.map +1 -1
- package/dist/sidecar-apply.js +1 -1
- package/dist/sidecar-apply.js.map +1 -1
- package/dist/sidecar.d.ts +47 -12
- package/dist/sidecar.d.ts.map +1 -1
- package/dist/sidecar.js +84 -4
- package/dist/sidecar.js.map +1 -1
- package/dist/skills/answer-questions/SKILL.md +89 -0
- package/dist/skills/capture-signal/SKILL.md +52 -0
- package/dist/skills/convert-from-prd-to-spec/SKILL.md +90 -0
- package/dist/skills/drive-tasks/SKILL.md +218 -0
- package/dist/skills/from-idea/SKILL.md +83 -0
- package/dist/skills/merge-prs/SKILL.md +70 -0
- package/dist/skills/orchestrate/SKILL.md +101 -0
- package/dist/skills/promote/SKILL.md +35 -0
- package/dist/skills/review/SKILL.md +16 -0
- package/dist/skills/setup/SKILL.md +258 -0
- package/dist/skills/setup/protocol/ADR-FORMAT.md +47 -0
- package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +224 -0
- package/dist/skills/setup/protocol/REVIEW-PROTOCOL.md +122 -0
- package/dist/skills/setup/protocol/SURFACE-PROTOCOL.md +135 -0
- package/dist/skills/setup/protocol/TASKING-PROTOCOL.md +124 -0
- package/dist/skills/setup/protocol/WORK-CONTRACT.md +280 -0
- package/dist/skills/setup/protocol/spec-template.md +71 -0
- package/dist/skills/setup/protocol/task-template.md +65 -0
- package/dist/skills/surface-questions/SKILL.md +16 -0
- package/dist/skills/to-spec/SKILL.md +34 -0
- package/dist/skills/to-task/SKILL.md +19 -0
- package/dist/skills/triage-observations/SKILL.md +78 -0
- package/dist/skills/work/SKILL.md +51 -0
- package/dist/slug-namespace.d.ts +6 -6
- package/dist/slug-namespace.js +8 -8
- package/dist/slug-namespace.js.map +1 -1
- package/dist/spec-complete.d.ts.map +1 -1
- package/dist/spec-complete.js +4 -5
- package/dist/spec-complete.js.map +1 -1
- package/dist/start.d.ts +1 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +54 -60
- package/dist/start.js.map +1 -1
- package/dist/status.d.ts +3 -3
- package/dist/status.js +4 -4
- package/dist/status.js.map +1 -1
- package/dist/surface-gate.d.ts +2 -2
- package/dist/surface-gate.d.ts.map +1 -1
- package/dist/surface-gate.js +10 -3
- package/dist/surface-gate.js.map +1 -1
- package/dist/surface-persist.d.ts +2 -2
- package/dist/surface-persist.d.ts.map +1 -1
- package/dist/surface-persist.js +1 -1
- package/dist/surface-persist.js.map +1 -1
- package/dist/tasker-review-loop.d.ts +11 -10
- package/dist/tasker-review-loop.d.ts.map +1 -1
- package/dist/tasker-review-loop.js +8 -8
- package/dist/tasker-review-loop.js.map +1 -1
- package/dist/tasking-eligibility.d.ts +21 -21
- package/dist/tasking-eligibility.d.ts.map +1 -1
- package/dist/tasking-eligibility.js +10 -10
- package/dist/tasking-lock.d.ts +10 -10
- package/dist/tasking-lock.d.ts.map +1 -1
- package/dist/tasking-lock.js +69 -49
- package/dist/tasking-lock.js.map +1 -1
- package/dist/tasking.d.ts +73 -39
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +309 -95
- package/dist/tasking.js.map +1 -1
- package/dist/triage-gate.d.ts +1 -1
- package/dist/triage-persist.d.ts +15 -11
- package/dist/triage-persist.d.ts.map +1 -1
- package/dist/triage-persist.js +48 -18
- package/dist/triage-persist.js.map +1 -1
- package/dist/vendor/incur/agents.d.ts +58 -0
- package/dist/vendor/incur/agents.d.ts.map +1 -0
- package/dist/vendor/incur/agents.js +343 -0
- package/dist/vendor/incur/agents.js.map +1 -0
- package/dist/verify-workflow-template.d.ts +1 -1
- package/dist/verify-workflow-template.js +3 -3
- package/dist/watch-session.d.ts +11 -3
- package/dist/watch-session.d.ts.map +1 -1
- package/dist/watch-session.js +94 -7
- package/dist/watch-session.js.map +1 -1
- package/dist/work-layout.d.ts +15 -9
- package/dist/work-layout.d.ts.map +1 -1
- package/dist/work-layout.js +14 -9
- package/dist/work-layout.js.map +1 -1
- package/dist/workspace.d.ts +2 -2
- package/package.json +2 -2
- package/src/advance-ci-template.ts +4 -4
- package/src/advance-classify.ts +5 -5
- package/src/advance-drivers.ts +26 -13
- package/src/advance-isolated.ts +3 -3
- package/src/advance-lifecycle-template.ts +98 -13
- package/src/advance-loop-driver.ts +3 -3
- package/src/advance-treeless-publish.ts +46 -1
- package/src/advance.ts +495 -115
- package/src/advancing-lock.ts +102 -7
- package/src/agent-launch.ts +37 -12
- package/src/agent-stop.ts +60 -2
- package/src/apply-decide.ts +38 -9
- package/src/apply-merge-action.ts +49 -11
- package/src/apply-persist.ts +236 -62
- package/src/apply-stuck-action.ts +260 -0
- package/src/brand.ts +14 -2
- package/src/buildable-body.ts +14 -14
- package/src/claim-cas.ts +4 -4
- package/src/cli-spinner.ts +1 -1
- package/src/cli.ts +472 -141
- package/src/close-job-template.ts +9 -9
- package/src/close-job.ts +26 -26
- package/src/complete.ts +121 -63
- package/src/concurrency.ts +1 -1
- package/src/config.ts +143 -49
- package/src/continue-branch.ts +23 -1
- package/src/cwd-section.ts +1 -1
- package/src/decision-engine.ts +57 -19
- package/src/do-autopick.ts +21 -10
- package/src/do-config.ts +7 -7
- package/src/do-remote-auto.ts +2 -2
- package/src/do.ts +558 -85
- package/src/drop-source.ts +2 -2
- package/src/env-config.ts +19 -11
- package/src/failure-cause.ts +30 -1
- package/src/format.ts +13 -33
- package/src/frontmatter.ts +55 -35
- package/src/gc.ts +172 -9
- package/src/github.ts +78 -0
- package/src/harness.ts +25 -0
- package/src/index.ts +10 -0
- package/src/install-ci-branch-protection.ts +283 -58
- package/src/install-ci-capabilities/advance-lifecycle.ts +1 -1
- package/src/install-ci-capabilities/close-job.ts +1 -1
- package/src/install-ci-capabilities/example-noop.ts +1 -1
- package/src/install-ci-capabilities/intake.ts +2 -2
- package/src/install-ci-capabilities/verify.ts +1 -1
- package/src/install-ci-core.ts +34 -7
- package/src/install-ci-github.ts +87 -1
- package/src/install-ci.ts +4 -4
- package/src/install-skills.ts +166 -0
- package/src/intake-event.ts +6 -6
- package/src/intake-marker.ts +3 -3
- package/src/intake-triage.ts +2 -2
- package/src/intake-trigger-template.ts +18 -18
- package/src/intake.ts +138 -103
- package/src/integration-core.ts +234 -100
- package/src/integrator.ts +22 -9
- package/src/isolation.ts +13 -4
- package/src/issue-provider.ts +4 -4
- package/src/item-lock.ts +505 -361
- package/src/item-path.ts +2 -2
- package/src/ledger-lint.ts +9 -9
- package/src/ledger-read.ts +81 -81
- package/src/ledger-write.ts +137 -152
- package/src/lifecycle-gather.ts +45 -22
- package/src/lifecycle-pools.ts +71 -13
- package/src/merge-question-surfacer.ts +32 -8
- package/src/migrate-stuck-locks.ts +451 -0
- package/src/mint-adr.ts +4 -4
- package/src/mirror-pool-scan.ts +5 -5
- package/src/needs-attention.ts +877 -104
- package/src/orphan-sidecar.ts +49 -8
- package/src/pi-harness.ts +82 -2
- package/src/placement.ts +8 -8
- package/src/prd-to-spec.ts +13 -5
- package/src/prompt.ts +20 -25
- package/src/readiness.ts +1 -1
- package/src/reap-branches.ts +38 -14
- package/src/recover-isolated.ts +18 -1
- package/src/repo-config.ts +66 -17
- package/src/repo-mirror.ts +23 -1
- package/src/review-gate.ts +12 -11
- package/src/review-verdict.ts +3 -3
- package/src/run.ts +69 -23
- package/src/scan.ts +36 -22
- package/src/select-order.ts +1 -1
- package/src/select-priority.ts +15 -15
- package/src/sidecar-apply.ts +1 -1
- package/src/sidecar.ts +141 -14
- package/src/slug-namespace.ts +10 -10
- package/src/spec-complete.ts +4 -5
- package/src/start.ts +57 -63
- package/src/status.ts +7 -7
- package/src/surface-gate.ts +12 -5
- package/src/surface-persist.ts +3 -3
- package/src/tasker-review-loop.ts +18 -17
- package/src/tasking-eligibility.ts +21 -21
- package/src/tasking-lock.ts +75 -60
- package/src/tasking.ts +381 -135
- package/src/triage-gate.ts +1 -1
- package/src/triage-persist.ts +71 -27
- package/src/vendor/incur/LICENSE +21 -0
- package/src/vendor/incur/README.md +19 -0
- package/src/vendor/incur/agents.ts +392 -0
- package/src/verify-workflow-template.ts +3 -3
- package/src/watch-session.ts +102 -7
- package/src/work-layout.ts +14 -9
- package/src/workspace.ts +2 -2
package/src/tasking.ts
CHANGED
|
@@ -48,44 +48,44 @@ import {
|
|
|
48
48
|
import type {ReviewGate} from './review-gate.js';
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
* The **`do
|
|
51
|
+
* The **`do spec:<slug>` tasking path** (spec `auto-slice`, task
|
|
52
52
|
* `autoslice-command`) — the orchestration that ties the tasking GATE
|
|
53
53
|
* (`tasking-eligibility.ts`) and the tasking LOCK (`tasking-lock.ts`) together to
|
|
54
|
-
* task a
|
|
54
|
+
* task a spec into `work/tasks/backlog/` STAGED items (task
|
|
55
55
|
* `pre-backlog-staging-folder-and-promote-step-a` — the runner-owned promotion
|
|
56
|
-
* moves them `
|
|
57
|
-
* transition. This is the
|
|
56
|
+
* moves them `tasks/backlog/ → tasks/ready/` later), with the RUNNER owning every git-state
|
|
57
|
+
* transition. This is the spec branch of the `do` worker (ADR
|
|
58
58
|
* `command-surface-and-journeys.md` §3/§3a), NOT a standalone `task` command;
|
|
59
|
-
* `do.ts` dispatches `resolved.namespace === '
|
|
59
|
+
* `do.ts` dispatches `resolved.namespace === 'spec'` here.
|
|
60
60
|
*
|
|
61
61
|
* The end-to-end flow (mirroring the `do`/`run` runner-owns-git discipline — the
|
|
62
62
|
* agent only EDITS files, the runner does ALL git):
|
|
63
63
|
*
|
|
64
|
-
* 1. **Resolve the gate** (agent path): refuse to task a
|
|
65
|
-
* `humanOnly`/`needsAnswers`, or whose `taskedAfter`
|
|
64
|
+
* 1. **Resolve the gate** (agent path): refuse to task a spec that is
|
|
65
|
+
* `humanOnly`/`needsAnswers`, or whose `taskedAfter` specs are not yet tasked.
|
|
66
66
|
* The repo's `autoTask` POLICY also refuses on the AUTO-PICK pool path, but
|
|
67
|
-
* NOT when the
|
|
67
|
+
* NOT when the spec was named EXPLICITLY (`do spec:<slug>`, `explicit: true`):
|
|
68
68
|
* naming it IS the authorization, exactly as `do <task>` builds regardless of
|
|
69
69
|
* `autoBuild` (the pool, not the explicit claim, gates the policy). The HUMAN
|
|
70
70
|
* path is unbound by the gate entirely.
|
|
71
71
|
* 2. **Acquire the lock** (agent path) via the unified per-item lock CAS —
|
|
72
|
-
* serialising concurrent taskers on the `
|
|
73
|
-
* `work/
|
|
72
|
+
* serialising concurrent taskers on the `spec:<slug>` ref (the body STAYS in
|
|
73
|
+
* `work/specs/ready/`; the lock no longer moves it). The HUMAN path with no contention
|
|
74
74
|
* may task on `main` directly WITHOUT the lock.
|
|
75
|
-
* 3. **Invoke the agent harness** with the `to-task`
|
|
75
|
+
* 3. **Invoke the agent harness** with the `to-task` spec — the agent runs the
|
|
76
76
|
* tasker methodology and produces `work/tasks/backlog/<slug>.md` FILES ONLY; it does
|
|
77
77
|
* NOT commit/push/move (the same in-band boundary as the build agent).
|
|
78
78
|
* 4. **The runner integrates the COMPLETING transition through the SHARED core**
|
|
79
79
|
* (`performIntegration`, task `slice-output-through-integration`): the agent's
|
|
80
80
|
* tasking runs on a `work/<slug>` branch cut from `<arbiter>/main` (whose base
|
|
81
|
-
* holds the
|
|
82
|
-
*
|
|
81
|
+
* holds the spec in `work/specs/ready/`), and the produced backlog tasks + the durable
|
|
82
|
+
* spec lifecycle move (`work/specs/ready/ → work/specs/tasked/`)
|
|
83
83
|
* integrate via the band honoring `--propose` (push the branch + open a
|
|
84
84
|
* PR, NO `main` touch) / `--merge` (land on `main`). Because the integrate-time
|
|
85
85
|
* args resolve ONCE in the shared core, EVERY `do task:` integrate arg applies
|
|
86
|
-
* to `do
|
|
86
|
+
* to `do spec:` by construction. A content-identity STALE CHECK (the lock's
|
|
87
87
|
* read-stability backstop) fires FIRST against the acquire-time `lockedBlob`,
|
|
88
|
-
* so a concurrent edit of the held
|
|
88
|
+
* so a concurrent edit of the held spec still fails loud (`stale`).
|
|
89
89
|
*
|
|
90
90
|
* The tasking LOCK (`tasking-lock.ts`, `acquireTaskingLock`/`releaseTaskingLock`)
|
|
91
91
|
* is the UNIFIED per-item lock (`refs/dorfl/lock/<entry>`, `action: task`)
|
|
@@ -98,20 +98,20 @@ import type {ReviewGate} from './review-gate.js';
|
|
|
98
98
|
* owned by `slicer-review-edit-loop`; this path produces + integrates the tasks.
|
|
99
99
|
*/
|
|
100
100
|
|
|
101
|
-
/** The terminal status of one `do
|
|
101
|
+
/** The terminal status of one `do spec:<slug>` tasking run. */
|
|
102
102
|
export type TaskOutcome =
|
|
103
103
|
| 'tasked' // gate passed (agent) / unbound (human) → lock → agent → committed
|
|
104
104
|
| 'gate-refused' // the agent gate refused (humanOnly/needsAnswers/autoTask/taskedAfter)
|
|
105
105
|
| 'lock-lost' // the lock was lost/contended (another tasker holds it)
|
|
106
106
|
| 'agent-failed' // the agent invocation itself errored
|
|
107
|
-
| 'stale' // the held
|
|
108
|
-
| 'needs-attention' // the tasker edit loop found the decomposition unclear →
|
|
109
|
-
| 'usage-error'; // usage / environment problem (missing
|
|
107
|
+
| 'stale' // the held spec was edited under the lock → the tasking is stale
|
|
108
|
+
| 'needs-attention' // the tasker edit loop found the decomposition unclear → spec routed to needs-attention (no tasks)
|
|
109
|
+
| 'usage-error'; // usage / environment problem (missing spec, bad release, …)
|
|
110
110
|
|
|
111
111
|
export interface TaskResult {
|
|
112
112
|
exitCode: 0 | 1 | 2 | 3 | 4;
|
|
113
113
|
outcome: TaskOutcome;
|
|
114
|
-
/** The
|
|
114
|
+
/** The spec slug acted on. */
|
|
115
115
|
slug: string;
|
|
116
116
|
/** Repo-relative paths of the backlog tasks the runner committed. */
|
|
117
117
|
emitted?: string[];
|
|
@@ -119,7 +119,7 @@ export interface TaskResult {
|
|
|
119
119
|
* The tasker review→edit LOOP's disposition (`slicer-review-edit-loop`), when
|
|
120
120
|
* the loop ran. `converged` = the improved tasks landed; `uncertain-tasks` =
|
|
121
121
|
* the cap was hit and specific tasks landed `needsAnswers: true`; absent when
|
|
122
|
-
* no loop ran or the
|
|
122
|
+
* no loop ran or the spec was routed to needs-attention (`outcome:
|
|
123
123
|
* 'needs-attention'`).
|
|
124
124
|
*/
|
|
125
125
|
loop?: 'converged' | 'uncertain-tasks';
|
|
@@ -128,8 +128,8 @@ export interface TaskResult {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
|
-
* The agent invocation: runs the `to-task`
|
|
132
|
-
* `work/tasks/backlog/<slug>.md` task files (and trimming the
|
|
131
|
+
* The agent invocation: runs the `to-task` spec in `cwd`, WRITING
|
|
132
|
+
* `work/tasks/backlog/<slug>.md` task files (and trimming the spec). It does NO git —
|
|
133
133
|
* the runner captures the produced files and commits them.
|
|
134
134
|
*/
|
|
135
135
|
export type TaskDorfl = (input: {
|
|
@@ -155,7 +155,7 @@ const DEFAULT_LOCK_SEAM: TaskingLockSeam = {
|
|
|
155
155
|
};
|
|
156
156
|
|
|
157
157
|
export interface PerformTaskOptions {
|
|
158
|
-
/** The
|
|
158
|
+
/** The spec slug to task (`work/specs/ready/<slug>.md`). */
|
|
159
159
|
slug: string;
|
|
160
160
|
/** The working clone/checkout the tasking runs in. */
|
|
161
161
|
cwd: string;
|
|
@@ -170,16 +170,16 @@ export interface PerformTaskOptions {
|
|
|
170
170
|
/** Per-repo `autoTask` policy (resolved by `autoslice-gate`). Agent path only. */
|
|
171
171
|
autoTask?: boolean;
|
|
172
172
|
/**
|
|
173
|
-
* The
|
|
174
|
-
* `autoTask` POLICY is already satisfied — naming the
|
|
173
|
+
* The spec was named EXPLICITLY by the operator (`do spec:<slug>`), so the
|
|
174
|
+
* `autoTask` POLICY is already satisfied — naming the spec IS the authorization,
|
|
175
175
|
* EXACTLY as `do <task>` builds a named task regardless of `autoBuild` (the
|
|
176
176
|
* build path's precedent: `autoBuild` gates the scan/selection POOL only, never
|
|
177
177
|
* `performDo`'s explicit claim). When `true`, the agent tasking gate drops the
|
|
178
|
-
* `autoTask` policy term and binds ONLY the
|
|
178
|
+
* `autoTask` policy term and binds ONLY the spec's own readiness axes
|
|
179
179
|
* (`humanOnly`/`needsAnswers`) + `taskedAfter`. Defaults `false`. Both the
|
|
180
|
-
* explicit `do
|
|
180
|
+
* explicit `do spec:` dispatch AND the auto-pick path pass `true` here: the
|
|
181
181
|
* auto-pick POOL (`do-autopick.ts`) is the single `autoTask`-enforcement point
|
|
182
|
-
* (a pool-ineligible
|
|
182
|
+
* (a pool-ineligible spec is never selected), so once a spec is dispatched its
|
|
183
183
|
* policy is already settled. Agent path only.
|
|
184
184
|
*/
|
|
185
185
|
explicit?: boolean;
|
|
@@ -201,7 +201,7 @@ export interface PerformTaskOptions {
|
|
|
201
201
|
* with (task `slice-output-through-integration`): `propose` (default — push the
|
|
202
202
|
* `work/<slug>` branch + open a PR carrying the tasks, NO `main` touch) or
|
|
203
203
|
* `merge` (land them on `main`). Resolved ONCE in {@link performIntegration},
|
|
204
|
-
* so EVERY `do task:` integrate-time arg applies to `do
|
|
204
|
+
* so EVERY `do task:` integrate-time arg applies to `do spec:` by construction.
|
|
205
205
|
* The AGENT path only; the human path commits its own output. Defaults to the
|
|
206
206
|
* system default (`propose`).
|
|
207
207
|
*/
|
|
@@ -226,7 +226,7 @@ export interface PerformTaskOptions {
|
|
|
226
226
|
* task-path mirror of the build Gate-2, riding {@link performIntegration}'s
|
|
227
227
|
* review-before-integrate block. When `review` resolves on, a FRESH-CONTEXT
|
|
228
228
|
* agent reviews the WHOLE produced task SET (coherence / dependency graph /
|
|
229
|
-
* gaps + overlap /
|
|
229
|
+
* gaps + overlap / spec-goal correct-if-implemented) BEFORE the tasks integrate;
|
|
230
230
|
* `approve` lands them, `block` routes the set to needs-attention. It is
|
|
231
231
|
* controlled by the BUILD `--review`/`--no-review`/`--review-model` family (ONE
|
|
232
232
|
* gate-configuration story shared with the build path) and is ONE-SHOT —
|
|
@@ -244,10 +244,22 @@ export interface PerformTaskOptions {
|
|
|
244
244
|
* loop's {@link taskerLoopModel} — see the note there.
|
|
245
245
|
*/
|
|
246
246
|
acceptanceReviewModel?: string;
|
|
247
|
+
/**
|
|
248
|
+
* **The cross-job merge-serialiser CAS-retry cap** (config `mergeRetries`, spec
|
|
249
|
+
* `land-time-reverify-and-parallel-merge-ceiling` Story 5 / Applied Answer q1
|
|
250
|
+
* (a)). Threaded VERBATIM into {@link performIntegration} so a wide-matrix CI's
|
|
251
|
+
* raised cap actually reaches the cross-job land queue (the CAS loop IS the
|
|
252
|
+
* queue across separate jobs) on the `do spec:<slug>` tasking-transition path
|
|
253
|
+
* too. Resolved ONCE at the entry point through the gate-family precedence chain
|
|
254
|
+
* (flag > env > per-repo > global > default), same as `complete`/`do`/`run`.
|
|
255
|
+
* Unset ⇒ falls through to the engine's `DEFAULT_MERGE_RETRIES = 1000`
|
|
256
|
+
* (byte-for-byte unchanged).
|
|
257
|
+
*/
|
|
258
|
+
mergeRetries?: number;
|
|
247
259
|
/** Injectable lock seam (tests stub acquire/release). Defaults to the real CAS. */
|
|
248
260
|
lock?: TaskingLockSeam;
|
|
249
261
|
/**
|
|
250
|
-
* **The per-repo TASK-PLACEMENT default** (
|
|
262
|
+
* **The per-repo TASK-PLACEMENT default** (spec
|
|
251
263
|
* `staging-pool-position-gate-and-trust-model` US #5, task
|
|
252
264
|
* `runner-deterministic-slice-placement-policy-and-precedence`). The
|
|
253
265
|
* resolved per-repo default landing for the tasker's emitted tasks, fed as
|
|
@@ -255,10 +267,10 @@ export interface PerformTaskOptions {
|
|
|
255
267
|
* resolver (`src/placement.ts`). The resolver overlays an EXPLICIT operator
|
|
256
268
|
* flag ({@link explicitTasksLandIn}, top) and the UNTRUSTED-ORIGIN force
|
|
257
269
|
* (`originTrust: untrusted` ⇒ staging) on top. Unset ⇒ the resolver's
|
|
258
|
-
* built-in floor applies (`staging` = `
|
|
270
|
+
* built-in floor applies (`staging` = `tasks/backlog/`, the conservative
|
|
259
271
|
* landing that preserves zero behaviour change for the normal path).
|
|
260
272
|
*/
|
|
261
|
-
tasksLandIn?: '
|
|
273
|
+
tasksLandIn?: 'backlog' | 'ready';
|
|
262
274
|
/**
|
|
263
275
|
* **The OPERATOR's EXPLICIT task-placement override** (the TOP precedence
|
|
264
276
|
* rung). When set, the runner-deterministic resolver lands the tasks HERE
|
|
@@ -268,15 +280,15 @@ export interface PerformTaskOptions {
|
|
|
268
280
|
* force-key"). Set ONLY when the operator typed `--tasks-land-in <where>`;
|
|
269
281
|
* never when the value came from config.
|
|
270
282
|
*/
|
|
271
|
-
explicitTasksLandIn?: '
|
|
283
|
+
explicitTasksLandIn?: 'backlog' | 'ready';
|
|
272
284
|
/**
|
|
273
|
-
* **The tasker review→edit→converge LOOP** (`slicer-review-edit-loop`, GATES
|
|
274
|
-
* `work/
|
|
285
|
+
* **The tasker review→edit→converge LOOP** (`slicer-review-edit-loop`, GATES spec
|
|
286
|
+
* `work/specs/ready/review.md` RESOLVED DESIGN — Shape 2 / insertion point A). When
|
|
275
287
|
* provided, AFTER the agent produces candidate tasks (step 3) and BEFORE the
|
|
276
288
|
* runner finalises them (step 4), run the `review` SKILL as a review→edit→
|
|
277
289
|
* re-review loop that IMPROVES the candidate tasks in place, then routes the
|
|
278
290
|
* verdict through the three outcomes (converge→land / uncertain-task→
|
|
279
|
-
* needsAnswers / decomposition-unclear→
|
|
291
|
+
* needsAnswers / decomposition-unclear→spec-to-needs-attention). The seam is the
|
|
280
292
|
* review+edit gate (tests inject a canned verdict+edits; production:
|
|
281
293
|
* {@link harnessTaskReviewGate}). Omitted ⇒ NO loop (the candidate tasks land
|
|
282
294
|
* as-is — the pre-loop behaviour). The HUMAN path is unaffected (the loop runs
|
|
@@ -318,14 +330,14 @@ export interface PerformTaskOptions {
|
|
|
318
330
|
const DEFAULT_ARBITER = 'origin';
|
|
319
331
|
|
|
320
332
|
/**
|
|
321
|
-
* **The STAGED-TASKS dir** (
|
|
333
|
+
* **The STAGED-TASKS dir** (spec `staging-pool-position-gate-and-trust-model`,
|
|
322
334
|
* task `pre-backlog-staging-folder-and-promote-step-a`, governing ADR
|
|
323
335
|
* `placement-is-runner-deterministic-humanonly-is-agent-judgement`). The runner
|
|
324
336
|
* lands the tasker's emitted task files HERE, NOT in `work/tasks/ready/`: an item
|
|
325
|
-
* born in `
|
|
337
|
+
* born in `tasks/backlog/` is durable + readable but NOT in the agent-eligible
|
|
326
338
|
* pool (`work/tasks/ready/` STILL means the pool — every reader is byte-for-byte
|
|
327
339
|
* unchanged). A runner/human-owned promotion (`promoteFromPreBacklog` in
|
|
328
|
-
* `needs-attention.ts`) moves an approved item `
|
|
340
|
+
* `needs-attention.ts`) moves an approved item `tasks/backlog/ → tasks/ready/` to make
|
|
329
341
|
* it claimable. STEP A: ADDITIVE — no `work/tasks/ready/` reader changes here.
|
|
330
342
|
*/
|
|
331
343
|
export const STAGED_TASKS_DIR = workFolderRel('tasks-backlog');
|
|
@@ -336,7 +348,7 @@ export const STAGED_TASKS_DIR = workFolderRel('tasks-backlog');
|
|
|
336
348
|
* origin, or an `--tasks-land-in ready` operator override). The agent NEVER
|
|
337
349
|
* writes here — it always writes to {@link STAGED_TASKS_DIR}; the runner
|
|
338
350
|
* redirects the emitted files to the resolved destination at integrate-stage
|
|
339
|
-
* time.
|
|
351
|
+
* time. spec US #4 / the governing ADR: the agent cannot self-place into the
|
|
340
352
|
* pool. Task `runner-deterministic-slice-placement-policy-and-precedence`.
|
|
341
353
|
*/
|
|
342
354
|
const POOL_TASKS_DIR = workFolderRel('tasks-ready');
|
|
@@ -348,17 +360,19 @@ const TASK_PLACEMENT_SLOTS = {
|
|
|
348
360
|
} as const;
|
|
349
361
|
|
|
350
362
|
/**
|
|
351
|
-
* Map the `tasksLandIn` value spelling (`
|
|
363
|
+
* Map the `tasksLandIn` value spelling (`backlog` | `ready`) onto the
|
|
352
364
|
* resolver's lifecycle-generic side enum (`staging` | `pool`). Returns
|
|
353
365
|
* `undefined` when no value is set, so the resolver's next precedence rung
|
|
354
|
-
* applies (the built-in floor). The legacy `'backlog'`/`'todo'`
|
|
355
|
-
* are NOT accepted (clean break — the value was renamed
|
|
356
|
-
* → `'
|
|
366
|
+
* applies (the built-in floor). The legacy `'pre-backlog'`/`'todo'` spellings
|
|
367
|
+
* are NOT accepted (clean break — the staging value was renamed
|
|
368
|
+
* `'pre-backlog'` → `'backlog'`, and the pool value was renamed
|
|
369
|
+
* `'backlog'` → `'todo'` → `'ready'`, ADR
|
|
370
|
+
* `rename-task-pool-folder-todo-to-ready`).
|
|
357
371
|
*/
|
|
358
372
|
function landingToSide(
|
|
359
|
-
landing: '
|
|
373
|
+
landing: 'backlog' | 'ready' | undefined,
|
|
360
374
|
): 'staging' | 'pool' | undefined {
|
|
361
|
-
if (landing === '
|
|
375
|
+
if (landing === 'backlog') return 'staging';
|
|
362
376
|
if (landing === 'ready') return 'pool';
|
|
363
377
|
return undefined;
|
|
364
378
|
}
|
|
@@ -369,7 +383,7 @@ function stagedTaskPath(name: string): string {
|
|
|
369
383
|
}
|
|
370
384
|
|
|
371
385
|
/**
|
|
372
|
-
* Run the `do
|
|
386
|
+
* Run the `do spec:<slug>` tasking path end-to-end. Never throws for the expected
|
|
373
387
|
* gate-refused / lock-lost / agent-failed / stale cases — those are returned with
|
|
374
388
|
* the appropriate exit code and outcome. The runner owns all git; the agent only
|
|
375
389
|
* writes task files.
|
|
@@ -389,11 +403,11 @@ export async function performTask(
|
|
|
389
403
|
const doer = options.doer ?? 'agent';
|
|
390
404
|
const lock = options.lock ?? DEFAULT_LOCK_SEAM;
|
|
391
405
|
|
|
392
|
-
// 0. The
|
|
406
|
+
// 0. The spec must exist in the checkout (`work/specs/ready/<slug>.md`) — it is the
|
|
393
407
|
// source the agent tasks + the file the lock holds.
|
|
394
408
|
const specPath = workItemPath(cwd, 'specs-ready', slug);
|
|
395
409
|
if (!existsSync(specPath)) {
|
|
396
|
-
const message = `no
|
|
410
|
+
const message = `no spec '${slug}' found at ${workFolderRel('specs-ready')}/${slug}.md.`;
|
|
397
411
|
note(message);
|
|
398
412
|
return {exitCode: 1, outcome: 'usage-error', slug, message};
|
|
399
413
|
}
|
|
@@ -401,7 +415,7 @@ export async function performTask(
|
|
|
401
415
|
const specFm = parseFrontmatter(specContent);
|
|
402
416
|
|
|
403
417
|
// 1. RESOLVE THE GATE (agent path only). The human path is UNBOUND — a human
|
|
404
|
-
// decides for themselves whether a
|
|
418
|
+
// decides for themselves whether a spec is taskable.
|
|
405
419
|
if (doer === 'agent') {
|
|
406
420
|
const eligibility = resolveAgentGate(
|
|
407
421
|
cwd,
|
|
@@ -452,9 +466,9 @@ export async function performTask(
|
|
|
452
466
|
|
|
453
467
|
// 2b. ONBOARD the agent's tasking work onto a `work/<slug>` BRANCH cut from the
|
|
454
468
|
// freshly-fetched `<arbiter>/main` (task `slice-output-through-integration`).
|
|
455
|
-
// The
|
|
456
|
-
// longer moves it), so the branch's base HOLDS the held
|
|
457
|
-
// stage below moves it `
|
|
469
|
+
// The spec body rests in `work/specs/ready/<slug>.md` on `<arbiter>/main` (the lock no
|
|
470
|
+
// longer moves it), so the branch's base HOLDS the held spec — the lifecycle
|
|
471
|
+
// stage below moves it `specs/ready/ → specs/tasked/` ON THIS BRANCH and the shared integrate core (`--propose`
|
|
458
472
|
// PR / `--merge` main) lands the whole transition, WITHOUT the lock release
|
|
459
473
|
// committing tasks straight to `main`. The agent runs IN-PLACE on this branch
|
|
460
474
|
// (branch ≠ worktree; the isolation seam upgrades it). The HUMAN path stays on
|
|
@@ -463,7 +477,7 @@ export async function performTask(
|
|
|
463
477
|
await switchToWorkBranch(cwd, arbiter, slug, env);
|
|
464
478
|
}
|
|
465
479
|
|
|
466
|
-
// 3. INVOKE THE AGENT with the to-task
|
|
480
|
+
// 3. INVOKE THE AGENT with the to-task spec. It WRITES
|
|
467
481
|
// `work/tasks/backlog/*.md` task files (the STAGED area — NOT `work/tasks/ready/`,
|
|
468
482
|
// which is the agent-eligible pool the runner owns the promotion into; task
|
|
469
483
|
// `pre-backlog-staging-folder-and-promote-step-a`); it does NO git. We
|
|
@@ -472,10 +486,10 @@ export async function performTask(
|
|
|
472
486
|
const before = snapshotStagedTasks(cwd);
|
|
473
487
|
// Also snapshot the POOL `work/tasks/ready/` BEFORE the agent runs: the runner's
|
|
474
488
|
// final commit must scrub any agent writes there (an attempt to self-place into
|
|
475
|
-
// the pool,
|
|
489
|
+
// the pool, spec US #4) before `git add -A` would sweep them in.
|
|
476
490
|
const poolBefore = snapshotPool(cwd);
|
|
477
491
|
// Read the parent-spec self-pointer off `specFm.spec` (populated from the
|
|
478
|
-
// `spec:` key
|
|
492
|
+
// `spec:` key).
|
|
479
493
|
const prompt = buildTaskingSpec(slug, specFm.spec);
|
|
480
494
|
let agent: {ok: boolean; detail?: string};
|
|
481
495
|
try {
|
|
@@ -521,10 +535,10 @@ export async function performTask(
|
|
|
521
535
|
env: agentEnv,
|
|
522
536
|
note,
|
|
523
537
|
});
|
|
524
|
-
// DECOMPOSITION UNCLEAR: emit NO guessed tasks — route the held
|
|
538
|
+
// DECOMPOSITION UNCLEAR: emit NO guessed tasks — route the held spec to
|
|
525
539
|
// needs-attention with the questions as the reason. The lock release amends the
|
|
526
|
-
// `
|
|
527
|
-
// is the stuck lock now — NO folder write; the
|
|
540
|
+
// `spec:<slug>` unified lock `active → stuck` (the tasking needs-attention surface
|
|
541
|
+
// is the stuck lock now — NO folder write; the spec body stays in `work/specs/ready/`).
|
|
528
542
|
if (loopDisposition.outcome === 'decomposition-unclear') {
|
|
529
543
|
const reason = decompositionUnclearReason(
|
|
530
544
|
slug,
|
|
@@ -563,7 +577,7 @@ export async function performTask(
|
|
|
563
577
|
}
|
|
564
578
|
|
|
565
579
|
// 4. The RUNNER commits the COMPLETING transition: drop the produced backlog
|
|
566
|
-
// tasks IN + move the
|
|
580
|
+
// tasks IN + move the spec specs/ready/ -> specs/tasked/ (residence = tasked-ness) — now
|
|
567
581
|
// through the SHARED integrate core (`--propose` PR / `--merge` main), NOT a
|
|
568
582
|
// direct commit to `main`. The agent never does git. (The backlog snapshot is
|
|
569
583
|
// taken AFTER any loop edits, so the runner integrates the IMPROVED tasks,
|
|
@@ -574,7 +588,7 @@ export async function performTask(
|
|
|
574
588
|
// `runner-deterministic-slice-placement-policy-and-precedence`). Resolve which
|
|
575
589
|
// folder the runner lands the emitted tasks in BEFORE handing them to the
|
|
576
590
|
// shared integrate band: precedence `explicit > untrusted-origin ⇒ staging >
|
|
577
|
-
// tasksLandIn > built-in (staging)`, all from unforgeable inputs (the
|
|
591
|
+
// tasksLandIn > built-in (staging)`, all from unforgeable inputs (the spec's
|
|
578
592
|
// stamped `originTrust:` + the resolved per-repo default + the operator's
|
|
579
593
|
// explicit flag). The agent NEVER influences this; it always writes to
|
|
580
594
|
// `work/tasks/backlog/`, and the runner redirects at `stage()` time.
|
|
@@ -602,11 +616,11 @@ export async function performTask(
|
|
|
602
616
|
if (useLock) {
|
|
603
617
|
// READ-STABILITY BACKSTOP (the lock's content-identity check, now owned at the
|
|
604
618
|
// integrate seam): the OUTPUT no longer rides the lock release, so the band
|
|
605
|
-
// below would otherwise rebase a concurrent edit of the held
|
|
606
|
-
// into
|
|
619
|
+
// below would otherwise rebase a concurrent edit of the held spec body CLEANLY
|
|
620
|
+
// into spec/ (a rename+edit merge) while the tasks were cut from the OLD body —
|
|
607
621
|
// the exact silent stale-task drift the lock forbids
|
|
608
|
-
// (`work/notes/observations/tasking-lock-does-not-stabilise-
|
|
609
|
-
// compare the CURRENTLY held `work/
|
|
622
|
+
// (`work/notes/observations/tasking-lock-does-not-stabilise-spec-content.md`). So we
|
|
623
|
+
// compare the CURRENTLY held `work/specs/ready/<slug>.md` blob on the arbiter against
|
|
610
624
|
// the snapshot the lock TOOK (`lockedBlob`); ANY change ⇒ STALE ⇒ fail loud,
|
|
611
625
|
// touch NOTHING (the lock stays held; a human re-tasks or routes to
|
|
612
626
|
// needs-attention). It is the SAME content-identity check `releaseTaskingLock`
|
|
@@ -616,17 +630,17 @@ export async function performTask(
|
|
|
616
630
|
if (stale) {
|
|
617
631
|
const specRel = workItemRel('specs-ready', `${slug}.md`);
|
|
618
632
|
const message =
|
|
619
|
-
`RELEASE CONFLICT for '${slug}': the
|
|
633
|
+
`RELEASE CONFLICT for '${slug}': the spec was edited (${specRel} ` +
|
|
620
634
|
`changed on ${arbiter}/main) while the tasking lock was held. The tasking is ` +
|
|
621
|
-
`STALE — re-task from the edited
|
|
635
|
+
`STALE — re-task from the edited spec or route it to needs-attention. ` +
|
|
622
636
|
`The arbiter was NOT modified (lock still held).`;
|
|
623
637
|
note(message);
|
|
624
638
|
return {exitCode: 4, outcome: 'stale', slug, message};
|
|
625
639
|
}
|
|
626
640
|
|
|
627
641
|
// Route the OUTPUT through the SHARED integrate back-half (task
|
|
628
|
-
// `slice-output-through-integration`): the produced backlog tasks + the
|
|
629
|
-
// lifecycle move (`work/
|
|
642
|
+
// `slice-output-through-integration`): the produced backlog tasks + the spec
|
|
643
|
+
// lifecycle move (`work/specs/ready/ -> work/specs/tasked/`, residence = tasked-ness) integrate
|
|
630
644
|
// via `performIntegration` honoring `--propose` (push the work branch + open a
|
|
631
645
|
// PR, NO `main` touch) / `--merge` (land on `main`). Because the integrate-time
|
|
632
646
|
// args resolve ONCE in the shared core, every `do task:` arg applies here by
|
|
@@ -661,18 +675,35 @@ export async function performTask(
|
|
|
661
675
|
reviewGate: options.reviewGate,
|
|
662
676
|
reviewModel: options.acceptanceReviewModel,
|
|
663
677
|
reviewMaxRounds: 1,
|
|
678
|
+
// The cross-job merge-serialiser CAS-retry cap (config `mergeRetries`) —
|
|
679
|
+
// threaded so a wide-matrix CI's raised cap reaches the tasking-
|
|
680
|
+
// transition's land tail too (task
|
|
681
|
+
// `thread-merge-retries-cross-task-and-ratify-default`). Unset ⇒ falls
|
|
682
|
+
// through to the engine default (byte-for-byte unchanged).
|
|
683
|
+
mergeRetries: options.mergeRetries,
|
|
664
684
|
// The EXPLICITLY-chosen integrate mode proceeds AS-IS on an APPROVE — a
|
|
665
685
|
// `--merge` tasking run lands on main, `--propose` opens a PR. The tasking
|
|
666
686
|
// path's merge-vs-propose decision is the `integration` mode the user typed;
|
|
667
687
|
// `merge` IS the auto-land mode, so a resolved `merge` is never downgraded.
|
|
668
688
|
// The task gate family is `--review`/`--no-review`/`--review-model` only
|
|
669
|
-
// (
|
|
689
|
+
// (spec US #6).
|
|
670
690
|
mode: options.integration ?? 'propose',
|
|
671
691
|
noPR: options.noPR,
|
|
672
692
|
providerInstance: options.providerInstance,
|
|
693
|
+
// PROPOSE-MODE PR BODY (task `slicing-pr-body-summary-threading`): mirror the
|
|
694
|
+
// BUILD path's body threading (`do.ts` — `body: agent.output`). The tasker
|
|
695
|
+
// agent produces no `LaunchResult.output` we can carry, but the slice-SET
|
|
696
|
+
// itself is a summary-worthy artifact: the emitted task slugs+titles, their
|
|
697
|
+
// coverage of the spec's user stories, the dependency graph (keystone +
|
|
698
|
+
// blockedBy edges), and any `needsAnswers` open questions the tasker review
|
|
699
|
+
// loop flagged. Compose it here and thread it into the shared core as `body`
|
|
700
|
+
// so the propose-mode PR carries the summary instead of degrading to
|
|
701
|
+
// `gh pr create --fill`. Empty-set (no emitted tasks) ⇒ undefined ⇒ the
|
|
702
|
+
// provider's `--fill` fallback (no regression on that edge).
|
|
703
|
+
body: composeTaskingProposeBody(slug, emitTasks),
|
|
673
704
|
type: 'tasking',
|
|
674
705
|
lifecycle: {
|
|
675
|
-
// Read the PR title / commit summary from the held
|
|
706
|
+
// Read the PR title / commit summary from the held spec (before it moves).
|
|
676
707
|
titlePath: workItemPath(cwd, 'specs-ready', slug),
|
|
677
708
|
commitTag: 'tasked',
|
|
678
709
|
stage: () =>
|
|
@@ -699,9 +730,9 @@ export async function performTask(
|
|
|
699
730
|
// the review BEFORE the stage/integrate and did NOT integrate the tasks
|
|
700
731
|
// (correct). The CORRECT task-path destination is the SAME needs-attention
|
|
701
732
|
// route the lock release owns for the decomposition-unclear verdict: it amends
|
|
702
|
-
// the `
|
|
733
|
+
// the `spec:<slug>` unified lock `active -> stuck` with the block reason (the
|
|
703
734
|
// tasking needs-attention surface is the stuck lock now — NO folder write; the
|
|
704
|
-
//
|
|
735
|
+
// spec body stays in `work/specs/ready/`). So on a block we route the held spec to
|
|
705
736
|
// needs-attention THROUGH the lock release — the set never lands.
|
|
706
737
|
if (core.outcome === 'review-blocked') {
|
|
707
738
|
const reason = taskGateBlockedReason(slug, core.reviewBlockReason);
|
|
@@ -729,9 +760,9 @@ export async function performTask(
|
|
|
729
760
|
}
|
|
730
761
|
if (core.outcome === 'review-unparseable') {
|
|
731
762
|
// The task-set acceptance gate RAN but its verdict was UNPARSEABLE (malformed
|
|
732
|
-
// JSON). Route the held
|
|
763
|
+
// JSON). Route the held spec to needs-attention through the SAME lock-release
|
|
733
764
|
// seam the block path uses (the tasking needs-attention surface is the stuck
|
|
734
|
-
// `
|
|
765
|
+
// `spec:<slug>` lock; no folder write). It is NOT a block (the gate output was
|
|
735
766
|
// unreadable) — record it as the transient-infra-class re-run signal so the
|
|
736
767
|
// stuck reason reads correctly; nothing landed.
|
|
737
768
|
const reason =
|
|
@@ -760,8 +791,8 @@ export async function performTask(
|
|
|
760
791
|
};
|
|
761
792
|
}
|
|
762
793
|
if (core.outcome === 'completed') {
|
|
763
|
-
// The durable `
|
|
764
|
-
// core (the body moved straight from `work/
|
|
794
|
+
// The durable `specs/ready → specs/tasked` `main` move landed through the shared integrate
|
|
795
|
+
// core (the body moved straight from `work/specs/ready/` — no transient `tasking/`
|
|
765
796
|
// marker). The completing commit is owned by the integrate band, NOT
|
|
766
797
|
// `releaseTaskingLock`, so the unified per-item lock that `acquireTaskingLock`
|
|
767
798
|
// took is released HERE (delete the ref). A
|
|
@@ -779,13 +810,13 @@ export async function performTask(
|
|
|
779
810
|
}
|
|
780
811
|
|
|
781
812
|
// HUMAN, no-lock path: the human commits on `main` directly (the runner does
|
|
782
|
-
// not own the human's git). We report the produced tasks; moving the
|
|
783
|
-
// `work/
|
|
813
|
+
// not own the human's git). We report the produced tasks; moving the spec into
|
|
814
|
+
// `work/specs/tasked/` (residence = tasked-ness) and committing is the human's to
|
|
784
815
|
// do, as with the human `complete`.
|
|
785
816
|
const message =
|
|
786
817
|
`Tasked '${slug}' -> ${emitted.length} backlog task` +
|
|
787
818
|
`${emitted.length === 1 ? '' : 's'} (human path, no lock). Inspect + commit ` +
|
|
788
|
-
`the produced files (and move the
|
|
819
|
+
`the produced files (and move the spec into work/specs/tasked/) yourself.`;
|
|
789
820
|
note(message);
|
|
790
821
|
return {
|
|
791
822
|
exitCode: 0,
|
|
@@ -827,11 +858,11 @@ function releaseFailureToResult(
|
|
|
827
858
|
* (propose pushed the work branch + opened a PR / merge landed on `main`) the
|
|
828
859
|
* tasking is `tasked`. The band's FAILURE outcomes are reported on the tasking
|
|
829
860
|
* contract: a `rebase-conflict` against a concurrently-advanced `main` maps to
|
|
830
|
-
* `stale` (exit 4) — the tasking analogue of "the held
|
|
861
|
+
* `stale` (exit 4) — the tasking analogue of "the held spec moved under us"; a
|
|
831
862
|
* a `gate-failed` cannot occur (the tasking path passes `skipVerify`) but maps to
|
|
832
863
|
* a usage error defensively. A `review-blocked` (the task-SET ACCEPTANCE GATE
|
|
833
864
|
* blocked the set, task `slice-acceptance-gate`) is handled by `performTask`
|
|
834
|
-
* BEFORE this mapper — it routes the held
|
|
865
|
+
* BEFORE this mapper — it routes the held spec to needs-attention via
|
|
835
866
|
* the lock release (the task-path needs-attention route) — so it never reaches
|
|
836
867
|
* here; it is mapped defensively to a usage error if it ever does.
|
|
837
868
|
*/
|
|
@@ -852,7 +883,7 @@ function integrationToTaskResult(
|
|
|
852
883
|
const message =
|
|
853
884
|
`Tasked '${slug}' -> ${emitted.length} backlog task` +
|
|
854
885
|
`${emitted.length === 1 ? '' : 's'}; the runner integrated the transition ` +
|
|
855
|
-
`through the shared core (moved work/
|
|
886
|
+
`through the shared core (moved work/specs/ready/ -> work/specs/tasked/, the ` +
|
|
856
887
|
`tasked resting state) and ${landed}.`;
|
|
857
888
|
return {exitCode: 0, outcome: 'tasked', slug, emitted, loop, message};
|
|
858
889
|
}
|
|
@@ -864,7 +895,7 @@ function integrationToTaskResult(
|
|
|
864
895
|
message:
|
|
865
896
|
core.reason ??
|
|
866
897
|
`Integrating the tasking of '${slug}' conflicted against the latest ` +
|
|
867
|
-
`${slug} main — the tasking is stale; re-task from the current
|
|
898
|
+
`${slug} main — the tasking is stale; re-task from the current spec.`,
|
|
868
899
|
};
|
|
869
900
|
}
|
|
870
901
|
return {
|
|
@@ -880,9 +911,9 @@ function integrationToTaskResult(
|
|
|
880
911
|
/**
|
|
881
912
|
* ONBOARD the tasking work onto a `work/<slug>` branch cut from the freshly-
|
|
882
913
|
* fetched `<arbiter>/main` (task `slice-output-through-integration`). Called
|
|
883
|
-
* AFTER the tasking lock is held, so the branch's base HOLDS the
|
|
884
|
-
* `work/
|
|
885
|
-
* it `
|
|
914
|
+
* AFTER the tasking lock is held, so the branch's base HOLDS the spec in
|
|
915
|
+
* `work/specs/ready/` (the lock no longer moves the body) — the lifecycle stage then moves
|
|
916
|
+
* it `specs/ready/ -> specs/tasked/` ON THIS BRANCH and the shared integrate core lands it. A
|
|
886
917
|
* pre-existing local `work/<slug>` (a re-run) is force-recreated off fresh main.
|
|
887
918
|
* The agent runs in-place on this branch (branch ≠ worktree).
|
|
888
919
|
*/
|
|
@@ -894,7 +925,7 @@ async function switchToWorkBranch(
|
|
|
894
925
|
): Promise<void> {
|
|
895
926
|
// The tasking path is the parent-spec namespace (`do spec:<slug>`): the branch
|
|
896
927
|
// is `work/spec-<slug>`, distinct from a same-slug task-build's `work/task-<slug>`.
|
|
897
|
-
// MIGRATE step (
|
|
928
|
+
// MIGRATE step (spec `prd-to-spec-vocabulary-cutover-and-migration-command`):
|
|
898
929
|
// MINT the work-branch under the `spec` namespace token (`workBranchRef` still
|
|
899
930
|
// parses the legacy `work/prd-<slug>` form, so in-flight branches keep resolving).
|
|
900
931
|
const branch = workBranchRef('spec', slug);
|
|
@@ -909,8 +940,8 @@ async function switchToWorkBranch(
|
|
|
909
940
|
/**
|
|
910
941
|
* The READ-STABILITY content-identity STALE CHECK (the lock's backstop, owned at
|
|
911
942
|
* the integrate seam now that the OUTPUT no longer rides the lock release): true
|
|
912
|
-
* iff the CURRENTLY held `work/
|
|
913
|
-
* from the snapshot the lock TOOK (`lockedBlob`, read from `work/
|
|
943
|
+
* iff the CURRENTLY held `work/specs/ready/<slug>.md` blob on `<arbiter>/main` DIFFERS
|
|
944
|
+
* from the snapshot the lock TOOK (`lockedBlob`, read from `work/specs/ready/<slug>.md` at
|
|
914
945
|
* acquire). ANY change = a concurrent edit under the lock = the tasking is STALE.
|
|
915
946
|
* Stronger than a textual rebase conflict (which a rename+edit merge can apply
|
|
916
947
|
* CLEANLY). When `lockedBlob` is absent (never, in production) it reads as
|
|
@@ -932,7 +963,7 @@ async function heldSpecIsStale(
|
|
|
932
963
|
cwd,
|
|
933
964
|
env,
|
|
934
965
|
);
|
|
935
|
-
// The
|
|
966
|
+
// The spec being absent (already tasked/moved) is NOT this check's concern
|
|
936
967
|
// (the integrate's rebase/push surfaces that); only a CHANGED held blob is stale.
|
|
937
968
|
if (held.status !== 0) {
|
|
938
969
|
return false;
|
|
@@ -942,18 +973,18 @@ async function heldSpecIsStale(
|
|
|
942
973
|
|
|
943
974
|
/**
|
|
944
975
|
* STAGE the tasking lifecycle into the index on the `work/<slug>` branch (the
|
|
945
|
-
* {@link performIntegration} lifecycle seam): move the held
|
|
946
|
-
* `git mv work/
|
|
976
|
+
* {@link performIntegration} lifecycle seam): move the held spec
|
|
977
|
+
* `git mv work/specs/ready/<slug>.md -> work/specs/tasked/<slug>.md` (the TASKED resting
|
|
947
978
|
* state — the build-machine `done/` analogue, the SOURCE OF TRUTH for tasked-ness),
|
|
948
979
|
* and write+`git add` the produced `work/tasks/backlog/*.md` files. The band's subsequent
|
|
949
980
|
* `git add -A` + atomic commit folds this AND the agent's uncommitted backlog writes
|
|
950
981
|
* into ONE runner-owned commit (the agent never does git).
|
|
951
982
|
*
|
|
952
|
-
* TASK `prd-sliced-folder-step-a` (
|
|
953
|
-
* destination is `work/
|
|
983
|
+
* TASK `prd-sliced-folder-step-a` (spec `slicing-coherence` US #8): the lifecycle
|
|
984
|
+
* destination is `work/specs/tasked/` (NOT back to `work/specs/ready/`) — `specs/tasked/`
|
|
954
985
|
* residence IS tasked-ness (like `done/` for tasks, with no `done:` marker). The
|
|
955
986
|
* `tasked:` frontmatter marker was removed entirely in `remove-sliced-marker-step-b`
|
|
956
|
-
* (sequenced last): residence in `work/
|
|
987
|
+
* (sequenced last): residence in `work/specs/tasked/` is now the sole signal.
|
|
957
988
|
*/
|
|
958
989
|
async function stageTaskingLifecycle(params: {
|
|
959
990
|
cwd: string;
|
|
@@ -964,7 +995,7 @@ async function stageTaskingLifecycle(params: {
|
|
|
964
995
|
* The runner-resolved destination folder (task
|
|
965
996
|
* `runner-deterministic-slice-placement-policy-and-precedence`). Computed
|
|
966
997
|
* ONCE in `performTask` via the shared {@link resolvePlacement} from the
|
|
967
|
-
*
|
|
998
|
+
* spec's `originTrust:` stamp + the configured `tasksLandIn` default + the
|
|
968
999
|
* operator's explicit override, then passed in here — so the call site sees
|
|
969
1000
|
* exactly where the emitted tasks landed (the placement decision is not
|
|
970
1001
|
* buried in the stage closure).
|
|
@@ -992,10 +1023,10 @@ async function stageTaskingLifecycle(params: {
|
|
|
992
1023
|
const spec = workItemRel('specs-ready', `${slug}.md`);
|
|
993
1024
|
const specTasked = workItemRel('specs-tasked', `${slug}.md`);
|
|
994
1025
|
// PROPAGATE the origin-trust PROVENANCE (task
|
|
995
|
-
// `untrusted-origin-forces-build-propose`): read the held
|
|
1026
|
+
// `untrusted-origin-forces-build-propose`): read the held spec's `origin`/
|
|
996
1027
|
// `originTrust` stamp BEFORE the move, so each emitted task can carry it. A
|
|
997
1028
|
// task's risk is its BUILD; the stamp must reach the task so the build
|
|
998
|
-
// transition can force `propose` for untrusted-origin work. An UNSTAMPED
|
|
1029
|
+
// transition can force `propose` for untrusted-origin work. An UNSTAMPED spec (a
|
|
999
1030
|
// human/local-authored one ⇒ trusted) propagates nothing — the normal path is
|
|
1000
1031
|
// untouched.
|
|
1001
1032
|
const specAbs = join(cwd, spec);
|
|
@@ -1004,19 +1035,19 @@ async function stageTaskingLifecycle(params: {
|
|
|
1004
1035
|
: {origin: undefined, originTrust: undefined};
|
|
1005
1036
|
if (placementReason === 'untrusted-origin') {
|
|
1006
1037
|
note(
|
|
1007
|
-
`Untrusted-origin
|
|
1038
|
+
`Untrusted-origin spec '${slug}': forcing the emitted tasks STAGED ` +
|
|
1008
1039
|
`(${placementDir}/) regardless of tasksLandIn (a human promotes ` +
|
|
1009
1040
|
'them into work/tasks/ready/). Pass --tasks-land-in <where> to override.',
|
|
1010
1041
|
);
|
|
1011
1042
|
}
|
|
1012
|
-
// Move the held
|
|
1013
|
-
// source of truth, like done/ for tasks). This is the DURABLE `
|
|
1043
|
+
// Move the held spec specs/ready/ -> specs/tasked/ (the TASKED resting state — folder =
|
|
1044
|
+
// source of truth, like done/ for tasks). This is the DURABLE `specs/ready → specs/tasked`
|
|
1014
1045
|
// success move, owned by THIS transition's commit (the lock no longer moved the
|
|
1015
|
-
// body, so the source is `work/
|
|
1046
|
+
// body, so the source is `work/specs/ready/`, never `work/tasking/`).
|
|
1016
1047
|
mkdirSync(dirname(join(cwd, specTasked)), {recursive: true});
|
|
1017
1048
|
await gitHard(['mv', spec, specTasked], cwd, env);
|
|
1018
1049
|
await gitHard(['add', '--', specTasked], cwd, env);
|
|
1019
|
-
// **POOL-PLACEMENT FENCE (
|
|
1050
|
+
// **POOL-PLACEMENT FENCE (spec US #4 / governing ADR
|
|
1020
1051
|
// `placement-is-runner-deterministic-humanonly-is-agent-judgement`).** The
|
|
1021
1052
|
// agent ALWAYS writes to the STAGING folder (`work/tasks/backlog/`); the POOL
|
|
1022
1053
|
// (`work/tasks/ready/`) is the agent-eligible pool the runner owns the promotion
|
|
@@ -1054,11 +1085,11 @@ async function stageTaskingLifecycle(params: {
|
|
|
1054
1085
|
* Revert any change/addition the agent made to the POOL `work/tasks/ready/` during a
|
|
1055
1086
|
* tasking run. The agent's STAGING folder is `work/tasks/backlog/`; a write to the
|
|
1056
1087
|
* pool is an attempt to self-place into the agent-eligible pool the runner owns
|
|
1057
|
-
* the promotion into (
|
|
1088
|
+
* the promotion into (spec US #4 / governing ADR). Compared to the `poolBefore`
|
|
1058
1089
|
* snapshot (the branch-base state of `work/tasks/ready/`, taken BEFORE the agent
|
|
1059
1090
|
* ran), any new file is removed from the worktree and any changed file is
|
|
1060
1091
|
* checked back out to HEAD — so the subsequent `git add -A` cannot land it. The
|
|
1061
|
-
* runner's commit then carries ONLY the explicit `
|
|
1092
|
+
* runner's commit then carries ONLY the explicit `tasks/backlog/` placement.
|
|
1062
1093
|
*/
|
|
1063
1094
|
async function scrubPoolDrift(
|
|
1064
1095
|
cwd: string,
|
|
@@ -1122,8 +1153,9 @@ async function gitHard(
|
|
|
1122
1153
|
/**
|
|
1123
1154
|
* Build the needs-attention REASON for a task-SET ACCEPTANCE GATE block (task
|
|
1124
1155
|
* `slice-acceptance-gate`): the fresh-context review of the produced set returned
|
|
1125
|
-
* `block`, so the
|
|
1126
|
-
*
|
|
1156
|
+
* `block`, so the spec is marked stuck on its per-item lock (post lock-cutover —
|
|
1157
|
+
* `state: stuck` with this reason on the lock entry, no `work/needs-attention/`
|
|
1158
|
+
* folder write) and NO tasks land. Takes the core's
|
|
1127
1159
|
* structured `reviewBlockReason` (the gate's blocking findings); falls back to a
|
|
1128
1160
|
* generic line when absent. DISTINCT from the improver loop's
|
|
1129
1161
|
* {@link decompositionUnclearReason} (which carries the loop's open questions).
|
|
@@ -1134,20 +1166,20 @@ function taskGateBlockedReason(
|
|
|
1134
1166
|
): string {
|
|
1135
1167
|
const head =
|
|
1136
1168
|
`The task acceptance gate (fresh-context review of the produced SET) blocked ` +
|
|
1137
|
-
`'${slug}'. The
|
|
1169
|
+
`'${slug}'. The spec is routed to needs-attention with no tasks landed; a human ` +
|
|
1138
1170
|
`must resolve the blocking findings, then re-task.`;
|
|
1139
1171
|
return findingsReason ? `${head}\n\n${findingsReason}` : head;
|
|
1140
1172
|
}
|
|
1141
1173
|
|
|
1142
1174
|
/**
|
|
1143
1175
|
* Build the needs-attention REASON for a decomposition-unclear loop verdict (the
|
|
1144
|
-
*
|
|
1145
|
-
* tasks). Prose only — recorded as the
|
|
1176
|
+
* spec is marked stuck on its per-item lock with these open questions, no guessed
|
|
1177
|
+
* tasks). Prose only — recorded as the spec's stuck-lock reason.
|
|
1146
1178
|
*/
|
|
1147
1179
|
function decompositionUnclearReason(slug: string, questions: string[]): string {
|
|
1148
1180
|
const head =
|
|
1149
1181
|
`The tasker review→edit loop could not converge on a sound decomposition of ` +
|
|
1150
|
-
`'${slug}' (--tasker-loop-max exhausted with unresolved blockers). The
|
|
1182
|
+
`'${slug}' (--tasker-loop-max exhausted with unresolved blockers). The spec is routed ` +
|
|
1151
1183
|
`to needs-attention with no guessed tasks; a human must resolve:`;
|
|
1152
1184
|
const body =
|
|
1153
1185
|
questions.length > 0
|
|
@@ -1209,8 +1241,8 @@ function appendQuestionsBlock(content: string, questions: string[]): string {
|
|
|
1209
1241
|
/**
|
|
1210
1242
|
* Resolve the AGENT tasking gate for `slug`: the pure predicate
|
|
1211
1243
|
* (`needsAnswers !== true && humanOnly !== true && autoTask`) plus the
|
|
1212
|
-
* cross-
|
|
1213
|
-
* the
|
|
1244
|
+
* cross-spec `taskedAfter` ordering, resolved against `work/specs/tasked/` residence of
|
|
1245
|
+
* the specs present in the checkout.
|
|
1214
1246
|
*/
|
|
1215
1247
|
function resolveAgentGate(
|
|
1216
1248
|
cwd: string,
|
|
@@ -1229,7 +1261,7 @@ function resolveAgentGate(
|
|
|
1229
1261
|
});
|
|
1230
1262
|
}
|
|
1231
1263
|
|
|
1232
|
-
/** Build an HONEST gate-refusal message naming WHY the agent skipped the
|
|
1264
|
+
/** Build an HONEST gate-refusal message naming WHY the agent skipped the spec. */
|
|
1233
1265
|
function gateRefusalReason(
|
|
1234
1266
|
slug: string,
|
|
1235
1267
|
specFm: {humanOnly?: boolean; needsAnswers?: boolean},
|
|
@@ -1238,15 +1270,15 @@ function gateRefusalReason(
|
|
|
1238
1270
|
): string {
|
|
1239
1271
|
const reasons: string[] = [];
|
|
1240
1272
|
if (specFm.humanOnly === true) {
|
|
1241
|
-
reasons.push('the
|
|
1273
|
+
reasons.push('the spec is humanOnly (a human must drive its tasking)');
|
|
1242
1274
|
}
|
|
1243
1275
|
if (specFm.needsAnswers === true) {
|
|
1244
1276
|
reasons.push(
|
|
1245
|
-
'the
|
|
1277
|
+
'the spec has needsAnswers (open questions block auto-tasking)',
|
|
1246
1278
|
);
|
|
1247
1279
|
}
|
|
1248
1280
|
// The autoTask POLICY only refuses on the NON-explicit (auto-pick pool) path:
|
|
1249
|
-
// an explicitly-named `do
|
|
1281
|
+
// an explicitly-named `do spec:<slug>` is authorized by the naming itself (the
|
|
1250
1282
|
// build path's autoBuild precedent), so the policy is never the reason there.
|
|
1251
1283
|
if (
|
|
1252
1284
|
options.explicit !== true &&
|
|
@@ -1258,7 +1290,7 @@ function gateRefusalReason(
|
|
|
1258
1290
|
}
|
|
1259
1291
|
if (!eligibility.taskedAfter.satisfied) {
|
|
1260
1292
|
reasons.push(
|
|
1261
|
-
`taskedAfter
|
|
1293
|
+
`taskedAfter spec(s) not yet tasked: ${eligibility.taskedAfter.missing.join(', ')}`,
|
|
1262
1294
|
);
|
|
1263
1295
|
}
|
|
1264
1296
|
const why =
|
|
@@ -1267,12 +1299,12 @@ function gateRefusalReason(
|
|
|
1267
1299
|
}
|
|
1268
1300
|
|
|
1269
1301
|
/**
|
|
1270
|
-
* Read the set of slugs whose
|
|
1271
|
-
* in `work/
|
|
1272
|
-
* /
|
|
1302
|
+
* Read the set of slugs whose specs are already TASKED in this checkout — RESIDENCE
|
|
1303
|
+
* in `work/specs/tasked/` (the tasked resting state, task `prd-sliced-folder-step-a`
|
|
1304
|
+
* / spec `slicing-coherence` US #9), the build-machine `done/` analogue. The FOLDER
|
|
1273
1305
|
* is the source of truth; the `tasked:` frontmatter marker was removed entirely in
|
|
1274
1306
|
* `remove-sliced-marker-step-b` and is NOT consulted. So `taskedAfter` resolves
|
|
1275
|
-
* against `
|
|
1307
|
+
* against `specs/tasked/` residence
|
|
1276
1308
|
* (mirroring `blockedBy` -> `done/`). A missing folder reads as empty. The slug is
|
|
1277
1309
|
* read from each file's frontmatter `slug:`, falling back to the filename — the same
|
|
1278
1310
|
* shape the task readers use.
|
|
@@ -1291,7 +1323,7 @@ function readTaskedSlugs(cwd: string): Set<string> {
|
|
|
1291
1323
|
/**
|
|
1292
1324
|
* Build the tasking PROMPT: instruct a fresh-context agent to apply the
|
|
1293
1325
|
* **tasking discipline** (`work/protocol/TASKING-PROTOCOL.md`) to the held
|
|
1294
|
-
*
|
|
1326
|
+
* spec at `work/specs/ready/<slug>.md` and to EMIT tracer-bullet vertical
|
|
1295
1327
|
* tasks under `work/tasks/backlog/`. The discipline body (the tracer-bullet
|
|
1296
1328
|
* rules, the two-axis gate guidance, the confidence check, file-orthogonality,
|
|
1297
1329
|
* the emitted task shape) lives in `TASKING-PROTOCOL.md` — NOT inlined here
|
|
@@ -1300,24 +1332,26 @@ function readTaskedSlugs(cwd: string): Set<string> {
|
|
|
1300
1332
|
* task-template.md`); the doc DESCRIBES it (D2).
|
|
1301
1333
|
*
|
|
1302
1334
|
* This builder owns ONLY the PER-BUILDER framing: who you are (a fresh-context
|
|
1303
|
-
* tasker for ONE
|
|
1335
|
+
* tasker for ONE spec), where the held spec is, where the emitted task files
|
|
1304
1336
|
* MUST land (the staging folder, never the pool), and the runner-owns-git
|
|
1305
1337
|
* boundary on the agent path. The shared discipline body is NOT duplicated
|
|
1306
1338
|
* here.
|
|
1307
1339
|
*/
|
|
1308
1340
|
function buildTaskingSpec(slug: string, _spec: string | undefined): string {
|
|
1341
|
+
const specReady = workFolderRel('specs-ready');
|
|
1342
|
+
const specTasked = workFolderRel('specs-tasked');
|
|
1309
1343
|
return [
|
|
1310
|
-
`You are a FRESH-CONTEXT tasker for the
|
|
1344
|
+
`You are a FRESH-CONTEXT tasker for the spec \`${specReady}/${slug}.md\`.`,
|
|
1311
1345
|
`Apply the tasking discipline defined in \`work/protocol/TASKING-PROTOCOL.md\``,
|
|
1312
1346
|
`(the in-band, protocol-native tasking protocol every set-up repo carries; the`,
|
|
1313
|
-
`human-facing pointer is \`skills/to-task/SKILL.md\`) to this ONE
|
|
1347
|
+
`human-facing pointer is \`skills/to-task/SKILL.md\`) to this ONE spec:`,
|
|
1314
1348
|
`decompose it into independently-grabbable, tracer-bullet vertical tasks.`,
|
|
1315
|
-
`Read the
|
|
1349
|
+
`Read the spec fully first.`,
|
|
1316
1350
|
``,
|
|
1317
1351
|
`The discipline rules — the tracer-bullet test, the two-axis gate guidance`,
|
|
1318
1352
|
`(\`humanOnly\` is NARROW; \`needsAnswers\` flags genuine uncertainty), the`,
|
|
1319
1353
|
`confidence check that REPLACES the human-quiz step when no human is present,`,
|
|
1320
|
-
`the file-orthogonality preference, the
|
|
1354
|
+
`the file-orthogonality preference, the spec-vs-task gate disjointness, and`,
|
|
1321
1355
|
`the emitted task shape — ALL live in that doc. Read them there.`,
|
|
1322
1356
|
``,
|
|
1323
1357
|
`No human is present, so apply the CONFIDENCE CHECK (\`TASKING-PROTOCOL.md\``,
|
|
@@ -1325,7 +1359,7 @@ function buildTaskingSpec(slug: string, _spec: string | undefined): string {
|
|
|
1325
1359
|
`granularity, dependency order, a gate, or a seam is genuinely unresolved, set`,
|
|
1326
1360
|
`\`needsAnswers: true\` on the specific uncertain task (questions in its body)`,
|
|
1327
1361
|
`rather than guessing — or, if the whole decomposition is unclear, stop and`,
|
|
1328
|
-
`route the
|
|
1362
|
+
`route the spec to needs-attention with the questions.`,
|
|
1329
1363
|
``,
|
|
1330
1364
|
`WRITE EVERY emitted task file under \`${STAGED_TASKS_DIR}/\` (the STAGING folder)`,
|
|
1331
1365
|
`— NEVER \`work/tasks/ready/\`. \`work/tasks/ready/\` is the agent-eligible POOL and`,
|
|
@@ -1333,12 +1367,12 @@ function buildTaskingSpec(slug: string, _spec: string | undefined): string {
|
|
|
1333
1367
|
`folder is \`work/tasks/backlog/\`. A write outside the staging folder is dropped`,
|
|
1334
1368
|
`by the runner-deterministic placement resolver.`,
|
|
1335
1369
|
``,
|
|
1336
|
-
`Set each task's \`
|
|
1337
|
-
`link back to the
|
|
1370
|
+
`Set each task's \`spec:\` field to the source spec slug (\`${slug}\`) so the`,
|
|
1371
|
+
`link back to the spec survives.`,
|
|
1338
1372
|
``,
|
|
1339
1373
|
`Do NOT perform any git operations — do not stage, commit, push, or move any`,
|
|
1340
1374
|
`files. The RUNNER owns every git-state transition (it commits the produced`,
|
|
1341
|
-
`tasks, releases the tasking lock, and moves the
|
|
1375
|
+
`tasks, releases the tasking lock, and moves the spec into \`${specTasked}/\`).`,
|
|
1342
1376
|
].join('\n');
|
|
1343
1377
|
}
|
|
1344
1378
|
|
|
@@ -1425,6 +1459,218 @@ function collectEmittedTasks(
|
|
|
1425
1459
|
return out;
|
|
1426
1460
|
}
|
|
1427
1461
|
|
|
1462
|
+
/**
|
|
1463
|
+
* Compose the propose-mode PR BODY for a slice-set (task
|
|
1464
|
+
* `slicing-pr-body-summary-threading`): mirror the BUILD path's body threading
|
|
1465
|
+
* (`do.ts` — `body: agent.output`) with a summary drawn from what the tasker
|
|
1466
|
+
* ALREADY has in hand — the emitted task files. The shared
|
|
1467
|
+
* {@link composeProposeBody} then wraps this prose with its runner-owned header.
|
|
1468
|
+
*
|
|
1469
|
+
* Sections (all suppressed when empty, so the body stays honest):
|
|
1470
|
+
* 1. A lead line naming the source spec + emitted task count.
|
|
1471
|
+
* 2. **Tasks** — one bullet per task: `slug — title`, plus `covers:` (which
|
|
1472
|
+
* spec user stories it maps to), `blockedBy:`, and `needsAnswers` if set.
|
|
1473
|
+
* 3. **Dependency graph** — the KEYSTONE(S) (tasks whose `blockedBy` names no
|
|
1474
|
+
* other task in this set, so they land first) and the `blockedBy` edges.
|
|
1475
|
+
* 4. **Needs answers** — any open questions the tasker review→edit loop
|
|
1476
|
+
* appended under `## Open questions` on `needsAnswers: true` tasks, so the
|
|
1477
|
+
* deferred seams are visible on the PR (not buried in task bodies).
|
|
1478
|
+
*
|
|
1479
|
+
* Returns `undefined` for the empty set (no emitted tasks) so the provider's
|
|
1480
|
+
* `gh pr create --fill` fallback still applies on that edge (matches the build
|
|
1481
|
+
* path's absent-body behaviour).
|
|
1482
|
+
*/
|
|
1483
|
+
export function composeTaskingProposeBody(
|
|
1484
|
+
specSlug: string,
|
|
1485
|
+
emitTasks: Record<string, string>,
|
|
1486
|
+
): string | undefined {
|
|
1487
|
+
const entries = Object.entries(emitTasks);
|
|
1488
|
+
if (entries.length === 0) {
|
|
1489
|
+
return undefined;
|
|
1490
|
+
}
|
|
1491
|
+
const tasks = entries
|
|
1492
|
+
.map(([rel, content]) => parseTaskSummary(rel, content))
|
|
1493
|
+
.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
1494
|
+
const setSlugs = new Set(tasks.map((t) => t.slug));
|
|
1495
|
+
const lines: string[] = [];
|
|
1496
|
+
const n = tasks.length;
|
|
1497
|
+
lines.push(
|
|
1498
|
+
`Auto-tasked \`${specSlug}\` into ${n} backlog task${n === 1 ? '' : 's'}.`,
|
|
1499
|
+
);
|
|
1500
|
+
lines.push('');
|
|
1501
|
+
lines.push('## Tasks');
|
|
1502
|
+
lines.push('');
|
|
1503
|
+
for (const t of tasks) {
|
|
1504
|
+
lines.push(`- **${t.slug}** — ${t.title}`);
|
|
1505
|
+
if (t.covers.length > 0) {
|
|
1506
|
+
lines.push(` - covers: ${t.covers.map((c) => `US #${c}`).join(', ')}`);
|
|
1507
|
+
}
|
|
1508
|
+
if (t.blockedBy.length > 0) {
|
|
1509
|
+
lines.push(` - blockedBy: ${t.blockedBy.join(', ')}`);
|
|
1510
|
+
}
|
|
1511
|
+
if (t.needsAnswers) {
|
|
1512
|
+
lines.push(' - needsAnswers: true');
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
// Dependency graph: keystone(s) are tasks whose blockedBy names no OTHER
|
|
1516
|
+
// member of this set (an external blocker — e.g. a task already in `done/` —
|
|
1517
|
+
// does not prevent this set-relative starting point); the edges list every
|
|
1518
|
+
// set-internal `blockedBy` link so a reviewer can trace the ordering.
|
|
1519
|
+
const keystones = tasks.filter((t) =>
|
|
1520
|
+
t.blockedBy.every((b) => !setSlugs.has(b)),
|
|
1521
|
+
);
|
|
1522
|
+
const edges = tasks.flatMap((t) =>
|
|
1523
|
+
t.blockedBy.filter((b) => setSlugs.has(b)).map((b) => `${t.slug} ← ${b}`),
|
|
1524
|
+
);
|
|
1525
|
+
if (keystones.length > 0 || edges.length > 0) {
|
|
1526
|
+
lines.push('');
|
|
1527
|
+
lines.push('## Dependency graph');
|
|
1528
|
+
lines.push('');
|
|
1529
|
+
if (keystones.length > 0) {
|
|
1530
|
+
const word = keystones.length === 1 ? 'Keystone' : 'Keystones';
|
|
1531
|
+
lines.push(`- ${word}: ${keystones.map((k) => k.slug).join(', ')}`);
|
|
1532
|
+
}
|
|
1533
|
+
for (const edge of edges) {
|
|
1534
|
+
lines.push(`- ${edge}`);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
const uncertain = tasks.filter((t) => t.needsAnswers);
|
|
1538
|
+
if (uncertain.length > 0) {
|
|
1539
|
+
lines.push('');
|
|
1540
|
+
lines.push('## Needs answers');
|
|
1541
|
+
lines.push('');
|
|
1542
|
+
for (const t of uncertain) {
|
|
1543
|
+
lines.push(`- **${t.slug}**:`);
|
|
1544
|
+
if (t.openQuestions.length === 0) {
|
|
1545
|
+
lines.push(' - (no specific questions surfaced)');
|
|
1546
|
+
continue;
|
|
1547
|
+
}
|
|
1548
|
+
for (const q of t.openQuestions) {
|
|
1549
|
+
lines.push(` - ${q}`);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
return lines.join('\n');
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
/** Summary of one emitted task, for {@link composeTaskingProposeBody}. */
|
|
1557
|
+
interface TaskSummary {
|
|
1558
|
+
slug: string;
|
|
1559
|
+
title: string;
|
|
1560
|
+
covers: string[];
|
|
1561
|
+
blockedBy: string[];
|
|
1562
|
+
needsAnswers: boolean;
|
|
1563
|
+
openQuestions: string[];
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
/**
|
|
1567
|
+
* Parse one emitted task file into a {@link TaskSummary}. Uses
|
|
1568
|
+
* {@link parseFrontmatter} for the shared fields; scans the raw frontmatter
|
|
1569
|
+
* block for `covers:` (not currently modelled on the parser's typed shape) and
|
|
1570
|
+
* the body for a trailing `## Open questions` bullet list (the loop's
|
|
1571
|
+
* uncertain-task disposition sink, see {@link appendQuestionsBlock}).
|
|
1572
|
+
*/
|
|
1573
|
+
function parseTaskSummary(rel: string, content: string): TaskSummary {
|
|
1574
|
+
const fm = parseFrontmatter(content);
|
|
1575
|
+
const slug = fm.slug ?? basename(rel).replace(/\.md$/i, '');
|
|
1576
|
+
const title = readTitleField(content) ?? slug;
|
|
1577
|
+
return {
|
|
1578
|
+
slug,
|
|
1579
|
+
title,
|
|
1580
|
+
covers: readCoversField(content),
|
|
1581
|
+
blockedBy: fm.blockedBy,
|
|
1582
|
+
needsAnswers: fm.needsAnswers === true,
|
|
1583
|
+
openQuestions: readOpenQuestions(content),
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
/**
|
|
1588
|
+
* Extract the `title:` scalar from the raw frontmatter block (not on
|
|
1589
|
+
* {@link parseFrontmatter}'s typed shape). Missing / malformed ⇒ `undefined`.
|
|
1590
|
+
* Comment-tolerant: a trailing `# ...` after an UNQUOTED value is stripped.
|
|
1591
|
+
*/
|
|
1592
|
+
function readTitleField(content: string): string | undefined {
|
|
1593
|
+
const normalized = content.replace(/\r\n/g, '\n').replace(/^\uFEFF/, '');
|
|
1594
|
+
if (!normalized.startsWith('---\n')) {
|
|
1595
|
+
return undefined;
|
|
1596
|
+
}
|
|
1597
|
+
const lines = normalized.split('\n');
|
|
1598
|
+
const closing = lines.indexOf('---', 1);
|
|
1599
|
+
if (closing === -1) {
|
|
1600
|
+
return undefined;
|
|
1601
|
+
}
|
|
1602
|
+
for (let i = 1; i < closing; i++) {
|
|
1603
|
+
const m = lines[i].match(/^title:\s*(.*)$/);
|
|
1604
|
+
if (!m) continue;
|
|
1605
|
+
let value = m[1].trim();
|
|
1606
|
+
if (value === '') return undefined;
|
|
1607
|
+
// Quoted ⇒ return inner text verbatim; unquoted ⇒ strip trailing `# ...`.
|
|
1608
|
+
if (
|
|
1609
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
1610
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
1611
|
+
) {
|
|
1612
|
+
return value.slice(1, -1);
|
|
1613
|
+
}
|
|
1614
|
+
const hash = value.indexOf('#');
|
|
1615
|
+
if (hash !== -1) value = value.slice(0, hash).trimEnd();
|
|
1616
|
+
return value === '' ? undefined : value;
|
|
1617
|
+
}
|
|
1618
|
+
return undefined;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
/**
|
|
1622
|
+
* Extract the `covers: [...]` inline-list values from the raw frontmatter block
|
|
1623
|
+
* of a task (not on {@link parseFrontmatter}'s typed shape, so we scan the raw
|
|
1624
|
+
* fence here). Missing / malformed / empty ⇒ `[]`. Comment-tolerant (a trailing
|
|
1625
|
+
* `# ...` after the `]` is ignored, matching the house style on `blockedBy: []
|
|
1626
|
+
* # startable now`).
|
|
1627
|
+
*/
|
|
1628
|
+
function readCoversField(content: string): string[] {
|
|
1629
|
+
const normalized = content.replace(/\r\n/g, '\n').replace(/^\uFEFF/, '');
|
|
1630
|
+
if (!normalized.startsWith('---\n')) {
|
|
1631
|
+
return [];
|
|
1632
|
+
}
|
|
1633
|
+
const lines = normalized.split('\n');
|
|
1634
|
+
const closing = lines.indexOf('---', 1);
|
|
1635
|
+
if (closing === -1) {
|
|
1636
|
+
return [];
|
|
1637
|
+
}
|
|
1638
|
+
for (let i = 1; i < closing; i++) {
|
|
1639
|
+
const line = lines[i];
|
|
1640
|
+
const m = line.match(/^covers:\s*\[([^\]]*)\]/);
|
|
1641
|
+
if (!m) continue;
|
|
1642
|
+
return m[1]
|
|
1643
|
+
.split(',')
|
|
1644
|
+
.map((s) => s.trim().replace(/^['"]|['"]$/g, ''))
|
|
1645
|
+
.filter((s) => s !== '');
|
|
1646
|
+
}
|
|
1647
|
+
return [];
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
/**
|
|
1651
|
+
* Extract the bullets under a trailing `## Open questions` heading in a task
|
|
1652
|
+
* body — the tasker review loop's uncertain-task sink (see
|
|
1653
|
+
* {@link appendQuestionsBlock}). Returns `[]` when no such block exists.
|
|
1654
|
+
*/
|
|
1655
|
+
function readOpenQuestions(content: string): string[] {
|
|
1656
|
+
const normalized = content.replace(/\r\n/g, '\n');
|
|
1657
|
+
const idx = normalized.lastIndexOf(`\n${OPEN_QUESTIONS_HEADING}`);
|
|
1658
|
+
if (idx === -1) {
|
|
1659
|
+
return [];
|
|
1660
|
+
}
|
|
1661
|
+
const tail = normalized.slice(idx + 1);
|
|
1662
|
+
const lines = tail.split('\n').slice(1);
|
|
1663
|
+
const questions: string[] = [];
|
|
1664
|
+
for (const raw of lines) {
|
|
1665
|
+
const line = raw.trimEnd();
|
|
1666
|
+
if (line === '') continue;
|
|
1667
|
+
const m = line.match(/^- (.*)$/);
|
|
1668
|
+
if (!m) break;
|
|
1669
|
+
questions.push(m[1]);
|
|
1670
|
+
}
|
|
1671
|
+
return questions;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1428
1674
|
/** List `*.md` files in `dir`, sorted; an absent dir reads as empty. */
|
|
1429
1675
|
function listMarkdown(dir: string): string[] {
|
|
1430
1676
|
let entries: string[];
|