dorfl 0.1.2 → 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-drivers.d.ts +1 -1
- package/dist/advance-drivers.d.ts.map +1 -1
- package/dist/advance-drivers.js +15 -3
- package/dist/advance-drivers.js.map +1 -1
- package/dist/advance-lifecycle-template.d.ts.map +1 -1
- package/dist/advance-lifecycle-template.js +76 -4
- package/dist/advance-lifecycle-template.js.map +1 -1
- package/dist/advance-treeless-publish.d.ts +23 -0
- 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 +59 -7
- package/dist/advance.d.ts.map +1 -1
- package/dist/advance.js +389 -82
- package/dist/advance.js.map +1 -1
- package/dist/advancing-lock.d.ts +30 -2
- package/dist/advancing-lock.d.ts.map +1 -1
- package/dist/advancing-lock.js +50 -0
- 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 +17 -3
- package/dist/apply-decide.d.ts.map +1 -1
- package/dist/apply-decide.js +36 -7
- package/dist/apply-decide.js.map +1 -1
- package/dist/apply-merge-action.d.ts +17 -5
- package/dist/apply-merge-action.d.ts.map +1 -1
- package/dist/apply-merge-action.js +43 -8
- package/dist/apply-merge-action.js.map +1 -1
- package/dist/apply-persist.d.ts +57 -26
- package/dist/apply-persist.d.ts.map +1 -1
- package/dist/apply-persist.js +166 -37
- 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/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 +320 -63
- package/dist/cli.js.map +1 -1
- package/dist/close-job-template.js +1 -1
- package/dist/complete.d.ts +6 -3
- package/dist/complete.d.ts.map +1 -1
- package/dist/complete.js +91 -37
- package/dist/complete.js.map +1 -1
- package/dist/config.d.ts +55 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +56 -4
- package/dist/config.js.map +1 -1
- package/dist/continue-branch.d.ts.map +1 -1
- package/dist/continue-branch.js +22 -0
- package/dist/continue-branch.js.map +1 -1
- package/dist/decision-engine.d.ts +38 -10
- 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.map +1 -1
- package/dist/do-autopick.js +11 -1
- package/dist/do-autopick.js.map +1 -1
- package/dist/do-config.d.ts +1 -1
- package/dist/do.d.ts +49 -56
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +419 -30
- package/dist/do.js.map +1 -1
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +12 -4
- 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 +10 -27
- package/dist/format.js.map +1 -1
- package/dist/frontmatter.d.ts +16 -4
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +27 -1
- 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 +132 -38
- package/dist/install-ci-branch-protection.d.ts.map +1 -1
- package/dist/install-ci-branch-protection.js +190 -48
- package/dist/install-ci-branch-protection.js.map +1 -1
- package/dist/install-ci-core.d.ts +21 -1
- package/dist/install-ci-core.d.ts.map +1 -1
- package/dist/install-ci-core.js +10 -3
- package/dist/install-ci-core.js.map +1 -1
- package/dist/install-ci-github.d.ts +32 -0
- package/dist/install-ci-github.d.ts.map +1 -1
- package/dist/install-ci-github.js +73 -0
- package/dist/install-ci-github.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-trigger-template.js +1 -1
- package/dist/intake.d.ts +17 -4
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +23 -7
- package/dist/intake.js.map +1 -1
- package/dist/integration-core.d.ts +10 -7
- package/dist/integration-core.d.ts.map +1 -1
- package/dist/integration-core.js +174 -60
- 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 +2 -2
- package/dist/isolation.d.ts.map +1 -1
- package/dist/isolation.js +9 -0
- package/dist/isolation.js.map +1 -1
- package/dist/item-lock.d.ts +196 -129
- package/dist/item-lock.d.ts.map +1 -1
- package/dist/item-lock.js +329 -254
- package/dist/item-lock.js.map +1 -1
- package/dist/ledger-write.d.ts +31 -26
- 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 +17 -0
- package/dist/lifecycle-gather.d.ts.map +1 -1
- package/dist/lifecycle-gather.js +7 -1
- package/dist/lifecycle-gather.js.map +1 -1
- package/dist/lifecycle-pools.d.ts +40 -3
- package/dist/lifecycle-pools.d.ts.map +1 -1
- package/dist/lifecycle-pools.js +26 -6
- package/dist/lifecycle-pools.js.map +1 -1
- package/dist/merge-question-surfacer.d.ts +15 -1
- package/dist/merge-question-surfacer.d.ts.map +1 -1
- package/dist/merge-question-surfacer.js +18 -3
- 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/needs-attention.d.ts +293 -16
- package/dist/needs-attention.d.ts.map +1 -1
- package/dist/needs-attention.js +532 -57
- package/dist/needs-attention.js.map +1 -1
- package/dist/orphan-sidecar.d.ts +8 -4
- 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 +3 -3
- package/dist/placement.js +1 -1
- 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/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/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 +5 -0
- 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 +53 -7
- 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-verdict.js +1 -1
- package/dist/review-verdict.js.map +1 -1
- package/dist/run.d.ts +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +54 -19
- package/dist/run.js.map +1 -1
- package/dist/scan.d.ts +17 -8
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +22 -8
- package/dist/scan.js.map +1 -1
- package/dist/select-priority.d.ts +2 -2
- package/dist/select-priority.js +1 -1
- 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 +45 -10
- package/dist/sidecar.d.ts.map +1 -1
- package/dist/sidecar.js +82 -2
- 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 +5 -5
- package/dist/slug-namespace.js +5 -5
- package/dist/slug-namespace.js.map +1 -1
- package/dist/spec-complete.d.ts.map +1 -1
- package/dist/spec-complete.js +2 -3
- package/dist/spec-complete.js.map +1 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +51 -57
- package/dist/start.js.map +1 -1
- 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.js +1 -1
- package/dist/surface-persist.js.map +1 -1
- package/dist/tasker-review-loop.d.ts +4 -3
- package/dist/tasker-review-loop.d.ts.map +1 -1
- package/dist/tasker-review-loop.js.map +1 -1
- package/dist/tasking-lock.d.ts +2 -2
- package/dist/tasking-lock.d.ts.map +1 -1
- package/dist/tasking-lock.js +53 -32
- package/dist/tasking-lock.js.map +1 -1
- package/dist/tasking.d.ts +40 -6
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +224 -12
- package/dist/tasking.js.map +1 -1
- package/dist/triage-persist.d.ts +10 -6
- package/dist/triage-persist.d.ts.map +1 -1
- package/dist/triage-persist.js +34 -4
- 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.js +1 -1
- 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 +7 -3
- package/dist/work-layout.d.ts.map +1 -1
- package/dist/work-layout.js +6 -3
- package/dist/work-layout.js.map +1 -1
- package/dist/workspace.d.ts +1 -1
- package/package.json +2 -2
- package/src/advance-drivers.ts +16 -3
- package/src/advance-lifecycle-template.ts +89 -4
- package/src/advance-treeless-publish.ts +45 -0
- package/src/advance.ts +467 -87
- package/src/advancing-lock.ts +96 -1
- package/src/agent-launch.ts +37 -12
- package/src/agent-stop.ts +60 -2
- package/src/apply-decide.ts +36 -7
- package/src/apply-merge-action.ts +45 -7
- package/src/apply-persist.ts +228 -54
- package/src/apply-stuck-action.ts +260 -0
- package/src/brand.ts +14 -2
- package/src/cli-spinner.ts +1 -1
- package/src/cli.ts +401 -70
- package/src/close-job-template.ts +1 -1
- package/src/complete.ts +110 -52
- package/src/config.ts +107 -13
- package/src/continue-branch.ts +22 -0
- package/src/decision-engine.ts +55 -17
- package/src/do-autopick.ts +12 -1
- package/src/do-config.ts +1 -1
- package/src/do.ts +511 -38
- package/src/env-config.ts +12 -4
- package/src/failure-cause.ts +30 -1
- package/src/format.ts +10 -30
- package/src/frontmatter.ts +35 -5
- 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 +282 -57
- package/src/install-ci-core.ts +30 -3
- package/src/install-ci-github.ts +86 -0
- package/src/install-skills.ts +166 -0
- package/src/intake-trigger-template.ts +1 -1
- package/src/intake.ts +46 -11
- package/src/integration-core.ts +199 -65
- package/src/integrator.ts +22 -9
- package/src/isolation.ts +11 -2
- package/src/item-lock.ts +485 -341
- package/src/ledger-write.ts +135 -150
- package/src/lifecycle-gather.ts +29 -6
- package/src/lifecycle-pools.ts +67 -9
- package/src/merge-question-surfacer.ts +28 -4
- package/src/migrate-stuck-locks.ts +451 -0
- package/src/needs-attention.ts +857 -84
- package/src/orphan-sidecar.ts +47 -6
- package/src/pi-harness.ts +82 -2
- package/src/placement.ts +3 -3
- package/src/prd-to-spec.ts +13 -5
- package/src/reap-branches.ts +38 -14
- package/src/recover-isolated.ts +17 -0
- package/src/repo-config.ts +55 -6
- package/src/repo-mirror.ts +23 -1
- package/src/review-verdict.ts +1 -1
- package/src/run.ts +64 -18
- package/src/scan.ts +22 -8
- package/src/select-priority.ts +3 -3
- package/src/sidecar-apply.ts +1 -1
- package/src/sidecar.ts +138 -11
- package/src/slug-namespace.ts +7 -7
- package/src/spec-complete.ts +2 -3
- package/src/start.ts +53 -59
- package/src/surface-gate.ts +10 -3
- package/src/surface-persist.ts +1 -1
- package/src/tasker-review-loop.ts +4 -3
- package/src/tasking-lock.ts +52 -36
- package/src/tasking.ts +261 -17
- package/src/triage-persist.ts +56 -12
- 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 +1 -1
- package/src/watch-session.ts +102 -7
- package/src/work-layout.ts +6 -3
- package/src/workspace.ts +1 -1
package/src/cli.ts
CHANGED
|
@@ -121,6 +121,7 @@ import {status, formatStatus} from './status.js';
|
|
|
121
121
|
import {ledgerWrite} from './ledger-write.js';
|
|
122
122
|
import {
|
|
123
123
|
releaseItemLock,
|
|
124
|
+
releaseLiteralLockEntry,
|
|
124
125
|
reportItemLocks,
|
|
125
126
|
formatItemLockReport,
|
|
126
127
|
itemLockReportNeedsAttention,
|
|
@@ -128,6 +129,11 @@ import {
|
|
|
128
129
|
formatReapReport,
|
|
129
130
|
reapReportNeedsAttention,
|
|
130
131
|
} from './item-lock.js';
|
|
132
|
+
import {
|
|
133
|
+
migrateStuckLocks,
|
|
134
|
+
formatMigrateStuckLocksReport,
|
|
135
|
+
migrateStuckLocksNeedsAttention,
|
|
136
|
+
} from './migrate-stuck-locks.js';
|
|
131
137
|
import {
|
|
132
138
|
promoteFromPreBacklog,
|
|
133
139
|
promoteFromPreSpec,
|
|
@@ -146,6 +152,7 @@ import {installCI, type WizardPrompts} from './install-ci.js';
|
|
|
146
152
|
import {GitHubCIContext} from './install-ci-github.js';
|
|
147
153
|
import {loadCapabilityRegistry} from './install-ci-core.js';
|
|
148
154
|
import {performCloseMergedIssues} from './close-job.js';
|
|
155
|
+
import {installSkills, type InstallSkillsResult} from './install-skills.js';
|
|
149
156
|
|
|
150
157
|
interface ScanFlags {
|
|
151
158
|
config?: string;
|
|
@@ -392,7 +399,7 @@ function buildRegistrySetAdvanceTick(options: {
|
|
|
392
399
|
// `taskingIntegration ?? integration`; the build path stays on `integration`.
|
|
393
400
|
taskingIntegration: config.taskingIntegration,
|
|
394
401
|
// The TASK-PLACEMENT configured default (`do spec:` tasking output:
|
|
395
|
-
// `
|
|
402
|
+
// `backlog` staged vs `ready` pool). No operator flag on this
|
|
396
403
|
// registry-driven advance context, so only the configured default rung is
|
|
397
404
|
// threaded (the resolver still layers untrusted-origin force + built-in floor).
|
|
398
405
|
tasksLandIn: config.tasksLandIn,
|
|
@@ -681,13 +688,13 @@ interface CompleteFlags {
|
|
|
681
688
|
*/
|
|
682
689
|
function explicitTasksLandInFromFlag(
|
|
683
690
|
raw: string | undefined,
|
|
684
|
-
): '
|
|
691
|
+
): 'backlog' | 'ready' | undefined {
|
|
685
692
|
if (raw === undefined) {
|
|
686
693
|
return undefined;
|
|
687
694
|
}
|
|
688
|
-
if (raw !== '
|
|
695
|
+
if (raw !== 'backlog' && raw !== 'ready') {
|
|
689
696
|
throw new Error(
|
|
690
|
-
`--tasks-land-in must be '
|
|
697
|
+
`--tasks-land-in must be 'backlog' or 'ready' (got '${raw}').`,
|
|
691
698
|
);
|
|
692
699
|
}
|
|
693
700
|
return raw;
|
|
@@ -726,8 +733,10 @@ interface DoFlags {
|
|
|
726
733
|
config?: string;
|
|
727
734
|
arbiter?: string;
|
|
728
735
|
remote?: string;
|
|
729
|
-
/** `--isolated`: build in a job worktree off THIS repo's arbiter (no checkout takeover). */
|
|
736
|
+
/** `--isolated`: build in a job worktree off THIS repo's arbiter (no checkout takeover). Since `make-isolated-default-build-mode` this is the DEFAULT; the flag remains accepted as a redundant explicit opt-IN alias (D3). */
|
|
730
737
|
isolated?: boolean;
|
|
738
|
+
/** `--in-place`: opt OUT of the new default (isolated off the arbiter) and build in the CURRENT checkout, restoring today's dirty-tree-refusing in-place behaviour (D3). */
|
|
739
|
+
inPlace?: boolean;
|
|
731
740
|
/** `-n <x>`: do x eligible items in sequence (auto-pick form). */
|
|
732
741
|
number?: string;
|
|
733
742
|
/** `--selection-order <order>`: a preset keyword (drain/groom) or comma-separated pool order. */
|
|
@@ -742,7 +751,7 @@ interface DoFlags {
|
|
|
742
751
|
strictMergeApproval?: boolean;
|
|
743
752
|
merge?: boolean;
|
|
744
753
|
propose?: boolean;
|
|
745
|
-
/** `--tasks-land-in <
|
|
754
|
+
/** `--tasks-land-in <backlog|ready>`: the explicit operator placement override for `do spec:` tasking output (top of the placement precedence). Resolves into the `tasksLandIn` config key. */
|
|
746
755
|
tasksLandIn?: string;
|
|
747
756
|
/** `--no-pr` ⇒ commander stores `pr === false` (the suppress-PR intent). */
|
|
748
757
|
pr?: boolean;
|
|
@@ -783,7 +792,7 @@ interface IntakeFlags {
|
|
|
783
792
|
proposeTask?: boolean;
|
|
784
793
|
/**
|
|
785
794
|
* `--origin-trust <trusted|untrusted>` — the author-trust verdict the CI shell
|
|
786
|
-
* passes IN so `intake` STAMPS the emitted
|
|
795
|
+
* passes IN so `intake` STAMPS the emitted spec/task (task
|
|
787
796
|
* `untrusted-origin-forces-build-propose`). `intake` does NOT resolve trust; the
|
|
788
797
|
* shell derives it from the SAME `author_association` case as the integration
|
|
789
798
|
* flags. UNSET (a local intake) ⇒ emit unstamped ⇒ human/trusted.
|
|
@@ -916,6 +925,8 @@ interface RequeueFlags {
|
|
|
916
925
|
cwd?: string;
|
|
917
926
|
arbiter?: string;
|
|
918
927
|
reset?: boolean;
|
|
928
|
+
reconcile?: boolean;
|
|
929
|
+
rebase?: boolean;
|
|
919
930
|
message?: string;
|
|
920
931
|
}
|
|
921
932
|
|
|
@@ -929,10 +940,10 @@ interface ReleaseLockFlags {
|
|
|
929
940
|
config?: string;
|
|
930
941
|
cwd?: string;
|
|
931
942
|
arbiter?: string;
|
|
943
|
+
entry?: string;
|
|
932
944
|
}
|
|
933
945
|
|
|
934
946
|
interface DropFlags {
|
|
935
|
-
config?: string;
|
|
936
947
|
cwd?: string;
|
|
937
948
|
reason?: string;
|
|
938
949
|
}
|
|
@@ -982,9 +993,9 @@ interface CloseMergedIssuesFlags {
|
|
|
982
993
|
* REJECT `spec:` with a clear "operates on tasks, not specs" error. On rejection
|
|
983
994
|
* it prints the error to stderr and exits 1 (the task-only commands never act on
|
|
984
995
|
* a spec). An OMITTED slug (`start`/`complete`/`prompt` infer it from the branch)
|
|
985
|
-
* passes through untouched. (The legacy
|
|
996
|
+
* passes through untouched. (The legacy ''prd:'' prefix is a DEAD namespace token
|
|
986
997
|
* after the hard cutover — it is neither special-cased nor rejected here; a
|
|
987
|
-
*
|
|
998
|
+
* ''prd:<slug>'' arg resolves as a bare literal task slug.)
|
|
988
999
|
*
|
|
989
1000
|
* `do` is the ONE command that spans both namespaces; it consumes the full
|
|
990
1001
|
* `resolveSlug` (with the cross-namespace collision check) in the `do-in-place`
|
|
@@ -1164,6 +1175,44 @@ export function buildProgram(): Command {
|
|
|
1164
1175
|
.name(brand.bin)
|
|
1165
1176
|
.description('Autonomous parallel agents over file-based work/ queues.');
|
|
1166
1177
|
|
|
1178
|
+
// `dorfl config --json` (spec `graceful-pre-timeout-wip-checkpoint` — step 5):
|
|
1179
|
+
// print the RESOLVED per-repo config as JSON, so BOTH the advance-lifecycle
|
|
1180
|
+
// workflow's `enumerate` job (which reads it to emit the dynamic
|
|
1181
|
+
// `githubTimeout` output) AND a human debugging a config-precedence question
|
|
1182
|
+
// share ONE honest primitive. A focused honest primitive — NOT overloaded
|
|
1183
|
+
// onto `scan`. Runs in the cwd (in-place), so the resolution chain reads THIS
|
|
1184
|
+
// repo's committed `dorfl.json` exactly like `scan --here`.
|
|
1185
|
+
program
|
|
1186
|
+
.command('config')
|
|
1187
|
+
.helpGroup(HEADLINE_GROUP)
|
|
1188
|
+
.description(
|
|
1189
|
+
"Print the resolved config for the current repo (flag > env > per-repo > global > default). Consumed by the advance-lifecycle workflow's enumerate job to emit the dynamic GitHub timeout backstop (agentDeadlineMinutes + checkpointHeadroomMinutes) at run time from the committed dorfl.json — so editing dorfl.json reflects everywhere (internal deadline AND GitHub cap) on the next tick, no install-ci re-run.",
|
|
1190
|
+
)
|
|
1191
|
+
.option('-c, --config <path>', 'config file path', defaultConfigPath())
|
|
1192
|
+
.option(
|
|
1193
|
+
'--arbiter <remote>',
|
|
1194
|
+
'name of the arbiter git remote (default: per-repo/global defaultArbiter)',
|
|
1195
|
+
)
|
|
1196
|
+
.option('--json', 'output the resolved config as JSON')
|
|
1197
|
+
.action(
|
|
1198
|
+
async (flags: {config?: string; arbiter?: string; json?: boolean}) => {
|
|
1199
|
+
if (flags.json !== true) {
|
|
1200
|
+
console.error(
|
|
1201
|
+
'error: `dorfl config` currently supports only --json output. Pass --json.',
|
|
1202
|
+
);
|
|
1203
|
+
process.exit(1);
|
|
1204
|
+
}
|
|
1205
|
+
const cwd = process.cwd();
|
|
1206
|
+
const {global, override} = loadGlobalAndOverride(flags.config);
|
|
1207
|
+
const resolved = resolveRepoConfig({
|
|
1208
|
+
repoPath: cwd,
|
|
1209
|
+
global,
|
|
1210
|
+
override,
|
|
1211
|
+
}).config;
|
|
1212
|
+
console.log(JSON.stringify(resolved, null, 2));
|
|
1213
|
+
},
|
|
1214
|
+
);
|
|
1215
|
+
|
|
1167
1216
|
program
|
|
1168
1217
|
.command('scan')
|
|
1169
1218
|
.helpGroup(HEADLINE_GROUP)
|
|
@@ -1516,7 +1565,7 @@ export function buildProgram(): Command {
|
|
|
1516
1565
|
.command('claim')
|
|
1517
1566
|
.helpGroup(ADVANCED_GROUP)
|
|
1518
1567
|
.description(
|
|
1519
|
-
|
|
1568
|
+
`Atomically claim a ${workFolderPrefix('tasks-ready')}<slug>.md item via a compare-and-swap push to the arbiter (in-process; mirrors scripts/claim.sh).`,
|
|
1520
1569
|
)
|
|
1521
1570
|
.argument('<slug>', 'the slug of the backlog item to claim')
|
|
1522
1571
|
.option(
|
|
@@ -1789,7 +1838,7 @@ export function buildProgram(): Command {
|
|
|
1789
1838
|
.command('prompt')
|
|
1790
1839
|
.helpGroup(ADVANCED_GROUP)
|
|
1791
1840
|
.description(
|
|
1792
|
-
|
|
1841
|
+
`Print to stdout the work-agent prompt for a task: the canonical CLAIM-PROTOCOL wrapper + the task's own ## Prompt (with <slug> and source spec substituted). Resolves work/in-progress/<slug>.md then ${workFolderPrefix('tasks-ready')}<slug>.md; infers <slug> from a work/<slug> branch when omitted. Read-only, stdout only — the same assembly the autonomous runner feeds agentCmd.`,
|
|
1793
1842
|
)
|
|
1794
1843
|
.argument(
|
|
1795
1844
|
'[slug]',
|
|
@@ -1927,6 +1976,11 @@ export function buildProgram(): Command {
|
|
|
1927
1976
|
override,
|
|
1928
1977
|
flags: {
|
|
1929
1978
|
...(flagMode ? {integration: flagMode} : {}),
|
|
1979
|
+
// `--merge-retries <n>` rides the SAME gate-family precedence chain on
|
|
1980
|
+
// the isolated-recovery path so a wide-matrix CI's raised cap actually
|
|
1981
|
+
// reaches the land tail here too (task
|
|
1982
|
+
// `thread-merge-retries-cross-task-and-ratify-default`).
|
|
1983
|
+
...mergeRetriesFlagOverrides(flags),
|
|
1930
1984
|
...noPRFlagOverrides(flags),
|
|
1931
1985
|
},
|
|
1932
1986
|
});
|
|
@@ -1941,6 +1995,9 @@ export function buildProgram(): Command {
|
|
|
1941
1995
|
workspacesDir: flags.workspace ?? isoConfig.workspacesDir,
|
|
1942
1996
|
integration: isoConfig.integration,
|
|
1943
1997
|
noPR: isoConfig.noPR,
|
|
1998
|
+
// The resolved cross-job CAS-retry cap threaded through so the isolated-
|
|
1999
|
+
// recovery land tail respects the per-repo cap (same chain as `complete`).
|
|
2000
|
+
mergeRetries: isoConfig.mergeRetries,
|
|
1944
2001
|
note: (message) => console.error(`>> ${message}`),
|
|
1945
2002
|
env: process.env,
|
|
1946
2003
|
});
|
|
@@ -2040,7 +2097,7 @@ export function buildProgram(): Command {
|
|
|
2040
2097
|
.command('do')
|
|
2041
2098
|
.helpGroup(HEADLINE_GROUP)
|
|
2042
2099
|
.description(
|
|
2043
|
-
|
|
2100
|
+
"The per-repo WORKER (the CI command): claim + onboard onto work/<slug>, run the agent, gate, integrate, and exit. Builds in an ISOLATED job worktree off THIS repo's arbiter by DEFAULT (task `make-isolated-default-build-mode` — the cwd is the origin SOURCE only; the human working tree is never written). --in-place opts OUT (build in the current checkout, refusing a dirty tree — today's pre-flip behaviour). --isolated remains accepted as a redundant explicit opt-in alias. With --remote <r>: against a REGISTERED repo with NO checkout — materialise a hub mirror + job worktree in the agents\u2019 area, run the same pipeline there, then reap. do <slug> | do task:<slug> | do spec:<slug> (the tasking path) | do (auto-pick one) | do <a> <b> (those, in sequence) | do -n <x> (x eligible, in sequence). Auto-pick draws TASKS-FIRST then SPECS-to-task by default (per-repo selectionOrder reorders the pools). --propose (default) / --merge resolved at integrate-time. Supersedes ar-run.sh.",
|
|
2044
2101
|
)
|
|
2045
2102
|
// EXTENSIBLE argument grammar (the three do-* tasks grow this one block):
|
|
2046
2103
|
// `do-autopick` widens the single optional positional into a VARIADIC one so
|
|
@@ -2070,7 +2127,11 @@ export function buildProgram(): Command {
|
|
|
2070
2127
|
)
|
|
2071
2128
|
.option(
|
|
2072
2129
|
'--isolated',
|
|
2073
|
-
"build in an ISOLATED job worktree off THIS repo's arbiter (inferred from cwd) instead of taking over the current checkout, then integrate + reap \u2014
|
|
2130
|
+
"build in an ISOLATED job worktree off THIS repo's arbiter (inferred from cwd) instead of taking over the current checkout, then integrate + reap \u2014 NOW THE DEFAULT since `make-isolated-default-build-mode`; the flag remains accepted as a redundant explicit opt-IN alias (some scripts/skills pin it, and it is harmless once implied). Shares the same grammar as the no-checkout forms: a single named item, multiple named items (in sequence), AND -n/auto-pick over the mirror-side eligible-pool scan. Always SEQUENTIAL (parallelism is `run` / the CI matrix). Orthogonal to --remote (a foreign repo); with --remote, remote wins (isolation is already implied). Mutually exclusive with --in-place (contradictory intents).",
|
|
2131
|
+
)
|
|
2132
|
+
.option(
|
|
2133
|
+
'--in-place',
|
|
2134
|
+
"OPT OUT of the new isolated default: build in the CURRENT checkout (integrates in place, refuses a dirty tree) \u2014 today's pre-flip behaviour, now explicit. Mutually exclusive with --isolated (contradictory intents) and with --remote (there is no local checkout to take over). Use this for the edit-locally-then-build loop; the default (isolated off the arbiter) never writes the cwd working tree.",
|
|
2074
2135
|
)
|
|
2075
2136
|
.option(
|
|
2076
2137
|
'--merge',
|
|
@@ -2082,7 +2143,7 @@ export function buildProgram(): Command {
|
|
|
2082
2143
|
)
|
|
2083
2144
|
.option(
|
|
2084
2145
|
'--tasks-land-in <where>',
|
|
2085
|
-
'where `do spec:<slug>` tasking output lands: `
|
|
2146
|
+
'where `do spec:<slug>` tasking output lands: `backlog` (staged, not agent-eligible) or `ready` (the agent POOL). The EXPLICIT operator override at the top of the placement precedence (explicit flag > untrusted-origin forces staging > tasksLandIn default > built-in). Resolved flag > env (DORFL_TASKS_LAND_IN) > per-repo > global > built-in.',
|
|
2086
2147
|
)
|
|
2087
2148
|
.option(
|
|
2088
2149
|
'--no-pr',
|
|
@@ -2221,14 +2282,52 @@ export function buildProgram(): Command {
|
|
|
2221
2282
|
// `remote-do-reads-per-repo-config-from-arbiter-main`). Only the whitelisted
|
|
2222
2283
|
// `REPO_ALLOWED_KEYS` are layered (host-only keys stay global/flag/env-only,
|
|
2223
2284
|
// rejected by the SAME `repo-config.ts` split).
|
|
2285
|
+
// Mutually-exclusive intent guards for the three FORM flags (task
|
|
2286
|
+
// `make-isolated-default-build-mode`, D3): `--in-place` opts OUT of the new
|
|
2287
|
+
// isolated default, `--isolated` is the redundant explicit opt-IN alias,
|
|
2288
|
+
// `--remote <r>` targets a foreign repo. `--in-place` + `--isolated` is
|
|
2289
|
+
// contradictory (build here vs build off the arbiter); `--in-place` + `--remote`
|
|
2290
|
+
// is nonsensical (there is no local checkout to take over). Reject loudly
|
|
2291
|
+
// rather than silently prefer one.
|
|
2292
|
+
if (flags.inPlace === true && flags.isolated === true) {
|
|
2293
|
+
console.error(
|
|
2294
|
+
'error: --in-place and --isolated are contradictory (build in the current ' +
|
|
2295
|
+
"checkout vs build in a worktree off this repo's arbiter). Pick one.",
|
|
2296
|
+
);
|
|
2297
|
+
process.exit(1);
|
|
2298
|
+
}
|
|
2299
|
+
if (flags.inPlace === true && flags.remote !== undefined) {
|
|
2300
|
+
console.error(
|
|
2301
|
+
'error: --in-place builds in the CURRENT checkout; --remote <r> targets a ' +
|
|
2302
|
+
'REGISTERED repo with NO checkout. Pick one.',
|
|
2303
|
+
);
|
|
2304
|
+
process.exit(1);
|
|
2305
|
+
}
|
|
2306
|
+
// The DEFAULT flip (task `make-isolated-default-build-mode`): with no
|
|
2307
|
+
// `--remote` and no `--in-place`, `do <slug>` builds in an ISOLATED job
|
|
2308
|
+
// worktree off THIS repo's arbiter — the SAME no-checkout path `--isolated`
|
|
2309
|
+
// already used. The cwd is the origin SOURCE only (arbiter-remote resolution
|
|
2310
|
+
// + per-repo config), never written. `--isolated` remains accepted as a
|
|
2311
|
+
// redundant explicit opt-IN alias (D3). `--in-place` opts out to today's
|
|
2312
|
+
// in-checkout path (the fall-through below). See ADR §3.
|
|
2224
2313
|
const isolatedNoRemote =
|
|
2225
|
-
flags.
|
|
2314
|
+
flags.remote === undefined && flags.inPlace !== true;
|
|
2315
|
+
// Whether the user DEFAULTED into isolated (no form flag typed) vs typed
|
|
2316
|
+
// `--isolated` explicitly. Only used to tune the no-arbiter error message so
|
|
2317
|
+
// the default path names `--in-place` as its natural escape (D2).
|
|
2318
|
+
const defaultedToIsolated = isolatedNoRemote && flags.isolated !== true;
|
|
2226
2319
|
if (flags.remote !== undefined || isolatedNoRemote) {
|
|
2227
2320
|
// The form's user-facing name + canonical usage, for the shared error
|
|
2228
|
-
// messages below (so
|
|
2229
|
-
const form = isolatedNoRemote
|
|
2321
|
+
// messages below (so isolated errors read in the form's own terms).
|
|
2322
|
+
const form = isolatedNoRemote
|
|
2323
|
+
? defaultedToIsolated
|
|
2324
|
+
? 'do'
|
|
2325
|
+
: '--isolated'
|
|
2326
|
+
: '--remote';
|
|
2230
2327
|
const usage = isolatedNoRemote
|
|
2231
|
-
?
|
|
2328
|
+
? defaultedToIsolated
|
|
2329
|
+
? '`do <slug>`'
|
|
2330
|
+
: '`do --isolated <slug>`'
|
|
2232
2331
|
: '`do --remote <r> <slug>`';
|
|
2233
2332
|
// The no-checkout forms now support the SAME variadic grammar the in-place
|
|
2234
2333
|
// form does: a single NAMED item, MULTIPLE named items (sequential), and
|
|
@@ -2258,12 +2357,33 @@ export function buildProgram(): Command {
|
|
|
2258
2357
|
identityEnv(bootstrapIdentity, process.env),
|
|
2259
2358
|
);
|
|
2260
2359
|
if (resolvedUrl === undefined) {
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2360
|
+
// Isolated needs an arbiter to build off. Two message variants:
|
|
2361
|
+
// - defaulted-to-isolated (no form flag typed) leads with the DEFAULT
|
|
2362
|
+
// and names `--in-place` as the natural local escape (D2). This is
|
|
2363
|
+
// the ERROR-not-silent-degrade behaviour the flip requires: a repo
|
|
2364
|
+
// with no arbiter must NOT be quietly built in-place invisibly.
|
|
2365
|
+
// - explicit `--isolated` keeps its own terms (the user asked for
|
|
2366
|
+
// isolation deliberately) but ALSO surfaces `--in-place` alongside
|
|
2367
|
+
// `--remote <url>` as the two escapes.
|
|
2368
|
+
if (defaultedToIsolated) {
|
|
2369
|
+
console.error(
|
|
2370
|
+
`error: no arbiter is configured for this repo ` +
|
|
2371
|
+
`(no '${arbiterName}' remote), and \`do\` now defaults to ` +
|
|
2372
|
+
`building in an ISOLATED worktree off THIS repo's arbiter ` +
|
|
2373
|
+
`(task \`make-isolated-default-build-mode\`). Configure an ` +
|
|
2374
|
+
`arbiter (e.g. \`dorfl remote add <url>\`), pass --in-place ` +
|
|
2375
|
+
`to build in the current checkout, or use --remote <url> to ` +
|
|
2376
|
+
`target another repo.`,
|
|
2377
|
+
);
|
|
2378
|
+
} else {
|
|
2379
|
+
console.error(
|
|
2380
|
+
`error: --isolated builds in a worktree off this repo's arbiter ` +
|
|
2381
|
+
`('${arbiterName}'), but no such arbiter remote is configured/found ` +
|
|
2382
|
+
`here. Run inside a participating repo (a clone with an arbiter ` +
|
|
2383
|
+
`remote), pass --in-place to build in the current checkout, or ` +
|
|
2384
|
+
`use --remote <url> to target another repo.`,
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2267
2387
|
process.exit(1);
|
|
2268
2388
|
}
|
|
2269
2389
|
effectiveRemote = resolvedUrl;
|
|
@@ -2369,6 +2489,11 @@ export function buildProgram(): Command {
|
|
|
2369
2489
|
})
|
|
2370
2490
|
: undefined,
|
|
2371
2491
|
watch: flags.watch === true,
|
|
2492
|
+
// The dorfl-internal agent deadline + anti-loop ceiling (spec
|
|
2493
|
+
// `graceful-pre-timeout-wip-checkpoint`). Threaded from the RESOLVED
|
|
2494
|
+
// remote-repo config (its committed `dorfl.json` on <arbiter>/main).
|
|
2495
|
+
agentDeadlineMinutes: remoteConfig.agentDeadlineMinutes,
|
|
2496
|
+
maxAutoCheckpoints: remoteConfig.maxAutoCheckpoints,
|
|
2372
2497
|
color: shouldUseColor(process.stdout),
|
|
2373
2498
|
note: (message) => console.error(`>> ${message}`),
|
|
2374
2499
|
noteBlock: (message) => console.error(message),
|
|
@@ -2550,6 +2675,11 @@ export function buildProgram(): Command {
|
|
|
2550
2675
|
// `--watch`: tail the pi session log live (pi harness only; the
|
|
2551
2676
|
// performDo guard errors clearly on any other adapter). READ-ONLY.
|
|
2552
2677
|
watch: flags.watch === true,
|
|
2678
|
+
// The dorfl-internal agent deadline + anti-loop ceiling (spec
|
|
2679
|
+
// `graceful-pre-timeout-wip-checkpoint`). Threaded from the resolved
|
|
2680
|
+
// per-repo config so an edit to dorfl.json reflects on the NEXT tick.
|
|
2681
|
+
agentDeadlineMinutes: config.agentDeadlineMinutes,
|
|
2682
|
+
maxAutoCheckpoints: config.maxAutoCheckpoints,
|
|
2553
2683
|
color: shouldUseColor(process.stdout),
|
|
2554
2684
|
note: (message) => console.error(`>> ${message}`),
|
|
2555
2685
|
noteBlock: (message) => console.error(message),
|
|
@@ -2685,7 +2815,7 @@ export function buildProgram(): Command {
|
|
|
2685
2815
|
)
|
|
2686
2816
|
.option(
|
|
2687
2817
|
'--tasks-land-in <where>',
|
|
2688
|
-
'where `advance spec:<slug>` tasking output lands: `
|
|
2818
|
+
'where `advance spec:<slug>` tasking output lands: `backlog` (staged) or `ready` (the agent POOL). The EXPLICIT operator override at the top of the placement precedence. Resolved flag > env (DORFL_TASKS_LAND_IN) > per-repo > global > built-in.',
|
|
2689
2819
|
)
|
|
2690
2820
|
.option(
|
|
2691
2821
|
'--watch',
|
|
@@ -2996,6 +3126,10 @@ export function buildProgram(): Command {
|
|
|
2996
3126
|
taskReviewGate: config.review
|
|
2997
3127
|
? harnessTaskAcceptanceGate({harness, agentCmd: config.agentCmd})
|
|
2998
3128
|
: undefined,
|
|
3129
|
+
// The dorfl-internal agent deadline + anti-loop ceiling (spec
|
|
3130
|
+
// `graceful-pre-timeout-wip-checkpoint`). Threaded from resolved config.
|
|
3131
|
+
agentDeadlineMinutes: config.agentDeadlineMinutes,
|
|
3132
|
+
maxAutoCheckpoints: config.maxAutoCheckpoints,
|
|
2999
3133
|
color: shouldUseColor(process.stdout),
|
|
3000
3134
|
note: (message) => console.error(`>> ${message}`),
|
|
3001
3135
|
noteBlock: (message) => console.error(message),
|
|
@@ -3115,7 +3249,7 @@ export function buildProgram(): Command {
|
|
|
3115
3249
|
)
|
|
3116
3250
|
.option(
|
|
3117
3251
|
'--reap-stale-locks',
|
|
3118
|
-
'(with --ledger) OPT-IN: also CLEAR every
|
|
3252
|
+
'(with --ledger) OPT-IN: also CLEAR every ORPHAN lock the report finds via the SAME leased delete `release-lock` uses, so one command sweeps all orphaned locks instead of N hand-run release-locks. Reaps the `cleared-stale` class: a held `active` lock that is EITHER terminal on `<arbiter>/main` (stranded between the durable move and the release) OR non-terminal + SURFACED (`needsAnswers:true` + sidecar on `<arbiter>/main`) = the crash-window orphan the ordered bounce transition leaves when step 1 lands but step 2 never runs. A `kept-in-flight` (active + non-terminal + NOT surfaced — a healthy build) is NEVER reaped, even with this flag. A concurrent change to a lock ref makes its leased delete REJECT (reported), never --force. WITHOUT this flag `gc --ledger` stays report-only (fail-loud, deletes nothing).',
|
|
3119
3253
|
)
|
|
3120
3254
|
.option(
|
|
3121
3255
|
'--remote-branches',
|
|
@@ -3160,13 +3294,16 @@ export function buildProgram(): Command {
|
|
|
3160
3294
|
// clearing (no auto-sweep; a human asserts a lock is dead via
|
|
3161
3295
|
// `release-lock`).
|
|
3162
3296
|
// OPT-IN SWEEP (`--reap-stale-locks`): the WRITE twin of the report. A
|
|
3163
|
-
// human asserting "clear the dead TERMINAL locks now": for
|
|
3164
|
-
//
|
|
3165
|
-
//
|
|
3166
|
-
//
|
|
3167
|
-
//
|
|
3168
|
-
//
|
|
3169
|
-
//
|
|
3297
|
+
// human asserting "clear the dead TERMINAL locks now": for BOTH
|
|
3298
|
+
// terminal-on-main orphan classes (task `reaper-reap-terminal-stuck-lock-orphans`;
|
|
3299
|
+
// ADR `ledger-status-on-per-item-lock-refs` § Addendum 2026-07-10) — the
|
|
3300
|
+
// stranded `cleared-stale` AND the stuck-terminal `cleared-stuck-terminal`
|
|
3301
|
+
// crash-orphan — perform the SAME leased delete `release-lock` / the
|
|
3302
|
+
// recovery use, so one command sweeps every orphaned terminal lock. A
|
|
3303
|
+
// `kept-stuck` (STUCK + NON-terminal) / `kept-in-flight` lock is NEVER
|
|
3304
|
+
// reaped (scope fence); a concurrent change makes a clear REJECT (reported
|
|
3305
|
+
// `lost`), never --force. WITHOUT the flag the surface below stays
|
|
3306
|
+
// report-only (fail-loud, deletes nothing).
|
|
3170
3307
|
if (flags.reapStaleLocks) {
|
|
3171
3308
|
const reap = await reapStaleItemLocks(
|
|
3172
3309
|
flags.cwd ?? repoPath,
|
|
@@ -3190,10 +3327,11 @@ export function buildProgram(): Command {
|
|
|
3190
3327
|
: formatLedgerSweep(result),
|
|
3191
3328
|
);
|
|
3192
3329
|
}
|
|
3193
|
-
// Fail-loud AFTER the sweep: a `kept-stuck` (
|
|
3194
|
-
// a `lost`/`error` (
|
|
3195
|
-
// needs attention; a clean sweep
|
|
3196
|
-
//
|
|
3330
|
+
// Fail-loud AFTER the sweep: a `kept-stuck` (STUCK + non-terminal —
|
|
3331
|
+
// rightly left for a human) or a `lost`/`error` (an orphan lock whose
|
|
3332
|
+
// leased delete lost the race) still needs attention; a clean sweep
|
|
3333
|
+
// that reaped every orphan lock (stale OR stuck-terminal) and left only
|
|
3334
|
+
// healthy in-flight holds exits 0.
|
|
3197
3335
|
process.exit(
|
|
3198
3336
|
result.duplicates.length > 0 || reapReportNeedsAttention(reap)
|
|
3199
3337
|
? 1
|
|
@@ -3233,13 +3371,15 @@ export function buildProgram(): Command {
|
|
|
3233
3371
|
// human clears a NAMED unified lock via `release-lock`).
|
|
3234
3372
|
//
|
|
3235
3373
|
// SCOPED to the ATTENTION verdicts only (spec US#14/#21, ADR
|
|
3236
|
-
// `ledger-status-on-per-item-lock-refs
|
|
3237
|
-
// crash-orphaned lock, NOT every held one): a `kept-stuck`
|
|
3238
|
-
//
|
|
3239
|
-
//
|
|
3240
|
-
//
|
|
3241
|
-
//
|
|
3242
|
-
//
|
|
3374
|
+
// `ledger-status-on-per-item-lock-refs` § Addendum 2026-07-10: this surface
|
|
3375
|
+
// is the STUCK / crash-orphaned lock, NOT every held one): a `kept-stuck`
|
|
3376
|
+
// (STUCK + NON-terminal — the genuine human-attention case), a
|
|
3377
|
+
// `cleared-stale`-eligible (terminal + stale active = stranded orphan), or
|
|
3378
|
+
// a `cleared-stuck-terminal`-eligible (terminal + stuck = crash-orphan the
|
|
3379
|
+
// reaper can now auto-clear) fails loud, but a `kept-in-flight` (active,
|
|
3380
|
+
// non-terminal) lock is the NORMAL in-flight state of a healthy concurrent
|
|
3381
|
+
// build (read by `status` as healthy) — it is reported informationally
|
|
3382
|
+
// and does NOT make a routine `gc --ledger` health check exit non-zero.
|
|
3243
3383
|
process.exit(
|
|
3244
3384
|
result.duplicates.length > 0 ||
|
|
3245
3385
|
itemLockReportNeedsAttention(lockReport)
|
|
@@ -3386,7 +3526,7 @@ export function buildProgram(): Command {
|
|
|
3386
3526
|
}
|
|
3387
3527
|
|
|
3388
3528
|
// Exit non-zero on a REFUSAL (quiescence gate) or a non-green leak scan
|
|
3389
|
-
// (a converted tree that still carries a dangling
|
|
3529
|
+
// (a converted tree that still carries a dangling ''prd'' ref). A clean
|
|
3390
3530
|
// dry-run or a green migration exits 0.
|
|
3391
3531
|
if (result.refused || result.leaks.length > 0) {
|
|
3392
3532
|
process.exit(1);
|
|
@@ -3498,7 +3638,7 @@ export function buildProgram(): Command {
|
|
|
3498
3638
|
.command('requeue <slug>')
|
|
3499
3639
|
.helpGroup(HEADLINE_GROUP)
|
|
3500
3640
|
.description(
|
|
3501
|
-
'Requeue a STUCK task to the backlog for re-claiming (ADR §12/§14). Recovers a task whose per-item lock is held — stuck (the resolved-recovery path: a previously-routed needs-attention item, now lock `state: stuck`) OR active (a claim that never surfaced — an un-surfaced abort, a killed run, or an in-place requeue note). The body rests in work/tasks/backlog/<slug>.md (claim never moves it under the per-item-lock model); requeue releases the lock so the item is claimable again. The release is published as a TREE-LESS compare-and-swap to the arbiter ref, EXACTLY like claim — it NEVER stages or commits in the cwd working tree, so a requeue in a shared checkout can never sweep up a concurrent writer’s uncommitted files. DEFAULT = keep + continue: leave the work/<slug> branch UNTOUCHED so the next claim CONTINUES from its tip (rebased onto fresh main at onboard-time). --reset =
|
|
3641
|
+
'Requeue a STUCK task to the backlog for re-claiming (ADR §12/§14). Recovers a task whose per-item lock is held — stuck (the resolved-recovery path: a previously-routed needs-attention item, now lock `state: stuck`) OR active (a claim that never surfaced — an un-surfaced abort, a killed run, or an in-place requeue note). The body rests in work/tasks/backlog/<slug>.md (claim never moves it under the per-item-lock model); requeue releases the lock so the item is claimable again. The release is published as a TREE-LESS compare-and-swap to the arbiter ref, EXACTLY like claim — it NEVER stages or commits in the cwd working tree, so a requeue in a shared checkout can never sweep up a concurrent writer’s uncommitted files. Escalation ladder (least to most destructive): (1) DEFAULT = keep + continue: leave the work/<slug> branch UNTOUCHED so the next claim CONTINUES from its tip (rebased onto fresh main at onboard-time); if the work/<slug> branch is not on the arbiter (never pushed, or a prior --reset already deleted it), default requeue succeeds anyway with a fresh-claim note. (2) --reconcile (alias --rebase) = NON-DESTRUCTIVE recovery: re-sync the mirror to the arbiter (prune-fetch) and RETRY the rebase of the kept branch onto latest arbiter/main in a scratch worktree; on a clean rebase, push the reconciled tip back (--force-with-lease, work branch only) and release the lock; on a genuine content conflict AFTER the clean re-sync, leave the item stuck and the branch UNTOUCHED (nothing deleted) so the human can retry when the churn settles. NEVER deletes the remote branch. (3) --reset = DESTRUCTIVE last resort: delete the remote work/<slug> branch FIRST (then release the lock) so the next claim starts fresh (guarded; never the default). -m/--message appends a dated handoff note to the item body (all modes; append-only).',
|
|
3502
3642
|
)
|
|
3503
3643
|
.option('-c, --config <path>', 'config file path', defaultConfigPath())
|
|
3504
3644
|
.option(
|
|
@@ -3509,9 +3649,14 @@ export function buildProgram(): Command {
|
|
|
3509
3649
|
'--arbiter <remote>',
|
|
3510
3650
|
'the arbiter git remote the tree-less move is CAS-published to (default: origin). --cwd resolves this remote; the move is never written to the cwd tree.',
|
|
3511
3651
|
)
|
|
3652
|
+
.option(
|
|
3653
|
+
'--reconcile',
|
|
3654
|
+
'NON-DESTRUCTIVE recovery (middle rung, between the default keep+continue and the destructive --reset): re-sync the mirror to the arbiter (prune-fetch) and RETRY the rebase of the kept work/<slug> onto latest arbiter/main in a scratch worktree; on success, push the reconciled tip back with --force-with-lease and release the lock; on a genuine content conflict after the clean re-sync, leave the item stuck and the branch UNTOUCHED (nothing deleted). NEVER deletes the remote branch. Incompatible with --reset.',
|
|
3655
|
+
)
|
|
3656
|
+
.option('--rebase', 'alias of --reconcile (canonical name)')
|
|
3512
3657
|
.option(
|
|
3513
3658
|
'--reset',
|
|
3514
|
-
'DISCARD the kept work
|
|
3659
|
+
'DESTRUCTIVE last resort: DISCARD the kept work by deleting the remote work/<slug> branch FIRST, then move to backlog so the next claim starts FRESH (guarded; a deliberate departure from the never-delete-the-remote-branch invariant). Never the default; try --reconcile first.',
|
|
3515
3660
|
)
|
|
3516
3661
|
.option(
|
|
3517
3662
|
'-m, --message <note>',
|
|
@@ -3533,6 +3678,14 @@ export function buildProgram(): Command {
|
|
|
3533
3678
|
// call site, rather than relying on the seam's silent `?? process.env`
|
|
3534
3679
|
// default by omission (the implicit fallback that made `requeue`'s human
|
|
3535
3680
|
// attribution accidental rather than declared).
|
|
3681
|
+
const reconcile = flags.reconcile === true || flags.rebase === true;
|
|
3682
|
+
if (reconcile && flags.reset) {
|
|
3683
|
+
console.error(
|
|
3684
|
+
'error: --reconcile and --reset are mutually exclusive ' +
|
|
3685
|
+
'(non-destructive recovery vs destructive last resort). Pick one.',
|
|
3686
|
+
);
|
|
3687
|
+
process.exit(1);
|
|
3688
|
+
}
|
|
3536
3689
|
const result = await ledgerWrite.applyReturnToBacklogTransition({
|
|
3537
3690
|
cwd,
|
|
3538
3691
|
slug,
|
|
@@ -3541,6 +3694,7 @@ export function buildProgram(): Command {
|
|
|
3541
3694
|
// `--cwd` is purely the ORIGIN SOURCE the remote is resolved from.
|
|
3542
3695
|
arbiter: flags.arbiter ?? 'origin',
|
|
3543
3696
|
reset: flags.reset,
|
|
3697
|
+
reconcile,
|
|
3544
3698
|
message: flags.message,
|
|
3545
3699
|
env: process.env,
|
|
3546
3700
|
note: (message) => console.error(`>> ${message}`),
|
|
@@ -3551,7 +3705,9 @@ export function buildProgram(): Command {
|
|
|
3551
3705
|
}
|
|
3552
3706
|
const how = result.deletedRemoteBranch
|
|
3553
3707
|
? ` (--reset: deleted the remote ${workBranchRef('task', slug)} branch; next claim starts fresh)`
|
|
3554
|
-
:
|
|
3708
|
+
: result.reconciled
|
|
3709
|
+
? ` (--reconcile: re-synced mirror + rebased ${workBranchRef('task', slug)} onto latest main and pushed the reconciled tip back; next claim continues from it)`
|
|
3710
|
+
: ' (kept the work branch; next claim continues from its tip)';
|
|
3555
3711
|
console.log(`Requeued '${slug}' to backlog for re-claiming.${how}`);
|
|
3556
3712
|
});
|
|
3557
3713
|
|
|
@@ -3559,7 +3715,7 @@ export function buildProgram(): Command {
|
|
|
3559
3715
|
// `pre-backlog-staging-folder-and-promote-step-a` /
|
|
3560
3716
|
// `pre-prd-staging-pool-split-and-untrusted-prd-placement`): the HUMAN/runner-
|
|
3561
3717
|
// owned verb that moves a STAGED item into its agent-eligible POOL — a task
|
|
3562
|
-
// `work/
|
|
3718
|
+
// `work/tasks/backlog/<slug>.md → work/tasks/ready/<slug>.md`, a spec
|
|
3563
3719
|
// `work/specs/proposed/<slug>.md → work/specs/ready/<slug>.md` — as a tree-less CAS on the
|
|
3564
3720
|
// arbiter, the SAME trust model + mechanism as `requeue`. The agent emits STAGED;
|
|
3565
3721
|
// only this verb (a human, or the runner) admits it to the pool. With NO argument
|
|
@@ -3569,7 +3725,7 @@ export function buildProgram(): Command {
|
|
|
3569
3725
|
.command('promote [item]')
|
|
3570
3726
|
.helpGroup(HEADLINE_GROUP)
|
|
3571
3727
|
.description(
|
|
3572
|
-
`Admit a STAGED item into its agent-eligible POOL (the runner/human side of the staging gate): a task
|
|
3728
|
+
`Admit a STAGED item into its agent-eligible POOL (the runner/human side of the staging gate): a task \`${workFolderPrefix('tasks-backlog')}<slug>.md → ${workFolderPrefix('tasks-ready')}<slug>.md\`, a spec \`${workFolderPrefix('specs-proposed')}<slug>.md → ${workFolderPrefix('specs-ready')}<slug>.md\`, published as a TREE-LESS compare-and-swap to the arbiter ref (EXACTLY like requeue/claim — it never stages/commits in the cwd tree). The agent only ever CREATES staged; this verb is the gate a human (or the runner) opens. Accepts \`task:<slug>\` / \`spec:<slug>\` / a bare \`<slug>\` (= task). With NO argument, LISTS every promotable item (the tasks in ${workFolderPrefix('tasks-backlog')} + the specs in ${workFolderPrefix('specs-proposed')} on the arbiter) so you can see what is staged waiting for promotion. Idempotent: promoting an already-pooled slug is a clean no-op success.`,
|
|
3573
3729
|
)
|
|
3574
3730
|
.option('-c, --config <path>', 'config file path', defaultConfigPath())
|
|
3575
3731
|
.option(
|
|
@@ -3597,7 +3753,7 @@ export function buildProgram(): Command {
|
|
|
3597
3753
|
}
|
|
3598
3754
|
if (listed.items.length === 0) {
|
|
3599
3755
|
console.log(
|
|
3600
|
-
`Nothing staged to promote on ${arbiter}/main (
|
|
3756
|
+
`Nothing staged to promote on ${arbiter}/main (${workFolderPrefix('tasks-backlog')} and ${workFolderPrefix('specs-proposed')} are empty).`,
|
|
3601
3757
|
);
|
|
3602
3758
|
return;
|
|
3603
3759
|
}
|
|
@@ -3610,7 +3766,7 @@ export function buildProgram(): Command {
|
|
|
3610
3766
|
|
|
3611
3767
|
// AN ITEM → promote it. `task:`/`spec:` are explicit; a bare slug defaults to
|
|
3612
3768
|
// a task (mirrors `requeue`). An `obs:`/`observation:` prefix is rejected
|
|
3613
|
-
// (observations have no pool). A legacy
|
|
3769
|
+
// (observations have no pool). A legacy ''prd:'' prefix is a DEAD namespace token
|
|
3614
3770
|
// after the hard cutover — it resolves as a bare literal task slug, not a spec.
|
|
3615
3771
|
const parsed = parseSlugArg(rawItem);
|
|
3616
3772
|
if (parsed.explicit === 'observation') {
|
|
@@ -3661,13 +3817,29 @@ export function buildProgram(): Command {
|
|
|
3661
3817
|
// Idempotent: deleting an absent ref is a clean exit-0 "nothing to clear"
|
|
3662
3818
|
// (`not-held`), NOT a failure — deleting the lock ref(s) is "all locks released"
|
|
3663
3819
|
// and recoverable (the work is safe on the `work/<slug>` branches + `main`).
|
|
3820
|
+
//
|
|
3821
|
+
// ESCAPE HATCH (`--entry <literal>`, task
|
|
3822
|
+
// `release-lock-entry-escape-hatch-and-literal-entry-reporting`): the item
|
|
3823
|
+
// positional is OPTIONAL because a lock whose entry name is NOT derivable from
|
|
3824
|
+
// any current item-form (a PRE-CUTOVER `slice-<slug>` / `prd-<slug>` entry minted
|
|
3825
|
+
// before the slice→task / `prd-to-spec` vocabulary cutover) is UN-NAMEABLE through
|
|
3826
|
+
// the item-form path. `--entry` takes the literal `<entry>` VERBATIM (bypassing
|
|
3827
|
+
// the namespace mapping) so a human can still NAME + clear such an orphan without
|
|
3828
|
+
// the raw `git push origin --delete refs/dorfl/lock/…` plumbing. It reuses the
|
|
3829
|
+
// SAME leased-delete path (`releaseLiteralLockEntry` → the shared entry-keyed
|
|
3830
|
+
// core), so the trust model is unchanged: the human still asserts liveness.
|
|
3831
|
+
// EXACTLY ONE of {item positional, --entry} must be given.
|
|
3664
3832
|
program
|
|
3665
|
-
.command('release-lock
|
|
3833
|
+
.command('release-lock [item]')
|
|
3666
3834
|
.helpGroup(HEADLINE_GROUP)
|
|
3667
3835
|
.description(
|
|
3668
|
-
'Clear a NAMED stuck/orphaned UNIFIED per-item lock (refs/dorfl/lock/<entry>) by DELETING the ref on the arbiter — the recovery verb for a lock the system orphaned (a crashed build/task/advance that left the hold behind). The generalisation of `release-advancing` from the advancing marker to the ONE lock per item. Same trust model as `requeue`: a HUMAN asserts the lock is dead by NAMING it; the tool never guesses liveness (the lock has NO heartbeat, so there is NO automatic sweep / age-based reaper anywhere). Accepts the same item forms as the lock API: `task:<slug>` / `spec:<slug>` / `obs:<slug>` / a bare `<slug>` (= task). Idempotent — re-running on an already-cleared lock is a clean exit-0 no-op (deleting the lock ref is “all locks released”, recoverable). NEVER `--force`. Discoverable via `gc --ledger` (it REPORTS every lingering lock,
|
|
3836
|
+
'Clear a NAMED stuck/orphaned UNIFIED per-item lock (refs/dorfl/lock/<entry>) by DELETING the ref on the arbiter — the recovery verb for a lock the system orphaned (a crashed build/task/advance that left the hold behind). The generalisation of `release-advancing` from the advancing marker to the ONE lock per item. Same trust model as `requeue`: a HUMAN asserts the lock is dead by NAMING it; the tool never guesses liveness (the lock has NO heartbeat, so there is NO automatic sweep / age-based reaper anywhere). Accepts the same item forms as the lock API: `task:<slug>` / `spec:<slug>` / `obs:<slug>` / a bare `<slug>` (= task). ESCAPE HATCH: `--entry <literal>` (no item positional) targets a LITERAL entry name directly, bypassing the namespace mapping — for a lock whose entry name is not derivable from any current item-form (a pre-vocabulary-cutover `slice-<slug>` / `prd-<slug>` prefix, or a future rename); this is the supported way to clear such orphans, so the raw `git push origin --delete refs/dorfl/lock/…` plumbing is no longer required. Idempotent — re-running on an already-cleared lock is a clean exit-0 no-op (deleting the lock ref is “all locks released”, recoverable). NEVER `--force`. Discoverable via `gc --ledger` (it REPORTS every lingering lock and, for a pre-cutover entry, the exact `release-lock --entry <literal>` invocation).',
|
|
3669
3837
|
)
|
|
3670
3838
|
.option('-c, --config <path>', 'config file path', defaultConfigPath())
|
|
3839
|
+
.option(
|
|
3840
|
+
'--entry <literal>',
|
|
3841
|
+
'ESCAPE HATCH: the LITERAL lock-entry name (`refs/dorfl/lock/<literal>`) to release VERBATIM, bypassing the namespace mapping — for a lock whose entry name is not derivable from any current item-form (a pre-cutover `slice-<slug>` / `prd-<slug>` entry). Mutually exclusive with the item positional (give EXACTLY one). Validated to `[A-Za-z0-9._-]+` (no slashes/whitespace) so it cannot escape the lock namespace.',
|
|
3842
|
+
)
|
|
3671
3843
|
.option(
|
|
3672
3844
|
'--cwd <dir>',
|
|
3673
3845
|
'the repo/working clone whose arbiter remote the lock ref is DELETED on (default: cwd)',
|
|
@@ -3676,26 +3848,54 @@ export function buildProgram(): Command {
|
|
|
3676
3848
|
'--arbiter <remote>',
|
|
3677
3849
|
'the arbiter git remote the lock ref is deleted on (default: origin)',
|
|
3678
3850
|
)
|
|
3679
|
-
.action(async (item: string, flags: ReleaseLockFlags) => {
|
|
3851
|
+
.action(async (item: string | undefined, flags: ReleaseLockFlags) => {
|
|
3680
3852
|
const cwd = flags.cwd ?? process.cwd();
|
|
3681
3853
|
const arbiter = flags.arbiter ?? 'origin';
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3854
|
+
// MUTUAL EXCLUSION: exactly one of {item positional, --entry}. BOTH or
|
|
3855
|
+
// NEITHER is a usage error (non-zero exit, actionable message) — the
|
|
3856
|
+
// item-form and the literal escape hatch are two ways to name ONE lock, not
|
|
3857
|
+
// combinable.
|
|
3858
|
+
if (item !== undefined && flags.entry !== undefined) {
|
|
3859
|
+
console.error(
|
|
3860
|
+
'error: give EITHER an item (task:<slug> / spec:<slug> / obs:<slug> / <slug>) OR --entry <literal>, not both.',
|
|
3861
|
+
);
|
|
3862
|
+
process.exit(1);
|
|
3863
|
+
}
|
|
3864
|
+
if (item === undefined && flags.entry === undefined) {
|
|
3865
|
+
console.error(
|
|
3866
|
+
'error: name the lock to release — an item (task:<slug> / spec:<slug> / obs:<slug> / <slug>) OR --entry <literal> for a pre-cutover entry with no current item-form.',
|
|
3867
|
+
);
|
|
3868
|
+
process.exit(1);
|
|
3869
|
+
}
|
|
3870
|
+
// The LITERAL escape-hatch path (`--entry`): take the entry name verbatim,
|
|
3871
|
+
// bypassing the namespace mapping, and reuse the SAME leased-delete core.
|
|
3872
|
+
const result =
|
|
3873
|
+
flags.entry !== undefined
|
|
3874
|
+
? await releaseLiteralLockEntry({
|
|
3875
|
+
entry: flags.entry,
|
|
3876
|
+
cwd,
|
|
3877
|
+
arbiter,
|
|
3878
|
+
env: process.env,
|
|
3879
|
+
})
|
|
3880
|
+
: await releaseItemLock({
|
|
3881
|
+
item: item as string,
|
|
3882
|
+
cwd,
|
|
3883
|
+
arbiter,
|
|
3884
|
+
env: process.env,
|
|
3885
|
+
});
|
|
3688
3886
|
if (result.outcome === 'released') {
|
|
3689
3887
|
console.log(
|
|
3690
3888
|
`Released lock '${result.entry}' (${result.ref} deleted on ${arbiter}; the item itself was untouched — it rests on main / its work/<slug> branch).`,
|
|
3691
3889
|
);
|
|
3692
3890
|
return;
|
|
3693
3891
|
}
|
|
3694
|
-
// IDEMPOTENT exit semantics:
|
|
3892
|
+
// IDEMPOTENT exit semantics: the release returns `not-held` when the
|
|
3695
3893
|
// ref is ALREADY absent. For a HUMAN re-running the verb on an
|
|
3696
3894
|
// already-cleared lock that is the CORRECT "nothing to clear" outcome —
|
|
3697
3895
|
// deleting the lock ref(s) is "all locks released" and recoverable — so map
|
|
3698
|
-
// it to a clean exit-0 with an honest message (NOT a failure).
|
|
3896
|
+
// it to a clean exit-0 with an honest message (NOT a failure). The message
|
|
3897
|
+
// names the LITERAL entry (`refs/dorfl/lock/<entry>`) so an operator can
|
|
3898
|
+
// copy-paste it straight back into a `--entry` invocation.
|
|
3699
3899
|
if (result.outcome === 'not-held') {
|
|
3700
3900
|
console.log(
|
|
3701
3901
|
`No lock to release for '${result.entry}' (${result.ref} is already absent on ${arbiter} — “all locks released”, recoverable).`,
|
|
@@ -3706,12 +3906,56 @@ export function buildProgram(): Command {
|
|
|
3706
3906
|
process.exit(1);
|
|
3707
3907
|
});
|
|
3708
3908
|
|
|
3909
|
+
// `migrate-stuck-locks` (spec `surface-stuck-as-questions-and-retire-stuck-lock-state`,
|
|
3910
|
+
// resolved decision #3, user story 5; task `migrate-existing-stuck-locks-one-shot`):
|
|
3911
|
+
// the ONE-SHOT ROLLOUT migration that converts every pre-existing `stuck` lock
|
|
3912
|
+
// ref (an item bounced under the OLD model, before `retire-stuck-lock-state`)
|
|
3913
|
+
// into the new resting shape (`needsAnswers:true` + `stuck`-kind sidecar on
|
|
3914
|
+
// `<arbiter>/main` + lock released) via the SAME surface-first-release-second
|
|
3915
|
+
// transition the new bounce uses. Idempotent (a re-run finds no legacy stuck
|
|
3916
|
+
// ref → clean no-op) and bare-arbiter-safe (a ref is a ref). Its own verb (not
|
|
3917
|
+
// folded into `gc --ledger`) because it is a one-shot rollout event with WRITE
|
|
3918
|
+
// semantics on `main`, distinct from `gc`'s report-oriented + terminal-orphan
|
|
3919
|
+
// reap surface — the two are different natures and their exit-code contracts
|
|
3920
|
+
// stay separate.
|
|
3921
|
+
program
|
|
3922
|
+
.command('migrate-stuck-locks')
|
|
3923
|
+
.helpGroup(ADVANCED_GROUP)
|
|
3924
|
+
.description(
|
|
3925
|
+
'ONE-SHOT rollout migration: convert every pre-existing `stuck` per-item lock ref (`refs/dorfl/lock/<entry>` written under the retired `stuck` lock state) into the new resting shape — a `needsAnswers:true` item body + a `stuck`-kind `work/questions/<entry>.md` sidecar on `<arbiter>/main`, with the lock ref released — via the SAME surface-first-release-second transition a fresh bounce uses. Idempotent: re-running finds no legacy `stuck` ref (the current lock module never writes `state: stuck`) and is a clean no-op. Bare-arbiter safe (a ref is a ref). Skips a pre-cutover `slice-*` / `prd-*` entry with no current item-form (clear those via `release-lock --entry <literal>`) and NEVER --force-deletes a lock. Exits non-zero only when a surface CAS lost the race or a read/plumbing fault prevented migration.',
|
|
3926
|
+
)
|
|
3927
|
+
.option(
|
|
3928
|
+
'--cwd <dir>',
|
|
3929
|
+
'the local repo/clone whose --arbiter remote the lock refs are read + mutated on (default: cwd); the working tree is never touched (the surface transition is tree-less)',
|
|
3930
|
+
)
|
|
3931
|
+
.option(
|
|
3932
|
+
'--arbiter <remote>',
|
|
3933
|
+
'the arbiter git remote the migration reads + mutates (default: origin)',
|
|
3934
|
+
)
|
|
3935
|
+
.action(async (flags: {cwd?: string; arbiter?: string}) => {
|
|
3936
|
+
const cwd = flags.cwd ?? process.cwd();
|
|
3937
|
+
const arbiter = flags.arbiter ?? 'origin';
|
|
3938
|
+
const note = (message: string) => console.error(`>> ${message}`);
|
|
3939
|
+
const report = await migrateStuckLocks({
|
|
3940
|
+
cwd,
|
|
3941
|
+
arbiter,
|
|
3942
|
+
env: process.env,
|
|
3943
|
+
note,
|
|
3944
|
+
});
|
|
3945
|
+
for (const line of formatMigrateStuckLocksReport(report)) {
|
|
3946
|
+
console.log(line);
|
|
3947
|
+
}
|
|
3948
|
+
if (migrateStuckLocksNeedsAttention(report)) {
|
|
3949
|
+
process.exit(1);
|
|
3950
|
+
}
|
|
3951
|
+
});
|
|
3952
|
+
|
|
3709
3953
|
// `drop <slug>` (spec `agentic-question-resolution-retire-disposition-vocabulary`,
|
|
3710
3954
|
// US #5/#11; task `direct-delete-question-cli-helper`): the DIRECT "throw it
|
|
3711
3955
|
// away" verb — `git rm` a source item AND its question sidecar (when present) in
|
|
3712
3956
|
// ONE revertible commit, the reason in the commit MESSAGE (git history is the
|
|
3713
3957
|
// archive). It does NOT round-trip through the decision engine or spawn an agent
|
|
3714
|
-
// (that is the SEPARATE agentic `
|
|
3958
|
+
// (that is the SEPARATE agentic `dispose` verdict in apply-persist.ts);
|
|
3715
3959
|
// this is the human/skill/CLI no-ceremony delete of decision 7. DISTINCT from
|
|
3716
3960
|
// the existing `remote rm` (the hub-MIRROR deleter) — different concern, no
|
|
3717
3961
|
// collision. A LOCAL one-commit primitive over the working tree (like apply): it
|
|
@@ -3723,7 +3967,6 @@ export function buildProgram(): Command {
|
|
|
3723
3967
|
.description(
|
|
3724
3968
|
'DIRECTLY delete a source item + its question sidecar (when present) in ONE revertible commit — the "I just want to throw this away" path that does NOT round-trip through the decision engine or any agent. Resolves the source by its namespaced identity (`task:<slug>` / `spec:<slug>` / `obs:<slug>` / a bare `<slug>` = task), `git rm`s the source AND its sidecar together, and records your --reason in the commit MESSAGE (git history is the archive). A single revertible commit, so a wrong delete is recoverable via `git revert`. DISTINCT from `remote rm` (the hub-mirror deleter). A LOCAL working-tree commit (like the apply rung); it does not touch the arbiter — push/integrate it as you normally would. If the named source is already gone it is a clean no-op (nothing to throw away).',
|
|
3725
3969
|
)
|
|
3726
|
-
.option('-c, --config <path>', 'config file path', defaultConfigPath())
|
|
3727
3970
|
.option(
|
|
3728
3971
|
'--cwd <dir>',
|
|
3729
3972
|
'the working clone the revertible delete commit is made in (default: cwd)',
|
|
@@ -3767,7 +4010,7 @@ export function buildProgram(): Command {
|
|
|
3767
4010
|
.command('intake')
|
|
3768
4011
|
.helpGroup(HEADLINE_GROUP)
|
|
3769
4012
|
.description(
|
|
3770
|
-
|
|
4013
|
+
`Front-of-funnel: turn a GitHub issue into the right work/ artifact. Reads issue #N + its comment thread via the issue seam (gh), runs a prompt→verdict decision, and dispatches it: a clear, small issue → a proposed ${workFolderPrefix('tasks-ready')}<slug>.md PR carrying an \`issue: N\` closure link (read by a future CI close-job; not \`Fixes #N\`). GATE-FREE — your explicit invocation IS the authorization (autoTask/autoBuild do NOT apply), exactly as \`do\`. A LOCAL one-shot AND the SAME command CI schedules. PER-OUTCOME integration modes (the artifact TYPE is decided at runtime): --merge/--propose set BOTH; --merge-spec/--propose-spec and --merge-task/--propose-task override per type; granular overrides the aggregate; unset ⇒ propose for both.`,
|
|
3771
4014
|
)
|
|
3772
4015
|
.argument(
|
|
3773
4016
|
'<number>',
|
|
@@ -3808,7 +4051,7 @@ export function buildProgram(): Command {
|
|
|
3808
4051
|
)
|
|
3809
4052
|
.option(
|
|
3810
4053
|
'--origin-trust <trusted|untrusted>',
|
|
3811
|
-
"the author-trust verdict to STAMP onto the emitted
|
|
4054
|
+
"the author-trust verdict to STAMP onto the emitted spec/task (origin: issue + originTrust: <value>), so an untrusted origin survives the merge boundary and later forces the task's BUILD transition to propose. CI's intake.yml derives it from the SAME author_association case as the integration flags. UNSET (a local intake) ⇒ emitted unstamped (human/trusted) — the human running intake IS the checkpoint.",
|
|
3812
4055
|
)
|
|
3813
4056
|
.option(
|
|
3814
4057
|
'--specs-land-in <where>',
|
|
@@ -3916,6 +4159,13 @@ export function buildProgram(): Command {
|
|
|
3916
4159
|
cwd,
|
|
3917
4160
|
arbiter: flags.arbiter ?? config.defaultArbiter,
|
|
3918
4161
|
integration: modes,
|
|
4162
|
+
// The resolved cross-job CAS-retry cap (config `mergeRetries`). `intake`
|
|
4163
|
+
// has no `--merge-retries` flag today (task
|
|
4164
|
+
// `thread-merge-retries-cross-task-and-ratify-default` deliberately does
|
|
4165
|
+
// NOT add one — intake is unattended-CI-first, env + per-repo cover the
|
|
4166
|
+
// need); env + per-repo + global fold into `config.mergeRetries` via the
|
|
4167
|
+
// SAME chain, and unset falls through to the engine default.
|
|
4168
|
+
mergeRetries: config.mergeRetries,
|
|
3919
4169
|
// The origin-trust stamp the CI shell passes IN (unset ⇒ unstamped).
|
|
3920
4170
|
originTrust,
|
|
3921
4171
|
noPR: config.noPR,
|
|
@@ -4108,7 +4358,7 @@ export function buildProgram(): Command {
|
|
|
4108
4358
|
.command('find <folder>')
|
|
4109
4359
|
.helpGroup(HEADLINE_GROUP)
|
|
4110
4360
|
.description(
|
|
4111
|
-
|
|
4361
|
+
`Discover work/-participating repos under <folder> (a populated ${workFolderPrefix('tasks-ready')}), then toggle-add the chosen ones via \`remote add\`. Interactive multi-select by default; --yes adds ALL discovered repos non-interactively.`,
|
|
4112
4362
|
)
|
|
4113
4363
|
.option('-c, --config <path>', 'config file path', defaultConfigPath())
|
|
4114
4364
|
.option('--yes', 'add all discovered participating repos (no prompt)')
|
|
@@ -4184,7 +4434,7 @@ export function buildProgram(): Command {
|
|
|
4184
4434
|
)
|
|
4185
4435
|
.option(
|
|
4186
4436
|
'--max-parallel <n>',
|
|
4187
|
-
'cap on CONCURRENT advance-lifecycle matrix legs (the propose/merge `max-parallel`). Each leg is a full agent session, so a large fan-out can exhaust the model provider rate limit + thrash the CAS. Default
|
|
4437
|
+
'cap on CONCURRENT advance-lifecycle matrix legs (the propose/merge `max-parallel`). Each leg is a full agent session, so a large fan-out can exhaust the model provider rate limit + thrash the CAS. Default 2.',
|
|
4188
4438
|
)
|
|
4189
4439
|
.action(async (flags: InstallCiFlags) => {
|
|
4190
4440
|
const workDir = flags.cwd ?? process.cwd();
|
|
@@ -4211,6 +4461,7 @@ export function buildProgram(): Command {
|
|
|
4211
4461
|
}
|
|
4212
4462
|
maxParallel = n;
|
|
4213
4463
|
}
|
|
4464
|
+
|
|
4214
4465
|
const ctx = new GitHubCIContext({
|
|
4215
4466
|
workDir,
|
|
4216
4467
|
repo: flags.repo,
|
|
@@ -4241,9 +4492,89 @@ export function buildProgram(): Command {
|
|
|
4241
4492
|
});
|
|
4242
4493
|
});
|
|
4243
4494
|
|
|
4495
|
+
// The `skills` group (ADR `skill-install-vendors-incur-agents-map`): install the
|
|
4496
|
+
// packaged dorfl skills (`from-idea`, `setup`, ...) into the OPERATOR's OWN
|
|
4497
|
+
// harness dirs. Distinct from any target-repo propagation (ADR
|
|
4498
|
+
// `methodology-and-skills` §6 — skills don't travel into target repos; only
|
|
4499
|
+
// `work/protocol/` does). Only `add` is exposed in this pass — `list`/`remove`
|
|
4500
|
+
// are deferred until we have a real use case (the vendored `install()` is
|
|
4501
|
+
// idempotent, so `add` alone covers upgrade + drift-repair; a future `remove`
|
|
4502
|
+
// would map onto the vendored `remove()` and is intentionally out of scope
|
|
4503
|
+
// here to keep the CLI surface minimal).
|
|
4504
|
+
const skills = program
|
|
4505
|
+
.command('skills')
|
|
4506
|
+
.helpGroup(HEADLINE_GROUP)
|
|
4507
|
+
.description(
|
|
4508
|
+
"Install the packaged dorfl skills (`from-idea`, `setup`, ...) into the operator's own agent harness(es). Installs into your OWN harness dirs (~/.agents/skills/ + per-harness symlinks) — this is operator tooling, NOT target-repo propagation (skills don't travel into `work/` repos; only `work/protocol/` does).",
|
|
4509
|
+
);
|
|
4510
|
+
|
|
4511
|
+
skills
|
|
4512
|
+
.command('add')
|
|
4513
|
+
.helpGroup(HEADLINE_GROUP)
|
|
4514
|
+
.description(
|
|
4515
|
+
'Copy the packaged dorfl skills into the canonical `~/.agents/skills/` (global; `--local` scopes to <cwd>/.agents/skills/) and symlink each detected non-universal harness (Claude Code, Cursor, Windsurf, ...) to it. Idempotent; re-run to pick up new/updated skills.',
|
|
4516
|
+
)
|
|
4517
|
+
.option(
|
|
4518
|
+
'--local',
|
|
4519
|
+
'install project-locally into <cwd>/.agents/skills/ (and each detected harness\u2019s PROJECT dir) instead of globally into ~/.agents/skills/. Default: global.',
|
|
4520
|
+
)
|
|
4521
|
+
.action((flags: SkillsAddFlags) => {
|
|
4522
|
+
const result = installSkills({global: flags.local !== true});
|
|
4523
|
+
console.log(formatSkillsAddReport(result, flags.local === true));
|
|
4524
|
+
});
|
|
4525
|
+
|
|
4244
4526
|
return program;
|
|
4245
4527
|
}
|
|
4246
4528
|
|
|
4529
|
+
interface SkillsAddFlags {
|
|
4530
|
+
/**
|
|
4531
|
+
* `--local`: project-local install (canonical base `<cwd>/.agents/skills/`)
|
|
4532
|
+
* instead of the global `~/.agents/skills/` default. Named to mirror the
|
|
4533
|
+
* `remote add --local` sibling ('local' = "scope to here") and the vendored
|
|
4534
|
+
* `install()`'s `global: false` semantic. Chosen over `--project` /
|
|
4535
|
+
* `--here` / `--global` inversion for consistency with the existing verb.
|
|
4536
|
+
*/
|
|
4537
|
+
local?: boolean;
|
|
4538
|
+
}
|
|
4539
|
+
|
|
4540
|
+
/**
|
|
4541
|
+
* Human-readable report for `dorfl skills add`. Prints the SOURCE the skills
|
|
4542
|
+
* were read from, each canonical install path (one per skill), and each
|
|
4543
|
+
* non-universal harness placement (symlink | copy) with its absolute path.
|
|
4544
|
+
* Universal harnesses read from the canonical dir directly and need no line.
|
|
4545
|
+
* Output goes to stdout; the shape is stable enough for a human to audit "what
|
|
4546
|
+
* landed where" but is NOT a machine contract (no JSON mode in this pass).
|
|
4547
|
+
*/
|
|
4548
|
+
export function formatSkillsAddReport(
|
|
4549
|
+
result: InstallSkillsResult,
|
|
4550
|
+
local: boolean,
|
|
4551
|
+
): string {
|
|
4552
|
+
const lines: string[] = [];
|
|
4553
|
+
const scope = local ? 'project-local' : 'global';
|
|
4554
|
+
lines.push(`Installed dorfl skills (${scope}) from ${result.sourceDir}:`);
|
|
4555
|
+
if (result.paths.length === 0) {
|
|
4556
|
+
lines.push(' (no skills found in source)');
|
|
4557
|
+
} else {
|
|
4558
|
+
for (const p of result.paths.slice().sort()) {
|
|
4559
|
+
lines.push(` ${p}`);
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
if (result.agents.length === 0) {
|
|
4563
|
+
lines.push(
|
|
4564
|
+
'Harnesses: no non-universal harness detected (universal harnesses read from the canonical dir directly).',
|
|
4565
|
+
);
|
|
4566
|
+
} else {
|
|
4567
|
+
lines.push('Harnesses:');
|
|
4568
|
+
for (const a of result.agents.slice().sort((x, y) => {
|
|
4569
|
+
const byAgent = x.agent.localeCompare(y.agent);
|
|
4570
|
+
return byAgent !== 0 ? byAgent : x.path.localeCompare(y.path);
|
|
4571
|
+
})) {
|
|
4572
|
+
lines.push(` ${a.agent}: ${a.mode} -> ${a.path}`);
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
return lines.join('\n');
|
|
4576
|
+
}
|
|
4577
|
+
|
|
4247
4578
|
/**
|
|
4248
4579
|
* A readline-backed {@link WizardPrompts} for the interactive `install-ci`
|
|
4249
4580
|
* wizard. Prompts go to stderr (stdout is reserved for any machine output); a
|