dorfl 0.8.0 → 0.10.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/cli.d.ts.map +1 -1
- package/dist/cli.js +63 -25
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +82 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -2
- package/dist/config.js.map +1 -1
- package/dist/do.d.ts +16 -1
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +14 -9
- package/dist/do.js.map +1 -1
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +25 -3
- package/dist/env-config.js.map +1 -1
- package/dist/intake-trigger-template.d.ts +91 -53
- package/dist/intake-trigger-template.d.ts.map +1 -1
- package/dist/intake-trigger-template.js +213 -114
- package/dist/intake-trigger-template.js.map +1 -1
- package/dist/intake.d.ts +53 -4
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +59 -15
- package/dist/intake.js.map +1 -1
- package/dist/placement.d.ts +42 -33
- package/dist/placement.d.ts.map +1 -1
- package/dist/placement.js +20 -17
- package/dist/placement.js.map +1 -1
- package/dist/repo-config.d.ts +1 -1
- package/dist/repo-config.d.ts.map +1 -1
- package/dist/repo-config.js +35 -0
- package/dist/repo-config.js.map +1 -1
- package/dist/tasking.d.ts +46 -1
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +48 -21
- package/dist/tasking.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +75 -28
- package/src/config.ts +96 -9
- package/src/do.ts +30 -10
- package/src/env-config.ts +25 -3
- package/src/intake-trigger-template.ts +263 -143
- package/src/intake.ts +136 -18
- package/src/placement.ts +42 -36
- package/src/repo-config.ts +35 -0
- package/src/tasking.ts +75 -43
package/src/intake.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {existsSync, mkdirSync, readFileSync, writeFileSync} from 'node:fs';
|
|
2
2
|
import {dirname, join} from 'node:path';
|
|
3
3
|
import {runAsync, type RunResult} from './git.js';
|
|
4
|
-
import {workFolderRel, workFolderPrefix
|
|
4
|
+
import {workFolderRel, workFolderPrefix} from './work-layout.js';
|
|
5
5
|
import {paramCase} from './brand.js';
|
|
6
6
|
import {
|
|
7
7
|
performIntegration,
|
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
} from './integration-core.js';
|
|
10
10
|
import type {IntegrateResult, ReviewProvider} from './integrator.js';
|
|
11
11
|
import {integrationFromFlags} from './complete.js';
|
|
12
|
-
import type {IntegrationMode, SpecsLandIn} from './config.js';
|
|
12
|
+
import type {IntegrationMode, SpecsLandIn, TasksLandIn} from './config.js';
|
|
13
13
|
import type {OriginTrust} from './frontmatter.js';
|
|
14
|
+
import {TASK_PLACEMENT_SLOTS, landingToSide} from './tasking.js';
|
|
14
15
|
import {
|
|
15
16
|
placementFolder,
|
|
16
17
|
resolvePlacement,
|
|
@@ -284,11 +285,47 @@ export interface PerformIntakeOptions {
|
|
|
284
285
|
* the PR opens normally.
|
|
285
286
|
*/
|
|
286
287
|
noPR?: boolean;
|
|
288
|
+
/**
|
|
289
|
+
* **The per-repo TASK-PLACEMENT default, passed IN** (spec
|
|
290
|
+
* `untrusted-origin-carries-via-stamp-intake-placement-symmetry-and-ci-gate-resolution`
|
|
291
|
+
* US #2, governing ADR `untrusted-origin-carries-via-stamp-not-forced-staging`).
|
|
292
|
+
* The resolved per-repo default landing for `intake`-authored TASKS emitted
|
|
293
|
+
* DIRECTLY from an issue (`backlog` = staging; `ready` = the agent pool), fed
|
|
294
|
+
* as the CONFIGURED-DEFAULT rung into the shared placement resolver
|
|
295
|
+
* (`src/placement.ts`) via the SAME task slots + `landingToSide` mapping the
|
|
296
|
+
* tasker (`tasking.ts`) uses — so the intake TASK emit reaches PARITY with the
|
|
297
|
+
* intake SPEC emit (both route through the resolver, both stamp origin-trust).
|
|
298
|
+
* Unset ⇒ the resolver's built-in floor applies (`staging` = `tasks/backlog/`,
|
|
299
|
+
* the conservative landing). The TASK twin of {@link specsLandIn}.
|
|
300
|
+
*/
|
|
301
|
+
tasksLandIn?: TasksLandIn;
|
|
302
|
+
/**
|
|
303
|
+
* **The per-repo UNTRUSTED-side TASK-PLACEMENT default, passed IN** (spec
|
|
304
|
+
* `untrusted-origin-carries-via-stamp-intake-placement-symmetry-and-ci-gate-resolution`
|
|
305
|
+
* US #5, governing ADR `untrusted-origin-carries-via-stamp-not-forced-staging`).
|
|
306
|
+
* The TWIN of {@link tasksLandIn} selected when the intake stamp is
|
|
307
|
+
* `originTrust: untrusted`: {@link dispatchTask} reads the stamp and feeds THIS
|
|
308
|
+
* default (rather than {@link tasksLandIn}) as the configured-default rung of
|
|
309
|
+
* the shared placement resolver. The resolver no longer has an
|
|
310
|
+
* untrusted-forces-staging rung; DEFAULTS to staging (`backlog`), opt-in
|
|
311
|
+
* `ready` (safety then via the task's carried stamp, not the folder). The TASK
|
|
312
|
+
* twin of {@link untrustedSpecsLandIn}; the SAME knob the tasker consults for
|
|
313
|
+
* an untrusted-origin spec's downstream tasks (one policy, both call sites).
|
|
314
|
+
*/
|
|
315
|
+
untrustedTasksLandIn?: TasksLandIn;
|
|
316
|
+
/**
|
|
317
|
+
* **The OPERATOR's EXPLICIT task-placement override** (the TOP precedence
|
|
318
|
+
* rung). When set, the runner-deterministic resolver lands the emitted task
|
|
319
|
+
* HERE regardless of {@link tasksLandIn} / {@link untrustedTasksLandIn} — the
|
|
320
|
+
* TASK twin of {@link explicitSpecsLandIn}. Set ONLY when the operator typed
|
|
321
|
+
* the explicit placement flag; never when the value came from config.
|
|
322
|
+
*/
|
|
323
|
+
explicitTasksLandIn?: TasksLandIn;
|
|
287
324
|
/**
|
|
288
325
|
* **The per-repo SPEC-PLACEMENT default, passed IN** (spec
|
|
289
326
|
* `staging-pool-position-gate-and-trust-model` US #2/#5, task
|
|
290
327
|
* `pre-prd-staging-pool-split-and-untrusted-prd-placement`). The resolved
|
|
291
|
-
* per-repo default landing for `intake`-authored prds (`
|
|
328
|
+
* per-repo default landing for `intake`-authored prds (`proposed` =
|
|
292
329
|
* staging; `ready` = the auto-tasking pool), fed as the CONFIGURED-DEFAULT rung
|
|
293
330
|
* into the shared placement resolver (`src/placement.ts`). The resolver
|
|
294
331
|
* overlays an EXPLICIT operator flag ({@link explicitSpecsLandIn}, top) and
|
|
@@ -298,11 +335,24 @@ export interface PerformIntakeOptions {
|
|
|
298
335
|
* path — one resolver, two lifecycles.
|
|
299
336
|
*/
|
|
300
337
|
specsLandIn?: SpecsLandIn;
|
|
338
|
+
/**
|
|
339
|
+
* **The per-repo UNTRUSTED-side SPEC-PLACEMENT default, passed IN** (spec
|
|
340
|
+
* `untrusted-origin-carries-via-stamp-intake-placement-symmetry-and-ci-gate-resolution`
|
|
341
|
+
* US #7, governing ADR
|
|
342
|
+
* `untrusted-origin-carries-via-stamp-not-forced-staging`). The TWIN of
|
|
343
|
+
* {@link specsLandIn} selected when the intake stamp is
|
|
344
|
+
* `originTrust: untrusted`: the spec dispatch reads the stamp and feeds THIS
|
|
345
|
+
* default (rather than {@link specsLandIn}) as the configured-default rung of
|
|
346
|
+
* the shared placement resolver. The resolver no longer has an
|
|
347
|
+
* untrusted-forces-staging rung; DEFAULTS to staging (`proposed`), opt-in
|
|
348
|
+
* `ready` (safety then via the tasks' carried stamp, not the folder).
|
|
349
|
+
*/
|
|
350
|
+
untrustedSpecsLandIn?: SpecsLandIn;
|
|
301
351
|
/**
|
|
302
352
|
* **The OPERATOR's EXPLICIT spec-placement override** (the TOP precedence
|
|
303
353
|
* rung). When set, the runner-deterministic resolver lands the spec HERE
|
|
304
|
-
* regardless of
|
|
305
|
-
* analogue of `explicitMerge` overriding the untrusted-origin
|
|
354
|
+
* regardless of {@link specsLandIn} / {@link untrustedSpecsLandIn} — the
|
|
355
|
+
* positional analogue of `explicitMerge` overriding the untrusted-origin
|
|
306
356
|
* build-propose rule ("the operator is present; CLI always wins, no
|
|
307
357
|
* special force-key"). Set ONLY when the operator typed
|
|
308
358
|
* `--specs-land-in <where>`; never when the value came from config.
|
|
@@ -374,7 +424,7 @@ const SPEC_PLACEMENT_SLOTS: PlacementSlots = {
|
|
|
374
424
|
};
|
|
375
425
|
|
|
376
426
|
/**
|
|
377
|
-
* Map the `specsLandIn` value spelling (`
|
|
427
|
+
* Map the `specsLandIn` value spelling (`proposed` | `ready`) onto the
|
|
378
428
|
* resolver's lifecycle-generic side enum (`staging` | `pool`). Returns
|
|
379
429
|
* `undefined` when no value is set, so the resolver's next precedence rung
|
|
380
430
|
* applies (the built-in floor). The spec twin of `landingToSide` on the
|
|
@@ -383,7 +433,7 @@ const SPEC_PLACEMENT_SLOTS: PlacementSlots = {
|
|
|
383
433
|
function specLandingToSide(
|
|
384
434
|
landing: SpecsLandIn | undefined,
|
|
385
435
|
): 'staging' | 'pool' | undefined {
|
|
386
|
-
if (landing === '
|
|
436
|
+
if (landing === 'proposed') return 'staging';
|
|
387
437
|
if (landing === 'ready') return 'pool';
|
|
388
438
|
return undefined;
|
|
389
439
|
}
|
|
@@ -858,6 +908,17 @@ async function decideAndDispatch(
|
|
|
858
908
|
// not laundered. Unset ⇒ unstamped (a local intake ⇒ human/trusted).
|
|
859
909
|
originTrust: options.originTrust,
|
|
860
910
|
noPR: options.noPR,
|
|
911
|
+
// RUNNER-DETERMINISTIC PLACEMENT for the DIRECT-from-issue task (ADR
|
|
912
|
+
// `untrusted-origin-carries-via-stamp-not-forced-staging`): the
|
|
913
|
+
// configured-default + explicit-flag rungs, fed into the SHARED placement
|
|
914
|
+
// resolver alongside the `originTrust` stamp above — the TASK twin of the
|
|
915
|
+
// spec dispatch below. `dispatchTask` selects the untrusted-side default
|
|
916
|
+
// (`untrustedTasksLandIn`) when the stamp is `untrusted`, else
|
|
917
|
+
// `tasksLandIn`; the resolver decides `tasks/backlog/` (staging) vs
|
|
918
|
+
// `tasks/ready/` (the pool). `intake` never places itself.
|
|
919
|
+
tasksLandIn: options.tasksLandIn,
|
|
920
|
+
untrustedTasksLandIn: options.untrustedTasksLandIn,
|
|
921
|
+
explicitTasksLandIn: options.explicitTasksLandIn,
|
|
861
922
|
providerInstance: options.providerInstance,
|
|
862
923
|
issueProvider,
|
|
863
924
|
// The bounded lone-task review seam (tests inject a canned verdict;
|
|
@@ -893,6 +954,10 @@ async function decideAndDispatch(
|
|
|
893
954
|
// `specs/proposed/` (staging) vs `specs/ready/` (the tasking pool); `intake` never
|
|
894
955
|
// places itself.
|
|
895
956
|
specsLandIn: options.specsLandIn,
|
|
957
|
+
// The UNTRUSTED-side default, selected in `dispatchSpec` when the
|
|
958
|
+
// `originTrust` stamp is `untrusted` (ADR
|
|
959
|
+
// `untrusted-origin-carries-via-stamp-not-forced-staging`).
|
|
960
|
+
untrustedSpecsLandIn: options.untrustedSpecsLandIn,
|
|
896
961
|
explicitSpecsLandIn: options.explicitSpecsLandIn,
|
|
897
962
|
providerInstance: options.providerInstance,
|
|
898
963
|
issueProvider,
|
|
@@ -1065,6 +1130,16 @@ async function dispatchTask(params: {
|
|
|
1065
1130
|
/** The origin-trust stamp passed IN (unset ⇒ emit unstamped ⇒ human/trusted). */
|
|
1066
1131
|
originTrust: OriginTrust | undefined;
|
|
1067
1132
|
noPR: boolean | undefined;
|
|
1133
|
+
/** The per-repo TRUSTED-side TASK-PLACEMENT default (configured-default rung when the task is trusted/unset). */
|
|
1134
|
+
tasksLandIn: TasksLandIn | undefined;
|
|
1135
|
+
/**
|
|
1136
|
+
* The per-repo UNTRUSTED-side TASK-PLACEMENT default, selected as the
|
|
1137
|
+
* configured-default rung when `originTrust` is `untrusted` (ADR
|
|
1138
|
+
* `untrusted-origin-carries-via-stamp-not-forced-staging`).
|
|
1139
|
+
*/
|
|
1140
|
+
untrustedTasksLandIn: TasksLandIn | undefined;
|
|
1141
|
+
/** The OPERATOR's EXPLICIT task-placement override (the TOP rung). */
|
|
1142
|
+
explicitTasksLandIn: TasksLandIn | undefined;
|
|
1068
1143
|
providerInstance: ReviewProvider | undefined;
|
|
1069
1144
|
/** The issue seam the completion comment is posted back through (runner-owned). */
|
|
1070
1145
|
issueProvider: IssueProvider;
|
|
@@ -1087,6 +1162,9 @@ async function dispatchTask(params: {
|
|
|
1087
1162
|
mergeRetries,
|
|
1088
1163
|
originTrust,
|
|
1089
1164
|
noPR,
|
|
1165
|
+
tasksLandIn,
|
|
1166
|
+
untrustedTasksLandIn,
|
|
1167
|
+
explicitTasksLandIn,
|
|
1090
1168
|
providerInstance,
|
|
1091
1169
|
issueProvider,
|
|
1092
1170
|
reviewTask,
|
|
@@ -1107,7 +1185,33 @@ async function dispatchTask(params: {
|
|
|
1107
1185
|
note(message);
|
|
1108
1186
|
return {exitCode: 1, outcome: 'usage-error', issueNumber, message};
|
|
1109
1187
|
}
|
|
1110
|
-
|
|
1188
|
+
// RUNNER-DETERMINISTIC PLACEMENT (task `intake-task-placement-symmetry`; the
|
|
1189
|
+
// untrusted-forces-staging rung RETIRED by ADR
|
|
1190
|
+
// `untrusted-origin-carries-via-stamp-not-forced-staging`). Resolve which
|
|
1191
|
+
// folder the runner writes the intake-authored task into BEFORE handing it to
|
|
1192
|
+
// the shared integrate band — the SAME precedence chain, the SAME shared
|
|
1193
|
+
// resolver, and (via `TASK_PLACEMENT_SLOTS` + `landingToSide` reused from
|
|
1194
|
+
// `tasking.ts`) the SAME task slots the tasker uses; only the intake dispatch
|
|
1195
|
+
// differs from the spec twin (`dispatchSpec`) in its lifecycle SLOTS. THIS
|
|
1196
|
+
// caller selects the trusted-vs-untrusted configured default by reading the
|
|
1197
|
+
// `originTrust` stamp: an untrusted task selects `untrustedTasksLandIn`
|
|
1198
|
+
// (default staging; `ready` when configured), a trusted/unset task selects
|
|
1199
|
+
// `tasksLandIn`. The agent (the intake decider) never influences placement.
|
|
1200
|
+
// Net vs the old hardcoded `tasks-ready`: an untrusted-author task now MERGES
|
|
1201
|
+
// a document to `main` in `backlog` (by default) carrying the stamp instead of
|
|
1202
|
+
// opening a document PR; safety for a `ready`-landed untrusted task flows
|
|
1203
|
+
// through its carried stamp at BUILD time (forces a code PR), not the folder.
|
|
1204
|
+
const configuredTasksLanding =
|
|
1205
|
+
originTrust === 'untrusted' ? untrustedTasksLandIn : tasksLandIn;
|
|
1206
|
+
const placementDecision = resolvePlacement({
|
|
1207
|
+
explicit: landingToSide(explicitTasksLandIn),
|
|
1208
|
+
configuredDefault: landingToSide(configuredTasksLanding),
|
|
1209
|
+
});
|
|
1210
|
+
const placementDir = placementFolder(
|
|
1211
|
+
TASK_PLACEMENT_SLOTS,
|
|
1212
|
+
placementDecision.choice,
|
|
1213
|
+
);
|
|
1214
|
+
const relPath = `${placementDir}/${slug}.md`;
|
|
1111
1215
|
|
|
1112
1216
|
// BOUNDED INTERNAL REVIEW (observation
|
|
1113
1217
|
// `intake-lone-task-skips-adversarial-review-the-prd-path-gets`, rulings A/B/C):
|
|
@@ -1267,8 +1371,14 @@ async function dispatchSpec(params: {
|
|
|
1267
1371
|
/** The origin-trust stamp passed IN (unset ⇒ emit unstamped ⇒ human/trusted). */
|
|
1268
1372
|
originTrust: OriginTrust | undefined;
|
|
1269
1373
|
noPR: boolean | undefined;
|
|
1270
|
-
/** The per-repo SPEC-PLACEMENT default (configured-default rung
|
|
1374
|
+
/** The per-repo TRUSTED-side SPEC-PLACEMENT default (configured-default rung when the spec is trusted/unset). */
|
|
1271
1375
|
specsLandIn: SpecsLandIn | undefined;
|
|
1376
|
+
/**
|
|
1377
|
+
* The per-repo UNTRUSTED-side SPEC-PLACEMENT default, selected as the
|
|
1378
|
+
* configured-default rung when `originTrust` is `untrusted` (ADR
|
|
1379
|
+
* `untrusted-origin-carries-via-stamp-not-forced-staging`).
|
|
1380
|
+
*/
|
|
1381
|
+
untrustedSpecsLandIn: SpecsLandIn | undefined;
|
|
1272
1382
|
/** The OPERATOR's EXPLICIT spec-placement override (the TOP rung). */
|
|
1273
1383
|
explicitSpecsLandIn: SpecsLandIn | undefined;
|
|
1274
1384
|
providerInstance: ReviewProvider | undefined;
|
|
@@ -1289,6 +1399,7 @@ async function dispatchSpec(params: {
|
|
|
1289
1399
|
originTrust,
|
|
1290
1400
|
noPR,
|
|
1291
1401
|
specsLandIn,
|
|
1402
|
+
untrustedSpecsLandIn,
|
|
1292
1403
|
explicitSpecsLandIn,
|
|
1293
1404
|
providerInstance,
|
|
1294
1405
|
issueProvider,
|
|
@@ -1308,18 +1419,25 @@ async function dispatchSpec(params: {
|
|
|
1308
1419
|
return {exitCode: 1, outcome: 'usage-error', issueNumber, message};
|
|
1309
1420
|
}
|
|
1310
1421
|
// RUNNER-DETERMINISTIC PLACEMENT (task
|
|
1311
|
-
// `pre-prd-staging-pool-split-and-untrusted-prd-placement
|
|
1312
|
-
//
|
|
1313
|
-
//
|
|
1314
|
-
//
|
|
1315
|
-
//
|
|
1316
|
-
//
|
|
1422
|
+
// `pre-prd-staging-pool-split-and-untrusted-prd-placement`; the
|
|
1423
|
+
// untrusted-forces-staging rung RETIRED by ADR
|
|
1424
|
+
// `untrusted-origin-carries-via-stamp-not-forced-staging`). Resolve which
|
|
1425
|
+
// folder the runner writes the intake-authored spec into BEFORE handing it to
|
|
1426
|
+
// the shared integrate band: the SAME precedence chain the tasker uses
|
|
1427
|
+
// (`explicit > configured default > built-in (staging)`), the SAME shared
|
|
1428
|
+
// resolver — only the lifecycle SLOTS differ. THIS caller selects the
|
|
1429
|
+
// trusted-vs-untrusted configured default by reading the `originTrust` stamp:
|
|
1430
|
+
// an untrusted spec selects `untrustedSpecsLandIn` (default staging; `ready`
|
|
1431
|
+
// when configured), a trusted/unset spec selects `specsLandIn`. The agent
|
|
1317
1432
|
// (the intake decider) never influences placement; it returns the verdict and
|
|
1318
|
-
// the runner computes the destination from unforgeable inputs.
|
|
1433
|
+
// the runner computes the destination from unforgeable inputs. Safety for an
|
|
1434
|
+
// untrusted spec landing in `ready` flows through its tasks' carried stamp,
|
|
1435
|
+
// not the folder.
|
|
1436
|
+
const configuredSpecsLanding =
|
|
1437
|
+
originTrust === 'untrusted' ? untrustedSpecsLandIn : specsLandIn;
|
|
1319
1438
|
const placementDecision = resolvePlacement({
|
|
1320
1439
|
explicit: specLandingToSide(explicitSpecsLandIn),
|
|
1321
|
-
|
|
1322
|
-
configuredDefault: specLandingToSide(specsLandIn),
|
|
1440
|
+
configuredDefault: specLandingToSide(configuredSpecsLanding),
|
|
1323
1441
|
});
|
|
1324
1442
|
const placementDir = placementFolder(
|
|
1325
1443
|
SPEC_PLACEMENT_SLOTS,
|
package/src/placement.ts
CHANGED
|
@@ -6,23 +6,28 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Pure function from UNFORGEABLE inputs to a staging-vs-pool destination:
|
|
8
8
|
*
|
|
9
|
-
* explicit operator flag >
|
|
10
|
-
* configured default > built-in
|
|
9
|
+
* explicit operator flag > configured default > built-in
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* the
|
|
17
|
-
*
|
|
18
|
-
*
|
|
11
|
+
* Author-trust is NO LONGER a rung here (ADR
|
|
12
|
+
* `untrusted-origin-carries-via-stamp-not-forced-staging`). This resolver was
|
|
13
|
+
* once the POSITIONAL twin of the untrusted-origin BUILD-propose rule in
|
|
14
|
+
* `integration-core.ts` (an `originTrust: untrusted ⇒ staging` rung sitting
|
|
15
|
+
* above the configured default). That rung is REMOVED: it made "untrusted
|
|
16
|
+
* lands in `ready`" inexpressible, and its safety was redundant with the
|
|
17
|
+
* build-time propose rule (an untrusted item in the pool still cannot become
|
|
18
|
+
* merged CODE without human review). The trusted-vs-untrusted destination is
|
|
19
|
+
* now selected BY THE CALLER, which reads the stamp and picks the matching
|
|
20
|
+
* configured default (`untrusted*LandIn` vs `*LandIn`) BEFORE calling — so
|
|
21
|
+
* this resolver stays a pure precedence over caller-supplied inputs. The
|
|
22
|
+
* agent cannot influence placement — it resolves runner-side from the resolved
|
|
23
|
+
* policy + the operator's explicit flag.
|
|
19
24
|
*
|
|
20
25
|
* LIFECYCLE-GENERIC. The folder names + the configured-default value are
|
|
21
26
|
* PARAMETERS (`slots`, `configuredDefault`), so the same resolver serves the
|
|
22
27
|
* TASK lifecycle (`tasksLandIn`: `backlog`/`ready` — the POOL value was
|
|
23
28
|
* renamed `'backlog'` → `'todo'` → `'ready'`, ADR
|
|
24
29
|
* `rename-task-pool-folder-todo-to-ready`) AND the SPEC-
|
|
25
|
-
* placement lifecycle (`specsLandIn`: `
|
|
30
|
+
* placement lifecycle (`specsLandIn`: `proposed`/`ready`) without forking. A future
|
|
26
31
|
* lifecycle (e.g. intake's lone-task) plugs its own `slots` in and reuses the
|
|
27
32
|
* exact precedence — no second implementation.
|
|
28
33
|
*/
|
|
@@ -54,34 +59,37 @@ export interface PlacementSlots {
|
|
|
54
59
|
export interface ResolvePlacementInput {
|
|
55
60
|
/**
|
|
56
61
|
* The operator's EXPLICIT override (the TOP of the chain) — when set, it
|
|
57
|
-
* wins over the
|
|
58
|
-
* `
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* rung is skipped and the next one applies.
|
|
62
|
+
* wins over the configured default. Mirrors `integration-core.ts`'s
|
|
63
|
+
* `explicitMerge` "operator is present; CLI always wins, no special
|
|
64
|
+
* force-key" shape. Unset (`undefined`) ⇒ this rung is skipped and the next
|
|
65
|
+
* one applies.
|
|
62
66
|
*/
|
|
63
67
|
explicit?: PlacementSide;
|
|
64
68
|
/**
|
|
65
|
-
* The
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
* The repo's resolved configured DEFAULT landing (`tasksLandIn` /
|
|
74
|
-
* `specsLandIn`). Caller resolves it like the existing `taskingIntegration`
|
|
75
|
-
* (flag > env > per-repo > global > built-in) and passes the result; this
|
|
76
|
-
* resolver just consumes it.
|
|
69
|
+
* The repo's resolved configured DEFAULT landing, ALREADY selected by the
|
|
70
|
+
* caller for the item's author-trust: the caller reads the `originTrust:`
|
|
71
|
+
* stamp and passes the `untrusted*LandIn` default for an untrusted item, or
|
|
72
|
+
* the trusted `*LandIn` default otherwise (ADR
|
|
73
|
+
* `untrusted-origin-carries-via-stamp-not-forced-staging`). Resolved like the
|
|
74
|
+
* existing `taskingIntegration` (flag > env > per-repo > global > built-in)
|
|
75
|
+
* and mapped to a {@link PlacementSide}; this resolver just consumes it. Unset
|
|
76
|
+
* ⇒ the built-in floor applies.
|
|
77
77
|
*/
|
|
78
78
|
configuredDefault?: PlacementSide;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* The resolved choice + which precedence rung won (for honest reporting).
|
|
83
|
+
*
|
|
84
|
+
* The `'untrusted-origin'` reason is RETIRED (ADR
|
|
85
|
+
* `untrusted-origin-carries-via-stamp-not-forced-staging`): author-trust no
|
|
86
|
+
* longer decides POSITION inside this resolver, so an untrusted item that lands
|
|
87
|
+
* in staging now does so via `'configured-default'` (its caller selected the
|
|
88
|
+
* `untrusted*LandIn` default), not via a distinct trust rung.
|
|
89
|
+
*/
|
|
82
90
|
export interface PlacementResult {
|
|
83
91
|
choice: PlacementSide;
|
|
84
|
-
reason: 'explicit' | '
|
|
92
|
+
reason: 'explicit' | 'configured-default' | 'built-in';
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
/**
|
|
@@ -97,11 +105,12 @@ const BUILT_IN_FLOOR: PlacementSide = 'staging';
|
|
|
97
105
|
* Resolve the staging-vs-pool placement from unforgeable inputs, via the fixed
|
|
98
106
|
* precedence chain:
|
|
99
107
|
*
|
|
100
|
-
* explicit >
|
|
108
|
+
* explicit > configured default > built-in
|
|
101
109
|
*
|
|
102
|
-
* Pure: no I/O, no env reads — the caller resolves config
|
|
103
|
-
*
|
|
104
|
-
*
|
|
110
|
+
* Pure: no I/O, no env reads, NO trust rung — the caller resolves config
|
|
111
|
+
* (INCLUDING selecting the trusted-vs-untrusted default from the stamp) and
|
|
112
|
+
* passes the result in. Reused by every lifecycle (task + spec placement +
|
|
113
|
+
* future intake variants) so a precedence change touches ONE place.
|
|
105
114
|
*/
|
|
106
115
|
export function resolvePlacement(
|
|
107
116
|
input: ResolvePlacementInput,
|
|
@@ -109,9 +118,6 @@ export function resolvePlacement(
|
|
|
109
118
|
if (input.explicit !== undefined) {
|
|
110
119
|
return {choice: input.explicit, reason: 'explicit'};
|
|
111
120
|
}
|
|
112
|
-
if (input.originTrust === 'untrusted') {
|
|
113
|
-
return {choice: 'staging', reason: 'untrusted-origin'};
|
|
114
|
-
}
|
|
115
121
|
if (input.configuredDefault !== undefined) {
|
|
116
122
|
return {choice: input.configuredDefault, reason: 'configured-default'};
|
|
117
123
|
}
|
package/src/repo-config.ts
CHANGED
|
@@ -72,6 +72,22 @@ export const REPO_ALLOWED_KEYS = [
|
|
|
72
72
|
// intake's per-EMITTED-TYPE `{task, spec}` resolver (front door, author-trust):
|
|
73
73
|
// this is a per-lifecycle-transition knob, inside the boundary, config-resolved.
|
|
74
74
|
'taskingIntegration',
|
|
75
|
+
// `intakeIntegration` (the per-TRANSITION INTAKE-DOCUMENT override — the twin of
|
|
76
|
+
// `taskingIntegration` for the intake front door) is a genuine repo property
|
|
77
|
+
// exactly like `taskingIntegration`/`integration`: whether THIS repo's intake
|
|
78
|
+
// front door emits a task/spec DOCUMENT straight onto `main` (merge, no PR) or
|
|
79
|
+
// as a reviewable PR is agreed by all collaborators + travels with the repo.
|
|
80
|
+
// Resolved per-repo through the SAME chain as `taskingIntegration` (flag > env
|
|
81
|
+
// `DORFL_INTAKE_INTEGRATION` > per-repo > global), then falls back to
|
|
82
|
+
// `integration` when unset. DECOUPLED from the autonomy gates
|
|
83
|
+
// (`autoBuild`/`autoTask`): the document PR-mode is operator/config, not a
|
|
84
|
+
// function of autonomy (ADR untrusted-origin-carries-via-stamp-not-forced-staging).
|
|
85
|
+
// A SINGLE value applies to both the task AND the spec document (US #1 chose one
|
|
86
|
+
// knob, not a per-type split). DISTINCT from `taskingIntegration` (a DIFFERENT
|
|
87
|
+
// lifecycle transition) — the third member of the per-transition integration
|
|
88
|
+
// family (`integration` build / `taskingIntegration` tasking / `intakeIntegration`
|
|
89
|
+
// intake), each falling back to `integration`, none tied to a gate.
|
|
90
|
+
'intakeIntegration',
|
|
75
91
|
// `tasksLandIn` (the per-repo TASK-PLACEMENT default — staging vs pool, spec
|
|
76
92
|
// `staging-pool-position-gate-and-trust-model` US #5) is a genuine repo property
|
|
77
93
|
// exactly like `taskingIntegration`/`integration`: whether THIS repo's tasker
|
|
@@ -96,6 +112,25 @@ export const REPO_ALLOWED_KEYS = [
|
|
|
96
112
|
// (`src/placement.ts`). The sole spec-placement key after the ''prd'' → `spec` HARD
|
|
97
113
|
// CUTOVER (the legacy `prdsLandIn` key is GONE — clean break, no accepted alias).
|
|
98
114
|
'specsLandIn',
|
|
115
|
+
// `untrustedTasksLandIn` (the UNTRUSTED-side TWIN of `tasksLandIn` — spec
|
|
116
|
+
// `untrusted-origin-carries-via-stamp-intake-placement-symmetry-and-ci-gate-resolution`
|
|
117
|
+
// US #5/#6, ADR `untrusted-origin-carries-via-stamp-not-forced-staging`) is a
|
|
118
|
+
// genuine repo property exactly like `tasksLandIn`: whether THIS repo's
|
|
119
|
+
// untrusted-origin tasks land STAGED (`tasks/backlog/`, human promotion) or in
|
|
120
|
+
// the agent POOL (`tasks/ready/`, claimable — safety then via the carried build
|
|
121
|
+
// STAMP) is a repo-wide policy agreed by all collaborators + travels with the
|
|
122
|
+
// repo. Resolved per-repo through the SAME chain as `tasksLandIn` (flag
|
|
123
|
+
// `--untrusted-tasks-land-in` > env `DORFL_UNTRUSTED_TASKS_LAND_IN` > per-repo >
|
|
124
|
+
// global > built-in `backlog`). The caller selects it over the trusted twin by
|
|
125
|
+
// reading the `originTrust` stamp before the placement resolver.
|
|
126
|
+
'untrustedTasksLandIn',
|
|
127
|
+
// `untrustedSpecsLandIn` (the UNTRUSTED-side TWIN of `specsLandIn` — same spec
|
|
128
|
+
// US #7) is a genuine repo property exactly like `specsLandIn`: whether THIS
|
|
129
|
+
// repo's untrusted intake specs land STAGED (`specs/proposed/`) or in the
|
|
130
|
+
// auto-tasking POOL (`specs/ready/`) is a repo-wide policy. Resolved per-repo
|
|
131
|
+
// through the SAME chain (flag `--untrusted-specs-land-in` > env
|
|
132
|
+
// `DORFL_UNTRUSTED_SPECS_LAND_IN` > per-repo > global > built-in `proposed`).
|
|
133
|
+
'untrustedSpecsLandIn',
|
|
99
134
|
// `noPR` (the PR-INTENT axis — push the branch but deliberately skip the PR) is
|
|
100
135
|
// a genuine repo property exactly like `integration`/`review`: whether this
|
|
101
136
|
// repo's propose runs open a PR is agreed by all collaborators + travels with
|
package/src/tasking.ts
CHANGED
|
@@ -271,6 +271,25 @@ export interface PerformTaskOptions {
|
|
|
271
271
|
* landing that preserves zero behaviour change for the normal path).
|
|
272
272
|
*/
|
|
273
273
|
tasksLandIn?: 'backlog' | 'ready';
|
|
274
|
+
/**
|
|
275
|
+
* **The per-repo UNTRUSTED-side TASK-PLACEMENT default** (spec
|
|
276
|
+
* `untrusted-origin-carries-via-stamp-intake-placement-symmetry-and-ci-gate-resolution`
|
|
277
|
+
* US #6/#10, governing ADR
|
|
278
|
+
* `untrusted-origin-carries-via-stamp-not-forced-staging`). The TWIN of
|
|
279
|
+
* {@link tasksLandIn} selected when the SPEC being tasked carries an
|
|
280
|
+
* `originTrust: untrusted` stamp: `performTask` reads the stamp and feeds
|
|
281
|
+
* THIS default (rather than {@link tasksLandIn}) as the configured-default
|
|
282
|
+
* rung of the placement resolver. This is the same knob intake uses for a
|
|
283
|
+
* task emitted directly from an issue (decision X: one policy governs an
|
|
284
|
+
* untrusted task's destination regardless of birth path). Resolved per-repo
|
|
285
|
+
* like {@link tasksLandIn} (flag `--untrusted-tasks-land-in` > env
|
|
286
|
+
* `DORFL_UNTRUSTED_TASKS_LAND_IN` > per-repo > global > built-in `backlog`).
|
|
287
|
+
* Unset ⇒ the resolver's built-in floor (`staging`) applies. The resolver no
|
|
288
|
+
* longer has an untrusted-forces-staging rung; safety for an untrusted task
|
|
289
|
+
* that lands in `ready` here is the carried stamp (its BUILD is forced to a
|
|
290
|
+
* code PR), NOT the folder.
|
|
291
|
+
*/
|
|
292
|
+
untrustedTasksLandIn?: 'backlog' | 'ready';
|
|
274
293
|
/**
|
|
275
294
|
* **The OPERATOR's EXPLICIT task-placement override** (the TOP precedence
|
|
276
295
|
* rung). When set, the runner-deterministic resolver lands the tasks HERE
|
|
@@ -353,8 +372,14 @@ export const STAGED_TASKS_DIR = workFolderRel('tasks-backlog');
|
|
|
353
372
|
*/
|
|
354
373
|
const POOL_TASKS_DIR = workFolderRel('tasks-ready');
|
|
355
374
|
|
|
356
|
-
/**
|
|
357
|
-
|
|
375
|
+
/**
|
|
376
|
+
* The placement slots for the TASK lifecycle (folder names). EXPORTED so the
|
|
377
|
+
* intake TASK emit (`intake.ts` `dispatchTask`) reuses the SAME slots + resolver
|
|
378
|
+
* the tasker uses, reaching parity with the intake SPEC emit (ADR
|
|
379
|
+
* `untrusted-origin-carries-via-stamp-not-forced-staging`) WITHOUT forking a
|
|
380
|
+
* second copy of the task folder names.
|
|
381
|
+
*/
|
|
382
|
+
export const TASK_PLACEMENT_SLOTS = {
|
|
358
383
|
staging: STAGED_TASKS_DIR,
|
|
359
384
|
pool: POOL_TASKS_DIR,
|
|
360
385
|
} as const;
|
|
@@ -367,9 +392,11 @@ const TASK_PLACEMENT_SLOTS = {
|
|
|
367
392
|
* are NOT accepted (clean break — the staging value was renamed
|
|
368
393
|
* `'pre-backlog'` → `'backlog'`, and the pool value was renamed
|
|
369
394
|
* `'backlog'` → `'todo'` → `'ready'`, ADR
|
|
370
|
-
* `rename-task-pool-folder-todo-to-ready`).
|
|
395
|
+
* `rename-task-pool-folder-todo-to-ready`). EXPORTED alongside
|
|
396
|
+
* {@link TASK_PLACEMENT_SLOTS} so the intake TASK emit reuses this exact mapping
|
|
397
|
+
* (the task twin of `specLandingToSide` on the intake spec path).
|
|
371
398
|
*/
|
|
372
|
-
function landingToSide(
|
|
399
|
+
export function landingToSide(
|
|
373
400
|
landing: 'backlog' | 'ready' | undefined,
|
|
374
401
|
): 'staging' | 'pool' | undefined {
|
|
375
402
|
if (landing === 'backlog') return 'staging';
|
|
@@ -593,17 +620,28 @@ export async function performTask(
|
|
|
593
620
|
const stagedEmitted = newOrChangedStagedTasks(cwd, before);
|
|
594
621
|
const emitTasks = collectEmittedTasks(cwd, stagedEmitted);
|
|
595
622
|
// RUNNER-DETERMINISTIC PLACEMENT (task
|
|
596
|
-
// `runner-deterministic-slice-placement-policy-and-precedence
|
|
623
|
+
// `runner-deterministic-slice-placement-policy-and-precedence`; the
|
|
624
|
+
// untrusted-forces-staging rung was RETIRED by ADR
|
|
625
|
+
// `untrusted-origin-carries-via-stamp-not-forced-staging`). Resolve which
|
|
597
626
|
// folder the runner lands the emitted tasks in BEFORE handing them to the
|
|
598
|
-
// shared integrate band: precedence `explicit >
|
|
599
|
-
//
|
|
600
|
-
//
|
|
601
|
-
//
|
|
602
|
-
// `
|
|
627
|
+
// shared integrate band: precedence `explicit > configured default >
|
|
628
|
+
// built-in (staging)`. THIS caller selects the trusted-vs-untrusted
|
|
629
|
+
// configured default by reading the spec's propagated `originTrust:` stamp:
|
|
630
|
+
// an untrusted-origin spec selects `untrustedTasksLandIn` (default staging;
|
|
631
|
+
// `ready` when configured), a trusted/unset spec selects `tasksLandIn`. One
|
|
632
|
+
// knob (`untrustedTasksLandIn`) governs an untrusted task's destination
|
|
633
|
+
// whether born from an issue directly (intake) or from an untrusted spec
|
|
634
|
+
// here. The agent NEVER influences this; it always writes to
|
|
635
|
+
// `work/tasks/backlog/`, and the runner redirects at `stage()` time. Safety
|
|
636
|
+
// for an untrusted task landing in `ready` is the carried stamp (its BUILD is
|
|
637
|
+
// forced to a code PR), not the folder.
|
|
638
|
+
const configuredTasksLanding =
|
|
639
|
+
specFm.originTrust === 'untrusted'
|
|
640
|
+
? options.untrustedTasksLandIn
|
|
641
|
+
: options.tasksLandIn;
|
|
603
642
|
const placementDecision = resolvePlacement({
|
|
604
643
|
explicit: landingToSide(options.explicitTasksLandIn),
|
|
605
|
-
|
|
606
|
-
configuredDefault: landingToSide(options.tasksLandIn),
|
|
644
|
+
configuredDefault: landingToSide(configuredTasksLanding),
|
|
607
645
|
});
|
|
608
646
|
const placementDir = placementFolder(
|
|
609
647
|
TASK_PLACEMENT_SLOTS,
|
|
@@ -721,7 +759,6 @@ export async function performTask(
|
|
|
721
759
|
emitTasks,
|
|
722
760
|
poolBefore,
|
|
723
761
|
placementDir,
|
|
724
|
-
placementReason: placementDecision.reason,
|
|
725
762
|
note,
|
|
726
763
|
env,
|
|
727
764
|
}),
|
|
@@ -1033,32 +1070,19 @@ async function stageTaskingLifecycle(params: {
|
|
|
1033
1070
|
/**
|
|
1034
1071
|
* The runner-resolved destination folder (task
|
|
1035
1072
|
* `runner-deterministic-slice-placement-policy-and-precedence`). Computed
|
|
1036
|
-
* ONCE in `performTask` via the shared {@link resolvePlacement}
|
|
1037
|
-
* spec's `originTrust:` stamp
|
|
1038
|
-
*
|
|
1039
|
-
*
|
|
1040
|
-
*
|
|
1073
|
+
* ONCE in `performTask` via the shared {@link resolvePlacement}. The caller
|
|
1074
|
+
* selects the configured default from the spec's `originTrust:` stamp
|
|
1075
|
+
* (`untrustedTasksLandIn` when untrusted, else `tasksLandIn`; ADR
|
|
1076
|
+
* `untrusted-origin-carries-via-stamp-not-forced-staging`) and overlays the
|
|
1077
|
+
* operator's explicit override, then passes the result in here — so the call
|
|
1078
|
+
* site sees exactly where the emitted tasks landed (the placement decision is
|
|
1079
|
+
* not buried in the stage closure).
|
|
1041
1080
|
*/
|
|
1042
1081
|
placementDir: string;
|
|
1043
|
-
/** Which precedence rung the resolver took (for honest reporting). */
|
|
1044
|
-
placementReason:
|
|
1045
|
-
| 'explicit'
|
|
1046
|
-
| 'untrusted-origin'
|
|
1047
|
-
| 'configured-default'
|
|
1048
|
-
| 'built-in';
|
|
1049
1082
|
note: (message: string) => void;
|
|
1050
1083
|
env: NodeJS.ProcessEnv | undefined;
|
|
1051
1084
|
}): Promise<void> {
|
|
1052
|
-
const {
|
|
1053
|
-
cwd,
|
|
1054
|
-
slug,
|
|
1055
|
-
emitTasks,
|
|
1056
|
-
poolBefore,
|
|
1057
|
-
placementDir,
|
|
1058
|
-
placementReason,
|
|
1059
|
-
note,
|
|
1060
|
-
env,
|
|
1061
|
-
} = params;
|
|
1085
|
+
const {cwd, slug, emitTasks, poolBefore, placementDir, note, env} = params;
|
|
1062
1086
|
const spec = workItemRel('specs-ready', `${slug}.md`);
|
|
1063
1087
|
const specTasked = workItemRel('specs-tasked', `${slug}.md`);
|
|
1064
1088
|
// PROPAGATE the origin-trust PROVENANCE (task
|
|
@@ -1072,13 +1096,14 @@ async function stageTaskingLifecycle(params: {
|
|
|
1072
1096
|
const specProvenance = existsSync(specAbs)
|
|
1073
1097
|
? parseFrontmatter(readFileSync(specAbs, 'utf8'))
|
|
1074
1098
|
: {origin: undefined, originTrust: undefined};
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1099
|
+
// NOTE: author-trust no longer FORCES staging here (ADR
|
|
1100
|
+
// `untrusted-origin-carries-via-stamp-not-forced-staging`). An untrusted spec's
|
|
1101
|
+
// emitted tasks land per `untrustedTasksLandIn` (default staging; `ready` when
|
|
1102
|
+
// configured), selected by the caller from the stamp before `resolvePlacement`;
|
|
1103
|
+
// safety for a `ready` landing is the propagated `originTrust: untrusted` stamp
|
|
1104
|
+
// below (its BUILD is forced to a code PR), not the folder. The placement
|
|
1105
|
+
// decision itself is fully computed in `performTask`; this stage just writes to
|
|
1106
|
+
// the resolved `placementDir`.
|
|
1082
1107
|
// Move the held spec specs/ready/ -> specs/tasked/ (the TASKED resting state — folder =
|
|
1083
1108
|
// source of truth, like done/ for tasks). This is the DURABLE `specs/ready → specs/tasked`
|
|
1084
1109
|
// success move, owned by THIS transition's commit (the lock no longer moved the
|
|
@@ -1603,7 +1628,9 @@ function collectEmittedTasks(
|
|
|
1603
1628
|
* Sections (all suppressed when empty, so the body stays honest):
|
|
1604
1629
|
* 1. A lead line naming the source spec + emitted task count.
|
|
1605
1630
|
* 2. **Tasks** — one bullet per task: `slug — title`, plus `covers:` (which
|
|
1606
|
-
* spec user stories it maps to
|
|
1631
|
+
* spec user stories it maps to, as `US-<n>` so GitHub does not autolink
|
|
1632
|
+
* the index as an issue/PR reference), `blockedBy:`, and `needsAnswers`
|
|
1633
|
+
* if set.
|
|
1607
1634
|
* 3. **Dependency graph** — the KEYSTONE(S) (tasks whose `blockedBy` names no
|
|
1608
1635
|
* other task in this set, so they land first) and the `blockedBy` edges.
|
|
1609
1636
|
* 4. **Needs answers** — any open questions the tasker review→edit loop
|
|
@@ -1637,7 +1664,12 @@ export function composeTaskingProposeBody(
|
|
|
1637
1664
|
for (const t of tasks) {
|
|
1638
1665
|
lines.push(`- **${t.slug}** — ${t.title}`);
|
|
1639
1666
|
if (t.covers.length > 0) {
|
|
1640
|
-
|
|
1667
|
+
// User-story ids are rendered as `US-<n>` (NOT `US #<n>`): a bare
|
|
1668
|
+
// `#<n>` in a GitHub PR body autolinks to issue/PR #<n>, which is a
|
|
1669
|
+
// confusing false reference (the number is a spec user-story index,
|
|
1670
|
+
// not an issue). The hyphenated form carries the same meaning without
|
|
1671
|
+
// tripping GitHub's autolinker.
|
|
1672
|
+
lines.push(` - covers: ${t.covers.map((c) => `US-${c}`).join(', ')}`);
|
|
1641
1673
|
}
|
|
1642
1674
|
if (t.blockedBy.length > 0) {
|
|
1643
1675
|
lines.push(` - blockedBy: ${t.blockedBy.join(', ')}`);
|