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/orphan-sidecar.ts
CHANGED
|
@@ -3,8 +3,44 @@ import {join} from 'node:path';
|
|
|
3
3
|
import {run} from './git.js';
|
|
4
4
|
import {resolveSidecarIdentity} from './sidecar.js';
|
|
5
5
|
import {resolveItemPathByIdentity} from './item-path.js';
|
|
6
|
+
import {terminalMainPaths} from './item-lock.js';
|
|
6
7
|
import {workFolderPath, workFolderRel} from './work-layout.js';
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the source item's CURRENT on-disk path INCLUDING its TERMINAL resting
|
|
11
|
+
* records — the orphan-sweep existence predicate.
|
|
12
|
+
*
|
|
13
|
+
* This deliberately WIDENS {@link resolveItemPathByIdentity} (which scans the
|
|
14
|
+
* ACTIVE lifecycle folders + staging, and EXCLUDES terminals on purpose: for the
|
|
15
|
+
* apply rung a terminal-reached item is `vanished`). The orphan sweep asks a
|
|
16
|
+
* DIFFERENT question — "does a source item for this sidecar exist AT ALL?" — and a
|
|
17
|
+
* task/spec that reached its terminal (`tasks/cancelled/`, `specs/dropped/`) is a
|
|
18
|
+
* durable RESTING record that still EXISTS, so its sidecar is NOT an orphan.
|
|
19
|
+
* Treating "reached a terminal" as "gone" would let a routine `gc` delete a live
|
|
20
|
+
* sidecar (and any human answer it carries) — the 2026-07-12 false-positive.
|
|
21
|
+
*
|
|
22
|
+
* Reuses the shared {@link terminalMainPaths} (`item-lock.ts`, the SAME per-type
|
|
23
|
+
* terminal set the lock/branch reapers key off) so the "where do terminals live?"
|
|
24
|
+
* knowledge stays single-sourced. Returns the ACTIVE path when present, else the
|
|
25
|
+
* first existing TERMINAL path, else `undefined` (a genuine orphan).
|
|
26
|
+
*/
|
|
27
|
+
function resolveSourceForOrphanSweep(
|
|
28
|
+
cwd: string,
|
|
29
|
+
item: string,
|
|
30
|
+
): string | undefined {
|
|
31
|
+
const active = resolveItemPathByIdentity(cwd, item);
|
|
32
|
+
if (active !== undefined) {
|
|
33
|
+
return active;
|
|
34
|
+
}
|
|
35
|
+
const {type, slug} = resolveSidecarIdentity(item);
|
|
36
|
+
for (const rel of terminalMainPaths(type, slug)) {
|
|
37
|
+
if (existsSync(join(cwd, rel))) {
|
|
38
|
+
return rel;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
8
44
|
/**
|
|
9
45
|
* The **orphan-sidecar reaper** — the WORKING-TREE counterpart of the job-worktree
|
|
10
46
|
* and remote-branch reapers (`gc.ts` / `reap-branches.ts`), folded into `dorfl gc`
|
|
@@ -26,10 +62,14 @@ import {workFolderPath, workFolderRel} from './work-layout.js';
|
|
|
26
62
|
*
|
|
27
63
|
* The existence check REUSES the keystone's extracted `resolveItemPathByIdentity`
|
|
28
64
|
* (`item-path.ts`, the neutral re-exported seam) — the same working-tree,
|
|
29
|
-
* by-identity "does the source item exist?" resolver the apply rung uses —
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
65
|
+
* by-identity "does the source item exist?" resolver the apply rung uses — WIDENED
|
|
66
|
+
* to also count a source that has reached a TERMINAL folder (`tasks/cancelled/`,
|
|
67
|
+
* `specs/dropped/`, via the shared `terminalMainPaths`) as EXISTING: a terminal is
|
|
68
|
+
* a durable resting record, not "gone", so its sidecar is NOT an orphan (see
|
|
69
|
+
* `resolveSourceForOrphanSweep`). A sidecar whose source EXISTS anywhere is left
|
|
70
|
+
* untouched; a sidecar whose source is ABSENT from every active AND terminal
|
|
71
|
+
* folder is `git rm`'d (deletion, not a `git mv`: the sidecar leaves by deletion,
|
|
72
|
+
* recoverable from git history).
|
|
33
73
|
*/
|
|
34
74
|
|
|
35
75
|
/** A sidecar the orphan sweep reaped (its source item was absent). */
|
|
@@ -111,9 +151,10 @@ export function sweepOrphanSidecars(
|
|
|
111
151
|
const {item} = resolveSidecarIdentity(stem.replace('-', ':'));
|
|
112
152
|
const path = `${workFolderRel('questions')}/${entry}`;
|
|
113
153
|
|
|
114
|
-
const sourcePath =
|
|
154
|
+
const sourcePath = resolveSourceForOrphanSweep(input.cwd, item);
|
|
115
155
|
if (sourcePath !== undefined) {
|
|
116
|
-
// The source item still exists
|
|
156
|
+
// The source item still exists (active lifecycle OR a terminal resting
|
|
157
|
+
// record) ⇒ a LIVE sidecar ⇒ leave it untouched.
|
|
117
158
|
retained.push({path, item, sourcePath});
|
|
118
159
|
note(`Retained ${path}: source ${item} still exists (${sourcePath}).`);
|
|
119
160
|
continue;
|
package/src/pi-harness.ts
CHANGED
|
@@ -65,6 +65,15 @@ import type {HarnessAdapter} from './config.js';
|
|
|
65
65
|
/** The default pi CLI binary name (resolved on `PATH`). */
|
|
66
66
|
export const DEFAULT_PI_BIN = 'pi';
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* The grace period between a deadline SIGTERM and the follow-up SIGKILL in
|
|
70
|
+
* {@link PiHarness.launchAsync} (spec `graceful-pre-timeout-wip-checkpoint`).
|
|
71
|
+
* Ten seconds gives pi enough time to flush its session `.jsonl` and exit
|
|
72
|
+
* cleanly; a wedged/frozen child that ignores SIGTERM still gets reaped in a
|
|
73
|
+
* bounded time so the promise settles and the caller's checkpoint routing runs.
|
|
74
|
+
*/
|
|
75
|
+
export const DEADLINE_SIGKILL_GRACE_MS = 10_000;
|
|
76
|
+
|
|
68
77
|
export interface PiHarnessOptions {
|
|
69
78
|
/**
|
|
70
79
|
* The pi CLI binary (default `pi` on `PATH`). Tests inject a stub script here
|
|
@@ -239,11 +248,60 @@ export class PiHarness implements Harness {
|
|
|
239
248
|
child.stdout?.on('data', () => {});
|
|
240
249
|
// Guard so `exit` and a late `error` never double-settle the promise.
|
|
241
250
|
let settled = false;
|
|
251
|
+
let timedOut = false;
|
|
252
|
+
// DEADLINE RACE (spec `graceful-pre-timeout-wip-checkpoint`): when the
|
|
253
|
+
// caller threads a `deadlineMs` (absolute wall-clock epoch-ms), arm a
|
|
254
|
+
// SOFT SIGTERM at that time, then a HARD SIGKILL after a ~10s grace. The
|
|
255
|
+
// child's own `exit` handler below still resolves the promise (with
|
|
256
|
+
// `timedOut: true`), so we preserve the settle-once + FD-release
|
|
257
|
+
// discipline and never double-settle. A run that finishes BEFORE the
|
|
258
|
+
// deadline clears both timers (see the `exit` handler) — byte-for-byte
|
|
259
|
+
// unchanged from the pre-task behaviour.
|
|
260
|
+
let softTimer: NodeJS.Timeout | undefined;
|
|
261
|
+
let hardTimer: NodeJS.Timeout | undefined;
|
|
262
|
+
const clearDeadlineTimers = (): void => {
|
|
263
|
+
if (softTimer !== undefined) {
|
|
264
|
+
clearTimeout(softTimer);
|
|
265
|
+
softTimer = undefined;
|
|
266
|
+
}
|
|
267
|
+
if (hardTimer !== undefined) {
|
|
268
|
+
clearTimeout(hardTimer);
|
|
269
|
+
hardTimer = undefined;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
if (input.deadlineMs !== undefined) {
|
|
273
|
+
const softMs = Math.max(0, input.deadlineMs - Date.now());
|
|
274
|
+
softTimer = setTimeout(() => {
|
|
275
|
+
if (settled) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
timedOut = true;
|
|
279
|
+
try {
|
|
280
|
+
child.kill('SIGTERM');
|
|
281
|
+
} catch {
|
|
282
|
+
// Best-effort: a already-exited child throws ESRCH; the `exit`
|
|
283
|
+
// handler will still settle the promise.
|
|
284
|
+
}
|
|
285
|
+
hardTimer = setTimeout(() => {
|
|
286
|
+
if (settled) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
child.kill('SIGKILL');
|
|
291
|
+
} catch {
|
|
292
|
+
// Best-effort: see above.
|
|
293
|
+
}
|
|
294
|
+
}, DEADLINE_SIGKILL_GRACE_MS);
|
|
295
|
+
hardTimer.unref?.();
|
|
296
|
+
}, softMs);
|
|
297
|
+
softTimer.unref?.();
|
|
298
|
+
}
|
|
242
299
|
child.on('error', (err) => {
|
|
243
300
|
if (settled) {
|
|
244
301
|
return;
|
|
245
302
|
}
|
|
246
303
|
settled = true;
|
|
304
|
+
clearDeadlineTimers();
|
|
247
305
|
reject(new Error(`failed to spawn pi (${this.piBin}): ${err.message}`));
|
|
248
306
|
});
|
|
249
307
|
// Resolve on `exit` (pi itself terminated), NOT `close`: `close` waits for
|
|
@@ -255,6 +313,7 @@ export class PiHarness implements Harness {
|
|
|
255
313
|
return;
|
|
256
314
|
}
|
|
257
315
|
settled = true;
|
|
316
|
+
clearDeadlineTimers();
|
|
258
317
|
// Release our end of the stdio pipes so a leaked grandchild's inherited
|
|
259
318
|
// FDs stop keeping our streams referenced; `unref` the child handle too.
|
|
260
319
|
child.stdout?.destroy();
|
|
@@ -263,9 +322,11 @@ export class PiHarness implements Harness {
|
|
|
263
322
|
child.unref?.();
|
|
264
323
|
const status = code ?? -1;
|
|
265
324
|
resolve({
|
|
266
|
-
ok: status === 0,
|
|
325
|
+
ok: status === 0 && !timedOut,
|
|
267
326
|
record,
|
|
268
|
-
detail:
|
|
327
|
+
detail:
|
|
328
|
+
status === 0 && !timedOut ? undefined : stderr.trim() || undefined,
|
|
329
|
+
timedOut: timedOut ? true : undefined,
|
|
269
330
|
// Read the agent's ANSWER from the `.jsonl` at `exit` — the same
|
|
270
331
|
// last-assistant-text read `launch` does at return (task
|
|
271
332
|
// `harness-agent-output`); the process has exited so the log is final.
|
|
@@ -354,6 +415,14 @@ export class PiHarness implements Harness {
|
|
|
354
415
|
* pointer with an `existsSync` check. This is the pi-native "is there an audit
|
|
355
416
|
* trail to look at?" signal — distinct from PID liveness, and STILL not mtime
|
|
356
417
|
* (we check existence, never modification time).
|
|
418
|
+
*
|
|
419
|
+
* Studied (task `pi-harness-polish`, finding
|
|
420
|
+
* `work/notes/findings/pi-harness-channels.md`): the recorded `--session <path>`
|
|
421
|
+
* file remains the right audit pointer even against pi's other output surfaces
|
|
422
|
+
* (`--mode json` stdout stream, `--mode rpc`, in-process SDK) — those are
|
|
423
|
+
* transient stdio channels that do not survive the process, whereas the session
|
|
424
|
+
* file is the versioned, dashboard-visible activity trail pi itself treats as
|
|
425
|
+
* the audit artefact. PID stays the liveness anchor alongside it.
|
|
357
426
|
*/
|
|
358
427
|
export function piSessionExists(record: HarnessRecord): boolean {
|
|
359
428
|
return record.session !== undefined && existsSync(record.session);
|
|
@@ -368,6 +437,17 @@ export function piSessionExists(record: HarnessRecord): boolean {
|
|
|
368
437
|
* It REUSES `watch-session.ts`'s {@link lastAssistantText} (one `.jsonl` parser,
|
|
369
438
|
* not two). An absent file (pi never wrote it) yields `undefined`, as does a log
|
|
370
439
|
* with no assistant text — a read error is never thrown back into the launch.
|
|
440
|
+
*
|
|
441
|
+
* Studied (task `pi-harness-polish`, finding
|
|
442
|
+
* `work/notes/findings/pi-harness-channels.md`, pinned against pi 0.73.1 +
|
|
443
|
+
* session format v3): the session `.jsonl` is the right channel for output
|
|
444
|
+
* because it is the only one that is BOTH post-mortem readable AND explicitly
|
|
445
|
+
* versioned + migrated (`docs/session-format.md`). The `--mode json` STDOUT
|
|
446
|
+
* event stream is not versioned and is transient; `--mode rpc` and the
|
|
447
|
+
* in-process SDK cost more without solving anything the file does not. The
|
|
448
|
+
* cross-harness `LaunchResult.output` seam (Option C) is a bare `string | undefined`,
|
|
449
|
+
* so a future stream/HTTP-shaped harness (opencode-style) still fits: the file
|
|
450
|
+
* shape lives BEHIND this reader and is not observable through the seam.
|
|
371
451
|
*/
|
|
372
452
|
function readLastAssistantText(sessionFile: string): string | undefined {
|
|
373
453
|
let jsonl: string;
|
package/src/placement.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*
|
|
20
20
|
* LIFECYCLE-GENERIC. The folder names + the configured-default value are
|
|
21
21
|
* PARAMETERS (`slots`, `configuredDefault`), so the same resolver serves the
|
|
22
|
-
* TASK lifecycle (`tasksLandIn`: `
|
|
22
|
+
* TASK lifecycle (`tasksLandIn`: `backlog`/`ready` — the POOL value was
|
|
23
23
|
* renamed `'backlog'` → `'todo'` → `'ready'`, ADR
|
|
24
24
|
* `rename-task-pool-folder-todo-to-ready`) AND the SPEC-
|
|
25
25
|
* placement lifecycle (`specsLandIn`: `pre-proposed`/`ready`) without forking. A future
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* Which side of the staging/pool split the runner chose. The CALLER maps this
|
|
32
32
|
* onto the lifecycle's concrete folder name via {@link PlacementSlots}.
|
|
33
33
|
*
|
|
34
|
-
* - `'staging'` — land in the staging area (tasks: `
|
|
34
|
+
* - `'staging'` — land in the staging area (tasks: `tasks/backlog/`; prds:
|
|
35
35
|
* `specs/proposed/`). Not in the agent pool; a human/runner promotion is needed to make
|
|
36
36
|
* the item eligible. Review-without-PR review surface.
|
|
37
37
|
* - `'pool'` — land directly in the agent-eligible pool (tasks: `tasks/ready/`;
|
|
@@ -42,7 +42,7 @@ export type PlacementSide = 'staging' | 'pool';
|
|
|
42
42
|
/**
|
|
43
43
|
* The two folder names a lifecycle uses for its staging/pool split. Supplied by
|
|
44
44
|
* the caller so this resolver stays lifecycle-generic (the TASK caller passes
|
|
45
|
-
* `{staging: '
|
|
45
|
+
* `{staging: 'tasks/backlog', pool: 'tasks/ready'}`; the SPEC-placement caller passes
|
|
46
46
|
* `{staging: 'specs/proposed', pool: 'specs/ready'}`).
|
|
47
47
|
*/
|
|
48
48
|
export interface PlacementSlots {
|
package/src/prd-to-spec.ts
CHANGED
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
mkdirSync,
|
|
9
9
|
rmdirSync,
|
|
10
10
|
} from 'node:fs';
|
|
11
|
-
import {join, relative, dirname} from 'node:path';
|
|
11
|
+
import {join, relative, dirname, basename} from 'node:path';
|
|
12
12
|
import {run, git, gitMv} from './git.js';
|
|
13
13
|
import {resolveProtocolDoc} from './prompt.js';
|
|
14
14
|
import {WORK_ROOT} from './work-layout.js';
|
|
15
|
+
import {repoConfigPath} from './repo-config.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* The **`dorfl prd-to-spec` migration ENGINE** (spec
|
|
@@ -675,7 +676,9 @@ export function migrateConfig(
|
|
|
675
676
|
migration: VocabularyMigration = MIGRATION,
|
|
676
677
|
options: {dryRun?: boolean} = {},
|
|
677
678
|
): ConfigRewrite[] {
|
|
678
|
-
|
|
679
|
+
// Resolve the repo's actual config file (prefers `dorfl.json`, falls back to
|
|
680
|
+
// the legacy `.dorfl.json`), so the key rewrite finds it under either name.
|
|
681
|
+
const configAbs = repoConfigPath(repoPath);
|
|
679
682
|
if (!existsSync(configAbs)) {
|
|
680
683
|
return [];
|
|
681
684
|
}
|
|
@@ -922,10 +925,15 @@ export function scanForLeaks(
|
|
|
922
925
|
scanText(rel, readFileSync(join(repoPath, rel), 'utf8'), []);
|
|
923
926
|
}
|
|
924
927
|
}
|
|
925
|
-
// The config file (the key layer).
|
|
926
|
-
|
|
928
|
+
// The config file (the key layer) — under either the preferred `dorfl.json`
|
|
929
|
+
// or the legacy `.dorfl.json`.
|
|
930
|
+
const configAbs = repoConfigPath(repoPath);
|
|
927
931
|
if (existsSync(configAbs)) {
|
|
928
|
-
scanText(
|
|
932
|
+
scanText(
|
|
933
|
+
basename(configAbs),
|
|
934
|
+
readFileSync(configAbs, 'utf8'),
|
|
935
|
+
configKeyPatterns,
|
|
936
|
+
);
|
|
929
937
|
}
|
|
930
938
|
// The FOLDER TREE itself: a surviving `work/prds/` directory (empty ⇒ no
|
|
931
939
|
// file, but the folder still leaks). Report it structurally.
|
package/src/reap-branches.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {run} from './git.js';
|
|
2
|
-
import {
|
|
2
|
+
import {isProvablyMergedForReap} from './gc.js';
|
|
3
3
|
import {parseWorkBranchRef} from './slug-namespace.js';
|
|
4
|
+
import type {SidecarType} from './sidecar.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* The **remote merged-work-branch reaper** — the cross-machine COUNTERPART of the
|
|
@@ -9,13 +10,15 @@ import {parseWorkBranchRef} from './slug-namespace.js';
|
|
|
9
10
|
* the arbiter after their propose-mode PR has MERGED.
|
|
10
11
|
*
|
|
11
12
|
* The safety lynchpin is the SAME provably-merged predicate the worktree reaper
|
|
12
|
-
* uses (`gc.ts` `
|
|
13
|
-
* `work/<slug>` branch is reapable **iff**
|
|
14
|
-
* `git merge-base --is-ancestor <tip> <arbiter>/main`
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
13
|
+
* uses (`gc.ts` `isProvablyMergedForReap`, REUSED here, never a second one): a
|
|
14
|
+
* remote `work/<slug>` branch is reapable **iff** it is provably on
|
|
15
|
+
* `<arbiter>/main` — EITHER `git merge-base --is-ancestor <tip> <arbiter>/main`
|
|
16
|
+
* (fast path) OR the squash-aware fallback (durable done/dropped record on main
|
|
17
|
+
* AND branch carries nothing main lacks). A merely-pushed-but-unmerged branch
|
|
18
|
+
* (an in-flight, kept-for-continue recovery point) has no terminal record on
|
|
19
|
+
* main AND is not an ancestor, so the predicate AUTOMATICALLY excludes it — the
|
|
20
|
+
* never-delete-the-in-flight-branch invariant (ADR §4 deletion-safety) stays
|
|
21
|
+
* absolute. Deletion of a fully-merged ref needs NO force, so this NEVER
|
|
19
22
|
* `--force`s.
|
|
20
23
|
*
|
|
21
24
|
* It is provider-AGNOSTIC plain git (`git ls-remote` to enumerate, `git push
|
|
@@ -101,8 +104,9 @@ export interface SweepRemoteBranchesResult {
|
|
|
101
104
|
* 2. Fetch `<arbiter>/main` into a local tracking ref so the ancestor check
|
|
102
105
|
* resolves the descendant LOCALLY; fetch each candidate tip object too (so
|
|
103
106
|
* `merge-base` can read it) — best-effort, into a scratch ref.
|
|
104
|
-
* 3. For each branch
|
|
105
|
-
*
|
|
107
|
+
* 3. For each branch that is provably merged for reap (fast-path ancestor
|
|
108
|
+
* OR squash-aware fallback — {@link isProvablyMergedForReap}) ⇒ delete
|
|
109
|
+
* via `git push <arbiter> --delete <branch>`; else retain with a reason.
|
|
106
110
|
*/
|
|
107
111
|
export function sweepRemoteMergedBranches(
|
|
108
112
|
input: SweepRemoteBranchesInput,
|
|
@@ -126,12 +130,32 @@ export function sweepRemoteMergedBranches(
|
|
|
126
130
|
retain(retained, note, branch, tip, 'main-unresolved');
|
|
127
131
|
continue;
|
|
128
132
|
}
|
|
129
|
-
// Make the candidate tip OBJECT readable locally so `merge-base`
|
|
130
|
-
// (ls-remote gave us the sha, but the
|
|
133
|
+
// Make the candidate tip OBJECT readable locally so `merge-base` /
|
|
134
|
+
// `diff` / `cherry` can read it (ls-remote gave us the sha, but the
|
|
135
|
+
// object may not be in the local repo).
|
|
131
136
|
fetchScratch(input.cwd, input.arbiter, branch, env);
|
|
132
137
|
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
const parsed = parseWorkBranchRef(branch);
|
|
139
|
+
if (parsed === undefined) {
|
|
140
|
+
// Guarded above by `listRemoteWorkBranches` (which only enumerates
|
|
141
|
+
// parseable heads), but keep a defensive belt: without a slug the
|
|
142
|
+
// squash-aware fallback has no anchor ⇒ retain.
|
|
143
|
+
retain(retained, note, branch, tip, 'not-merged');
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (
|
|
148
|
+
!isProvablyMergedForReap({
|
|
149
|
+
cwd: input.cwd,
|
|
150
|
+
tip,
|
|
151
|
+
arbiterMain: mainRef,
|
|
152
|
+
namespace: parsed.namespace as SidecarType,
|
|
153
|
+
slug: parsed.slug,
|
|
154
|
+
env,
|
|
155
|
+
})
|
|
156
|
+
) {
|
|
157
|
+
// NOT provably merged (neither ancestor nor squash-aware equivalent)
|
|
158
|
+
// ⇒ still in-flight ⇒ NEVER delete (the invariant).
|
|
135
159
|
retain(retained, note, branch, tip, 'not-merged');
|
|
136
160
|
continue;
|
|
137
161
|
}
|
package/src/recover-isolated.ts
CHANGED
|
@@ -57,6 +57,18 @@ export interface RecoverIsolatedOptions {
|
|
|
57
57
|
integration?: IntegrationMode;
|
|
58
58
|
/** Suppress the PR on the propose path (push the branch, open no review request). */
|
|
59
59
|
noPR?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* **The cross-job merge-serialiser CAS-retry cap** (config `mergeRetries`, spec
|
|
62
|
+
* `land-time-reverify-and-parallel-merge-ceiling` Story 5 / Applied Answer q1
|
|
63
|
+
* (a)). Threaded VERBATIM into {@link performIntegration} so a wide-matrix CI's
|
|
64
|
+
* raised cap actually reaches the cross-job land queue on the isolated-recovery
|
|
65
|
+
* path too (task `thread-merge-retries-cross-task-and-ratify-default`). Resolved
|
|
66
|
+
* ONCE at the CLI (the `complete --isolated` command) through the SAME
|
|
67
|
+
* gate-family precedence chain the plain `complete` path uses (flag > env >
|
|
68
|
+
* per-repo > global > default). Unset ⇒ the engine's `DEFAULT_MERGE_RETRIES =
|
|
69
|
+
* 1000` (byte-for-byte unchanged).
|
|
70
|
+
*/
|
|
71
|
+
mergeRetries?: number;
|
|
60
72
|
/** Optional fully-formed provider used verbatim (test/embedding seam). */
|
|
61
73
|
providerInstance?: ReviewProvider;
|
|
62
74
|
/** Optional injectable PR opener (legacy bridge); used in `propose` mode. */
|
|
@@ -177,6 +189,11 @@ export async function performRecoverIsolated(
|
|
|
177
189
|
recovering: false,
|
|
178
190
|
committedRecovery: true,
|
|
179
191
|
mode: options.integration ?? 'propose',
|
|
192
|
+
// The cross-job merge-serialiser CAS-retry cap (config `mergeRetries`) — the
|
|
193
|
+
// git-alone floor of the cross-job land queue, resolved through the gate-
|
|
194
|
+
// family precedence chain in the CLI and threaded here so the isolated-
|
|
195
|
+
// recovery land tail respects the per-repo cap.
|
|
196
|
+
mergeRetries: options.mergeRetries,
|
|
180
197
|
noPR: options.noPR,
|
|
181
198
|
providerInstance: options.providerInstance,
|
|
182
199
|
openPr: options.openPr,
|
package/src/repo-config.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {readFileSync, existsSync} from 'node:fs';
|
|
|
2
2
|
import {join} from 'node:path';
|
|
3
3
|
import {
|
|
4
4
|
mergeConfig,
|
|
5
|
+
validateDeadlineConfig,
|
|
5
6
|
warnDeprecatedConfigKeys,
|
|
6
7
|
type Config,
|
|
7
8
|
type PartialConfig,
|
|
@@ -67,17 +68,17 @@ export const REPO_ALLOWED_KEYS = [
|
|
|
67
68
|
// still BUILDS each task as a reviewable PR is agreed by all collaborators +
|
|
68
69
|
// travels with the repo. Resolved per-repo through the SAME chain as
|
|
69
70
|
// `integration`, then falls back to `integration` when unset. DISTINCT from
|
|
70
|
-
// intake's per-EMITTED-TYPE `{task,
|
|
71
|
+
// intake's per-EMITTED-TYPE `{task, spec}` resolver (front door, author-trust):
|
|
71
72
|
// this is a per-lifecycle-transition knob, inside the boundary, config-resolved.
|
|
72
73
|
'taskingIntegration',
|
|
73
74
|
// `tasksLandIn` (the per-repo TASK-PLACEMENT default — staging vs pool, spec
|
|
74
75
|
// `staging-pool-position-gate-and-trust-model` US #5) is a genuine repo property
|
|
75
76
|
// exactly like `taskingIntegration`/`integration`: whether THIS repo's tasker
|
|
76
|
-
// output lands STAGED (`
|
|
77
|
+
// output lands STAGED (`tasks/backlog/`, review-without-PR human-promote path) or
|
|
77
78
|
// straight in the agent-eligible POOL (`ready/`, trusted fast-path) is agreed
|
|
78
79
|
// by all collaborators + travels with the repo. Resolved per-repo through the
|
|
79
80
|
// SAME chain as `taskingIntegration` (flag > env > per-repo > global > built-in
|
|
80
|
-
// `
|
|
81
|
+
// `backlog`). DISTINCT from intake's per-emitted-type stamps (front door):
|
|
81
82
|
// this is a per-lifecycle PLACEMENT knob, inside the trust boundary,
|
|
82
83
|
// config-resolved. Fed into the shared placement resolver as the
|
|
83
84
|
// configured-default rung (`src/placement.ts`).
|
|
@@ -91,7 +92,7 @@ export const REPO_ALLOWED_KEYS = [
|
|
|
91
92
|
// through the SAME chain as `tasksLandIn` (flag `--specs-land-in` > env
|
|
92
93
|
// `DORFL_SPECS_LAND_IN` > per-repo > global > built-in). Fed into the shared
|
|
93
94
|
// placement resolver as the configured-default rung for the spec lifecycle
|
|
94
|
-
// (`src/placement.ts`). The sole spec-placement key after the
|
|
95
|
+
// (`src/placement.ts`). The sole spec-placement key after the ''prd'' → `spec` HARD
|
|
95
96
|
// CUTOVER (the legacy `prdsLandIn` key is GONE — clean break, no accepted alias).
|
|
96
97
|
'specsLandIn',
|
|
97
98
|
// `noPR` (the PR-INTENT axis — push the branch but deliberately skip the PR) is
|
|
@@ -207,6 +208,16 @@ export const REPO_ALLOWED_KEYS = [
|
|
|
207
208
|
// default or shape. The re-surface vs. land branch is
|
|
208
209
|
// `apply-rung-merge-disposition`'s consumer, never here.
|
|
209
210
|
'strictMergeApproval',
|
|
211
|
+
// `agentDeadlineMinutes` / `checkpointHeadroomMinutes` / `maxAutoCheckpoints`
|
|
212
|
+
// (spec `graceful-pre-timeout-wip-checkpoint`) are genuine repo properties:
|
|
213
|
+
// how long an agent session runs before checkpointing, and how much head-room
|
|
214
|
+
// the GitHub backstop leaves above it, are agreed by all collaborators + travel
|
|
215
|
+
// with the repo. The dynamic GitHub `timeout-minutes` cap is rendered by the
|
|
216
|
+
// `enumerate` job at run time FROM this committed config, so an edit reflects
|
|
217
|
+
// EVERYWHERE (internal deadline AND GitHub cap) with no install-ci re-run.
|
|
218
|
+
'agentDeadlineMinutes',
|
|
219
|
+
'checkpointHeadroomMinutes',
|
|
220
|
+
'maxAutoCheckpoints',
|
|
210
221
|
// `promptGuidance` (the NUDGE namespace — prompt-text knobs that strengthen
|
|
211
222
|
// the worker's in-band wrapper line, e.g. `testFirst`) is a genuine repo
|
|
212
223
|
// property: "is this repo nudged toward test-first?" travels with the repo
|
|
@@ -257,9 +268,42 @@ export type RepoRejectedKey = (typeof REPO_REJECTED_KEYS)[number];
|
|
|
257
268
|
|
|
258
269
|
const ALLOWED_SET = new Set<string>(REPO_ALLOWED_KEYS);
|
|
259
270
|
|
|
260
|
-
/**
|
|
271
|
+
/**
|
|
272
|
+
* The LEGACY per-repo config filename (`.dorfl.json`, the original dotfile form)
|
|
273
|
+
* still honoured on READ for backward compatibility. Reads prefer
|
|
274
|
+
* {@link REPO_CONFIG_FILENAME} (`dorfl.json`) and fall back to this; writes /
|
|
275
|
+
* `setup` use the preferred name. Derived from the single brand identity so a
|
|
276
|
+
* rename flips it in lockstep (see `brand.ts`).
|
|
277
|
+
*/
|
|
278
|
+
export const REPO_CONFIG_FILENAME_LEGACY = brand.repoConfigFilenameLegacy;
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Resolve which per-repo config file a repo USES: the PREFERRED
|
|
282
|
+
* `dorfl.json` if present, else the LEGACY `.dorfl.json` if present, else the
|
|
283
|
+
* preferred path (the location a missing file WOULD live / be created at). So a
|
|
284
|
+
* repo that already committed the legacy dotfile keeps working, while a fresh
|
|
285
|
+
* repo (and every write) uses the plain name. If BOTH exist, the preferred
|
|
286
|
+
* `dorfl.json` wins (a deliberate, documented precedence, not a merge).
|
|
287
|
+
*/
|
|
288
|
+
export function resolveRepoConfigPath(repoPath: string): string {
|
|
289
|
+
const preferred = join(repoPath, REPO_CONFIG_FILENAME);
|
|
290
|
+
if (existsSync(preferred)) {
|
|
291
|
+
return preferred;
|
|
292
|
+
}
|
|
293
|
+
const legacy = join(repoPath, REPO_CONFIG_FILENAME_LEGACY);
|
|
294
|
+
if (existsSync(legacy)) {
|
|
295
|
+
return legacy;
|
|
296
|
+
}
|
|
297
|
+
return preferred;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* The path to a repo's config file. Prefers the plain `dorfl.json`, falls back
|
|
302
|
+
* to the legacy `.dorfl.json` when only that exists (see
|
|
303
|
+
* {@link resolveRepoConfigPath}).
|
|
304
|
+
*/
|
|
261
305
|
export function repoConfigPath(repoPath: string): string {
|
|
262
|
-
return
|
|
306
|
+
return resolveRepoConfigPath(repoPath);
|
|
263
307
|
}
|
|
264
308
|
|
|
265
309
|
/** The result of reading (and filtering) a repo's `.dorfl.json`. */
|
|
@@ -542,6 +586,11 @@ export function resolveRepoConfigFromLoaded(
|
|
|
542
586
|
...envOverrides(env),
|
|
543
587
|
...(flags ?? {}),
|
|
544
588
|
});
|
|
589
|
+
// Fail-loud range validation for the deadline / backstop / ceiling triple
|
|
590
|
+
// (spec `graceful-pre-timeout-wip-checkpoint`): a bad value from ANY layer
|
|
591
|
+
// (flag / env / per-repo / global) throws with a clear message naming the
|
|
592
|
+
// field + range — NEVER silently clamped.
|
|
593
|
+
validateDeadlineConfig(config);
|
|
545
594
|
return {
|
|
546
595
|
config,
|
|
547
596
|
rejected: repo.rejected,
|
package/src/repo-mirror.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {Config, PartialConfig} from './config.js';
|
|
|
5
5
|
import type {ConfigOverrideMap} from './config-override.js';
|
|
6
6
|
import {
|
|
7
7
|
REPO_CONFIG_FILENAME,
|
|
8
|
+
REPO_CONFIG_FILENAME_LEGACY,
|
|
8
9
|
loadRepoConfigFromContent,
|
|
9
10
|
resolveRepoConfigFromLoaded,
|
|
10
11
|
type LoadedRepoConfig,
|
|
@@ -247,7 +248,28 @@ export function readRepoConfigFromMirrorMain(
|
|
|
247
248
|
mirrorDir: string,
|
|
248
249
|
env?: NodeJS.ProcessEnv,
|
|
249
250
|
): string | undefined {
|
|
250
|
-
|
|
251
|
+
// Prefer the plain `dorfl.json`; fall back to the legacy `.dorfl.json` when the
|
|
252
|
+
// preferred name is absent on `main` (a repo that committed the dotfile before
|
|
253
|
+
// dual-support). A genuine read fault on the FIRST attempt propagates; a
|
|
254
|
+
// path-missing on the first falls through to the legacy attempt.
|
|
255
|
+
const preferred = showConfigOnMain(mirrorDir, REPO_CONFIG_FILENAME, env);
|
|
256
|
+
if (preferred !== undefined) {
|
|
257
|
+
return preferred;
|
|
258
|
+
}
|
|
259
|
+
return showConfigOnMain(mirrorDir, REPO_CONFIG_FILENAME_LEGACY, env);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* `git show main:<filename>` in a mirror, returning the bytes, or `undefined`
|
|
264
|
+
* when the PATH is absent on `main` (the expected config-less / wrong-name case).
|
|
265
|
+
* A genuine fault (unresolvable `main` / bad object) throws.
|
|
266
|
+
*/
|
|
267
|
+
function showConfigOnMain(
|
|
268
|
+
mirrorDir: string,
|
|
269
|
+
filename: string,
|
|
270
|
+
env?: NodeJS.ProcessEnv,
|
|
271
|
+
): string | undefined {
|
|
272
|
+
const spec = `main:${filename}`;
|
|
251
273
|
const res = run('git', ['show', spec], mirrorDir, {env});
|
|
252
274
|
if (res.status === 0) {
|
|
253
275
|
return res.stdout;
|
package/src/review-verdict.ts
CHANGED
|
@@ -397,7 +397,7 @@ export function reviewDisciplinePrompt(): string {
|
|
|
397
397
|
'(the in-band, protocol-native review protocol every set-up repo carries;',
|
|
398
398
|
'the human-facing pointer is `skills/review/SKILL.md`). Apply its lenses',
|
|
399
399
|
'IN ORDER, ENDING in the destination check ("if this is built/merged',
|
|
400
|
-
'exactly as written, do we end up with the system the
|
|
400
|
+
'exactly as written, do we end up with the system the spec/task',
|
|
401
401
|
'goal describes?"). Be ADVERSARIAL; verify against what ACTUALLY LANDED',
|
|
402
402
|
'(the bytes on disk), not intent. Weight findings by REAL impact \u2014 the',
|
|
403
403
|
'lenses find candidates, impact decides severity (a technically-true nit',
|