@agent-plan/core 0.2.26 → 0.2.28

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.
Files changed (47) hide show
  1. package/dist/description-freshness.d.ts +37 -0
  2. package/dist/description-freshness.d.ts.map +1 -0
  3. package/dist/description-freshness.js +84 -0
  4. package/dist/handoff-context.d.ts +135 -3
  5. package/dist/handoff-context.d.ts.map +1 -1
  6. package/dist/handoff-context.js +336 -25
  7. package/dist/index.d.ts +4 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +4 -1
  10. package/dist/package-version.d.ts +2 -0
  11. package/dist/package-version.d.ts.map +1 -1
  12. package/dist/package-version.js +1 -1
  13. package/dist/payload-fallback.d.ts +1 -1
  14. package/dist/payload-fallback.d.ts.map +1 -1
  15. package/dist/payload-fallback.js +6 -0
  16. package/dist/plan-store.d.ts +129 -30
  17. package/dist/plan-store.d.ts.map +1 -1
  18. package/dist/plan-store.js +690 -115
  19. package/dist/planner-rules.d.ts.map +1 -1
  20. package/dist/planner-rules.js +6 -2
  21. package/dist/read-tracking.d.ts +27 -3
  22. package/dist/read-tracking.d.ts.map +1 -1
  23. package/dist/read-tracking.js +53 -4
  24. package/dist/recap.d.ts.map +1 -1
  25. package/dist/recap.js +33 -9
  26. package/dist/renderer.d.ts.map +1 -1
  27. package/dist/renderer.js +0 -1
  28. package/dist/requirement-macro-tasks.d.ts +2 -2
  29. package/dist/requirement-macro-tasks.d.ts.map +1 -1
  30. package/dist/requirement-macro-tasks.js +2 -2
  31. package/dist/runtime-diagnostics.d.ts +34 -0
  32. package/dist/runtime-diagnostics.d.ts.map +1 -0
  33. package/dist/runtime-diagnostics.js +39 -0
  34. package/dist/schema.d.ts +487 -17
  35. package/dist/schema.d.ts.map +1 -1
  36. package/dist/schema.js +24 -4
  37. package/dist/task-context.d.ts +41 -2
  38. package/dist/task-context.d.ts.map +1 -1
  39. package/dist/task-context.js +102 -4
  40. package/dist/task-selection.d.ts +44 -1
  41. package/dist/task-selection.d.ts.map +1 -1
  42. package/dist/task-selection.js +158 -7
  43. package/dist/write-coordination.d.ts +27 -0
  44. package/dist/write-coordination.d.ts.map +1 -0
  45. package/dist/write-coordination.js +223 -0
  46. package/package.json +1 -1
  47. package/planner-skill.md +36 -20
@@ -1,6 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { buildPhaseWorkMap } from "./task-context.js";
2
3
  export const COMPLETION_SUMMARY_HEADING = "**Completion summary:**";
3
4
  export const HANDOFF_COMPLETENESS_AUDIT_VERSION = 1;
5
+ export const HANDOFF_COLD_START_INVENTORY_VERSION = 1;
6
+ export const TARGET_HANDOFF_CONTENT_CHARS = 8_000;
4
7
  export const MAX_HANDOFF_CONTENT_CHARS = 24_000;
5
8
  export const HANDOFF_AUDIT_START_MARKER = "<!-- agent-plan:handoff-audit:start -->";
6
9
  export const HANDOFF_AUDIT_END_MARKER = "<!-- agent-plan:handoff-audit:end -->";
@@ -24,6 +27,37 @@ export const HANDOFF_COMPLETENESS_CATEGORIES = [
24
27
  { id: "project-operating-notes", label: "Project-specific operating notes" },
25
28
  { id: "conversation-only-facts", label: "Conversation-only facts" },
26
29
  ];
30
+ export const HANDOFF_COLD_START_SOURCE_REVIEWS = [
31
+ { id: "conversation", label: "Conversation, user corrections, and authorization state" },
32
+ { id: "planner-entities", label: "Task, sibling tasks, phase, feature, requirements, and prior handoff" },
33
+ { id: "working-tree", label: "Changed files, diffs, implementation state, and ownership" },
34
+ { id: "verification-runtime", label: "Commands, test results, runtime observations, and limitations" },
35
+ { id: "peer-agent-output", label: "Peer-agent messages and delegated-work results, or confirmation that none exist" },
36
+ ];
37
+ export const HANDOFF_COLD_START_INVENTORY_CATEGORIES = [
38
+ { id: "files", label: "Exact files" },
39
+ { id: "symbols", label: "Exact symbols and identifiers" },
40
+ { id: "working-tree-ownership", label: "Working-tree state, work ownership, and commit/discard authorization" },
41
+ { id: "negative-state", label: "Work not started, removals not performed, and intentionally untouched state" },
42
+ { id: "commands-tools", label: "Commands and tool paths" },
43
+ { id: "runtime-wiring", label: "Runtime wiring, call sites, and data flow" },
44
+ { id: "preservation-constraints", label: "Behavior and code paths that must survive" },
45
+ { id: "verification-evidence", label: "Concrete observations proving completed, live, dead, or inert state" },
46
+ { id: "related-planned-work", label: "Sibling tasks and related planned capabilities" },
47
+ { id: "user-visible-behavior", label: "User-visible behavior" },
48
+ { id: "operator-actions", label: "Operator actions" },
49
+ { id: "blockers-risks", label: "Blockers and risks" },
50
+ { id: "remaining-work", label: "Remaining work" },
51
+ { id: "ordered-resume-steps", label: "Ordered resume steps" },
52
+ ];
53
+ export const HANDOFF_CANONICAL_SECTIONS = [
54
+ "Current focus",
55
+ "Current and partial state",
56
+ "Preservation constraints",
57
+ "Supporting documents",
58
+ "Blockers and risks",
59
+ "How to resume",
60
+ ];
27
61
  export class HandoffContractError extends Error {
28
62
  code;
29
63
  details;
@@ -41,6 +75,37 @@ export function hasTaskCompletionEvidence(task) {
41
75
  return true;
42
76
  return task.statusLog.some((entry) => entry.toStatus === "done" && entry.description.trim().length > 0);
43
77
  }
78
+ export function buildHandoffDraftTemplate(phase, feature) {
79
+ const featureRef = `F${String(feature.number).padStart(3, "0")}`;
80
+ const phaseRef = `P${String(phase.number).padStart(3, "0")}(${featureRef})`;
81
+ return [
82
+ `# ${phaseRef} — {{REQUIRED: meaningful handoff title}}`,
83
+ "",
84
+ "<!-- Planner-generated: Created at, Updated at, and structured Reason. Do not write these lines in the draft. -->",
85
+ "",
86
+ "## Current focus",
87
+ `- Feature: ${featureRef} — ${feature.name}`,
88
+ `- Phase: ${phaseRef} — ${phase.title}`,
89
+ "- Task: {{REQUIRED: exact composite task ref and title}}",
90
+ "- Exact resume point: {{REQUIRED: file, symbol, command, or state boundary}}",
91
+ "",
92
+ "## Current and partial state",
93
+ "{{REQUIRED: concise completed/current/partial state only; keep extended detail in supporting documents}}",
94
+ "",
95
+ "## Preservation constraints",
96
+ "{{REQUIRED: minimal behaviors and boundaries that must survive}}",
97
+ "",
98
+ "## Supporting documents",
99
+ "- {{REQUIRED: ordered .planner/docs/*.md links with why each is needed, or an explicit verified statement that none are needed}}",
100
+ "",
101
+ "## Blockers and risks",
102
+ "- {{REQUIRED: blocker/risk, or a substantive verified statement that none apply}}",
103
+ "",
104
+ "## How to resume",
105
+ "1. {{REQUIRED: first exact action, including file/symbol/command}}",
106
+ "2. {{REQUIRED: subsequent ordered actions and verification}}",
107
+ ].join("\n");
108
+ }
44
109
  export function auditPhaseHandoff(phase, feature) {
45
110
  const missingCompletionTasks = phase.tasks
46
111
  .filter((task) => task.status === "done" && !hasTaskCompletionEvidence(task))
@@ -53,9 +118,20 @@ export function auditPhaseHandoff(phase, feature) {
53
118
  missingCompletionTaskIds: missingCompletionTasks.map((task) => task.id),
54
119
  missingCompletionTasks,
55
120
  completenessVersion: HANDOFF_COMPLETENESS_AUDIT_VERSION,
121
+ targetContentChars: TARGET_HANDOFF_CONTENT_CHARS,
56
122
  maxContentChars: MAX_HANDOFF_CONTENT_CHARS,
57
123
  completenessCategories: HANDOFF_COMPLETENESS_CATEGORIES,
124
+ canonicalSections: HANDOFF_CANONICAL_SECTIONS,
125
+ requiredHumanInputs: [
126
+ { id: "title", label: "Meaningful title", description: "A concise handoff title; the planner renders it as the H1." },
127
+ { id: "reason", label: "Reason", description: "Why work is stopping and why a cold agent needs this handoff; the planner renders it as metadata." },
128
+ ],
129
+ draftTemplate: buildHandoffDraftTemplate(phase, feature),
130
+ coldStartInventoryVersion: HANDOFF_COLD_START_INVENTORY_VERSION,
131
+ coldStartSourceReviews: HANDOFF_COLD_START_SOURCE_REVIEWS,
132
+ coldStartInventoryCategories: HANDOFF_COLD_START_INVENTORY_CATEGORIES,
58
133
  existingCompletenessAudit: phase.handoffAudit,
134
+ phaseWorkMap: buildPhaseWorkMap(phase, feature.number),
59
135
  };
60
136
  }
61
137
  function nonEmpty(value, field) {
@@ -93,7 +169,14 @@ export function handoffContentHash(content) {
93
169
  }
94
170
  export function validateHandoffCompletenessAudit(audit) {
95
171
  const expectedIds = HANDOFF_COMPLETENESS_CATEGORIES.map((entry) => entry.id);
96
- if (!audit || audit.version !== HANDOFF_COMPLETENESS_AUDIT_VERSION) {
172
+ if (!audit) {
173
+ return expectedIds.map((category) => ({
174
+ category,
175
+ status: "captured",
176
+ detail: "Derived from the compact handoff and persisted planner state; no duplicate prose audit was supplied.",
177
+ }));
178
+ }
179
+ if (audit.version !== HANDOFF_COMPLETENESS_AUDIT_VERSION) {
97
180
  throw new HandoffContractError("HANDOFF_COMPLETENESS_AUDIT_REQUIRED", `Handoff completeness audit version ${HANDOFF_COMPLETENESS_AUDIT_VERSION} is required.`, { requiredVersion: HANDOFF_COMPLETENESS_AUDIT_VERSION, missingCategories: expectedIds });
98
181
  }
99
182
  const byCategory = new Map();
@@ -115,6 +198,202 @@ export function validateHandoffCompletenessAudit(audit) {
115
198
  }
116
199
  return expectedIds.map((id) => byCategory.get(id));
117
200
  }
201
+ export function validateHandoffColdStartInventory(inventory, content, supportingDocumentContents = []) {
202
+ const expectedIds = HANDOFF_COLD_START_INVENTORY_CATEGORIES.map((entry) => entry.id);
203
+ if (!inventory) {
204
+ return expectedIds.map((category) => ({
205
+ category,
206
+ items: ["Derived from the compact handoff and persisted planner state."],
207
+ }));
208
+ }
209
+ if (inventory.version !== HANDOFF_COLD_START_INVENTORY_VERSION) {
210
+ throw new HandoffContractError("HANDOFF_COLD_START_INVENTORY_REQUIRED", `Cold-start inventory version ${HANDOFF_COLD_START_INVENTORY_VERSION} is required when legacy inventory evidence is supplied.`, { requiredVersion: HANDOFF_COLD_START_INVENTORY_VERSION, missingCategories: expectedIds });
211
+ }
212
+ const requiredSources = HANDOFF_COLD_START_SOURCE_REVIEWS.map((entry) => entry.id);
213
+ const requiredSourceIds = new Set(requiredSources);
214
+ const sourceReviews = new Map();
215
+ const duplicateSources = [];
216
+ for (const review of inventory.sourceReviews) {
217
+ if (sourceReviews.has(review.source))
218
+ duplicateSources.push(review.source);
219
+ else
220
+ sourceReviews.set(review.source, review.detail);
221
+ }
222
+ const missingSources = requiredSources.filter((source) => !sourceReviews.has(source));
223
+ const unknownSources = [...sourceReviews.keys()].filter((source) => !requiredSourceIds.has(source));
224
+ const invalidSources = requiredSources.filter((source) => {
225
+ const detail = sourceReviews.get(source);
226
+ return detail !== undefined && !isSubstantive(detail);
227
+ });
228
+ const byCategory = new Map();
229
+ const duplicates = [];
230
+ for (const entry of inventory.entries) {
231
+ if (byCategory.has(entry.category))
232
+ duplicates.push(entry.category);
233
+ else
234
+ byCategory.set(entry.category, entry);
235
+ }
236
+ const missingCategories = expectedIds.filter((id) => !byCategory.has(id));
237
+ const unknownCategories = [...byCategory.keys()].filter((id) => !expectedIds.includes(id));
238
+ const invalidCategories = [];
239
+ for (const id of expectedIds) {
240
+ const entry = byCategory.get(id);
241
+ if (!entry)
242
+ continue;
243
+ const items = uniqueStrings(entry.items);
244
+ const notApplicableReason = entry.notApplicableReason?.trim() ?? "";
245
+ if (items.length === 0)
246
+ invalidCategories.push(id);
247
+ if (notApplicableReason && !isSubstantive(notApplicableReason))
248
+ invalidCategories.push(id);
249
+ if (items.some((item) => /^(?:n\/?a|none|nothing|unknown|tbd|see above)$/i.test(item.trim())))
250
+ invalidCategories.push(id);
251
+ }
252
+ if (missingSources.length > 0 || unknownSources.length > 0 || duplicateSources.length > 0 || invalidSources.length > 0
253
+ || missingCategories.length > 0 || unknownCategories.length > 0 || duplicates.length > 0 || invalidCategories.length > 0) {
254
+ throw new HandoffContractError("HANDOFF_COLD_START_INVENTORY_REQUIRED", "The cold-start inventory is incomplete. Review every prepared source before drafting; every category needs at least one concrete item. When nothing exists, the item must explicitly state the verified absence and why it matters (for example, 'No deletion has started; all original files remain intact').", {
255
+ missingSources,
256
+ invalidSources,
257
+ unknownSources,
258
+ duplicateSources: [...new Set(duplicateSources)],
259
+ missingCategories,
260
+ invalidCategories: [...new Set(invalidCategories)],
261
+ unknownCategories,
262
+ duplicateCategories: [...new Set(duplicates)],
263
+ });
264
+ }
265
+ const normalize = (value) => value.normalize("NFKC").replace(/[`*_]/g, "").replace(/\s+/g, " ").toLowerCase();
266
+ const corpus = normalize([content, ...supportingDocumentContents].join("\n"));
267
+ const uncoveredItems = expectedIds.flatMap((id) => {
268
+ const entry = byCategory.get(id);
269
+ return uniqueStrings(entry.items)
270
+ .filter((item) => !corpus.includes(normalize(item)))
271
+ .map((item) => ({ category: id, item }));
272
+ });
273
+ if (uncoveredItems.length > 0) {
274
+ throw new HandoffContractError("HANDOFF_COLD_START_INVENTORY_UNCOVERED", "Resume-critical inventory items are missing from the canonical handoff and validated supporting documents. Add each exact item before retrying; do not certify from generic prose.", { uncoveredItems });
275
+ }
276
+ return expectedIds.map((id) => {
277
+ const entry = byCategory.get(id);
278
+ return {
279
+ category: entry.category,
280
+ items: uniqueStrings(entry.items),
281
+ ...(entry.notApplicableReason?.trim() ? { notApplicableReason: entry.notApplicableReason.trim() } : {}),
282
+ };
283
+ });
284
+ }
285
+ export function validateHandoffReadBackVerification(phase, input) {
286
+ const audit = phase.handoffAudit;
287
+ const actualContentHash = phase.handoff.trim() ? handoffContentHash(phase.handoff) : "";
288
+ if (!phase.handoff.trim() || !audit || audit.contentHash !== actualContentHash || audit.contentLength !== phase.handoff.length) {
289
+ throw new HandoffContractError("HANDOFF_READBACK_VERIFICATION_REQUIRED", "The persisted handoff or its audit metadata is missing or inconsistent. Run handoff_prepare, rewrite the handoff, then read it back before verification.", { expectedContentHash: input.expectedContentHash, actualContentHash, auditContentHash: audit?.contentHash ?? "" });
290
+ }
291
+ if (!input.expectedContentHash.trim() || input.expectedContentHash !== actualContentHash) {
292
+ throw new HandoffContractError("HANDOFF_READBACK_VERIFICATION_REQUIRED", "The handoff changed after the read-back candidate was selected. Call handoff_show again and verify the returned contentHash.", { expectedContentHash: input.expectedContentHash, actualContentHash });
293
+ }
294
+ const requiredSources = HANDOFF_COLD_START_SOURCE_REVIEWS.map((entry) => entry.id);
295
+ const suppliedReviews = input.sourceReviews ?? [];
296
+ if (suppliedReviews.length === 0 && (input.omissionsFound ?? []).length === 0) {
297
+ return requiredSources.map((source) => ({
298
+ source,
299
+ detail: "Derived from persisted handoff content, planner entities, and read-back state; no duplicate source inventory was supplied.",
300
+ }));
301
+ }
302
+ const bySource = new Map();
303
+ const duplicateSources = [];
304
+ for (const review of suppliedReviews) {
305
+ if (bySource.has(review.source))
306
+ duplicateSources.push(review.source);
307
+ else
308
+ bySource.set(review.source, review.detail.trim());
309
+ }
310
+ const missingSources = requiredSources.filter((source) => !bySource.has(source));
311
+ const unknownSources = [...bySource.keys()].filter((source) => !requiredSources.includes(source));
312
+ const invalidSources = requiredSources.filter((source) => {
313
+ const detail = bySource.get(source);
314
+ return detail !== undefined && !isSubstantive(detail);
315
+ });
316
+ if (missingSources.length > 0 || unknownSources.length > 0 || duplicateSources.length > 0 || invalidSources.length > 0) {
317
+ throw new HandoffContractError("HANDOFF_READBACK_VERIFICATION_REQUIRED", "Legacy read-back source evidence is incomplete. For compact handoffs omit sourceReviews and omissionsFound so the planner can derive evidence from persisted state.", { missingSources, unknownSources, duplicateSources: [...new Set(duplicateSources)], invalidSources });
318
+ }
319
+ const omissionsFound = uniqueStrings(input.omissionsFound ?? []);
320
+ if (omissionsFound.length > 0) {
321
+ throw new HandoffContractError("HANDOFF_READBACK_GAPS_FOUND", "The persisted handoff is not resume-ready because the read-back found omissions. Run handoff_prepare again, reconcile every listed gap, and rewrite before retrying verification.", { omissionsFound });
322
+ }
323
+ return requiredSources.map((source) => ({ source, detail: bySource.get(source) }));
324
+ }
325
+ function sectionBody(content, headings) {
326
+ const lines = content.split(/\r?\n/);
327
+ const normalized = new Set(headings.map((heading) => heading.toLowerCase()));
328
+ const start = lines.findIndex((line) => {
329
+ const match = line.match(/^##\s+(.+?)\s*$/);
330
+ return Boolean(match && normalized.has(match[1].toLowerCase()));
331
+ });
332
+ if (start < 0)
333
+ return "";
334
+ const body = [];
335
+ for (let index = start + 1; index < lines.length; index += 1) {
336
+ if (/^##\s+/.test(lines[index]))
337
+ break;
338
+ body.push(lines[index]);
339
+ }
340
+ return body.join("\n").trim();
341
+ }
342
+ function boundedSection(value, fallback, maxChars) {
343
+ const normalized = value.trim() || fallback;
344
+ if (normalized.length <= maxChars)
345
+ return normalized;
346
+ return `${normalized.slice(0, Math.max(0, maxChars - 86)).trimEnd()}\n\n[Extended detail continues in the linked planner document.]`;
347
+ }
348
+ /**
349
+ * Mechanically compact oversized legacy or verbose handoffs while retaining the
350
+ * complete submitted body in one planner-owned Markdown document.
351
+ */
352
+ export function externalizeOversizedHandoffContent(content, supportingDocumentPath) {
353
+ const base = stripRenderedCompletenessAudit(content);
354
+ if (base.length <= TARGET_HANDOFF_CONTENT_CHARS) {
355
+ return { content: base, externalized: false, extendedContent: "" };
356
+ }
357
+ const firstHeading = base.split(/\r?\n/).find((line) => /^#\s+/.test(line.trim()))?.trim() ?? "# Handoff resume capsule";
358
+ const metadata = ["Created at", "Updated at", "Reason"].map((label) => base.split(/\r?\n/).find((line) => line.toLowerCase().startsWith(`${label.toLowerCase()}:`))?.trim() ?? `${label}: See supporting document.`);
359
+ const currentFocus = boundedSection(sectionBody(base, ["Current focus"]), "Exact focus and resume point are preserved in the supporting document.", 1_200);
360
+ const currentState = boundedSection(sectionBody(base, ["Current and partial state", "What was being done"]), "Current and partial work details are preserved in the supporting document.", 1_500);
361
+ const preservation = boundedSection(sectionBody(base, ["Preservation constraints"]), "Preservation constraints are recorded in the supporting document.", 900);
362
+ const blockers = boundedSection(sectionBody(base, ["Blockers and risks", "Blockers"]), "Blockers and risks are recorded in the supporting document.", 900);
363
+ const resume = boundedSection(sectionBody(base, ["How to resume", "Next steps"]), "1. Read the linked supporting document completely, then resume from its first ordered action.", 1_500);
364
+ const compact = [
365
+ firstHeading,
366
+ "",
367
+ ...metadata,
368
+ "",
369
+ "## Current focus",
370
+ currentFocus,
371
+ "",
372
+ "## Current and partial state",
373
+ currentState,
374
+ "",
375
+ "## Preservation constraints",
376
+ preservation,
377
+ "",
378
+ "## Supporting documents",
379
+ `- ${supportingDocumentPath} — Full submitted handoff detail externalized automatically because the inline resume capsule exceeded ${TARGET_HANDOFF_CONTENT_CHARS} characters. Read this document before resuming.`,
380
+ "",
381
+ "## Blockers and risks",
382
+ blockers,
383
+ "",
384
+ "## How to resume",
385
+ resume,
386
+ ].join("\n").trim();
387
+ return {
388
+ content: compact,
389
+ externalized: true,
390
+ extendedContent: `${firstHeading} — extended detail\n\n${base}\n`,
391
+ supportingDocument: {
392
+ path: supportingDocumentPath,
393
+ description: "Full submitted handoff detail externalized automatically; required for cold resume and reconciliation.",
394
+ },
395
+ };
396
+ }
118
397
  export function renderHandoffCompletenessAudit(audit) {
119
398
  const entries = validateHandoffCompletenessAudit(audit);
120
399
  const labels = new Map(HANDOFF_COMPLETENESS_CATEGORIES.map((entry) => [entry.id, entry.label]));
@@ -131,41 +410,58 @@ export function renderHandoffCompletenessAudit(audit) {
131
410
  HANDOFF_AUDIT_END_MARKER,
132
411
  ].join("\n").trim();
133
412
  }
134
- export function renderVerifiedHandoffContent(content, audit) {
413
+ export function renderVerifiedHandoffContent(content, audit, coldStartInventory, supportingDocumentContents = []) {
135
414
  const base = stripRenderedCompletenessAudit(content);
136
415
  validateCanonicalHandoffContent(base);
137
416
  validateHandoffCompletenessAudit(audit);
138
- const rendered = `${base}\n\n${renderHandoffCompletenessAudit(audit)}`.trim();
139
- if (rendered.length > MAX_HANDOFF_CONTENT_CHARS) {
140
- throw new HandoffContractError("HANDOFF_CONTENT_LIMIT_EXCEEDED", `Canonical handoff content is ${rendered.length} characters; the maximum is ${MAX_HANDOFF_CONTENT_CHARS}. Move extended detail to committed Markdown under .planner/docs/ and link it with a substantive explanation.`, { contentLength: rendered.length, maxContentChars: MAX_HANDOFF_CONTENT_CHARS });
417
+ validateHandoffColdStartInventory(coldStartInventory, base, supportingDocumentContents);
418
+ if (base.length > MAX_HANDOFF_CONTENT_CHARS) {
419
+ throw new HandoffContractError("HANDOFF_CONTENT_LIMIT_EXCEEDED", `Inline handoff content is ${base.length} characters after compaction; the absolute compatibility ceiling is ${MAX_HANDOFF_CONTENT_CHARS}. Retry through PlanStore so extended detail can be externalized automatically.`, { contentLength: base.length, targetContentChars: TARGET_HANDOFF_CONTENT_CHARS, maxContentChars: MAX_HANDOFF_CONTENT_CHARS, continuation: "externalize-and-retry" });
141
420
  }
142
- return rendered;
421
+ return base;
422
+ }
423
+ export function materializeHandoffMetadata(content, reason, createdAt, updatedAt) {
424
+ const normalizedReason = reason.trim();
425
+ if (!normalizedReason) {
426
+ throw new HandoffContractError("HANDOFF_REASON_REQUIRED", "A structured handoff reason is required before drafting or persistence. Run handoff_prepare and provide reason; timestamps are planner-generated.", { requiredInputs: ["title", "reason"], generatedMetadata: ["Created at", "Updated at"] });
427
+ }
428
+ const existingCreatedAt = content.match(/^Created at:\s*(\S+)\s*$/im)?.[1] ?? createdAt;
429
+ const withoutMetadata = content
430
+ .split(/\r?\n/)
431
+ .filter((line) => !/^(?:Created at|Updated at|Reason):\s*/i.test(line.trim()))
432
+ .join("\n")
433
+ .trim();
434
+ const lines = withoutMetadata.split("\n");
435
+ const firstContent = lines.findIndex((line) => line.trim().length > 0);
436
+ const metadata = [`Created at: ${existingCreatedAt}`, `Updated at: ${updatedAt}`, `Reason: ${normalizedReason}`];
437
+ if (firstContent >= 0 && /^#\s+/.test(lines[firstContent] ?? "")) {
438
+ lines.splice(firstContent + 1, 0, "", ...metadata);
439
+ }
440
+ else {
441
+ lines.unshift(...metadata, "");
442
+ }
443
+ return lines.join("\n").trim();
143
444
  }
144
445
  export function validateCanonicalHandoffContent(content) {
446
+ // A handoff is a resume capsule, not a form. The prepared scaffold documents
447
+ // useful headings, but requiring agents to reproduce those headings creates a
448
+ // late write-time failure and makes them draft the same handoff twice.
145
449
  const body = nonEmpty(content, "Handoff content");
146
- const required = [
147
- { label: "Created at", pattern: /^Created at:\s*\S+/im },
148
- { label: "Updated at", pattern: /^Updated at:\s*\S+/im },
149
- { label: "Reason", pattern: /^Reason:\s*\S+/im },
150
- { label: "Current focus", pattern: /^##\s+Current focus\s*$/im },
151
- { label: "What was being done", pattern: /^##\s+What was being done\s*$/im },
152
- { label: "How to resume", pattern: /^##\s+How to resume\s*$/im },
153
- { label: "Files touched", pattern: /^##\s+Files touched\s*$/im },
154
- { label: "Blockers", pattern: /^##\s+Blockers\s*$/im },
155
- { label: "Next steps", pattern: /^##\s+Next steps\s*$/im },
156
- { label: "Recent decisions", pattern: /^##\s+Recent decisions\s*$/im },
157
- ];
158
- const missing = required.filter((entry) => !entry.pattern.test(body)).map((entry) => entry.label);
159
- if (missing.length > 0)
160
- throw new Error(`Canonical handoff is missing required sections: ${missing.join(", ")}.`);
450
+ const unresolvedPlaceholders = [...body.matchAll(/\{\{REQUIRED:[^}]+\}\}/g)].map((match) => match[0]);
451
+ if (unresolvedPlaceholders.length > 0) {
452
+ throw new HandoffContractError("HANDOFF_CANONICAL_SECTIONS_REQUIRED", "The handoff still contains unresolved placeholders. Replace every placeholder before writing.", { missingSections: [], unresolvedPlaceholders: [...new Set(unresolvedPlaceholders)] });
453
+ }
161
454
  }
162
455
  export function validateHandoffContextSync(phase, feature, input) {
163
- renderVerifiedHandoffContent(input.content, input.completenessAudit);
164
456
  const requestedDocuments = input.supportingDocuments ?? [];
165
457
  const verifiedDocuments = input.verifiedSupportingDocuments ?? [];
166
458
  if (requestedDocuments.length !== verifiedDocuments.length) {
167
459
  throw new HandoffContractError("HANDOFF_SUPPORTING_DOCUMENT_INVALID", "Every supporting document must be validated by PlanStore before the handoff is written.", { requestedCount: requestedDocuments.length, verifiedCount: verifiedDocuments.length });
168
460
  }
461
+ const verifiedContents = input.verifiedSupportingDocumentContents ?? [];
462
+ if (verifiedContents.length !== verifiedDocuments.length) {
463
+ throw new HandoffContractError("HANDOFF_SUPPORTING_DOCUMENT_INVALID", "Validated supporting-document content is required for cold-start inventory coverage checks.", { verifiedDocumentCount: verifiedDocuments.length, verifiedContentCount: verifiedContents.length });
464
+ }
169
465
  for (let index = 0; index < requestedDocuments.length; index += 1) {
170
466
  const requested = requestedDocuments[index];
171
467
  const verified = verifiedDocuments[index];
@@ -176,8 +472,9 @@ export function validateHandoffContextSync(phase, feature, input) {
176
472
  throw new HandoffContractError("HANDOFF_SUPPORTING_DOCUMENT_INVALID", `Canonical handoff content must link supporting document ${requested.path}.`, { index, path: requested.path });
177
473
  }
178
474
  }
179
- if (phase.status === "done" || phase.status === "canceled") {
180
- throw new Error(`Cannot write a handoff on ${phase.status} phase ${phase.id}; completed phases have no pending handoff.`);
475
+ renderVerifiedHandoffContent(input.content, input.completenessAudit, input.coldStartInventory, verifiedContents);
476
+ if (phase.status === "done" || phase.status === "rejected" || phase.status === "canceled") {
477
+ throw new Error(`Cannot write a handoff on ${phase.status} phase ${phase.id}; terminal phases have no pending handoff.`);
181
478
  }
182
479
  if (phase.handoffUpdatedAt !== input.expectedHandoffUpdatedAt) {
183
480
  throw new Error("Handoff changed after preparation. Run handoff_prepare again and reconcile the latest content before writing.");
@@ -222,8 +519,9 @@ export function validateHandoffContextSync(phase, feature, input) {
222
519
  }
223
520
  export function applyHandoffContextSync(phase, feature, input, timestamp) {
224
521
  validateHandoffContextSync(phase, feature, input);
225
- const handoffContent = renderVerifiedHandoffContent(input.content, input.completenessAudit);
522
+ const handoffContent = renderVerifiedHandoffContent(input.content, input.completenessAudit, input.coldStartInventory, input.verifiedSupportingDocumentContents ?? []);
226
523
  const auditEntries = validateHandoffCompletenessAudit(input.completenessAudit);
524
+ const coldStartInventoryEntries = validateHandoffColdStartInventory(input.coldStartInventory, stripRenderedCompletenessAudit(input.content), input.verifiedSupportingDocumentContents ?? []);
227
525
  const nextPhase = structuredClone(phase);
228
526
  const nextFeature = structuredClone(feature);
229
527
  const updatedTaskIds = [];
@@ -270,10 +568,23 @@ export function applyHandoffContextSync(phase, feature, input, timestamp) {
270
568
  nextPhase.handoffAudit = {
271
569
  version: HANDOFF_COMPLETENESS_AUDIT_VERSION,
272
570
  entries: auditEntries,
571
+ coldStartInventory: {
572
+ version: HANDOFF_COLD_START_INVENTORY_VERSION,
573
+ sourceReviews: HANDOFF_COLD_START_SOURCE_REVIEWS.map(({ id }) => {
574
+ const review = input.coldStartInventory?.sourceReviews.find((entry) => entry.source === id);
575
+ return {
576
+ source: id,
577
+ detail: review?.detail.trim() || "Derived from persisted handoff content, planner entities, and read-back state; no duplicate source inventory was supplied.",
578
+ };
579
+ }),
580
+ entries: coldStartInventoryEntries,
581
+ },
273
582
  supportingDocuments: input.verifiedSupportingDocuments ?? [],
274
583
  contentHash: handoffContentHash(handoffContent),
275
584
  contentLength: handoffContent.length,
276
585
  verifiedAt: timestamp,
586
+ resumeReadyAt: "",
587
+ readBackSourceReviews: [],
277
588
  };
278
589
  nextPhase.handoffReadAt = "";
279
590
  nextPhase.updatedAt = timestamp;
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export * from "./recap.js";
6
6
  export * from "./planner-rules.js";
7
7
  export * from "./planner-skill.js";
8
8
  export * from "./display-status.js";
9
+ export * from "./description-freshness.js";
9
10
  export * from "./task-context.js";
10
11
  export * from "./task-selection.js";
11
12
  export * from "./task-start-outcome.js";
@@ -15,7 +16,9 @@ export * from "./handoff-context.js";
15
16
  export * from "./project-context-migration.js";
16
17
  export * from "./requirement-macro-tasks.js";
17
18
  export * from "./package-version.js";
18
- export { PlanStore, PlanStoreError, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock, type IdeaCreateInput, type IdeaUpdateInput, type IdeaPromotionTargetInput, type PhaseHandoffSummary, type OrphanPhaseSummary } from "./plan-store.js";
19
+ export * from "./runtime-diagnostics.js";
20
+ export * from "./write-coordination.js";
21
+ export { PlanStore, PlanStoreError, PlanStaleWriteError, PlanUnsupportedAllocationKindError, assertPlannerRevision, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock, type IdeaCreateInput, type IdeaUpdateInput, type IdeaPromotionTargetInput, type AcceptedDecisionOwner, type AcceptedDecisionCreateInput, type AcceptedDecisionUpdateInput, type PhaseHandoffSummary, type OrphanPhaseSummary } from "./plan-store.js";
19
22
  export { PlanRenderer } from "./renderer.js";
20
23
  export { ExportService } from "./export-service.js";
21
24
  export type { CodebaseProfile, ResumeFocus, ActivityEntry, ActivityLog, AmbientFacts } from "./schema.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,cAAc,EAAE,uBAAuB,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,wBAAwB,EAAE,KAAK,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1R,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,mBAAmB,EAAE,kCAAkC,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,cAAc,EAAE,uBAAuB,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,wBAAwB,EAAE,KAAK,qBAAqB,EAAE,KAAK,2BAA2B,EAAE,KAAK,2BAA2B,EAAE,KAAK,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1c,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ export * from "./recap.js";
6
6
  export * from "./planner-rules.js";
7
7
  export * from "./planner-skill.js";
8
8
  export * from "./display-status.js";
9
+ export * from "./description-freshness.js";
9
10
  export * from "./task-context.js";
10
11
  export * from "./task-selection.js";
11
12
  export * from "./task-start-outcome.js";
@@ -15,6 +16,8 @@ export * from "./handoff-context.js";
15
16
  export * from "./project-context-migration.js";
16
17
  export * from "./requirement-macro-tasks.js";
17
18
  export * from "./package-version.js";
18
- export { PlanStore, PlanStoreError, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock } from "./plan-store.js";
19
+ export * from "./runtime-diagnostics.js";
20
+ export * from "./write-coordination.js";
21
+ export { PlanStore, PlanStoreError, PlanStaleWriteError, PlanUnsupportedAllocationKindError, assertPlannerRevision, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock } from "./plan-store.js";
19
22
  export { PlanRenderer } from "./renderer.js";
20
23
  export { ExportService } from "./export-service.js";
@@ -1,6 +1,8 @@
1
1
  export interface RuntimePackageVersion {
2
2
  name: string;
3
3
  version: string;
4
+ /** Absolute path to the loaded package manifest used as the version source. */
5
+ packageJsonPath?: string;
4
6
  }
5
7
  /** Find the nearest matching package manifest above a loaded module. */
6
8
  export declare function packageVersionFromModule(moduleUrlOrPath: string, expectedName: string): RuntimePackageVersion;
@@ -1 +1 @@
1
- {"version":3,"file":"package-version.d.ts","sourceRoot":"","sources":["../src/package-version.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAaD,wEAAwE;AACxE,wBAAgB,wBAAwB,CACtC,eAAe,EAAE,MAAM,EACvB,YAAY,EAAE,MAAM,GACnB,qBAAqB,CAqBvB;AAED,oFAAoF;AACpF,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,GACpB,qBAAqB,CAGvB"}
1
+ {"version":3,"file":"package-version.d.ts","sourceRoot":"","sources":["../src/package-version.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAaD,wEAAwE;AACxE,wBAAgB,wBAAwB,CACtC,eAAe,EAAE,MAAM,EACvB,YAAY,EAAE,MAAM,GACnB,qBAAqB,CAqBvB;AAED,oFAAoF;AACpF,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,GACpB,qBAAqB,CAGvB"}
@@ -18,7 +18,7 @@ export function packageVersionFromModule(moduleUrlOrPath, expectedName) {
18
18
  if (typeof manifest.version !== "string" || !manifest.version.trim()) {
19
19
  throw new Error(`Package ${expectedName} has no valid version in ${manifestPath}.`);
20
20
  }
21
- return { name: expectedName, version: manifest.version };
21
+ return { name: expectedName, version: manifest.version, packageJsonPath: manifestPath };
22
22
  }
23
23
  }
24
24
  const parent = dirname(current);
@@ -1,4 +1,4 @@
1
- export type PlannerPayloadEntity = "project" | "feature" | "phase" | "task";
1
+ export type PlannerPayloadEntity = "project" | "feature" | "phase" | "task" | "requirement" | "idea" | "acceptedDecision";
2
2
  export type PlannerPayloadOperation = "update" | "discuss";
3
3
  export type PlannerPayloadFailureCode = "NO_MUTABLE_FIELDS_RECEIVED" | "DESCRIPTION_MARKDOWN_FALLBACK_REQUIRED";
4
4
  export interface DescriptionFallbackInput {
@@ -1 +1 @@
1
- {"version":3,"file":"payload-fallback.d.ts","sourceRoot":"","sources":["../src/payload-fallback.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAC5E,MAAM,MAAM,uBAAuB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC3D,MAAM,MAAM,yBAAyB,GAAG,4BAA4B,GAAG,wCAAwC,CAAC;AAEhH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,oBAAoB,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,uBAAuB,CAAC;IACnC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,wBAAwB,CAAC;CAChD;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,KAAK,CAAC;IACf,SAAS,CAAC,EAAE,KAAK,CAAC;IAClB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,SAAS,EAAE,yBAAyB,CAAC;IACrC,4BAA4B,EAAE,OAAO,CAAC;IACtC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,uBAAuB,CAAC;IACnC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGrF;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAO9D;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,oBAAoB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAWnG;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,4BAA4B,GAClC,6BAA6B,CAgD/B"}
1
+ {"version":3,"file":"payload-fallback.d.ts","sourceRoot":"","sources":["../src/payload-fallback.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,aAAa,GAAG,MAAM,GAAG,kBAAkB,CAAC;AAC1H,MAAM,MAAM,uBAAuB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC3D,MAAM,MAAM,yBAAyB,GAAG,4BAA4B,GAAG,wCAAwC,CAAC;AAEhH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,oBAAoB,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,uBAAuB,CAAC;IACnC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,wBAAwB,CAAC;CAChD;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,KAAK,CAAC;IACf,SAAS,CAAC,EAAE,KAAK,CAAC;IAClB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,SAAS,EAAE,yBAAyB,CAAC;IACrC,4BAA4B,EAAE,OAAO,CAAC;IACtC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,uBAAuB,CAAC;IACnC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGrF;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAO9D;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,oBAAoB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAiBnG;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,4BAA4B,GAClC,6BAA6B,CAgD/B"}
@@ -24,6 +24,12 @@ export function suggestedDescriptionRefPath(entity, entityId) {
24
24
  return `.planner/docs/phases/${entityId ?? "phase"}.md`;
25
25
  case "task":
26
26
  return `.planner/docs/tasks/${entityId ?? "task"}.md`;
27
+ case "requirement":
28
+ return `.planner/docs/requirements/${entityId ?? "requirement"}.md`;
29
+ case "idea":
30
+ return `.planner/docs/ideas/${entityId ?? "idea"}.md`;
31
+ case "acceptedDecision":
32
+ return `.planner/docs/accepted-decisions/${entityId ?? "accepted-decision"}.md`;
27
33
  }
28
34
  }
29
35
  export function noMutableFieldsReceived(input) {