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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +7 -1
- package/dist/cli.js.map +1 -1
- package/dist/do.js +2 -2
- package/dist/do.js.map +1 -1
- package/dist/frontmatter.d.ts +24 -31
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +15 -16
- package/dist/frontmatter.js.map +1 -1
- package/dist/prd-to-spec.d.ts +15 -1
- package/dist/prd-to-spec.d.ts.map +1 -1
- package/dist/prd-to-spec.js +36 -14
- package/dist/prd-to-spec.js.map +1 -1
- package/dist/prompt.d.ts +12 -9
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +17 -17
- package/dist/prompt.js.map +1 -1
- package/dist/protocol/CLAIM-PROTOCOL.md +4 -4
- package/dist/protocol/REVIEW-PROTOCOL.md +1 -1
- package/dist/protocol/TASKING-PROTOCOL.md +12 -12
- package/dist/protocol/WORK-CONTRACT.md +39 -39
- package/dist/protocol/spec-template.md +1 -1
- package/dist/protocol/task-template.md +3 -3
- package/dist/run.js +1 -1
- package/dist/run.js.map +1 -1
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +2 -3
- package/dist/tasking.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +11 -1
- package/src/do.ts +2 -2
- package/src/frontmatter.ts +33 -41
- package/src/prd-to-spec.ts +51 -15
- package/src/prompt.ts +22 -19
- package/src/run.ts +1 -1
- package/src/tasking.ts +2 -3
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(
|
|
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.
|
|
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.
|
|
2248
|
+
prompt = buildAgentPrompt(task.slug, task.spec, task.taskPrompt, {
|
|
2249
2249
|
cwd,
|
|
2250
2250
|
continueContext,
|
|
2251
2251
|
promptGuidance: itemGuidance,
|
package/src/frontmatter.ts
CHANGED
|
@@ -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
|
|
48
|
-
* `work/specs/ready/<spec>.md`. The `spec` vocabulary name
|
|
49
|
-
*
|
|
50
|
-
* the
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
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 **
|
|
71
|
-
* `task.
|
|
72
|
-
* ONLY on the
|
|
73
|
-
* - a **lone task** (`intake`'s TASK outcome, no `
|
|
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 `
|
|
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
|
-
*
|
|
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 /
|
|
82
|
-
* a human hand-edit could produce both, and the precedence rule (`
|
|
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
|
|
85
|
-
* crashing. `undefined` when omitted (every non-intake
|
|
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
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
// (
|
|
387
|
-
//
|
|
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
|
|
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 `
|
|
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 `
|
|
464
|
-
* hand-edit could) BOTH are present, `
|
|
465
|
-
* `issue:` and IGNORES the task's own `issue:` (the fanned-
|
|
466
|
-
* authoritative one; a lone `issue:` on a `
|
|
467
|
-
* that degrades to "use the
|
|
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
|
package/src/prd-to-spec.ts
CHANGED
|
@@ -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: {
|
|
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 =
|
|
324
|
+
const source = resolveDoc(name);
|
|
308
325
|
const destAbs = join(protocolDir, name);
|
|
309
326
|
const destRel = relative(repoPath, destAbs);
|
|
310
|
-
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
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
|
|
325
|
-
//
|
|
326
|
-
// (
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
-
|
|
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>`, `<
|
|
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>` / `<
|
|
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>`/`<
|
|
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
|
|
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. `
|
|
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
|
-
|
|
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(/<
|
|
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
|
-
|
|
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,
|
|
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
|
-
/**
|
|
463
|
-
|
|
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
|
-
//
|
|
628
|
-
//
|
|
629
|
-
// `
|
|
630
|
-
//
|
|
631
|
-
return {slug, path, folder,
|
|
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
|
-
//
|
|
726
|
-
//
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
478
|
-
//
|
|
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 {
|