dorfl 0.11.0 → 0.11.2
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/arbiter-refs.d.ts +139 -0
- package/dist/arbiter-refs.d.ts.map +1 -0
- package/dist/arbiter-refs.js +114 -0
- package/dist/arbiter-refs.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +32 -18
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +73 -12
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +28 -0
- package/dist/config.js.map +1 -1
- package/dist/do-config.d.ts +21 -1
- package/dist/do-config.d.ts.map +1 -1
- package/dist/do-config.js +19 -0
- package/dist/do-config.js.map +1 -1
- package/dist/do.d.ts +14 -2
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +108 -14
- package/dist/do.js.map +1 -1
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +3 -0
- package/dist/env-config.js.map +1 -1
- package/dist/harness.d.ts +29 -0
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js.map +1 -1
- package/dist/ledger-write.d.ts.map +1 -1
- package/dist/ledger-write.js +38 -3
- package/dist/ledger-write.js.map +1 -1
- package/dist/needs-attention.d.ts +43 -0
- package/dist/needs-attention.d.ts.map +1 -1
- package/dist/needs-attention.js +211 -50
- package/dist/needs-attention.js.map +1 -1
- package/dist/pi-harness.d.ts.map +1 -1
- package/dist/pi-harness.js +109 -13
- package/dist/pi-harness.js.map +1 -1
- package/dist/protocol/WORK-CONTRACT.md +11 -2
- package/dist/protocol/spec-template.md +1 -1
- package/dist/protocol/task-template.md +1 -1
- package/dist/reap-agent-tree.d.ts +108 -0
- package/dist/reap-agent-tree.d.ts.map +1 -0
- package/dist/reap-agent-tree.js +173 -0
- package/dist/reap-agent-tree.js.map +1 -0
- package/dist/repo-config.d.ts +1 -1
- package/dist/repo-config.d.ts.map +1 -1
- package/dist/repo-config.js +19 -2
- package/dist/repo-config.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +4 -3
- package/dist/run.js.map +1 -1
- package/dist/skills/drive-tasks/SKILL.md +20 -2
- package/dist/skills/setup/protocol/WORK-CONTRACT.md +11 -2
- package/dist/skills/setup/protocol/spec-template.md +1 -1
- package/dist/skills/setup/protocol/task-template.md +1 -1
- package/dist/worktree-writer-lock.d.ts +99 -0
- package/dist/worktree-writer-lock.d.ts.map +1 -0
- package/dist/worktree-writer-lock.js +158 -0
- package/dist/worktree-writer-lock.js.map +1 -0
- package/package.json +1 -1
- package/src/arbiter-refs.ts +222 -0
- package/src/cli.ts +57 -17
- package/src/config.ts +90 -12
- package/src/do-config.ts +38 -0
- package/src/do.ts +158 -19
- package/src/env-config.ts +3 -0
- package/src/harness.ts +30 -0
- package/src/ledger-write.ts +41 -5
- package/src/needs-attention.ts +282 -59
- package/src/pi-harness.ts +109 -15
- package/src/reap-agent-tree.ts +221 -0
- package/src/repo-config.ts +19 -1
- package/src/run.ts +4 -3
- package/src/worktree-writer-lock.ts +217 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree-writer-lock.js","sourceRoot":"","sources":["../src/worktree-writer-lock.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import {runAsync} from './git.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* **The ONE arbiter-ref refresh + authoritative-read seam** (observation
|
|
5
|
+
* `checkpoint-path-reports-its-own-write-as-absent`).
|
|
6
|
+
*
|
|
7
|
+
* Every "did my own write land?" / "is the branch on the arbiter?" question in
|
|
8
|
+
* the checkpoint + surface paths used to be answered the same wrong way: run a
|
|
9
|
+
* PLAIN `git fetch <arbiter>`, then `git rev-parse <arbiter>/<branch>` — i.e.
|
|
10
|
+
* read a REMOTE-TRACKING ref (`refs/remotes/<arbiter>/…`) and trust it. That is
|
|
11
|
+
* unsound in the configuration dorfl itself creates for `--isolated` runs, and
|
|
12
|
+
* it produced two field defects where dorfl reported its OWN successful write as
|
|
13
|
+
* absent:
|
|
14
|
+
*
|
|
15
|
+
* 1. A job worktree is `git worktree add`ed from the BARE HUB MIRROR
|
|
16
|
+
* (`workspace.ts` `createJob` → `repo-mirror.ts` `ensureMirror`), whose
|
|
17
|
+
* `origin` carries the MIRROR-style refspec `+refs/heads/*:refs/heads/*`.
|
|
18
|
+
* So a plain fetch there writes `refs/heads/main`, and **never populates**
|
|
19
|
+
* `refs/remotes/origin/main` at all. `rev-parse origin/main` then returns
|
|
20
|
+
* whatever a PRIOR explicit-refspec fetch happened to leave behind — a value
|
|
21
|
+
* that PREDATES the write being verified. A push that genuinely landed reads
|
|
22
|
+
* back as "not our commit ⇒ rejected".
|
|
23
|
+
* 2. Worse, in that same worktree a plain `git fetch origin` **fails outright**
|
|
24
|
+
* (`fatal: refusing to fetch into branch 'refs/heads/work/<slug>' checked out
|
|
25
|
+
* at …`), because the mirror refspec's destination IS the branch the worktree
|
|
26
|
+
* has checked out. So it refreshes NOTHING, and a follow-up
|
|
27
|
+
* `rev-parse <arbiter>/work/<slug>` fails against a ref that never existed —
|
|
28
|
+
* reported as "no work branch on <arbiter>" while the branch (and an hour of
|
|
29
|
+
* agent work) sits on the arbiter.
|
|
30
|
+
*
|
|
31
|
+
* Both call sites now route through this module, which fixes the class rather
|
|
32
|
+
* than the two instances:
|
|
33
|
+
*
|
|
34
|
+
* - {@link refreshArbiterRefs} prune-fetches with an EXPLICIT, per-branch
|
|
35
|
+
* refspec into the `refs/remotes/<arbiter>/…` namespace the readers actually
|
|
36
|
+
* read, tolerating the checked-out-branch refusal instead of being silently
|
|
37
|
+
* defeated by it.
|
|
38
|
+
* - {@link resolveArbiterBranch} answers the sha question from the ARBITER
|
|
39
|
+
* ITSELF (`git ls-remote`), so no local ref-namespace/refspec accident can
|
|
40
|
+
* make a landed write look absent. The local tracking ref is only a FALLBACK,
|
|
41
|
+
* used when the arbiter cannot be reached at all.
|
|
42
|
+
*
|
|
43
|
+
* The `ls-remote`-is-authoritative stance is not new — it is the same one
|
|
44
|
+
* `continue-branch.ts` (`branchAheadOfArbiter`), `workspace.ts`, `integrator.ts`
|
|
45
|
+
* and `reap-branches.ts` already take for continue-detection and branch reaping.
|
|
46
|
+
* This module makes it the SHARED default for the post-write verification too,
|
|
47
|
+
* instead of each site re-deciding.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/** How a {@link ResolvedArbiterBranch} sha was obtained — the read's PROVENANCE. */
|
|
51
|
+
export type ArbiterRefAuthority =
|
|
52
|
+
/** Read from the arbiter itself (`git ls-remote`): AUTHORITATIVE. */
|
|
53
|
+
| 'arbiter'
|
|
54
|
+
/**
|
|
55
|
+
* The arbiter could not be reached (offline / broken remote), so the local
|
|
56
|
+
* remote-tracking ref was used. Best-effort: it may be stale, so a caller
|
|
57
|
+
* deciding "did MY write land?" must NOT treat a mismatch here as proof of
|
|
58
|
+
* a loss (see {@link ResolvedArbiterBranch.trustworthy}).
|
|
59
|
+
*/
|
|
60
|
+
| 'local-fallback'
|
|
61
|
+
/** Neither the arbiter nor any local ref has this branch. */
|
|
62
|
+
| 'absent';
|
|
63
|
+
|
|
64
|
+
/** The resolved state of ONE branch on the arbiter (a single, coherent read). */
|
|
65
|
+
export interface ResolvedArbiterBranch {
|
|
66
|
+
/** The unqualified branch name that was resolved (e.g. `main`, `work/task-x`). */
|
|
67
|
+
branch: string;
|
|
68
|
+
/** Its sha, or `undefined` when the branch exists nowhere we could look. */
|
|
69
|
+
sha?: string;
|
|
70
|
+
/** Where {@link sha} came from. */
|
|
71
|
+
authority: ArbiterRefAuthority;
|
|
72
|
+
/**
|
|
73
|
+
* True iff the arbiter answered (`authority` is `arbiter` or `absent` off a
|
|
74
|
+
* REACHABLE arbiter). When false the read is a stale-capable local fallback,
|
|
75
|
+
* so a mismatch proves nothing and callers must not report a loss from it.
|
|
76
|
+
*/
|
|
77
|
+
trustworthy: boolean;
|
|
78
|
+
/** The `ls-remote` stderr when the arbiter could not be reached (diagnostics). */
|
|
79
|
+
unreachableDetail?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The explicit refspec that maps an arbiter branch into the namespace we READ. */
|
|
83
|
+
function trackingRefspec(arbiter: string, branch: string): string {
|
|
84
|
+
return `+refs/heads/${branch}:refs/remotes/${arbiter}/${branch}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* PRUNE-FETCH the named arbiter branches into `refs/remotes/<arbiter>/<branch>`
|
|
89
|
+
* — the namespace every reader in this codebase actually reads — using an
|
|
90
|
+
* EXPLICIT per-branch refspec.
|
|
91
|
+
*
|
|
92
|
+
* Three properties matter, and all three are the reason this is not just
|
|
93
|
+
* `git fetch <arbiter>`:
|
|
94
|
+
*
|
|
95
|
+
* - **Explicit refspec.** A bare-hub-mirror worktree's `origin` maps
|
|
96
|
+
* `+refs/heads/*:refs/heads/*`, so a plain fetch never writes
|
|
97
|
+
* `refs/remotes/<arbiter>/*`. Naming the destination makes the refresh work
|
|
98
|
+
* identically in a normal clone AND in a mirror worktree.
|
|
99
|
+
* - **`--prune`.** A branch DELETED on the arbiter (a `requeue --reset`, a
|
|
100
|
+
* merge-reap, a cross-machine `gc`) must disappear from our view too;
|
|
101
|
+
* otherwise a stale tracking ref answers a liveness question with a ghost.
|
|
102
|
+
* - **Per-branch and SOFT.** Fetching branch-at-a-time means the one refspec
|
|
103
|
+
* git refuses (the destination that is checked out in THIS worktree — see the
|
|
104
|
+
* module doc) cannot abort the refresh of the others, which is exactly how the
|
|
105
|
+
* single combined fetch silently refreshed nothing. Every failure is
|
|
106
|
+
* tolerated and reported rather than thrown: this is a REFRESH, and the
|
|
107
|
+
* authoritative answer comes from {@link resolveArbiterBranch} anyway.
|
|
108
|
+
*
|
|
109
|
+
* Returns the branches that could not be refreshed (for diagnostics only — a
|
|
110
|
+
* caller should not gate on it, because the authoritative read does not depend
|
|
111
|
+
* on the refresh succeeding).
|
|
112
|
+
*/
|
|
113
|
+
export async function refreshArbiterRefs(params: {
|
|
114
|
+
cwd: string;
|
|
115
|
+
arbiter: string;
|
|
116
|
+
/** Unqualified branch names to refresh (e.g. `['main', 'work/task-x']`). */
|
|
117
|
+
branches: readonly string[];
|
|
118
|
+
env?: NodeJS.ProcessEnv;
|
|
119
|
+
}): Promise<{failed: string[]}> {
|
|
120
|
+
const {cwd, arbiter, branches, env} = params;
|
|
121
|
+
const failed: string[] = [];
|
|
122
|
+
for (const branch of branches) {
|
|
123
|
+
const fetched = await runAsync(
|
|
124
|
+
'git',
|
|
125
|
+
[
|
|
126
|
+
'fetch',
|
|
127
|
+
'--quiet',
|
|
128
|
+
'--prune',
|
|
129
|
+
arbiter,
|
|
130
|
+
trackingRefspec(arbiter, branch),
|
|
131
|
+
],
|
|
132
|
+
cwd,
|
|
133
|
+
{env},
|
|
134
|
+
);
|
|
135
|
+
if (fetched.status !== 0) {
|
|
136
|
+
failed.push(branch);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return {failed};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Resolve ONE branch's sha on the arbiter, ARBITER-AUTHORITATIVELY.
|
|
144
|
+
*
|
|
145
|
+
* `git ls-remote --heads <arbiter> <branch>` asks the arbiter directly, so the
|
|
146
|
+
* answer cannot be defeated by a local refspec/namespace accident — which is the
|
|
147
|
+
* whole point: this is the read a post-write verification uses to decide whether
|
|
148
|
+
* its OWN push landed, and that decision must never be made from a view that
|
|
149
|
+
* predates the push.
|
|
150
|
+
*
|
|
151
|
+
* Decision order:
|
|
152
|
+
* - `ls-remote` exits 0 with a sha ⇒ `{sha, authority: 'arbiter'}` (trustworthy).
|
|
153
|
+
* - `ls-remote` exits 0 with EMPTY output ⇒ the arbiter genuinely does not have
|
|
154
|
+
* the branch ⇒ `{authority: 'absent'}` (trustworthy: a definite "no").
|
|
155
|
+
* - `ls-remote` exits non-zero (unreachable / no such remote) ⇒ fall back to the
|
|
156
|
+
* local `refs/remotes/<arbiter>/<branch>`, flagged `trustworthy: false` so a
|
|
157
|
+
* caller cannot mistake a stale local read for proof of anything.
|
|
158
|
+
*
|
|
159
|
+
* Always call {@link refreshArbiterRefs} first when the caller ALSO needs the
|
|
160
|
+
* objects locally (a CAS base, a `merge-base` / `rev-list` comparison): a sha
|
|
161
|
+
* from `ls-remote` names a commit this repo may not have yet.
|
|
162
|
+
*/
|
|
163
|
+
export async function resolveArbiterBranch(params: {
|
|
164
|
+
cwd: string;
|
|
165
|
+
arbiter: string;
|
|
166
|
+
branch: string;
|
|
167
|
+
env?: NodeJS.ProcessEnv;
|
|
168
|
+
}): Promise<ResolvedArbiterBranch> {
|
|
169
|
+
const {cwd, arbiter, branch, env} = params;
|
|
170
|
+
const ls = await runAsync(
|
|
171
|
+
'git',
|
|
172
|
+
['ls-remote', '--heads', arbiter, branch],
|
|
173
|
+
cwd,
|
|
174
|
+
{env},
|
|
175
|
+
);
|
|
176
|
+
if (ls.status === 0) {
|
|
177
|
+
// `<sha>\t<ref>` lines. `--heads <branch>` can match several refs when the
|
|
178
|
+
// name is a glob-ish prefix, so take the line whose ref is EXACTLY ours.
|
|
179
|
+
const sha = ls.stdout
|
|
180
|
+
.split('\n')
|
|
181
|
+
.map((line) => line.trim())
|
|
182
|
+
.filter((line) => line !== '')
|
|
183
|
+
.map((line) => line.split(/\s+/))
|
|
184
|
+
.find(([, ref]) => ref === `refs/heads/${branch}`)?.[0];
|
|
185
|
+
if (sha !== undefined && sha !== '') {
|
|
186
|
+
return {branch, sha, authority: 'arbiter', trustworthy: true};
|
|
187
|
+
}
|
|
188
|
+
// Reachable arbiter that does NOT have the branch: a definite, trustworthy
|
|
189
|
+
// "absent" (a stale local ref must not be able to resurrect it).
|
|
190
|
+
return {branch, authority: 'absent', trustworthy: true};
|
|
191
|
+
}
|
|
192
|
+
// Unreachable arbiter: best-effort local read, explicitly NOT trustworthy.
|
|
193
|
+
const local = await runAsync(
|
|
194
|
+
'git',
|
|
195
|
+
[
|
|
196
|
+
'rev-parse',
|
|
197
|
+
'--verify',
|
|
198
|
+
'--quiet',
|
|
199
|
+
`refs/remotes/${arbiter}/${branch}^{commit}`,
|
|
200
|
+
],
|
|
201
|
+
cwd,
|
|
202
|
+
{env},
|
|
203
|
+
);
|
|
204
|
+
const localSha = local.status === 0 ? local.stdout.trim() : '';
|
|
205
|
+
const unreachableDetail =
|
|
206
|
+
ls.stderr.trim() || `git ls-remote exit ${ls.status}`;
|
|
207
|
+
if (localSha !== '') {
|
|
208
|
+
return {
|
|
209
|
+
branch,
|
|
210
|
+
sha: localSha,
|
|
211
|
+
authority: 'local-fallback',
|
|
212
|
+
trustworthy: false,
|
|
213
|
+
unreachableDetail,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
branch,
|
|
218
|
+
authority: 'absent',
|
|
219
|
+
trustworthy: false,
|
|
220
|
+
unreachableDetail,
|
|
221
|
+
};
|
|
222
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -99,6 +99,7 @@ import {
|
|
|
99
99
|
freshWorktreeGateFlagOverrides,
|
|
100
100
|
mergeRetriesFlagOverrides,
|
|
101
101
|
noPRFlagOverrides,
|
|
102
|
+
triageModelFlagOverrides,
|
|
102
103
|
} from './do-config.js';
|
|
103
104
|
import {harnessReviewGate, harnessTaskAcceptanceGate} from './review-gate.js';
|
|
104
105
|
import {harnessSurfaceGate} from './surface-gate.js';
|
|
@@ -107,7 +108,7 @@ import {harnessApplyDecider} from './apply-decide.js';
|
|
|
107
108
|
import {harnessTaskReviewGate} from './tasker-review-loop.js';
|
|
108
109
|
import {runVerify} from './verify.js';
|
|
109
110
|
import {renderPrompt} from './prompt.js';
|
|
110
|
-
import {resolvePromptGuidance} from './config.js';
|
|
111
|
+
import {resolvePromptGuidance, applyModelFallbacks} from './config.js';
|
|
111
112
|
import {
|
|
112
113
|
gc,
|
|
113
114
|
resolveArbiterKeyFromCwd,
|
|
@@ -216,7 +217,9 @@ function resolveGlobalConfig(
|
|
|
216
217
|
fileConfig: PartialConfig,
|
|
217
218
|
flags: PartialConfig,
|
|
218
219
|
): Config {
|
|
219
|
-
|
|
220
|
+
const config = mergeConfig({...fileConfig, ...envOverrides(), ...flags});
|
|
221
|
+
applyModelFallbacks(config);
|
|
222
|
+
return config;
|
|
220
223
|
}
|
|
221
224
|
|
|
222
225
|
/**
|
|
@@ -423,7 +426,7 @@ function buildRegistrySetAdvanceTick(options: {
|
|
|
423
426
|
noPR: config.noPR,
|
|
424
427
|
harness,
|
|
425
428
|
agentCmd: config.agentCmd,
|
|
426
|
-
model: config.
|
|
429
|
+
model: config.buildModel,
|
|
427
430
|
sessionsDir: config.sessionsDir,
|
|
428
431
|
review: config.review,
|
|
429
432
|
reviewModel: config.reviewModel,
|
|
@@ -448,12 +451,12 @@ function buildRegistrySetAdvanceTick(options: {
|
|
|
448
451
|
arbiter: arbiter ?? config.defaultArbiter,
|
|
449
452
|
doOptions,
|
|
450
453
|
surfaceGate: harnessSurfaceGate({harness, agentCmd: config.agentCmd}),
|
|
451
|
-
surfaceModel: config.
|
|
454
|
+
surfaceModel: config.triageModel,
|
|
452
455
|
applyDecide: harnessApplyDecider({harness, agentCmd: config.agentCmd}),
|
|
453
|
-
applyModel: config.
|
|
456
|
+
applyModel: config.triageModel,
|
|
454
457
|
observationTriage: config.observationTriage,
|
|
455
458
|
triageGate: harnessTriageGate({harness, agentCmd: config.agentCmd}),
|
|
456
|
-
triageModel: config.
|
|
459
|
+
triageModel: config.triageModel,
|
|
457
460
|
// The ANSWERED-MERGE LAND DISPATCH context (task
|
|
458
461
|
// `apply-rung-merge-disposition`, spec `land-time-reverify-and-parallel-
|
|
459
462
|
// merge-ceiling`): the dispatcher cuts a per-job worktree via
|
|
@@ -551,6 +554,8 @@ interface RunFlags extends ScanFlags {
|
|
|
551
554
|
pr?: boolean;
|
|
552
555
|
agentCmd?: string;
|
|
553
556
|
model?: string;
|
|
557
|
+
/** `--build-model <id>` — the builder-specific model override (falls back to `--model`). */
|
|
558
|
+
buildModel?: string;
|
|
554
559
|
harness?: string;
|
|
555
560
|
piBin?: string;
|
|
556
561
|
sessionsDir?: string;
|
|
@@ -558,6 +563,8 @@ interface RunFlags extends ScanFlags {
|
|
|
558
563
|
review?: boolean;
|
|
559
564
|
reviewModel?: string;
|
|
560
565
|
reviewMaxRounds?: string;
|
|
566
|
+
/** `--triage-model <id>` — the advance lifecycle gates' model (surface, apply, triage). */
|
|
567
|
+
triageModel?: string;
|
|
561
568
|
/** `--fresh-worktree-gate` / `--no-fresh-worktree-gate` — gate the REBASED tip in a clean throwaway worktree (ON by default). */
|
|
562
569
|
freshWorktreeGate?: boolean;
|
|
563
570
|
/** `--merge-retries <n>` — the cross-job merge-serialiser CAS-retry cap (spec `land-time-reverify-and-parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)). */
|
|
@@ -593,6 +600,8 @@ function runFlagOverrides(flags: RunFlags, command?: Commander): PartialConfig {
|
|
|
593
600
|
// flag > env > per-repo > global > default — mirroring the `do` command (the
|
|
594
601
|
// fleet inherits the review gate via the converged `performIntegration` core).
|
|
595
602
|
Object.assign(overrides, reviewFlagOverrides(flags));
|
|
603
|
+
// `--triage-model` (the advance lifecycle gates' model) rides the SAME chain.
|
|
604
|
+
Object.assign(overrides, triageModelFlagOverrides(flags));
|
|
596
605
|
// `--fresh-worktree-gate`/`--no-fresh-worktree-gate` rides the SAME chain: gate
|
|
597
606
|
// the REBASED tip in a clean throwaway worktree (ON by default). The `run` fleet
|
|
598
607
|
// caller additionally downgrades it to today's gate at `perRepoMax > 1` (the
|
|
@@ -769,6 +778,8 @@ interface DoFlags {
|
|
|
769
778
|
allowBacklog?: boolean;
|
|
770
779
|
agentCmd?: string;
|
|
771
780
|
model?: string;
|
|
781
|
+
/** `--build-model <id>` — the builder-specific model override (falls back to `--model`). */
|
|
782
|
+
buildModel?: string;
|
|
772
783
|
harness?: string;
|
|
773
784
|
piBin?: string;
|
|
774
785
|
sessionsDir?: string;
|
|
@@ -776,6 +787,8 @@ interface DoFlags {
|
|
|
776
787
|
review?: boolean;
|
|
777
788
|
reviewModel?: string;
|
|
778
789
|
reviewMaxRounds?: string;
|
|
790
|
+
/** `--triage-model <id>` — the advance lifecycle gates' model (surface, apply, triage). */
|
|
791
|
+
triageModel?: string;
|
|
779
792
|
/** `--tasker-loop` / `--no-tasker-loop` — the tasker improver loop on/off toggle (`do spec:` path). Resolves into the `taskerLoop` config key. */
|
|
780
793
|
taskerLoop?: boolean;
|
|
781
794
|
/** `--tasker-loop-max <n>` — the tasker improver loop's in-context convergence cap (`do spec:` path). Resolves into the `taskerLoopMax` config key. */
|
|
@@ -813,6 +826,8 @@ interface IntakeFlags {
|
|
|
813
826
|
tasksLandIn?: string;
|
|
814
827
|
agentCmd?: string;
|
|
815
828
|
model?: string;
|
|
829
|
+
/** `--intake-model <id>` — the intake decision agent's model (falls back to `--model`). */
|
|
830
|
+
intakeModel?: string;
|
|
816
831
|
harness?: string;
|
|
817
832
|
piBin?: string;
|
|
818
833
|
sessionsDir?: string;
|
|
@@ -1491,6 +1506,10 @@ export function buildProgram(): Command {
|
|
|
1491
1506
|
'--model <id>',
|
|
1492
1507
|
'model the agent runs on (routing intent; auth/keys stay the harness\u2019s job). pi: passed as --model; null/shell: substitutes a {model} placeholder in agentCmd. Resolved flag > env > per-repo > global > default (unset).',
|
|
1493
1508
|
)
|
|
1509
|
+
.option(
|
|
1510
|
+
'--build-model <id>',
|
|
1511
|
+
'model the BUILD agent runs on (the builder-specific override of --model). Unset at every level inherits --model. Resolved flag > env > per-repo > global > fallback to model > default (unset).',
|
|
1512
|
+
)
|
|
1494
1513
|
.option(
|
|
1495
1514
|
'--harness <adapter>',
|
|
1496
1515
|
'harness adapter that launches the agent + reports liveness: null (default, shells out to agentCmd) or pi (the pi CLI)',
|
|
@@ -1516,6 +1535,10 @@ export function buildProgram(): Command {
|
|
|
1516
1535
|
'--review-model <id>',
|
|
1517
1536
|
'model the Gate-2 review agent runs on (de-correlated from the builder; routing intent). Resolved flag > env > per-repo > global > default.',
|
|
1518
1537
|
)
|
|
1538
|
+
.option(
|
|
1539
|
+
'--triage-model <id>',
|
|
1540
|
+
'model the advance lifecycle gates (surface, apply, triage) run on. Unset at every level inherits --model. Resolved flag > env > per-repo > global > fallback to model > default (unset).',
|
|
1541
|
+
)
|
|
1519
1542
|
.option(
|
|
1520
1543
|
'--review-max-rounds <n>',
|
|
1521
1544
|
'bound the revise/review loop; on exhaustion force needs-attention (default 2)',
|
|
@@ -2323,6 +2346,10 @@ export function buildProgram(): Command {
|
|
|
2323
2346
|
'--model <id>',
|
|
2324
2347
|
'model the agent runs on (routing intent; resolved flag > env > per-repo > global > default)',
|
|
2325
2348
|
)
|
|
2349
|
+
.option(
|
|
2350
|
+
'--build-model <id>',
|
|
2351
|
+
'model the BUILD agent runs on (the builder-specific override of --model). Unset at every level inherits --model. Resolved flag > env > per-repo > global > fallback to model > default (unset).',
|
|
2352
|
+
)
|
|
2326
2353
|
.option(
|
|
2327
2354
|
'--harness <adapter>',
|
|
2328
2355
|
'harness adapter that launches the agent: null (default, shells out to agentCmd) or pi (the pi CLI)',
|
|
@@ -2351,6 +2378,10 @@ export function buildProgram(): Command {
|
|
|
2351
2378
|
'--review-model <id>',
|
|
2352
2379
|
'model the Gate-2 review agent runs on (de-correlated from the builder; routing intent). Resolved flag > env > per-repo > global > default.',
|
|
2353
2380
|
)
|
|
2381
|
+
.option(
|
|
2382
|
+
'--triage-model <id>',
|
|
2383
|
+
'model the advance lifecycle gates (surface, apply, triage) run on. Unset at every level inherits --model. Resolved flag > env > per-repo > global > fallback to model > default (unset).',
|
|
2384
|
+
)
|
|
2354
2385
|
.option(
|
|
2355
2386
|
'--review-max-rounds <n>',
|
|
2356
2387
|
'bound the revise/review loop; on exhaustion force needs-attention (default 2)',
|
|
@@ -2620,7 +2651,7 @@ export function buildProgram(): Command {
|
|
|
2620
2651
|
noPR: remoteConfig.noPR,
|
|
2621
2652
|
harness: remoteHarness,
|
|
2622
2653
|
agentCmd: remoteConfig.agentCmd,
|
|
2623
|
-
model: remoteConfig.
|
|
2654
|
+
model: remoteConfig.buildModel,
|
|
2624
2655
|
sessionsDir: remoteConfig.sessionsDir,
|
|
2625
2656
|
review: remoteConfig.review,
|
|
2626
2657
|
reviewModel: remoteConfig.reviewModel,
|
|
@@ -2799,7 +2830,7 @@ export function buildProgram(): Command {
|
|
|
2799
2830
|
noPR: config.noPR,
|
|
2800
2831
|
harness,
|
|
2801
2832
|
agentCmd: config.agentCmd,
|
|
2802
|
-
model: config.
|
|
2833
|
+
model: config.buildModel,
|
|
2803
2834
|
// The HOST-ONLY sessions root (resolved Config → DoOptions bridge, like
|
|
2804
2835
|
// model/agentCmd): the path generator turns it into
|
|
2805
2836
|
// `<sessionsDir>/<id>.jsonl` for `--session`. Without this map the key
|
|
@@ -3132,7 +3163,7 @@ export function buildProgram(): Command {
|
|
|
3132
3163
|
noPR: remoteConfig.noPR,
|
|
3133
3164
|
harness: isoHarness,
|
|
3134
3165
|
agentCmd: remoteConfig.agentCmd,
|
|
3135
|
-
model: remoteConfig.
|
|
3166
|
+
model: remoteConfig.buildModel,
|
|
3136
3167
|
sessionsDir: remoteConfig.sessionsDir,
|
|
3137
3168
|
review: remoteConfig.review,
|
|
3138
3169
|
reviewModel: remoteConfig.reviewModel,
|
|
@@ -3174,18 +3205,18 @@ export function buildProgram(): Command {
|
|
|
3174
3205
|
harness: isoHarness,
|
|
3175
3206
|
agentCmd: remoteConfig.agentCmd,
|
|
3176
3207
|
}),
|
|
3177
|
-
surfaceModel: remoteConfig.
|
|
3208
|
+
surfaceModel: remoteConfig.triageModel,
|
|
3178
3209
|
applyDecide: harnessApplyDecider({
|
|
3179
3210
|
harness: isoHarness,
|
|
3180
3211
|
agentCmd: remoteConfig.agentCmd,
|
|
3181
3212
|
}),
|
|
3182
|
-
applyModel: remoteConfig.
|
|
3213
|
+
applyModel: remoteConfig.triageModel,
|
|
3183
3214
|
observationTriage: remoteConfig.observationTriage,
|
|
3184
3215
|
triageGate: harnessTriageGate({
|
|
3185
3216
|
harness: isoHarness,
|
|
3186
3217
|
agentCmd: remoteConfig.agentCmd,
|
|
3187
3218
|
}),
|
|
3188
|
-
triageModel: remoteConfig.
|
|
3219
|
+
triageModel: remoteConfig.triageModel,
|
|
3189
3220
|
note: (message) => console.error(`>> ${message}`),
|
|
3190
3221
|
env: process.env,
|
|
3191
3222
|
};
|
|
@@ -3276,7 +3307,7 @@ export function buildProgram(): Command {
|
|
|
3276
3307
|
noPR: config.noPR,
|
|
3277
3308
|
harness,
|
|
3278
3309
|
agentCmd: config.agentCmd,
|
|
3279
|
-
model: config.
|
|
3310
|
+
model: config.buildModel,
|
|
3280
3311
|
sessionsDir: config.sessionsDir,
|
|
3281
3312
|
review: config.review,
|
|
3282
3313
|
reviewModel: config.reviewModel,
|
|
@@ -3312,12 +3343,12 @@ export function buildProgram(): Command {
|
|
|
3312
3343
|
arbiter: flags.arbiter ?? config.defaultArbiter,
|
|
3313
3344
|
doOptions,
|
|
3314
3345
|
surfaceGate: harnessSurfaceGate({harness, agentCmd: config.agentCmd}),
|
|
3315
|
-
surfaceModel: config.
|
|
3346
|
+
surfaceModel: config.triageModel,
|
|
3316
3347
|
applyDecide: harnessApplyDecider({harness, agentCmd: config.agentCmd}),
|
|
3317
|
-
applyModel: config.
|
|
3348
|
+
applyModel: config.triageModel,
|
|
3318
3349
|
observationTriage: config.observationTriage,
|
|
3319
3350
|
triageGate: harnessTriageGate({harness, agentCmd: config.agentCmd}),
|
|
3320
|
-
triageModel: config.
|
|
3351
|
+
triageModel: config.triageModel,
|
|
3321
3352
|
note: (message) => console.error(`>> ${message}`),
|
|
3322
3353
|
};
|
|
3323
3354
|
|
|
@@ -4386,6 +4417,10 @@ export function buildProgram(): Command {
|
|
|
4386
4417
|
'--model <id>',
|
|
4387
4418
|
'model the decision agent runs on (routing intent; resolved flag > env > per-repo > global > default)',
|
|
4388
4419
|
)
|
|
4420
|
+
.option(
|
|
4421
|
+
'--intake-model <id>',
|
|
4422
|
+
'model the intake decision agent runs on (the builder-specific override of --model). Unset at every level inherits --model. Resolved flag > env > per-repo > global > fallback to model > default (unset).',
|
|
4423
|
+
)
|
|
4389
4424
|
.option(
|
|
4390
4425
|
'--harness <adapter>',
|
|
4391
4426
|
'harness adapter that launches the decision agent: null (default) or pi',
|
|
@@ -4420,6 +4455,11 @@ export function buildProgram(): Command {
|
|
|
4420
4455
|
...harnessFlagOverrides(flags),
|
|
4421
4456
|
// `--no-pr` (the PR-INTENT axis) rides the SAME chain.
|
|
4422
4457
|
...noPRFlagOverrides(flags),
|
|
4458
|
+
// `--intake-model` rides the SAME chain (falls back to `model`
|
|
4459
|
+
// when unset at all levels via `applyModelFallbacks`).
|
|
4460
|
+
...(flags.intakeModel !== undefined
|
|
4461
|
+
? {intakeModel: flags.intakeModel}
|
|
4462
|
+
: {}),
|
|
4423
4463
|
},
|
|
4424
4464
|
});
|
|
4425
4465
|
if (resolved.message) {
|
|
@@ -4534,7 +4574,7 @@ export function buildProgram(): Command {
|
|
|
4534
4574
|
explicitTasksLandIn,
|
|
4535
4575
|
harness,
|
|
4536
4576
|
agentCmd: config.agentCmd,
|
|
4537
|
-
model: config.
|
|
4577
|
+
model: config.intakeModel,
|
|
4538
4578
|
sessionsDir: config.sessionsDir,
|
|
4539
4579
|
// Host-only runner IDENTITY — scopes intake's `gh`/git ops (not the
|
|
4540
4580
|
// decision/review AGENT launches); absent ⇒ ambient.
|
package/src/config.ts
CHANGED
|
@@ -528,9 +528,25 @@ export interface Config {
|
|
|
528
528
|
* (`LaunchInput.model`); the ADAPTER decides HOW it reaches its tool — the pi
|
|
529
529
|
* adapter passes `--model <model>` natively, the null/shell adapter substitutes
|
|
530
530
|
* a `{model}` placeholder in `agentCmd`. Resolved per-repo like `integration`:
|
|
531
|
-
* flag (`--model`) > env > per-repo > global > default (unset).
|
|
531
|
+
* flag (`--model`) > env > per-repo > global > default (unset). The fallback
|
|
532
|
+
* to this field from `buildModel` / `reviewModel` / `taskerLoopModel` is
|
|
533
|
+
* applied AFTER the full precedence chain resolves (in `applyModelFallbacks`),
|
|
534
|
+
* so a per-role knob set at ANY level (even global config) is NEVER reset by a
|
|
535
|
+
* higher-precedence `--model`.
|
|
532
536
|
*/
|
|
533
537
|
model?: string;
|
|
538
|
+
/**
|
|
539
|
+
* The model the BUILD agent runs on (the builder-specific override of the
|
|
540
|
+
* general `model`). Optional with NO default so "unset" means "inherit the
|
|
541
|
+
* general `model`" (via `applyModelFallbacks`, applied at the resolution FINAL
|
|
542
|
+
* point — after the full flag > env > per-repo > global chain). Carried to the
|
|
543
|
+
* build launch through the same harness seam (`LaunchInput.model`). Resolved
|
|
544
|
+
* per-repo like `model`: flag (`--build-model`) > env (`DORFL_BUILD_MODEL`) >
|
|
545
|
+
* per-repo > global > fallback to `model` > default (unset). DISTINCT from
|
|
546
|
+
* `reviewModel` (Gate 2) and `taskerLoopModel` (the improver loop) — each has
|
|
547
|
+
* its OWN knob and each falls back to `model` independently.
|
|
548
|
+
*/
|
|
549
|
+
buildModel?: string;
|
|
534
550
|
/**
|
|
535
551
|
* Which harness adapter launches + reports liveness for a job's agent (the
|
|
536
552
|
* harness seam, ADR §5): `null` (default — shells out to `agentCmd`,
|
|
@@ -600,11 +616,14 @@ export interface Config {
|
|
|
600
616
|
review: boolean;
|
|
601
617
|
/**
|
|
602
618
|
* The model the REVIEW agent runs on (de-correlation from the builder's
|
|
603
|
-
* `
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
619
|
+
* `buildModel` when explicitly set). Optional with NO default so "unset" means
|
|
620
|
+
* "inherit the general `model`" (via `applyModelFallbacks`, applied at the
|
|
621
|
+
* resolution FINAL point — after the full flag > env > per-repo > global chain).
|
|
622
|
+
* Carried to the review-agent launch through the EXISTING harness seam
|
|
623
|
+
* (`LaunchInput.model` / `substituteModel`) — NOT a new mechanism. Resolved like
|
|
624
|
+
* `model`: flag (`--review-model`) > env > per-repo > global > fallback to
|
|
625
|
+
* `model` > default (unset). Distinct from the builder's `buildModel` — each
|
|
626
|
+
* has its OWN knob and each falls back to `model` independently.
|
|
608
627
|
*/
|
|
609
628
|
reviewModel?: string;
|
|
610
629
|
/**
|
|
@@ -650,14 +669,44 @@ export interface Config {
|
|
|
650
669
|
taskerLoopMax: number;
|
|
651
670
|
/**
|
|
652
671
|
* The model the tasker IMPROVER loop's review agent runs on (de-correlation
|
|
653
|
-
* from the tasker). Optional with NO default so "unset"
|
|
654
|
-
*
|
|
655
|
-
*
|
|
656
|
-
*
|
|
657
|
-
* (
|
|
658
|
-
* `--
|
|
672
|
+
* from the tasker when explicitly set). Optional with NO default so "unset"
|
|
673
|
+
* means "inherit the general `model`" (via `applyModelFallbacks`, applied at
|
|
674
|
+
* the resolution FINAL point — after the full flag > env > per-repo > global
|
|
675
|
+
* chain). Carried to the review-agent launch through the EXISTING harness seam
|
|
676
|
+
* (`LaunchInput.model` / `substituteModel`). Resolved like `model`: flag
|
|
677
|
+
* (`--tasker-loop-model`) > env > per-repo > global > fallback to `model` >
|
|
678
|
+
* default (unset). DISTINCT from the acceptance gate's `reviewModel` (build
|
|
679
|
+
* `--review-model`) and from `buildModel` — each has its OWN knob and each falls
|
|
680
|
+
* back to `model` independently.
|
|
659
681
|
*/
|
|
660
682
|
taskerLoopModel?: string;
|
|
683
|
+
/**
|
|
684
|
+
* The model the ADVANCE LIFECYCLE GATES (surface, apply, triage) run on —
|
|
685
|
+
* the fresh-context judgement agents that surface questions from blocked work
|
|
686
|
+
* (`surface`), interpret answered questions and decide what to mint (`apply`),
|
|
687
|
+
* and auto-disposition no-question observations (`triage`). Optional with NO
|
|
688
|
+
* default so "unset" means "inherit the general `model`" (via
|
|
689
|
+
* `applyModelFallbacks`, applied at the resolution FINAL point). Carried to each
|
|
690
|
+
* gate's launch through the EXISTING harness seam (`LaunchInput.model`). Resolved
|
|
691
|
+
* like `model`: flag (`--triage-model`) > env (`DORFL_TRIAGE_MODEL`) > per-repo >
|
|
692
|
+
* global > fallback to `model` > default (unset). DISTINCT from `reviewModel`,
|
|
693
|
+
* `taskerLoopModel`, `buildModel`, and `intakeModel` — each has its OWN knob and
|
|
694
|
+
* each falls back to `model` independently.
|
|
695
|
+
*/
|
|
696
|
+
triageModel?: string;
|
|
697
|
+
/**
|
|
698
|
+
* The model the INTAKE decision agent runs on — the front-door fresh-context
|
|
699
|
+
* agent that reads an issue/PR and decides whether to mint a task, mint a spec,
|
|
700
|
+
* or ask a follow-up question. Optional with NO default so "unset" means
|
|
701
|
+
* "inherit the general `model`" (via `applyModelFallbacks`, applied at the
|
|
702
|
+
* resolution FINAL point). Carried to the intake-agent launch through the
|
|
703
|
+
* EXISTING harness seam (`LaunchInput.model`). Resolved like `model`: flag
|
|
704
|
+
* (`--intake-model`) > env (`DORFL_INTAKE_MODEL`) > per-repo > global > fallback
|
|
705
|
+
* to `model` > default (unset). DISTINCT from the advance lifecycle gates'
|
|
706
|
+
* `triageModel` and from `buildModel`/`reviewModel`/`taskerLoopModel` — each has
|
|
707
|
+
* its OWN knob and each falls back to `model` independently.
|
|
708
|
+
*/
|
|
709
|
+
intakeModel?: string;
|
|
661
710
|
/**
|
|
662
711
|
* **The fresh-worktree acceptance-gate toggle** (`--fresh-worktree-gate` /
|
|
663
712
|
* `--no-fresh-worktree-gate`). When ON (the default), the acceptance gate
|
|
@@ -1078,6 +1127,35 @@ export function mergeConfig(overrides: PartialConfig): Config {
|
|
|
1078
1127
|
return merged;
|
|
1079
1128
|
}
|
|
1080
1129
|
|
|
1130
|
+
/**
|
|
1131
|
+
* Apply the per-role model fallbacks IN PLACE on a FULLY RESOLVED {@link Config}:
|
|
1132
|
+
* `buildModel`, `reviewModel`, and `taskerLoopModel` each inherit the general
|
|
1133
|
+
* `model` when they are unset (i.e. were not set at ANY precedence level — flag,
|
|
1134
|
+
* env, per-repo, or global). This is the single place the fallback lives; it is
|
|
1135
|
+
* called at the resolution FINAL points (`resolveRepoConfigFromLoaded` for the
|
|
1136
|
+
* per-repo chain; `resolveGlobalConfig` for the global-only chain) so the
|
|
1137
|
+
* fallback uses the FINAL resolved `model`, never an intermediate layer's. A
|
|
1138
|
+
* per-role knob set at ANY level is NEVER reset — `applyModelFallbacks` only
|
|
1139
|
+
* touches fields that are still `undefined` after the full chain resolved.
|
|
1140
|
+
*/
|
|
1141
|
+
export function applyModelFallbacks(config: Config): void {
|
|
1142
|
+
if (config.buildModel === undefined) {
|
|
1143
|
+
config.buildModel = config.model;
|
|
1144
|
+
}
|
|
1145
|
+
if (config.reviewModel === undefined) {
|
|
1146
|
+
config.reviewModel = config.model;
|
|
1147
|
+
}
|
|
1148
|
+
if (config.taskerLoopModel === undefined) {
|
|
1149
|
+
config.taskerLoopModel = config.model;
|
|
1150
|
+
}
|
|
1151
|
+
if (config.triageModel === undefined) {
|
|
1152
|
+
config.triageModel = config.model;
|
|
1153
|
+
}
|
|
1154
|
+
if (config.intakeModel === undefined) {
|
|
1155
|
+
config.intakeModel = config.model;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1081
1159
|
/**
|
|
1082
1160
|
* Persist `config` to `path` as pretty JSON (creating the parent dir). Used by
|
|
1083
1161
|
* `work-on` to SAVE the prompted `humanWorktreesDir` on first use so the human is
|