dorfl 0.3.1 → 0.4.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-lifecycle-template.js +6 -6
- package/dist/advance-lifecycle-template.js.map +1 -1
- package/dist/brand.d.ts +1 -1
- package/dist/brand.js +1 -1
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +119 -11
- package/dist/cli.js.map +1 -1
- package/dist/config-override.d.ts +1 -1
- package/dist/do-autopick.d.ts +1 -1
- package/dist/do-config.d.ts.map +1 -1
- package/dist/do-config.js +1 -1
- package/dist/do-config.js.map +1 -1
- package/dist/env-config.d.ts +1 -1
- package/dist/env-config.js +1 -1
- package/dist/identity.d.ts +1 -1
- package/dist/install-ci.js +2 -2
- package/dist/install-ci.js.map +1 -1
- package/dist/intake-trigger-template.js +2 -2
- package/dist/lifecycle-pools.d.ts +1 -1
- package/dist/mirror-pool-scan.d.ts +2 -2
- package/dist/mirror-pool-scan.js +1 -1
- package/dist/mirror-pool-scan.js.map +1 -1
- package/dist/prd-to-spec.d.ts +8 -57
- package/dist/prd-to-spec.d.ts.map +1 -1
- package/dist/prd-to-spec.js +17 -99
- package/dist/prd-to-spec.js.map +1 -1
- package/dist/protocol/WORK-CONTRACT.md +1 -1
- package/dist/repo-config.d.ts +11 -11
- package/dist/repo-config.d.ts.map +1 -1
- package/dist/repo-config.js +9 -9
- package/dist/repo-mirror.d.ts +6 -6
- package/dist/repo-mirror.d.ts.map +1 -1
- package/dist/repo-mirror.js +6 -6
- package/dist/repo-mirror.js.map +1 -1
- package/dist/resync-protocol.d.ts +73 -0
- package/dist/resync-protocol.d.ts.map +1 -0
- package/dist/resync-protocol.js +114 -0
- package/dist/resync-protocol.js.map +1 -0
- package/dist/run.d.ts +1 -1
- package/dist/run.js +1 -1
- package/dist/run.js.map +1 -1
- package/dist/scan.d.ts +2 -2
- package/dist/scan.js +5 -5
- package/dist/scan.js.map +1 -1
- package/dist/skills/convert-from-prd-to-spec/SKILL.md +2 -2
- package/dist/skills/drive-tasks/SKILL.md +1 -1
- package/dist/skills/from-idea/SKILL.md +2 -2
- package/dist/skills/setup/SKILL.md +2 -2
- package/dist/skills/setup/protocol/WORK-CONTRACT.md +1 -1
- package/dist/verify-workflow-template.d.ts +1 -1
- package/dist/verify-workflow-template.js +1 -1
- package/package.json +1 -1
- package/src/advance-drivers.ts +1 -1
- package/src/advance-lifecycle-template.ts +6 -6
- package/src/brand.ts +1 -1
- package/src/cli.ts +168 -11
- package/src/config-override.ts +1 -1
- package/src/do-autopick.ts +1 -1
- package/src/do-config.ts +1 -1
- package/src/env-config.ts +1 -1
- package/src/identity.ts +1 -1
- package/src/install-ci.ts +2 -2
- package/src/intake-trigger-template.ts +2 -2
- package/src/lifecycle-pools.ts +1 -1
- package/src/mirror-pool-scan.ts +3 -3
- package/src/prd-to-spec.ts +26 -148
- package/src/repo-config.ts +11 -11
- package/src/repo-mirror.ts +7 -7
- package/src/resync-protocol.ts +162 -0
- package/src/run.ts +2 -2
- package/src/scan.ts +5 -5
- package/src/verify-workflow-template.ts +1 -1
package/src/mirror-pool-scan.ts
CHANGED
|
@@ -47,7 +47,7 @@ import {heldTaskSlugs} from './item-lock.js';
|
|
|
47
47
|
* over the mirror's `work/specs/ready` + `work/specs/tasked` read via the mirror-ref
|
|
48
48
|
* {@link LedgerReadStrategy.resolveMirrorSpecPool}.
|
|
49
49
|
*
|
|
50
|
-
* Per-repo policy parity: a bare mirror has no checked-out
|
|
50
|
+
* Per-repo policy parity: a bare mirror has no checked-out `dorfl.json`,
|
|
51
51
|
* but the COMMITTED one is reachable on `main` (the `do --remote` per-repo seam).
|
|
52
52
|
* We layer it via {@link resolveRepoConfigFromMirror} so the same logical `work/`
|
|
53
53
|
* state yields the SAME `autoBuild`/`autoTask` decision an in-place checkout
|
|
@@ -59,7 +59,7 @@ export interface ScanMirrorPoolOptions {
|
|
|
59
59
|
/** The bare hub mirror directory (`<workspacesDir>/repos/<key>.git`). */
|
|
60
60
|
mirrorPath: string;
|
|
61
61
|
/**
|
|
62
|
-
* The global + default config layer. The repo's COMMITTED
|
|
62
|
+
* The global + default config layer. The repo's COMMITTED `dorfl.json`
|
|
63
63
|
* (read from the mirror's `main`) is layered on top, so `autoBuild`/`autoTask`
|
|
64
64
|
* resolve per-repo exactly as the in-place scan resolves them from the checkout.
|
|
65
65
|
*/
|
|
@@ -146,7 +146,7 @@ export async function scanMirrorPool(
|
|
|
146
146
|
const read = options.read ?? ledgerRead;
|
|
147
147
|
const warn = options.warn;
|
|
148
148
|
|
|
149
|
-
// Per-repo policy parity: layer the COMMITTED
|
|
149
|
+
// Per-repo policy parity: layer the COMMITTED `dorfl.json` from the
|
|
150
150
|
// mirror's `main` (the `do --remote` per-repo seam) onto global. Never fatal —
|
|
151
151
|
// a config-less repo or a read fault falls back to global + default.
|
|
152
152
|
let repoConfig: Config;
|
package/src/prd-to-spec.ts
CHANGED
|
@@ -2,17 +2,27 @@ import {
|
|
|
2
2
|
existsSync,
|
|
3
3
|
readdirSync,
|
|
4
4
|
readFileSync,
|
|
5
|
-
statSync,
|
|
6
5
|
writeFileSync,
|
|
7
|
-
copyFileSync,
|
|
8
6
|
mkdirSync,
|
|
9
7
|
rmdirSync,
|
|
10
8
|
} from 'node:fs';
|
|
11
|
-
import {join,
|
|
9
|
+
import {join, basename} from 'node:path';
|
|
12
10
|
import {run, git, gitMv} from './git.js';
|
|
13
|
-
import {resolveProtocolDoc} from './prompt.js';
|
|
14
11
|
import {WORK_ROOT} from './work-layout.js';
|
|
15
12
|
import {repoConfigPath} from './repo-config.js';
|
|
13
|
+
import {
|
|
14
|
+
resyncProtocol,
|
|
15
|
+
PROTOCOL_DOCS,
|
|
16
|
+
type ResyncedDoc,
|
|
17
|
+
type ResyncResult,
|
|
18
|
+
} from './resync-protocol.js';
|
|
19
|
+
|
|
20
|
+
// The protocol re-sync now lives in the shared `resync-protocol.ts` module (so
|
|
21
|
+
// the standalone `dorfl sync` command and this migration engine share ONE
|
|
22
|
+
// implementation). Re-exported here for backward compatibility with existing
|
|
23
|
+
// importers/tests that reach for these symbols on `prd-to-spec.js`.
|
|
24
|
+
export {resyncProtocol, PROTOCOL_DOCS};
|
|
25
|
+
export type {ResyncedDoc, ResyncResult};
|
|
16
26
|
|
|
17
27
|
/**
|
|
18
28
|
* The **`dorfl prd-to-spec` migration ENGINE** (spec
|
|
@@ -41,7 +51,7 @@ import {repoConfigPath} from './repo-config.js';
|
|
|
41
51
|
* (a) FOLDERS `work/prds/{proposed,ready,tasked,dropped}/ → work/specs/…`
|
|
42
52
|
* via `git mv`; (b) FRONTMATTER/body `prd: → spec:` + inert path/token refs
|
|
43
53
|
* across ALL items INCLUDING `work/tasks/done/` and `work/specs/tasked/`;
|
|
44
|
-
* (c) CONFIG
|
|
54
|
+
* (c) CONFIG `dorfl.json` (`prdsLandIn → specsLandIn`, keep-case);
|
|
45
55
|
* (d) LIVE GIT REFS (inert lock-refs `refs/dorfl/lock/prd-<slug>` and
|
|
46
56
|
* work-branches `work/prd-<slug>` — the quiescence gate guarantees none are
|
|
47
57
|
* held/in-flight, so this renames only INERT refs).
|
|
@@ -86,7 +96,7 @@ import {repoConfigPath} from './repo-config.js';
|
|
|
86
96
|
* A single vocabulary cutover the engine enacts: the retired word `from` (all
|
|
87
97
|
* three case variants) → the new word `to`. The `prd → spec` migration pins
|
|
88
98
|
* {@link MIGRATION}; a future cutover supplies a different value and reuses the
|
|
89
|
-
* same engine (user story 9). `configKeys` are the exact
|
|
99
|
+
* same engine (user story 9). `configKeys` are the exact `dorfl.json` keys that
|
|
90
100
|
* carry the `from` word (their VALUES are preserved — only the key renames).
|
|
91
101
|
*/
|
|
92
102
|
export interface VocabularyMigration {
|
|
@@ -111,25 +121,6 @@ export const MIGRATION: VocabularyMigration = {
|
|
|
111
121
|
configKeys: [{from: 'prdsLandIn', to: 'specsLandIn'}],
|
|
112
122
|
};
|
|
113
123
|
|
|
114
|
-
/**
|
|
115
|
-
* The FULL set of protocol docs `setup` propagates into a repo's
|
|
116
|
-
* `work/protocol/`. The re-sync copies EACH from the package's canonical source
|
|
117
|
-
* (via {@link resolveProtocolDoc}) so the migrated repo carries the new `spec`
|
|
118
|
-
* contract. Kept in lockstep with `skills/setup/protocol/` (the source of truth)
|
|
119
|
-
* and the `vendor-protocol` build step (which vendors these into `dist/protocol/`
|
|
120
|
-
* so the published CLI is self-contained).
|
|
121
|
-
*/
|
|
122
|
-
export const PROTOCOL_DOCS: readonly string[] = [
|
|
123
|
-
'WORK-CONTRACT.md',
|
|
124
|
-
'CLAIM-PROTOCOL.md',
|
|
125
|
-
'REVIEW-PROTOCOL.md',
|
|
126
|
-
'SURFACE-PROTOCOL.md',
|
|
127
|
-
'TASKING-PROTOCOL.md',
|
|
128
|
-
'ADR-FORMAT.md',
|
|
129
|
-
'task-template.md',
|
|
130
|
-
'spec-template.md',
|
|
131
|
-
];
|
|
132
|
-
|
|
133
124
|
// ───────────────────────────────────────────────────────────────────────────
|
|
134
125
|
// Layer: keep-case replace (the bespoke sweep — see the tooling JSDoc above).
|
|
135
126
|
// ───────────────────────────────────────────────────────────────────────────
|
|
@@ -265,122 +256,6 @@ export function checkQuiescence(
|
|
|
265
256
|
return undefined;
|
|
266
257
|
}
|
|
267
258
|
|
|
268
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
269
|
-
// Layer 2: the setup contract re-sync (the deterministic slice of `setup`).
|
|
270
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
271
|
-
|
|
272
|
-
/** One protocol doc the re-sync copied (or WOULD copy under `--dry-run`). */
|
|
273
|
-
export interface ResyncedDoc {
|
|
274
|
-
/** The doc basename (e.g. `'WORK-CONTRACT.md'`). */
|
|
275
|
-
name: string;
|
|
276
|
-
/** Repo-relative destination (`work/protocol/<name>`). */
|
|
277
|
-
dest: string;
|
|
278
|
-
/** True when the target already had byte-identical content (a no-op copy). */
|
|
279
|
-
unchanged: boolean;
|
|
280
|
-
/**
|
|
281
|
-
* True when the doc's SOURCE could not be resolved (the package/dev copy is
|
|
282
|
-
* missing), so NOTHING was copied to `dest`. A skipped doc is NEVER counted as
|
|
283
|
-
* a change (it must not bump `VERSION` — the latent bug this field guards) and
|
|
284
|
-
* is surfaced LOUDLY by the caller. Distinct from `unchanged` (which means the
|
|
285
|
-
* source WAS resolved and matched the dest byte-for-byte).
|
|
286
|
-
*/
|
|
287
|
-
skipped: boolean;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/** What the protocol re-sync did (or would do). */
|
|
291
|
-
export interface ResyncResult {
|
|
292
|
-
docs: ResyncedDoc[];
|
|
293
|
-
/** Repo-relative `work/protocol/VERSION` path (written unless dry-run). */
|
|
294
|
-
versionPath: string;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Re-sync the target repo's `work/protocol/*` from the package's canonical
|
|
299
|
-
* source (the deterministic part of the `setup` skill — copy the docs verbatim +
|
|
300
|
-
* bump `VERSION`). Resolves each doc's SOURCE via {@link resolveProtocolDoc} with
|
|
301
|
-
* NO `cwd`, so it reads the package-vendored (`dist/protocol/`) or dev-source
|
|
302
|
-
* (`skills/setup/protocol/`) copy — NEVER the target repo's own (old) copy. This
|
|
303
|
-
* is how the migrated repo picks up the new `spec` contract before its data is
|
|
304
|
-
* converted. Idempotent: a doc already byte-identical is reported `unchanged`.
|
|
305
|
-
* A doc whose SOURCE cannot be resolved is reported `skipped` (NOT copied, NEVER
|
|
306
|
-
* a VERSION-bump) rather than silently bumping VERSION with nothing copied.
|
|
307
|
-
*
|
|
308
|
-
* `dryRun` reports what WOULD be copied without writing. `resolveDoc` overrides
|
|
309
|
-
* how a doc's source path is resolved (defaults to {@link resolveProtocolDoc});
|
|
310
|
-
* it exists so a test can force a non-resolvable source (a path that does not
|
|
311
|
-
* exist) to exercise the skip path.
|
|
312
|
-
*/
|
|
313
|
-
export function resyncProtocol(
|
|
314
|
-
repoPath: string,
|
|
315
|
-
options: {
|
|
316
|
-
dryRun?: boolean;
|
|
317
|
-
resolveDoc?: (name: string) => string;
|
|
318
|
-
} = {},
|
|
319
|
-
): ResyncResult {
|
|
320
|
-
const protocolDir = join(repoPath, WORK_ROOT, 'protocol');
|
|
321
|
-
const resolveDoc = options.resolveDoc ?? ((name) => resolveProtocolDoc(name));
|
|
322
|
-
const docs: ResyncedDoc[] = [];
|
|
323
|
-
for (const name of PROTOCOL_DOCS) {
|
|
324
|
-
// Resolve the SOURCE (package/dev), never the target's adopted copy.
|
|
325
|
-
const source = resolveDoc(name);
|
|
326
|
-
const destAbs = join(protocolDir, name);
|
|
327
|
-
const destRel = relative(repoPath, destAbs);
|
|
328
|
-
const sourceExists = existsSync(source);
|
|
329
|
-
if (!sourceExists) {
|
|
330
|
-
// The source doc could not be resolved: copy NOTHING and record the doc as
|
|
331
|
-
// SKIPPED (never a change). This is the latent-bug fix: a non-resolvable
|
|
332
|
-
// source must not count as `changed` and bump `VERSION` while copying no
|
|
333
|
-
// file. `unchanged: false` here is honest (the dest was not made to match a
|
|
334
|
-
// source), but `skipped: true` keeps it OUT of the VERSION-bump tally.
|
|
335
|
-
docs.push({name, dest: destRel, unchanged: false, skipped: true});
|
|
336
|
-
continue;
|
|
337
|
-
}
|
|
338
|
-
// Source resolved: a doc is CHANGED (VERSION-bump-worthy) only when the dest
|
|
339
|
-
// is absent OR its content differs from the source. An identical dest is a
|
|
340
|
-
// no-op copy (`unchanged`), so an already-synced re-run stays a true no-op.
|
|
341
|
-
const unchanged =
|
|
342
|
-
existsSync(destAbs) &&
|
|
343
|
-
readFileSync(source, 'utf8') === readFileSync(destAbs, 'utf8');
|
|
344
|
-
if (!options.dryRun) {
|
|
345
|
-
mkdirSync(dirname(destAbs), {recursive: true});
|
|
346
|
-
copyFileSync(source, destAbs);
|
|
347
|
-
}
|
|
348
|
-
docs.push({name, dest: destRel, unchanged, skipped: false});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const versionAbs = join(protocolDir, 'VERSION');
|
|
352
|
-
const versionRel = relative(repoPath, versionAbs);
|
|
353
|
-
// Bump VERSION only when a doc was ACTUALLY COPIED with new content, or when
|
|
354
|
-
// VERSION is absent AND at least one doc was genuinely synced (copied). A
|
|
355
|
-
// SKIPPED doc (unresolvable source, nothing copied) is NEVER a change —
|
|
356
|
-
// counting it would bump VERSION without copying a single doc (the latent bug),
|
|
357
|
-
// and the write-when-absent fallback must ALSO require a real copy so an
|
|
358
|
-
// all-skipped resync leaves no VERSION behind. A re-run on an already-synced
|
|
359
|
-
// repo must stay a true no-op — otherwise the (always-fresh) `synced-at`
|
|
360
|
-
// timestamp would dirty the tree on every idempotent re-run.
|
|
361
|
-
const anyDocChanged = docs.some((d) => !d.unchanged && !d.skipped);
|
|
362
|
-
const anyDocSynced = docs.some((d) => !d.skipped);
|
|
363
|
-
if (
|
|
364
|
-
!options.dryRun &&
|
|
365
|
-
(anyDocChanged || (anyDocSynced && !existsSync(versionAbs)))
|
|
366
|
-
) {
|
|
367
|
-
mkdirSync(dirname(versionAbs), {recursive: true});
|
|
368
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
369
|
-
writeFileSync(
|
|
370
|
-
versionAbs,
|
|
371
|
-
[
|
|
372
|
-
`protocol-version: ${today}`,
|
|
373
|
-
'synced-from: skills/setup/protocol/',
|
|
374
|
-
`synced-at: ${new Date().toISOString()}`,
|
|
375
|
-
'source-commit: dorfl prd-to-spec',
|
|
376
|
-
'',
|
|
377
|
-
].join('\n'),
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
return {docs, versionPath: versionRel};
|
|
382
|
-
}
|
|
383
|
-
|
|
384
259
|
// ───────────────────────────────────────────────────────────────────────────
|
|
385
260
|
// Layer 3a: the FOLDER move (in lockstep with work-layout.ts).
|
|
386
261
|
// ───────────────────────────────────────────────────────────────────────────
|
|
@@ -653,7 +528,7 @@ export function migrateAllItemContent(
|
|
|
653
528
|
}
|
|
654
529
|
|
|
655
530
|
// ───────────────────────────────────────────────────────────────────────────
|
|
656
|
-
// Layer 3c: the CONFIG key rewrite (
|
|
531
|
+
// Layer 3c: the CONFIG key rewrite (dorfl.json).
|
|
657
532
|
// ───────────────────────────────────────────────────────────────────────────
|
|
658
533
|
|
|
659
534
|
/** One config key the rewrite renamed (or would rename). */
|
|
@@ -663,12 +538,12 @@ export interface ConfigRewrite {
|
|
|
663
538
|
}
|
|
664
539
|
|
|
665
540
|
/**
|
|
666
|
-
* Rewrite the retired config KEYS in
|
|
541
|
+
* Rewrite the retired config KEYS in `dorfl.json` (`prdsLandIn → specsLandIn`),
|
|
667
542
|
* preserving each key's VALUE and the file's surrounding formatting — a TEXTUAL
|
|
668
543
|
* key rename on the raw JSON, NOT a parse+reserialise (which would drop unknown
|
|
669
544
|
* keys / reflow the file). Only renames a key that is actually present, so it is
|
|
670
545
|
* idempotent (a config already on `specsLandIn` changes nothing) and safe on a
|
|
671
|
-
* repo with no
|
|
546
|
+
* repo with no `dorfl.json`. Returns the keys renamed. `dryRun` reports without
|
|
672
547
|
* writing.
|
|
673
548
|
*/
|
|
674
549
|
export function migrateConfig(
|
|
@@ -677,7 +552,7 @@ export function migrateConfig(
|
|
|
677
552
|
options: {dryRun?: boolean} = {},
|
|
678
553
|
): ConfigRewrite[] {
|
|
679
554
|
// Resolve the repo's actual config file (prefers `dorfl.json`, falls back to
|
|
680
|
-
// the legacy
|
|
555
|
+
// the legacy `dorfl.json`), so the key rewrite finds it under either name.
|
|
681
556
|
const configAbs = repoConfigPath(repoPath);
|
|
682
557
|
if (!existsSync(configAbs)) {
|
|
683
558
|
return [];
|
|
@@ -806,7 +681,7 @@ export interface DataLeak {
|
|
|
806
681
|
* command's OUTPUT (ADR §7e: the leak scan is exhaustive-by-construction). This
|
|
807
682
|
* is the DATA analogue of the source-part `prd-to-spec-leak-scan.test.ts`
|
|
808
683
|
* (which scans `src/`/`skills/`/`docs/`): here it walks the CONVERTED `work/`
|
|
809
|
-
* tree +
|
|
684
|
+
* tree + `dorfl.json` + the git refs.
|
|
810
685
|
*
|
|
811
686
|
* - **FORWARD:** fails on any surviving retired word (`prd`/`Prd`/`PRD`) in a
|
|
812
687
|
* data STRUCTURE position — a frontmatter `prd:` KEY, a `work/prds/…` folder
|
|
@@ -926,7 +801,7 @@ export function scanForLeaks(
|
|
|
926
801
|
}
|
|
927
802
|
}
|
|
928
803
|
// The config file (the key layer) — under either the preferred `dorfl.json`
|
|
929
|
-
// or the legacy
|
|
804
|
+
// or the legacy `dorfl.json`.
|
|
930
805
|
const configAbs = repoConfigPath(repoPath);
|
|
931
806
|
if (existsSync(configAbs)) {
|
|
932
807
|
scanText(
|
|
@@ -1040,7 +915,10 @@ export function runPrdToSpec(options: PrdToSpecOptions): PrdToSpecResult {
|
|
|
1040
915
|
}
|
|
1041
916
|
|
|
1042
917
|
// 2. Setup contract re-sync FIRST (decision B).
|
|
1043
|
-
const resync = resyncProtocol(repoPath, {
|
|
918
|
+
const resync = resyncProtocol(repoPath, {
|
|
919
|
+
dryRun,
|
|
920
|
+
sourceCommit: 'dorfl prd-to-spec',
|
|
921
|
+
});
|
|
1044
922
|
// 3a. Folders (git mv) — before content, so the content sweep also fixes the
|
|
1045
923
|
// moved items' own `work/prds/…` body refs in their NEW location.
|
|
1046
924
|
const folderMoves = migrateFolders(repoPath, migration, {dryRun});
|
package/src/repo-config.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {run} from './git.js';
|
|
|
16
16
|
/**
|
|
17
17
|
* The per-repo config layer.
|
|
18
18
|
*
|
|
19
|
-
* A repo may commit a
|
|
19
|
+
* A repo may commit a `dorfl.json` at its root. It travels WITH the repo
|
|
20
20
|
* (it is committed) and overrides the global `~/.config/dorfl/config.json`
|
|
21
21
|
* FOR THAT REPO ONLY — so repo-local properties (how this repo integrates, its
|
|
22
22
|
* acceptance `verify` gate, which remote arbitrates its claims) are agreed by all
|
|
@@ -28,7 +28,7 @@ import {run} from './git.js';
|
|
|
28
28
|
* global > built-in default
|
|
29
29
|
*
|
|
30
30
|
* The mechanism is multi-repo aware: each repo resolves against its OWN
|
|
31
|
-
*
|
|
31
|
+
* `dorfl.json`, so repo A can be `merge` while repo B is `propose` in the
|
|
32
32
|
* SAME run (see {@link resolveRepoConfig}).
|
|
33
33
|
*
|
|
34
34
|
* Only keys that are genuinely repo properties are honoured in the COMMITTED
|
|
@@ -44,7 +44,7 @@ import {run} from './git.js';
|
|
|
44
44
|
* the global file / a flag) may set ANY key, host-only included (see
|
|
45
45
|
* {@link envOverrides}).
|
|
46
46
|
*
|
|
47
|
-
* A repo with no
|
|
47
|
+
* A repo with no `dorfl.json` resolves to exactly the global config —
|
|
48
48
|
* behaviour is unchanged from before this layer existed.
|
|
49
49
|
*/
|
|
50
50
|
|
|
@@ -57,7 +57,7 @@ export const REPO_CONFIG_FILENAME = brand.repoConfigFilename;
|
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Config keys that are genuinely repo properties and so are honoured in a
|
|
60
|
-
* per-repo
|
|
60
|
+
* per-repo `dorfl.json`. Deliberately a subset of {@link Config};
|
|
61
61
|
* extend this list as more keys become legitimately repo-scoped.
|
|
62
62
|
*/
|
|
63
63
|
export const REPO_ALLOWED_KEYS = [
|
|
@@ -306,7 +306,7 @@ export function repoConfigPath(repoPath: string): string {
|
|
|
306
306
|
return resolveRepoConfigPath(repoPath);
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
/** The result of reading (and filtering) a repo's
|
|
309
|
+
/** The result of reading (and filtering) a repo's `dorfl.json`. */
|
|
310
310
|
export interface LoadedRepoConfig {
|
|
311
311
|
/** Where we looked (whether or not the file exists). */
|
|
312
312
|
path: string;
|
|
@@ -328,7 +328,7 @@ export interface LoadedRepoConfig {
|
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
/**
|
|
331
|
-
* Read a repo's
|
|
331
|
+
* Read a repo's `dorfl.json` and split it into the honoured subset and
|
|
332
332
|
* the rejected runner/host-only keys. A missing file is not an error (the repo
|
|
333
333
|
* simply resolves to the global config); invalid JSON is. Unknown keys are
|
|
334
334
|
* silently dropped (neither honoured nor reported as rejected). Only keys in
|
|
@@ -352,8 +352,8 @@ export function loadRepoConfig(repoPath: string): LoadedRepoConfig {
|
|
|
352
352
|
|
|
353
353
|
/**
|
|
354
354
|
* The content-based half of {@link loadRepoConfig}: parse + apply the SAME
|
|
355
|
-
* allow/reject split to ALREADY-READ
|
|
356
|
-
* source as `sourceLabel` (a path, or e.g. `<arbiter>/main
|
|
355
|
+
* allow/reject split to ALREADY-READ `dorfl.json` bytes, labelling the
|
|
356
|
+
* source as `sourceLabel` (a path, or e.g. `<arbiter>/main:dorfl.json`)
|
|
357
357
|
* in the rejected-key message. Used wherever the committed repo file is sourced
|
|
358
358
|
* from somewhere OTHER than a working-tree path — notably `do --remote`, which
|
|
359
359
|
* reads it from the arbiter's `main` (`git show`) since there is no checkout.
|
|
@@ -413,7 +413,7 @@ function isRejectedKey(key: string): boolean {
|
|
|
413
413
|
|
|
414
414
|
/** Inputs to {@link resolveRepoConfig}. */
|
|
415
415
|
export interface ResolveRepoConfigOptions {
|
|
416
|
-
/** Absolute path to the repo root (where
|
|
416
|
+
/** Absolute path to the repo root (where `dorfl.json` would live). */
|
|
417
417
|
repoPath: string;
|
|
418
418
|
/**
|
|
419
419
|
* The fully-resolved GLOBAL config (already merged over built-in defaults,
|
|
@@ -491,7 +491,7 @@ export interface ResolvedRepoConfig {
|
|
|
491
491
|
/**
|
|
492
492
|
* Resolve the effective config for ONE repo by layering, per key:
|
|
493
493
|
*
|
|
494
|
-
* flag > ENV (DORFL_*) > per-repo
|
|
494
|
+
* flag > ENV (DORFL_*) > per-repo `dorfl.json` > global >
|
|
495
495
|
* built-in default
|
|
496
496
|
*
|
|
497
497
|
* The `global` argument already carries the global + default layers (it is the
|
|
@@ -502,7 +502,7 @@ export interface ResolvedRepoConfig {
|
|
|
502
502
|
* in a multi-repo run yields INDEPENDENT results — repo A can be `merge` while
|
|
503
503
|
* repo B is `propose` in the same run.
|
|
504
504
|
*
|
|
505
|
-
* A repo with no
|
|
505
|
+
* A repo with no `dorfl.json` (and no env) resolves to exactly `global`
|
|
506
506
|
* (unchanged behaviour).
|
|
507
507
|
*/
|
|
508
508
|
export function resolveRepoConfig(
|
package/src/repo-mirror.ts
CHANGED
|
@@ -151,7 +151,7 @@ export function ensureMirror(options: EnsureMirrorOptions): EnsureMirrorResult {
|
|
|
151
151
|
* but on REUSE refreshes via {@link fetchMirrorMain} (main-only, no-prune).
|
|
152
152
|
*
|
|
153
153
|
* This is the MIRROR-ENSURE for the no-checkout CONFIG READ (`do --remote`/
|
|
154
|
-
* `do --isolated` per-repo
|
|
154
|
+
* `do --isolated` per-repo `dorfl.json`): `git show main:dorfl.json`
|
|
155
155
|
* only needs `main`, and using the pruning all-heads fetch here would let a
|
|
156
156
|
* `work/<slug>` branch CHECKED OUT in some other (stale) job worktree block the
|
|
157
157
|
* fetch (`git refuses to fetch into branch … checked out`), throwing the config
|
|
@@ -226,12 +226,12 @@ export function fetchMirrorMain(
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
/**
|
|
229
|
-
* Read the target repo's COMMITTED
|
|
230
|
-
* mirror's `main` (`git show main
|
|
229
|
+
* Read the target repo's COMMITTED `dorfl.json` from the bare hub
|
|
230
|
+
* mirror's `main` (`git show main:dorfl.json`) — the per-repo config
|
|
231
231
|
* layer for the NO-CHECKOUT paths (`do --remote`). The committed file is a
|
|
232
232
|
* tracked file on `<arbiter>/main`, so it is reachable from the mirror without a
|
|
233
233
|
* worktree. Returns the raw file CONTENT, or `undefined` when the repo has no
|
|
234
|
-
*
|
|
234
|
+
* `dorfl.json` on `main` (a config-less repo — the caller then resolves to
|
|
235
235
|
* exactly global + default, byte-identical to before this layer existed).
|
|
236
236
|
*
|
|
237
237
|
* This is the task's ONE genuinely-new seam: sourcing the bytes from the arbiter
|
|
@@ -248,7 +248,7 @@ export function readRepoConfigFromMirrorMain(
|
|
|
248
248
|
mirrorDir: string,
|
|
249
249
|
env?: NodeJS.ProcessEnv,
|
|
250
250
|
): string | undefined {
|
|
251
|
-
// Prefer the plain `dorfl.json`; fall back to the legacy
|
|
251
|
+
// Prefer the plain `dorfl.json`; fall back to the legacy `dorfl.json` when the
|
|
252
252
|
// preferred name is absent on `main` (a repo that committed the dotfile before
|
|
253
253
|
// dual-support). A genuine read fault on the FIRST attempt propagates; a
|
|
254
254
|
// path-missing on the first falls through to the legacy attempt.
|
|
@@ -293,7 +293,7 @@ function showConfigOnMain(
|
|
|
293
293
|
|
|
294
294
|
/**
|
|
295
295
|
* Resolve a repo's effective {@link Config} for a NO-CHECKOUT path by layering its
|
|
296
|
-
* COMMITTED
|
|
296
|
+
* COMMITTED `dorfl.json` (read from the bare hub mirror's `main` via
|
|
297
297
|
* {@link readRepoConfigFromMirrorMain}) into the SAME
|
|
298
298
|
* `flag > env > per-repo > global > default` chain a working checkout uses
|
|
299
299
|
* ({@link resolveRepoConfigFromLoaded}). This is the reusable core of `do
|
|
@@ -308,7 +308,7 @@ function showConfigOnMain(
|
|
|
308
308
|
* global + flags itself.
|
|
309
309
|
*/
|
|
310
310
|
export function resolveRepoConfigFromMirror(options: {
|
|
311
|
-
/** The bare hub mirror directory whose `main
|
|
311
|
+
/** The bare hub mirror directory whose `main:dorfl.json` to read. */
|
|
312
312
|
mirrorPath: string;
|
|
313
313
|
/** The global + default config layer (from `loadConfig`/`mergeConfig`). */
|
|
314
314
|
global: Config;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
readFileSync,
|
|
4
|
+
writeFileSync,
|
|
5
|
+
copyFileSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
} from 'node:fs';
|
|
8
|
+
import {join, relative, dirname} from 'node:path';
|
|
9
|
+
import {resolveProtocolDoc} from './prompt.js';
|
|
10
|
+
import {WORK_ROOT} from './work-layout.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The **protocol re-sync** primitive: copy the package's canonical
|
|
14
|
+
* `work/protocol/*` docs into a target repo's `work/protocol/` and bump
|
|
15
|
+
* `work/protocol/VERSION`. This is the deterministic slice of what the `setup`
|
|
16
|
+
* skill does — the SINGLE source of truth for "make this repo carry the current
|
|
17
|
+
* protocol contract".
|
|
18
|
+
*
|
|
19
|
+
* Two verbs drive it:
|
|
20
|
+
* - `dorfl sync` — the standalone "get the latest protocol" command (a repo
|
|
21
|
+
* that adopted an OLD protocol picks up the new docs in one command);
|
|
22
|
+
* - `dorfl prd-to-spec` — the vocabulary-migration engine, which re-syncs the
|
|
23
|
+
* contract FIRST (before converting the repo's data) so the migrated repo
|
|
24
|
+
* lands on the new `spec` contract.
|
|
25
|
+
*
|
|
26
|
+
* It was lifted out of `prd-to-spec.ts` (where it began life) into this shared
|
|
27
|
+
* module so both verbs import ONE implementation; `prd-to-spec.ts` re-exports
|
|
28
|
+
* the symbols for backward compatibility.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The FULL set of protocol docs `setup` propagates into a repo's
|
|
33
|
+
* `work/protocol/`. The re-sync copies EACH from the package's canonical source
|
|
34
|
+
* (via {@link resolveProtocolDoc}) so a set-up repo carries the current
|
|
35
|
+
* contract. Kept in lockstep with `skills/setup/protocol/` (the source of truth)
|
|
36
|
+
* and the `vendor-protocol` build step (which vendors these into `dist/protocol/`
|
|
37
|
+
* so the published CLI is self-contained).
|
|
38
|
+
*/
|
|
39
|
+
export const PROTOCOL_DOCS: readonly string[] = [
|
|
40
|
+
'WORK-CONTRACT.md',
|
|
41
|
+
'CLAIM-PROTOCOL.md',
|
|
42
|
+
'REVIEW-PROTOCOL.md',
|
|
43
|
+
'SURFACE-PROTOCOL.md',
|
|
44
|
+
'TASKING-PROTOCOL.md',
|
|
45
|
+
'ADR-FORMAT.md',
|
|
46
|
+
'task-template.md',
|
|
47
|
+
'spec-template.md',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
/** One protocol doc the re-sync copied (or WOULD copy under `--dry-run`). */
|
|
51
|
+
export interface ResyncedDoc {
|
|
52
|
+
/** The doc basename (e.g. `'WORK-CONTRACT.md'`). */
|
|
53
|
+
name: string;
|
|
54
|
+
/** Repo-relative destination (`work/protocol/<name>`). */
|
|
55
|
+
dest: string;
|
|
56
|
+
/** True when the target already had byte-identical content (a no-op copy). */
|
|
57
|
+
unchanged: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* True when the doc's SOURCE could not be resolved (the package/dev copy is
|
|
60
|
+
* missing), so NOTHING was copied to `dest`. A skipped doc is NEVER counted as
|
|
61
|
+
* a change (it must not bump `VERSION` — the latent bug this field guards) and
|
|
62
|
+
* is surfaced LOUDLY by the caller. Distinct from `unchanged` (which means the
|
|
63
|
+
* source WAS resolved and matched the dest byte-for-byte).
|
|
64
|
+
*/
|
|
65
|
+
skipped: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** What the protocol re-sync did (or would do). */
|
|
69
|
+
export interface ResyncResult {
|
|
70
|
+
docs: ResyncedDoc[];
|
|
71
|
+
/** Repo-relative `work/protocol/VERSION` path (written unless dry-run). */
|
|
72
|
+
versionPath: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Re-sync the target repo's `work/protocol/*` from the package's canonical
|
|
77
|
+
* source (the deterministic part of the `setup` skill — copy the docs verbatim +
|
|
78
|
+
* bump `VERSION`). Resolves each doc's SOURCE via {@link resolveProtocolDoc} with
|
|
79
|
+
* NO `cwd`, so it reads the package-vendored (`dist/protocol/`) or dev-source
|
|
80
|
+
* (`skills/setup/protocol/`) copy — NEVER the target repo's own (old) copy. This
|
|
81
|
+
* is how a repo picks up the current contract. Idempotent: a doc already
|
|
82
|
+
* byte-identical is reported `unchanged`. A doc whose SOURCE cannot be resolved
|
|
83
|
+
* is reported `skipped` (NOT copied, NEVER a VERSION-bump) rather than silently
|
|
84
|
+
* bumping VERSION with nothing copied.
|
|
85
|
+
*
|
|
86
|
+
* `dryRun` reports what WOULD be copied without writing. `resolveDoc` overrides
|
|
87
|
+
* how a doc's source path is resolved (defaults to {@link resolveProtocolDoc});
|
|
88
|
+
* it exists so a test can force a non-resolvable source (a path that does not
|
|
89
|
+
* exist) to exercise the skip path. `sourceCommit` is stamped into the written
|
|
90
|
+
* `VERSION` provenance line so a reader can tell which verb wrote it.
|
|
91
|
+
*/
|
|
92
|
+
export function resyncProtocol(
|
|
93
|
+
repoPath: string,
|
|
94
|
+
options: {
|
|
95
|
+
dryRun?: boolean;
|
|
96
|
+
resolveDoc?: (name: string) => string;
|
|
97
|
+
sourceCommit?: string;
|
|
98
|
+
} = {},
|
|
99
|
+
): ResyncResult {
|
|
100
|
+
const protocolDir = join(repoPath, WORK_ROOT, 'protocol');
|
|
101
|
+
const resolveDoc = options.resolveDoc ?? ((name) => resolveProtocolDoc(name));
|
|
102
|
+
const docs: ResyncedDoc[] = [];
|
|
103
|
+
for (const name of PROTOCOL_DOCS) {
|
|
104
|
+
// Resolve the SOURCE (package/dev), never the target's adopted copy.
|
|
105
|
+
const source = resolveDoc(name);
|
|
106
|
+
const destAbs = join(protocolDir, name);
|
|
107
|
+
const destRel = relative(repoPath, destAbs);
|
|
108
|
+
const sourceExists = existsSync(source);
|
|
109
|
+
if (!sourceExists) {
|
|
110
|
+
// The source doc could not be resolved: copy NOTHING and record the doc as
|
|
111
|
+
// SKIPPED (never a change). This is the latent-bug fix: a non-resolvable
|
|
112
|
+
// source must not count as `changed` and bump `VERSION` while copying no
|
|
113
|
+
// file. `unchanged: false` here is honest (the dest was not made to match a
|
|
114
|
+
// source), but `skipped: true` keeps it OUT of the VERSION-bump tally.
|
|
115
|
+
docs.push({name, dest: destRel, unchanged: false, skipped: true});
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
// Source resolved: a doc is CHANGED (VERSION-bump-worthy) only when the dest
|
|
119
|
+
// is absent OR its content differs from the source. An identical dest is a
|
|
120
|
+
// no-op copy (`unchanged`), so an already-synced re-run stays a true no-op.
|
|
121
|
+
const unchanged =
|
|
122
|
+
existsSync(destAbs) &&
|
|
123
|
+
readFileSync(source, 'utf8') === readFileSync(destAbs, 'utf8');
|
|
124
|
+
if (!options.dryRun) {
|
|
125
|
+
mkdirSync(dirname(destAbs), {recursive: true});
|
|
126
|
+
copyFileSync(source, destAbs);
|
|
127
|
+
}
|
|
128
|
+
docs.push({name, dest: destRel, unchanged, skipped: false});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const versionAbs = join(protocolDir, 'VERSION');
|
|
132
|
+
const versionRel = relative(repoPath, versionAbs);
|
|
133
|
+
// Bump VERSION only when a doc was ACTUALLY COPIED with new content, or when
|
|
134
|
+
// VERSION is absent AND at least one doc was genuinely synced (copied). A
|
|
135
|
+
// SKIPPED doc (unresolvable source, nothing copied) is NEVER a change —
|
|
136
|
+
// counting it would bump VERSION without copying a single doc (the latent bug),
|
|
137
|
+
// and the write-when-absent fallback must ALSO require a real copy so an
|
|
138
|
+
// all-skipped resync leaves no VERSION behind. A re-run on an already-synced
|
|
139
|
+
// repo must stay a true no-op — otherwise the (always-fresh) `synced-at`
|
|
140
|
+
// timestamp would dirty the tree on every idempotent re-run.
|
|
141
|
+
const anyDocChanged = docs.some((d) => !d.unchanged && !d.skipped);
|
|
142
|
+
const anyDocSynced = docs.some((d) => !d.skipped);
|
|
143
|
+
if (
|
|
144
|
+
!options.dryRun &&
|
|
145
|
+
(anyDocChanged || (anyDocSynced && !existsSync(versionAbs)))
|
|
146
|
+
) {
|
|
147
|
+
mkdirSync(dirname(versionAbs), {recursive: true});
|
|
148
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
149
|
+
writeFileSync(
|
|
150
|
+
versionAbs,
|
|
151
|
+
[
|
|
152
|
+
`protocol-version: ${today}`,
|
|
153
|
+
'synced-from: skills/setup/protocol/',
|
|
154
|
+
`synced-at: ${new Date().toISOString()}`,
|
|
155
|
+
`source-commit: ${options.sourceCommit ?? 'dorfl sync'}`,
|
|
156
|
+
'',
|
|
157
|
+
].join('\n'),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {docs, versionPath: versionRel};
|
|
162
|
+
}
|
package/src/run.ts
CHANGED
|
@@ -294,7 +294,7 @@ export interface RunOnceOptions {
|
|
|
294
294
|
/** Override agent-id generation (tests). Retained for API compat; unused for branch naming. */
|
|
295
295
|
agentId?: () => string;
|
|
296
296
|
/**
|
|
297
|
-
* Sink for non-fatal warnings (e.g. a repo's
|
|
297
|
+
* Sink for non-fatal warnings (e.g. a repo's `dorfl.json` naming
|
|
298
298
|
* runner/host-only keys that were ignored). Defaults to a no-op so the core
|
|
299
299
|
* stays pure; the CLI wires this to stderr.
|
|
300
300
|
*/
|
|
@@ -537,7 +537,7 @@ async function runOneItem(
|
|
|
537
537
|
const {slug, repoPath} = candidate;
|
|
538
538
|
const base: ItemResult = {repoPath, slug, status: 'lost-race'};
|
|
539
539
|
|
|
540
|
-
// Resolve THIS repo's effective config against its own
|
|
540
|
+
// Resolve THIS repo's effective config against its own `dorfl.json`
|
|
541
541
|
// layered over the global config (flag > per-repo > global > default). Each
|
|
542
542
|
// repo gets its own integration mode / arbiter, so repo A can be `merge`
|
|
543
543
|
// while repo B is `propose` in one tick.
|
package/src/scan.ts
CHANGED
|
@@ -384,7 +384,7 @@ export function scoreItems(
|
|
|
384
384
|
* primitive; the per-repo `work/` read goes through the seam's mirror-ref method.
|
|
385
385
|
*
|
|
386
386
|
* The autonomy gate's `autoBuild` policy is resolved PER REPO. NOTE: a bare
|
|
387
|
-
* mirror has no checked-out
|
|
387
|
+
* mirror has no checked-out `dorfl.json`, so the per-repo file cannot be
|
|
388
388
|
* read from it — the global/env-resolved policy applies (the per-repo override is
|
|
389
389
|
* a working-checkout concern, served by {@link scanRepoPaths}).
|
|
390
390
|
*/
|
|
@@ -450,7 +450,7 @@ export async function scan(
|
|
|
450
450
|
);
|
|
451
451
|
// Spec pool — the TASKABLE-SPEC companion of the task pool above
|
|
452
452
|
// (`ci-propose-matrix-must-enumerate-sliceable-prds-not-only-slices`). Resolve
|
|
453
|
-
// `autoTask` PER REPO from the mirror's COMMITTED
|
|
453
|
+
// `autoTask` PER REPO from the mirror's COMMITTED `dorfl.json`
|
|
454
454
|
// (exactly as the mirror-side pool scan does — NOT forked); a read fault is
|
|
455
455
|
// non-fatal (warn + global fall-back), since `scan` is read-only and must
|
|
456
456
|
// degrade gracefully (ADR §5/§6).
|
|
@@ -476,7 +476,7 @@ export async function scan(
|
|
|
476
476
|
const specs = scoreSpecs(mirror.path, specPool, repoAutoTask);
|
|
477
477
|
// The per-repo LIFECYCLE pool (`ci-propose-matrix-enumerates-lifecycle-items`),
|
|
478
478
|
// gated by this mirror's question-surfacing config (resolved from its committed
|
|
479
|
-
//
|
|
479
|
+
// `dorfl.json`, with the same non-fatal global fall-back as `autoTask`
|
|
480
480
|
// above) and computed by REUSING `gatherLifecycleMirror` → `buildLifecyclePools`
|
|
481
481
|
// (NOT a forked predicate), so it AGREES with the `run` selection.
|
|
482
482
|
let repoLifecycleConfig = {
|
|
@@ -536,7 +536,7 @@ export async function scan(
|
|
|
536
536
|
* checkout IS the local state); the fetch-first contract (ADR §5/§6) applies to
|
|
537
537
|
* the REGISTRY `scan` above, which refreshes each bare mirror before reading.
|
|
538
538
|
* Reads each repo's `work/` via the read seam's local-tree method and honours its
|
|
539
|
-
* per-repo
|
|
539
|
+
* per-repo `dorfl.json` `autoBuild`. The registry `scan` above is the
|
|
540
540
|
* mirror-ref counterpart; this is its working-tree sibling.
|
|
541
541
|
*/
|
|
542
542
|
export function scanRepoPaths(
|
|
@@ -579,7 +579,7 @@ export function scanRepoPaths(
|
|
|
579
579
|
override,
|
|
580
580
|
}).config;
|
|
581
581
|
// Spec pool — the TASKABLE-SPEC companion of the task pool. Resolve
|
|
582
|
-
// `autoTask` PER REPO from the working-tree
|
|
582
|
+
// `autoTask` PER REPO from the working-tree `dorfl.json` (the same
|
|
583
583
|
// way `autoBuild` is resolved); `taskableSpecs` (the SAME `autoslice-gate`
|
|
584
584
|
// predicate the autopick paths run) decides what is taskable — no forked
|
|
585
585
|
// predicate. This is what makes the propose-mode CI matrix enumerate `spec:`
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* The workflow is deliberately thin: it wires the same composite setup action
|
|
15
15
|
* the other capabilities use, then runs `dorfl verify` — the deterministic shell
|
|
16
|
-
* gate the repo declares in
|
|
16
|
+
* gate the repo declares in `dorfl.json`. No agent, no provider secrets, no
|
|
17
17
|
* `gh` mutation; it is read-only with respect to `work/`.
|
|
18
18
|
*
|
|
19
19
|
* `merge_group` is present (with no extra wiring) so a follow-on Tier-2 task
|