dorfl 0.1.0 → 0.1.1

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/src/cli.ts CHANGED
@@ -844,11 +844,21 @@ function printPrdToSpecReport(result: PrdToSpecResult): void {
844
844
  );
845
845
 
846
846
  if (result.resync) {
847
- const changed = result.resync.docs.filter((d) => !d.unchanged).length;
847
+ const changed = result.resync.docs.filter(
848
+ (d) => !d.unchanged && !d.skipped,
849
+ ).length;
848
850
  console.log(
849
851
  `Contract re-sync: ${verb} sync ${result.resync.docs.length} protocol ` +
850
852
  `doc(s) (${changed} changed) + bump ${result.resync.versionPath}.`,
851
853
  );
854
+ // Surface any doc whose SOURCE could not be resolved LOUDLY: it was NOT
855
+ // copied, so the contract in the target repo is incomplete for that doc.
856
+ for (const skipped of result.resync.docs.filter((d) => d.skipped)) {
857
+ console.warn(
858
+ ` !! ${skipped.name}: SOURCE could not be resolved — NOT copied ` +
859
+ `(the contract doc is missing/unchanged in the target repo).`,
860
+ );
861
+ }
852
862
  }
853
863
  console.log(`Folders: ${verb} move ${result.folderMoves.length} folder(s).`);
854
864
  for (const m of result.folderMoves) {
package/src/do.ts CHANGED
@@ -1054,7 +1054,7 @@ export async function performDo(options: DoOptions): Promise<DoResult> {
1054
1054
  repoResolved: {testFirst: options.promptGuidance?.testFirst === true},
1055
1055
  taskContent: readFileSync(task.path, 'utf8'),
1056
1056
  });
1057
- prompt = buildAgentPrompt(task.slug, task.prd, task.taskPrompt, {
1057
+ prompt = buildAgentPrompt(task.slug, task.spec, task.taskPrompt, {
1058
1058
  cwd: tree.dir,
1059
1059
  continueContext,
1060
1060
  promptGuidance: itemGuidance,
@@ -2245,7 +2245,7 @@ async function runRemotePipeline(
2245
2245
  repoResolved: {testFirst: options.promptGuidance?.testFirst === true},
2246
2246
  taskContent: readFileSync(task.path, 'utf8'),
2247
2247
  });
2248
- prompt = buildAgentPrompt(task.slug, task.prd, task.taskPrompt, {
2248
+ prompt = buildAgentPrompt(task.slug, task.spec, task.taskPrompt, {
2249
2249
  cwd,
2250
2250
  continueContext,
2251
2251
  promptGuidance: itemGuidance,
@@ -44,45 +44,38 @@ export interface Frontmatter {
44
44
  /** Content-derived slug id (frontmatter `slug:`). */
45
45
  slug: string | undefined;
46
46
  /**
47
- * Source SPEC slug (frontmatter `spec:`); the spec lives at
48
- * `work/specs/ready/<spec>.md`. The `spec` vocabulary name for the parent-spec
49
- * pointer (prd `prd-to-spec-vocabulary-cutover-and-migration-command`). This is
50
- * the EXPAND-step ADDITION beside {@link prd}: `parseFrontmatter` populates BOTH
51
- * from EITHER the `spec:` OR the `prd:` key with the SAME value, so a caller
52
- * reading either sees the parent-spec slug. The migrate batches move `fm.prd`
53
- * reads onto `fm.spec` one at a time; the contract task removes `prd`.
47
+ * Source SPEC slug: the parent-spec pointer this artifact derives from; the
48
+ * spec lives at `work/specs/ready/<spec>.md`. The `spec` vocabulary name (spec
49
+ * `prd-to-spec-vocabulary-cutover-and-migration-command`). `parseFrontmatter`
50
+ * populates it from EITHER the canonical `spec:` key OR the LEGACY `prd:` key
51
+ * (the latter kept as a read-only BACK-COMPAT alias for un-migrated downstream
52
+ * repos, whose data still carries `prd:` until `dorfl prd-to-spec` converts it).
53
+ * `spec:` wins when both are present. There is no longer a separate `fm.prd`
54
+ * FIELD — both keys feed this one field.
54
55
  */
55
56
  spec: string | undefined;
56
- /**
57
- * Source spec slug (frontmatter `prd:`); the spec lives at
58
- * `work/specs/ready/<prd>.md`. The LEGACY vocabulary name for the parent-spec
59
- * pointer, kept BESIDE {@link spec} through the expand→migrate→contract cutover
60
- * (prd `prd-to-spec-vocabulary-cutover-and-migration-command`) so every existing
61
- * `fm.prd` reader keeps working; populated from EITHER the `spec:` or `prd:` key.
62
- */
63
- prd: string | undefined;
64
57
  /**
65
58
  * The GitHub issue number an `intake`-emitted artifact was transformed from
66
59
  * (frontmatter `issue:`). Parsed so the `issue: N` intake writes is
67
60
  * MACHINE-READABLE — the close JOB (`runner-in-ci`'s) reaches it to resolve the
68
61
  * issue from folder + field state. Carried on EITHER:
69
62
  *
70
- * - a **prd** (`intake`'s prd outcome) — a fanned task reaches it via
71
- * `task.prd: → work/prds/ready/<prd>.md → prd issue:` (the number lives
72
- * ONLY on the prd, never duplicated across the N fanned tasks); OR
73
- * - a **lone task** (`intake`'s TASK outcome, no `prd:`) — the provider-agnostic
63
+ * - a **spec** (`intake`'s spec outcome) — a fanned task reaches it via
64
+ * `task.spec: → work/specs/ready/<spec>.md → spec issue:` (the number lives
65
+ * ONLY on the spec, never duplicated across the N fanned tasks); OR
66
+ * - a **lone task** (`intake`'s TASK outcome, no `spec:`) — the provider-agnostic
74
67
  * closure link for a task that closes its own issue directly (replaces the old
75
68
  * GitHub-only `Fixes #N` body line, which is now a deferred optimisation).
76
69
  *
77
- * INVARIANT — one closure path per task: a task uses `issue:` XOR `prd:`, never
70
+ * INVARIANT — one closure path per task: a task uses `issue:` XOR `spec:`, never
78
71
  * both. Either it closes its own issue directly (`issue:`) or it contributes to a
79
- * prd that closes the issue (`prd:` → prd `issue:`). This is NOT enforced by a
72
+ * spec that closes the issue (`spec:` → spec `issue:`). This is NOT enforced by a
80
73
  * throwing validator (there is no frontmatter-validation layer): intake never
81
- * emits both — its task / prd dispatch branches are mutually exclusive — so only
82
- * a human hand-edit could produce both, and the precedence rule (`prd:` wins,
74
+ * emits both — its task / spec dispatch branches are mutually exclusive — so only
75
+ * a human hand-edit could produce both, and the precedence rule (`spec:` wins,
83
76
  * `issue:` ignored) is the future close-job's concern (see {@link
84
- * resolveClosingIssue}), degrading a typo to "use the prd's number" rather than
85
- * crashing. `undefined` when omitted (every non-intake prd and most tasks).
77
+ * resolveClosingIssue}), degrading a typo to "use the spec's number" rather than
78
+ * crashing. `undefined` when omitted (every non-intake spec and most tasks).
86
79
  */
87
80
  issue: number | undefined;
88
81
  /**
@@ -334,7 +327,6 @@ export function parseFrontmatter(content: string): Frontmatter {
334
327
  originTrust: undefined,
335
328
  slug: undefined,
336
329
  spec: undefined,
337
- prd: undefined,
338
330
  issue: undefined,
339
331
  humanOnly: undefined,
340
332
  needsAnswers: undefined,
@@ -379,23 +371,23 @@ export function parseFrontmatter(content: string): Frontmatter {
379
371
  } else if (key === 'slug') {
380
372
  result.slug = rawValue === '' ? undefined : unquote(rawValue);
381
373
  } else if (key === 'spec' || key === 'prd') {
382
- // EXPAND step (prd `prd-to-spec-vocabulary-cutover-and-migration-command`):
383
- // the parent-spec pointer is written under EITHER the new `spec:` key or the
384
- // legacy `prd:` key. Populate BOTH `fm.spec` and `fm.prd` with the same value
385
- // so a caller reading either field sees it. `spec:` wins if both are present
386
- // (the canonical key takes precedence over the legacy alias); an empty value
387
- // on one key does NOT clobber a non-empty value already read from the other.
374
+ // The parent-spec pointer, read from EITHER the canonical `spec:` key OR the
375
+ // LEGACY `prd:` key into the SINGLE `fm.spec` field (spec
376
+ // `prd-to-spec-vocabulary-cutover-and-migration-command`). The `prd:` key
377
+ // read is a permanent-for-now BACK-COMPAT alias so an un-migrated downstream
378
+ // repo (whose data still carries `prd:` until `dorfl prd-to-spec` converts
379
+ // it) keeps resolving its parent spec. `spec:` WINS when both are present
380
+ // (the canonical key over the legacy alias); an empty value on one key does
381
+ // NOT clobber a non-empty value already read from the other.
388
382
  const value = rawValue === '' ? undefined : unquote(rawValue);
389
383
  if (key === 'spec') {
390
384
  // The canonical key wins: overwrite whatever `prd:` may have set.
391
385
  if (value !== undefined) {
392
386
  result.spec = value;
393
- result.prd = value;
394
387
  }
395
388
  } else if (result.spec === undefined) {
396
- // Legacy `prd:` only fills in when `spec:` has not already set the pair.
389
+ // Legacy `prd:` only fills in when `spec:` has not already set it.
397
390
  result.spec = value;
398
- result.prd = value;
399
391
  }
400
392
  } else if (key === 'issue') {
401
393
  // Integer issue link (`intake`'s prd-emit OR a lone-task emit). A
@@ -456,15 +448,15 @@ export function parseFrontmatter(content: string): Frontmatter {
456
448
 
457
449
  /**
458
450
  * Resolve, from a parsed artifact's frontmatter, HOW it closes its source issue:
459
- * the `prd:` hop or the lone-task `issue:` field. A PURE helper for the FUTURE
451
+ * the `spec:` hop or the lone-task `issue:` field. A PURE helper for the FUTURE
460
452
  * CI close-job (`runner-in-ci`'s) — it is NOT wired into intake or any reader
461
453
  * today; it merely pins the one-closure-path PRECEDENCE in one place.
462
454
  *
463
- * Encodes the invariant: a task uses `issue:` XOR `prd:`. When (only a human
464
- * hand-edit could) BOTH are present, `prd:` WINS — the close-job hops to the prd's
465
- * `issue:` and IGNORES the task's own `issue:` (the fanned-prd path is the
466
- * authoritative one; a lone `issue:` on a `prd:`-bearing task is a contradiction
467
- * that degrades to "use the prd" rather than crashing).
455
+ * Encodes the invariant: a task uses `issue:` XOR `spec:`. When (only a human
456
+ * hand-edit could) BOTH are present, `spec:` WINS — the close-job hops to the
457
+ * spec's `issue:` and IGNORES the task's own `issue:` (the fanned-spec path is
458
+ * the authoritative one; a lone `issue:` on a `spec:`-bearing task is a
459
+ * contradiction that degrades to "use the spec" rather than crashing).
468
460
  *
469
461
  * Returns:
470
462
  * - `{via: 'spec', spec}` when a `spec:` is present (hop to the spec's `issue:`; the
@@ -276,6 +276,14 @@ export interface ResyncedDoc {
276
276
  dest: string;
277
277
  /** True when the target already had byte-identical content (a no-op copy). */
278
278
  unchanged: boolean;
279
+ /**
280
+ * True when the doc's SOURCE could not be resolved (the package/dev copy is
281
+ * missing), so NOTHING was copied to `dest`. A skipped doc is NEVER counted as
282
+ * a change (it must not bump `VERSION` — the latent bug this field guards) and
283
+ * is surfaced LOUDLY by the caller. Distinct from `unchanged` (which means the
284
+ * source WAS resolved and matched the dest byte-for-byte).
285
+ */
286
+ skipped: boolean;
279
287
  }
280
288
 
281
289
  /** What the protocol re-sync did (or would do). */
@@ -293,40 +301,68 @@ export interface ResyncResult {
293
301
  * (`skills/setup/protocol/`) copy — NEVER the target repo's own (old) copy. This
294
302
  * is how the migrated repo picks up the new `spec` contract before its data is
295
303
  * converted. Idempotent: a doc already byte-identical is reported `unchanged`.
304
+ * A doc whose SOURCE cannot be resolved is reported `skipped` (NOT copied, NEVER
305
+ * a VERSION-bump) rather than silently bumping VERSION with nothing copied.
296
306
  *
297
- * `dryRun` reports what WOULD be copied without writing.
307
+ * `dryRun` reports what WOULD be copied without writing. `resolveDoc` overrides
308
+ * how a doc's source path is resolved (defaults to {@link resolveProtocolDoc});
309
+ * it exists so a test can force a non-resolvable source (a path that does not
310
+ * exist) to exercise the skip path.
298
311
  */
299
312
  export function resyncProtocol(
300
313
  repoPath: string,
301
- options: {dryRun?: boolean} = {},
314
+ options: {
315
+ dryRun?: boolean;
316
+ resolveDoc?: (name: string) => string;
317
+ } = {},
302
318
  ): ResyncResult {
303
319
  const protocolDir = join(repoPath, WORK_ROOT, 'protocol');
320
+ const resolveDoc = options.resolveDoc ?? ((name) => resolveProtocolDoc(name));
304
321
  const docs: ResyncedDoc[] = [];
305
322
  for (const name of PROTOCOL_DOCS) {
306
323
  // Resolve the SOURCE (package/dev), never the target's adopted copy.
307
- const source = resolveProtocolDoc(name, undefined);
324
+ const source = resolveDoc(name);
308
325
  const destAbs = join(protocolDir, name);
309
326
  const destRel = relative(repoPath, destAbs);
310
- let unchanged = false;
311
- if (existsSync(source) && existsSync(destAbs)) {
312
- unchanged =
313
- readFileSync(source, 'utf8') === readFileSync(destAbs, 'utf8');
327
+ const sourceExists = existsSync(source);
328
+ if (!sourceExists) {
329
+ // The source doc could not be resolved: copy NOTHING and record the doc as
330
+ // SKIPPED (never a change). This is the latent-bug fix: a non-resolvable
331
+ // source must not count as `changed` and bump `VERSION` while copying no
332
+ // file. `unchanged: false` here is honest (the dest was not made to match a
333
+ // source), but `skipped: true` keeps it OUT of the VERSION-bump tally.
334
+ docs.push({name, dest: destRel, unchanged: false, skipped: true});
335
+ continue;
314
336
  }
315
- if (!options.dryRun && existsSync(source)) {
337
+ // Source resolved: a doc is CHANGED (VERSION-bump-worthy) only when the dest
338
+ // is absent OR its content differs from the source. An identical dest is a
339
+ // no-op copy (`unchanged`), so an already-synced re-run stays a true no-op.
340
+ const unchanged =
341
+ existsSync(destAbs) &&
342
+ readFileSync(source, 'utf8') === readFileSync(destAbs, 'utf8');
343
+ if (!options.dryRun) {
316
344
  mkdirSync(dirname(destAbs), {recursive: true});
317
345
  copyFileSync(source, destAbs);
318
346
  }
319
- docs.push({name, dest: destRel, unchanged});
347
+ docs.push({name, dest: destRel, unchanged, skipped: false});
320
348
  }
321
349
 
322
350
  const versionAbs = join(protocolDir, 'VERSION');
323
351
  const versionRel = relative(repoPath, versionAbs);
324
- // Bump VERSION only when a doc actually CHANGED (or VERSION is absent). A
325
- // re-run on an already-synced repo must stay a true no-op — otherwise the
326
- // (always-fresh) `synced-at` timestamp would dirty the tree on every idempotent
327
- // re-run.
328
- const anyDocChanged = docs.some((d) => !d.unchanged);
329
- if (!options.dryRun && (anyDocChanged || !existsSync(versionAbs))) {
352
+ // Bump VERSION only when a doc was ACTUALLY COPIED with new content, or when
353
+ // VERSION is absent AND at least one doc was genuinely synced (copied). A
354
+ // SKIPPED doc (unresolvable source, nothing copied) is NEVER a change —
355
+ // counting it would bump VERSION without copying a single doc (the latent bug),
356
+ // and the write-when-absent fallback must ALSO require a real copy so an
357
+ // all-skipped resync leaves no VERSION behind. A re-run on an already-synced
358
+ // repo must stay a true no-op — otherwise the (always-fresh) `synced-at`
359
+ // timestamp would dirty the tree on every idempotent re-run.
360
+ const anyDocChanged = docs.some((d) => !d.unchanged && !d.skipped);
361
+ const anyDocSynced = docs.some((d) => !d.skipped);
362
+ if (
363
+ !options.dryRun &&
364
+ (anyDocChanged || (anyDocSynced && !existsSync(versionAbs)))
365
+ ) {
330
366
  mkdirSync(dirname(versionAbs), {recursive: true});
331
367
  const today = new Date().toISOString().slice(0, 10);
332
368
  writeFileSync(
package/src/prompt.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * The wrapper is NOT hardcoded here: it is read VERBATIM from the work-contract
8
8
  * (`skills/setup/protocol/CLAIM-PROTOCOL.md` → "The prompt handed to the work agent"),
9
9
  * so the emitted text can never silently diverge from the canonical contract.
10
- * We only substitute the per-task placeholders (`<slug>`, `<prd>`).
10
+ * We only substitute the per-task placeholders (`<slug>`, `<spec>`).
11
11
  *
12
12
  * The wrapper draws the git boundary IN-BAND — the spawned agent does NO git ops
13
13
  * on the repo (no commit/push, no moving `work/` files); the RUNNER owns every
@@ -137,7 +137,7 @@ export function resolveProtocolDoc(
137
137
  /**
138
138
  * Pull the canonical wrapper TEMPLATE out of CLAIM-PROTOCOL.md: the first fenced
139
139
  * code block following the "The prompt handed to the work agent" heading. The
140
- * returned text still contains the `<slug>` / `<prd>` placeholders verbatim — it
140
+ * returned text still contains the `<slug>` / `<spec>` placeholders verbatim — it
141
141
  * is the single source of truth for the wrapper.
142
142
  */
143
143
  export function extractCanonicalWrapperTemplate(protocol: string): string {
@@ -208,7 +208,7 @@ export function extractCanonicalWrapperTemplate(protocol: string): string {
208
208
  *
209
209
  * Pure string transform; runs AFTER `extractCanonicalWrapperTemplate` (a pure
210
210
  * verbatim extractor stays a pure verbatim extractor) and BEFORE the
211
- * `<slug>`/`<prd>` substitution.
211
+ * `<slug>`/`<spec>` substitution.
212
212
  */
213
213
  export function applyPromptGuidance(
214
214
  template: string,
@@ -227,17 +227,17 @@ export function applyPromptGuidance(
227
227
  }
228
228
 
229
229
  /**
230
- * The constant wrapper, parameterised only by the task slug, its source prd
230
+ * The constant wrapper, parameterised only by the task slug, its source spec
231
231
  * slug, and (optionally) the resolved {@link PromptGuidance} nudges. Read
232
232
  * verbatim from the work-contract and substituted — never a divergent hardcoded
233
- * copy. `prd` may be `undefined` when the task has no `prd:` field. When
233
+ * copy. `spec` may be `undefined` when the task has no `spec:` field. When
234
234
  * `promptGuidance` is omitted (or every member resolves false) the output is
235
235
  * BYTE-IDENTICAL to today's wrapper (the ELSE-branch of every conditional is
236
236
  * the historic text).
237
237
  */
238
238
  export function wrapper(
239
239
  slug: string,
240
- prd: string | undefined,
240
+ spec: string | undefined,
241
241
  options: {
242
242
  protocolPath?: string;
243
243
  cwd?: string;
@@ -252,7 +252,7 @@ export function wrapper(
252
252
  const protocol = readFileSync(protocolPath, 'utf8');
253
253
  const template = extractCanonicalWrapperTemplate(protocol);
254
254
  const resolved = applyPromptGuidance(template, options.promptGuidance);
255
- return resolved.replace(/<slug>/g, slug).replace(/<prd>/g, prd ?? '<prd>');
255
+ return resolved.replace(/<slug>/g, slug).replace(/<spec>/g, spec ?? '<spec>');
256
256
  }
257
257
 
258
258
  /**
@@ -427,7 +427,7 @@ export function buildContinueBlock(slug: string, ctx: ContinueContext): string {
427
427
  */
428
428
  export function buildAgentPrompt(
429
429
  slug: string,
430
- prd: string | undefined,
430
+ spec: string | undefined,
431
431
  taskPrompt: string,
432
432
  options: {
433
433
  protocolPath?: string;
@@ -436,7 +436,7 @@ export function buildAgentPrompt(
436
436
  promptGuidance?: {testFirst?: boolean};
437
437
  } = {},
438
438
  ): string {
439
- const head = wrapper(slug, prd, options);
439
+ const head = wrapper(slug, spec, options);
440
440
  if (options.continueContext) {
441
441
  const block = buildContinueBlock(slug, options.continueContext);
442
442
  return `${head}\n\n${block}\n\n${taskPrompt}\n`;
@@ -459,8 +459,11 @@ export interface ResolvedTask {
459
459
  path: string;
460
460
  /** The folder the task was resolved from (in-progress wins over tasks-ready). */
461
461
  folder: TaskFolder;
462
- /** The task's source prd slug (frontmatter `prd:`), if any. */
463
- prd: string | undefined;
462
+ /**
463
+ * The task's source SPEC slug (frontmatter `spec:`, or the legacy `prd:` key
464
+ * as read-only back-compat), if any.
465
+ */
466
+ spec: string | undefined;
464
467
  /** The extracted `## Prompt` body. */
465
468
  taskPrompt: string;
466
469
  }
@@ -624,11 +627,11 @@ export function resolveTask(
624
627
  );
625
628
  }
626
629
  const fm = parseFrontmatter(content);
627
- // MIGRATE step (prd `prd-to-spec-vocabulary-cutover-and-migration-command`):
628
- // read the parent-spec pointer off the new `fm.spec` field (populated beside
629
- // `fm.prd` by the expand task). The `ResolvedTask.prd` field name is left
630
- // unchanged (a struct-field rename is out of this batch's scope).
631
- return {slug, path, folder, prd: fm.spec, taskPrompt};
630
+ // Read the parent-spec pointer off `fm.spec` (populated from EITHER the
631
+ // canonical `spec:` key or the legacy `prd:` back-compat alias) into the
632
+ // `spec`-only `ResolvedTask` (spec
633
+ // `prd-to-spec-vocabulary-cutover-and-migration-command`).
634
+ return {slug, path, folder, spec: fm.spec, taskPrompt};
632
635
  }
633
636
  const searched = order.map((f) => `${workFolderRel(f)}/`).join(', ');
634
637
  throw new PromptError(`no task '${slug}' found in ${searched}`);
@@ -722,8 +725,8 @@ export function resolvePromptGuidanceForItem(options: {
722
725
  }): PromptGuidance {
723
726
  const taskFm = parseFrontmatter(options.taskContent);
724
727
  let specFm: Frontmatter | undefined;
725
- // MIGRATE step (prd `prd-to-spec-vocabulary-cutover-and-migration-command`):
726
- // read the parent-spec pointer off `fm.spec` (populated beside `fm.prd`).
728
+ // Read the parent-spec pointer off `fm.spec` (populated from the `spec:` key or
729
+ // the legacy `prd:` back-compat alias).
727
730
  if (taskFm.spec !== undefined) {
728
731
  const specPath = findSpecPath(options.cwd, taskFm.spec);
729
732
  if (specPath !== undefined) {
@@ -752,7 +755,7 @@ export function renderPrompt(options: PromptOptions): string {
752
755
  repoResolved: {testFirst: options.promptGuidance?.testFirst === true},
753
756
  taskContent: readFileSync(task.path, 'utf8'),
754
757
  });
755
- return buildAgentPrompt(task.slug, task.prd, task.taskPrompt, {
758
+ return buildAgentPrompt(task.slug, task.spec, task.taskPrompt, {
756
759
  protocolPath: options.protocolPath,
757
760
  cwd: options.cwd,
758
761
  promptGuidance: resolvedGuidance,
package/src/run.ts CHANGED
@@ -808,7 +808,7 @@ async function runOneItem(
808
808
  repoResolved: resolvePromptGuidance(config),
809
809
  taskContent: readFileSync(task.path, 'utf8'),
810
810
  });
811
- prompt = buildAgentPrompt(task.slug, task.prd, task.taskPrompt, {
811
+ prompt = buildAgentPrompt(task.slug, task.spec, task.taskPrompt, {
812
812
  cwd: tree.dir,
813
813
  continueContext,
814
814
  promptGuidance: itemGuidance,
package/src/tasking.ts CHANGED
@@ -474,9 +474,8 @@ export async function performTask(
474
474
  // final commit must scrub any agent writes there (an attempt to self-place into
475
475
  // the pool, prd US #4) before `git add -A` would sweep them in.
476
476
  const poolBefore = snapshotPool(cwd);
477
- // MIGRATE step (prd `prd-to-spec-vocabulary-cutover-and-migration-command`):
478
- // read the parent-spec self-pointer off `prdFm.spec` (populated beside
479
- // `prdFm.prd` by the expand task).
477
+ // Read the parent-spec self-pointer off `specFm.spec` (populated from the
478
+ // `spec:` key or the legacy `prd:` back-compat alias).
480
479
  const prompt = buildTaskingSpec(slug, specFm.spec);
481
480
  let agent: {ok: boolean; detail?: string};
482
481
  try {