@agent-plan/core 0.2.26 → 0.2.27

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 +134 -3
  5. package/dist/handoff-context.d.ts.map +1 -1
  6. package/dist/handoff-context.js +334 -16
  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 +685 -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,43 @@ 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
+ ];
61
+ const LEGACY_HANDOFF_CANONICAL_SECTIONS = [
62
+ "Created at", "Updated at", "Reason", "Current focus", "What was being done",
63
+ "Working tree and ownership", "Work not started or intentionally untouched", "Runtime wiring",
64
+ "Preservation constraints", "Verification evidence", "Related planned work", "How to resume",
65
+ "Files touched", "Blockers", "Next steps", "Recent decisions",
66
+ ];
27
67
  export class HandoffContractError extends Error {
28
68
  code;
29
69
  details;
@@ -41,6 +81,37 @@ export function hasTaskCompletionEvidence(task) {
41
81
  return true;
42
82
  return task.statusLog.some((entry) => entry.toStatus === "done" && entry.description.trim().length > 0);
43
83
  }
84
+ export function buildHandoffDraftTemplate(phase, feature) {
85
+ const featureRef = `F${String(feature.number).padStart(3, "0")}`;
86
+ const phaseRef = `P${String(phase.number).padStart(3, "0")}(${featureRef})`;
87
+ return [
88
+ `# ${phaseRef} — {{REQUIRED: meaningful handoff title}}`,
89
+ "",
90
+ "<!-- Planner-generated: Created at, Updated at, and structured Reason. Do not write these lines in the draft. -->",
91
+ "",
92
+ "## Current focus",
93
+ `- Feature: ${featureRef} — ${feature.name}`,
94
+ `- Phase: ${phaseRef} — ${phase.title}`,
95
+ "- Task: {{REQUIRED: exact composite task ref and title}}",
96
+ "- Exact resume point: {{REQUIRED: file, symbol, command, or state boundary}}",
97
+ "",
98
+ "## Current and partial state",
99
+ "{{REQUIRED: concise completed/current/partial state only; keep extended detail in supporting documents}}",
100
+ "",
101
+ "## Preservation constraints",
102
+ "{{REQUIRED: minimal behaviors and boundaries that must survive}}",
103
+ "",
104
+ "## Supporting documents",
105
+ "- {{REQUIRED: ordered .planner/docs/*.md links with why each is needed, or an explicit verified statement that none are needed}}",
106
+ "",
107
+ "## Blockers and risks",
108
+ "- {{REQUIRED: blocker/risk, or a substantive verified statement that none apply}}",
109
+ "",
110
+ "## How to resume",
111
+ "1. {{REQUIRED: first exact action, including file/symbol/command}}",
112
+ "2. {{REQUIRED: subsequent ordered actions and verification}}",
113
+ ].join("\n");
114
+ }
44
115
  export function auditPhaseHandoff(phase, feature) {
45
116
  const missingCompletionTasks = phase.tasks
46
117
  .filter((task) => task.status === "done" && !hasTaskCompletionEvidence(task))
@@ -53,9 +124,20 @@ export function auditPhaseHandoff(phase, feature) {
53
124
  missingCompletionTaskIds: missingCompletionTasks.map((task) => task.id),
54
125
  missingCompletionTasks,
55
126
  completenessVersion: HANDOFF_COMPLETENESS_AUDIT_VERSION,
127
+ targetContentChars: TARGET_HANDOFF_CONTENT_CHARS,
56
128
  maxContentChars: MAX_HANDOFF_CONTENT_CHARS,
57
129
  completenessCategories: HANDOFF_COMPLETENESS_CATEGORIES,
130
+ canonicalSections: HANDOFF_CANONICAL_SECTIONS,
131
+ requiredHumanInputs: [
132
+ { id: "title", label: "Meaningful title", description: "A concise handoff title; the planner renders it as the H1." },
133
+ { id: "reason", label: "Reason", description: "Why work is stopping and why a cold agent needs this handoff; the planner renders it as metadata." },
134
+ ],
135
+ draftTemplate: buildHandoffDraftTemplate(phase, feature),
136
+ coldStartInventoryVersion: HANDOFF_COLD_START_INVENTORY_VERSION,
137
+ coldStartSourceReviews: HANDOFF_COLD_START_SOURCE_REVIEWS,
138
+ coldStartInventoryCategories: HANDOFF_COLD_START_INVENTORY_CATEGORIES,
58
139
  existingCompletenessAudit: phase.handoffAudit,
140
+ phaseWorkMap: buildPhaseWorkMap(phase, feature.number),
59
141
  };
60
142
  }
61
143
  function nonEmpty(value, field) {
@@ -115,6 +197,189 @@ export function validateHandoffCompletenessAudit(audit) {
115
197
  }
116
198
  return expectedIds.map((id) => byCategory.get(id));
117
199
  }
200
+ export function validateHandoffColdStartInventory(inventory, content, supportingDocumentContents = []) {
201
+ const expectedIds = HANDOFF_COLD_START_INVENTORY_CATEGORIES.map((entry) => entry.id);
202
+ if (!inventory || inventory.version !== HANDOFF_COLD_START_INVENTORY_VERSION) {
203
+ throw new HandoffContractError("HANDOFF_COLD_START_INVENTORY_REQUIRED", `Cold-start inventory version ${HANDOFF_COLD_START_INVENTORY_VERSION} is required before handoff persistence. Populate it from the scaffold returned by handoff_prepare.`, { requiredVersion: HANDOFF_COLD_START_INVENTORY_VERSION, missingCategories: expectedIds });
204
+ }
205
+ const requiredSources = HANDOFF_COLD_START_SOURCE_REVIEWS.map((entry) => entry.id);
206
+ const requiredSourceIds = new Set(requiredSources);
207
+ const sourceReviews = new Map();
208
+ const duplicateSources = [];
209
+ for (const review of inventory.sourceReviews) {
210
+ if (sourceReviews.has(review.source))
211
+ duplicateSources.push(review.source);
212
+ else
213
+ sourceReviews.set(review.source, review.detail);
214
+ }
215
+ const missingSources = requiredSources.filter((source) => !sourceReviews.has(source));
216
+ const unknownSources = [...sourceReviews.keys()].filter((source) => !requiredSourceIds.has(source));
217
+ const invalidSources = requiredSources.filter((source) => {
218
+ const detail = sourceReviews.get(source);
219
+ return detail !== undefined && !isSubstantive(detail);
220
+ });
221
+ const byCategory = new Map();
222
+ const duplicates = [];
223
+ for (const entry of inventory.entries) {
224
+ if (byCategory.has(entry.category))
225
+ duplicates.push(entry.category);
226
+ else
227
+ byCategory.set(entry.category, entry);
228
+ }
229
+ const missingCategories = expectedIds.filter((id) => !byCategory.has(id));
230
+ const unknownCategories = [...byCategory.keys()].filter((id) => !expectedIds.includes(id));
231
+ const invalidCategories = [];
232
+ for (const id of expectedIds) {
233
+ const entry = byCategory.get(id);
234
+ if (!entry)
235
+ continue;
236
+ const items = uniqueStrings(entry.items);
237
+ const notApplicableReason = entry.notApplicableReason?.trim() ?? "";
238
+ if (items.length === 0)
239
+ invalidCategories.push(id);
240
+ if (notApplicableReason && !isSubstantive(notApplicableReason))
241
+ invalidCategories.push(id);
242
+ if (items.some((item) => /^(?:n\/?a|none|nothing|unknown|tbd|see above)$/i.test(item.trim())))
243
+ invalidCategories.push(id);
244
+ }
245
+ if (missingSources.length > 0 || unknownSources.length > 0 || duplicateSources.length > 0 || invalidSources.length > 0
246
+ || missingCategories.length > 0 || unknownCategories.length > 0 || duplicates.length > 0 || invalidCategories.length > 0) {
247
+ 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').", {
248
+ missingSources,
249
+ invalidSources,
250
+ unknownSources,
251
+ duplicateSources: [...new Set(duplicateSources)],
252
+ missingCategories,
253
+ invalidCategories: [...new Set(invalidCategories)],
254
+ unknownCategories,
255
+ duplicateCategories: [...new Set(duplicates)],
256
+ });
257
+ }
258
+ const normalize = (value) => value.normalize("NFKC").replace(/[`*_]/g, "").replace(/\s+/g, " ").toLowerCase();
259
+ const corpus = normalize([content, ...supportingDocumentContents].join("\n"));
260
+ const uncoveredItems = expectedIds.flatMap((id) => {
261
+ const entry = byCategory.get(id);
262
+ return uniqueStrings(entry.items)
263
+ .filter((item) => !corpus.includes(normalize(item)))
264
+ .map((item) => ({ category: id, item }));
265
+ });
266
+ if (uncoveredItems.length > 0) {
267
+ 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 });
268
+ }
269
+ return expectedIds.map((id) => {
270
+ const entry = byCategory.get(id);
271
+ return {
272
+ category: entry.category,
273
+ items: uniqueStrings(entry.items),
274
+ ...(entry.notApplicableReason?.trim() ? { notApplicableReason: entry.notApplicableReason.trim() } : {}),
275
+ };
276
+ });
277
+ }
278
+ export function validateHandoffReadBackVerification(phase, input) {
279
+ const audit = phase.handoffAudit;
280
+ const actualContentHash = phase.handoff.trim() ? handoffContentHash(phase.handoff) : "";
281
+ if (!phase.handoff.trim() || !audit || audit.contentHash !== actualContentHash || audit.contentLength !== phase.handoff.length) {
282
+ 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 ?? "" });
283
+ }
284
+ if (!input.expectedContentHash.trim() || input.expectedContentHash !== actualContentHash) {
285
+ 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 });
286
+ }
287
+ const requiredSources = HANDOFF_COLD_START_SOURCE_REVIEWS.map((entry) => entry.id);
288
+ const bySource = new Map();
289
+ const duplicateSources = [];
290
+ for (const review of input.sourceReviews) {
291
+ if (bySource.has(review.source))
292
+ duplicateSources.push(review.source);
293
+ else
294
+ bySource.set(review.source, review.detail.trim());
295
+ }
296
+ const missingSources = requiredSources.filter((source) => !bySource.has(source));
297
+ const unknownSources = [...bySource.keys()].filter((source) => !requiredSources.includes(source));
298
+ const invalidSources = requiredSources.filter((source) => {
299
+ const detail = bySource.get(source);
300
+ return detail !== undefined && !isSubstantive(detail);
301
+ });
302
+ if (missingSources.length > 0 || unknownSources.length > 0 || duplicateSources.length > 0 || invalidSources.length > 0) {
303
+ throw new HandoffContractError("HANDOFF_READBACK_VERIFICATION_REQUIRED", "Read-back verification must re-check every required source against the persisted handoff with substantive findings.", { missingSources, unknownSources, duplicateSources: [...new Set(duplicateSources)], invalidSources });
304
+ }
305
+ const omissionsFound = uniqueStrings(input.omissionsFound);
306
+ if (omissionsFound.length > 0) {
307
+ 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 });
308
+ }
309
+ return requiredSources.map((source) => ({ source, detail: bySource.get(source) }));
310
+ }
311
+ function sectionBody(content, headings) {
312
+ const lines = content.split(/\r?\n/);
313
+ const normalized = new Set(headings.map((heading) => heading.toLowerCase()));
314
+ const start = lines.findIndex((line) => {
315
+ const match = line.match(/^##\s+(.+?)\s*$/);
316
+ return Boolean(match && normalized.has(match[1].toLowerCase()));
317
+ });
318
+ if (start < 0)
319
+ return "";
320
+ const body = [];
321
+ for (let index = start + 1; index < lines.length; index += 1) {
322
+ if (/^##\s+/.test(lines[index]))
323
+ break;
324
+ body.push(lines[index]);
325
+ }
326
+ return body.join("\n").trim();
327
+ }
328
+ function boundedSection(value, fallback, maxChars) {
329
+ const normalized = value.trim() || fallback;
330
+ if (normalized.length <= maxChars)
331
+ return normalized;
332
+ return `${normalized.slice(0, Math.max(0, maxChars - 86)).trimEnd()}\n\n[Extended detail continues in the linked planner document.]`;
333
+ }
334
+ /**
335
+ * Mechanically compact oversized legacy or verbose handoffs while retaining the
336
+ * complete submitted body in one planner-owned Markdown document.
337
+ */
338
+ export function externalizeOversizedHandoffContent(content, supportingDocumentPath) {
339
+ const base = stripRenderedCompletenessAudit(content);
340
+ if (base.length <= TARGET_HANDOFF_CONTENT_CHARS) {
341
+ return { content: base, externalized: false, extendedContent: "" };
342
+ }
343
+ const firstHeading = base.split(/\r?\n/).find((line) => /^#\s+/.test(line.trim()))?.trim() ?? "# Handoff resume capsule";
344
+ 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.`);
345
+ const currentFocus = boundedSection(sectionBody(base, ["Current focus"]), "Exact focus and resume point are preserved in the supporting document.", 1_200);
346
+ 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);
347
+ const preservation = boundedSection(sectionBody(base, ["Preservation constraints"]), "Preservation constraints are recorded in the supporting document.", 900);
348
+ const blockers = boundedSection(sectionBody(base, ["Blockers and risks", "Blockers"]), "Blockers and risks are recorded in the supporting document.", 900);
349
+ 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);
350
+ const compact = [
351
+ firstHeading,
352
+ "",
353
+ ...metadata,
354
+ "",
355
+ "## Current focus",
356
+ currentFocus,
357
+ "",
358
+ "## Current and partial state",
359
+ currentState,
360
+ "",
361
+ "## Preservation constraints",
362
+ preservation,
363
+ "",
364
+ "## Supporting documents",
365
+ `- ${supportingDocumentPath} — Full submitted handoff detail externalized automatically because the inline resume capsule exceeded ${TARGET_HANDOFF_CONTENT_CHARS} characters. Read this document before resuming.`,
366
+ "",
367
+ "## Blockers and risks",
368
+ blockers,
369
+ "",
370
+ "## How to resume",
371
+ resume,
372
+ ].join("\n").trim();
373
+ return {
374
+ content: compact,
375
+ externalized: true,
376
+ extendedContent: `${firstHeading} — extended detail\n\n${base}\n`,
377
+ supportingDocument: {
378
+ path: supportingDocumentPath,
379
+ description: "Full submitted handoff detail externalized automatically; required for cold resume and reconciliation.",
380
+ },
381
+ };
382
+ }
118
383
  export function renderHandoffCompletenessAudit(audit) {
119
384
  const entries = validateHandoffCompletenessAudit(audit);
120
385
  const labels = new Map(HANDOFF_COMPLETENESS_CATEGORIES.map((entry) => [entry.id, entry.label]));
@@ -131,15 +396,37 @@ export function renderHandoffCompletenessAudit(audit) {
131
396
  HANDOFF_AUDIT_END_MARKER,
132
397
  ].join("\n").trim();
133
398
  }
134
- export function renderVerifiedHandoffContent(content, audit) {
399
+ export function renderVerifiedHandoffContent(content, audit, coldStartInventory, supportingDocumentContents = []) {
135
400
  const base = stripRenderedCompletenessAudit(content);
136
401
  validateCanonicalHandoffContent(base);
137
402
  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 });
403
+ validateHandoffColdStartInventory(coldStartInventory, base, supportingDocumentContents);
404
+ if (base.length > MAX_HANDOFF_CONTENT_CHARS) {
405
+ 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" });
406
+ }
407
+ return base;
408
+ }
409
+ export function materializeHandoffMetadata(content, reason, createdAt, updatedAt) {
410
+ const normalizedReason = reason.trim();
411
+ if (!normalizedReason) {
412
+ 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"] });
413
+ }
414
+ const existingCreatedAt = content.match(/^Created at:\s*(\S+)\s*$/im)?.[1] ?? createdAt;
415
+ const withoutMetadata = content
416
+ .split(/\r?\n/)
417
+ .filter((line) => !/^(?:Created at|Updated at|Reason):\s*/i.test(line.trim()))
418
+ .join("\n")
419
+ .trim();
420
+ const lines = withoutMetadata.split("\n");
421
+ const firstContent = lines.findIndex((line) => line.trim().length > 0);
422
+ const metadata = [`Created at: ${existingCreatedAt}`, `Updated at: ${updatedAt}`, `Reason: ${normalizedReason}`];
423
+ if (firstContent >= 0 && /^#\s+/.test(lines[firstContent] ?? "")) {
424
+ lines.splice(firstContent + 1, 0, "", ...metadata);
141
425
  }
142
- return rendered;
426
+ else {
427
+ lines.unshift(...metadata, "");
428
+ }
429
+ return lines.join("\n").trim();
143
430
  }
144
431
  export function validateCanonicalHandoffContent(content) {
145
432
  const body = nonEmpty(content, "Handoff content");
@@ -148,24 +435,43 @@ export function validateCanonicalHandoffContent(content) {
148
435
  { label: "Updated at", pattern: /^Updated at:\s*\S+/im },
149
436
  { label: "Reason", pattern: /^Reason:\s*\S+/im },
150
437
  { label: "Current focus", pattern: /^##\s+Current focus\s*$/im },
151
- { label: "What was being done", pattern: /^##\s+What was being done\s*$/im },
438
+ { label: "Current and partial state", pattern: /^##\s+Current and partial state\s*$/im },
439
+ { label: "Preservation constraints", pattern: /^##\s+Preservation constraints\s*$/im },
440
+ { label: "Supporting documents", pattern: /^##\s+Supporting documents\s*$/im },
441
+ { label: "Blockers and risks", pattern: /^##\s+Blockers and risks\s*$/im },
152
442
  { 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
443
  ];
158
444
  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(", ")}.`);
445
+ if (missing.length === 0) {
446
+ const unresolvedPlaceholders = [...body.matchAll(/\{\{REQUIRED:[^}]+\}\}/g)].map((match) => match[0]);
447
+ if (unresolvedPlaceholders.length > 0) {
448
+ throw new HandoffContractError("HANDOFF_CANONICAL_SECTIONS_REQUIRED", "The canonical handoff still contains unresolved placeholders. Replace every placeholder before writing.", { missingSections: [], unresolvedPlaceholders: [...new Set(unresolvedPlaceholders)] });
449
+ }
450
+ return;
451
+ }
452
+ const legacyMissing = LEGACY_HANDOFF_CANONICAL_SECTIONS.filter((label) => {
453
+ if (label === "Created at")
454
+ return !/^Created at:\s*\S+/im.test(body);
455
+ if (label === "Updated at")
456
+ return !/^Updated at:\s*\S+/im.test(body);
457
+ if (label === "Reason")
458
+ return !/^Reason:\s*\S+/im.test(body);
459
+ return !new RegExp(`^##\\s+${label.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&")}\\s*$`, "im").test(body);
460
+ });
461
+ if (legacyMissing.length > 0) {
462
+ throw new HandoffContractError("HANDOFF_CANONICAL_SECTIONS_REQUIRED", "The canonical handoff does not match the prepared scaffold. Fill every required heading and replace every placeholder before writing.", { missingSections: missing, legacyMissingSections: legacyMissing, unresolvedPlaceholders: [...new Set([...body.matchAll(/\{\{REQUIRED:[^}]+\}\}/g)].map((m) => m[0]))] });
463
+ }
161
464
  }
162
465
  export function validateHandoffContextSync(phase, feature, input) {
163
- renderVerifiedHandoffContent(input.content, input.completenessAudit);
164
466
  const requestedDocuments = input.supportingDocuments ?? [];
165
467
  const verifiedDocuments = input.verifiedSupportingDocuments ?? [];
166
468
  if (requestedDocuments.length !== verifiedDocuments.length) {
167
469
  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
470
  }
471
+ const verifiedContents = input.verifiedSupportingDocumentContents ?? [];
472
+ if (verifiedContents.length !== verifiedDocuments.length) {
473
+ 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 });
474
+ }
169
475
  for (let index = 0; index < requestedDocuments.length; index += 1) {
170
476
  const requested = requestedDocuments[index];
171
477
  const verified = verifiedDocuments[index];
@@ -176,8 +482,9 @@ export function validateHandoffContextSync(phase, feature, input) {
176
482
  throw new HandoffContractError("HANDOFF_SUPPORTING_DOCUMENT_INVALID", `Canonical handoff content must link supporting document ${requested.path}.`, { index, path: requested.path });
177
483
  }
178
484
  }
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.`);
485
+ renderVerifiedHandoffContent(input.content, input.completenessAudit, input.coldStartInventory, verifiedContents);
486
+ if (phase.status === "done" || phase.status === "rejected" || phase.status === "canceled") {
487
+ throw new Error(`Cannot write a handoff on ${phase.status} phase ${phase.id}; terminal phases have no pending handoff.`);
181
488
  }
182
489
  if (phase.handoffUpdatedAt !== input.expectedHandoffUpdatedAt) {
183
490
  throw new Error("Handoff changed after preparation. Run handoff_prepare again and reconcile the latest content before writing.");
@@ -222,8 +529,9 @@ export function validateHandoffContextSync(phase, feature, input) {
222
529
  }
223
530
  export function applyHandoffContextSync(phase, feature, input, timestamp) {
224
531
  validateHandoffContextSync(phase, feature, input);
225
- const handoffContent = renderVerifiedHandoffContent(input.content, input.completenessAudit);
532
+ const handoffContent = renderVerifiedHandoffContent(input.content, input.completenessAudit, input.coldStartInventory, input.verifiedSupportingDocumentContents ?? []);
226
533
  const auditEntries = validateHandoffCompletenessAudit(input.completenessAudit);
534
+ const coldStartInventoryEntries = validateHandoffColdStartInventory(input.coldStartInventory, stripRenderedCompletenessAudit(input.content), input.verifiedSupportingDocumentContents ?? []);
227
535
  const nextPhase = structuredClone(phase);
228
536
  const nextFeature = structuredClone(feature);
229
537
  const updatedTaskIds = [];
@@ -270,10 +578,20 @@ export function applyHandoffContextSync(phase, feature, input, timestamp) {
270
578
  nextPhase.handoffAudit = {
271
579
  version: HANDOFF_COMPLETENESS_AUDIT_VERSION,
272
580
  entries: auditEntries,
581
+ coldStartInventory: {
582
+ version: HANDOFF_COLD_START_INVENTORY_VERSION,
583
+ sourceReviews: HANDOFF_COLD_START_SOURCE_REVIEWS.map(({ id }) => {
584
+ const review = input.coldStartInventory.sourceReviews.find((entry) => entry.source === id);
585
+ return { source: id, detail: review.detail.trim() };
586
+ }),
587
+ entries: coldStartInventoryEntries,
588
+ },
273
589
  supportingDocuments: input.verifiedSupportingDocuments ?? [],
274
590
  contentHash: handoffContentHash(handoffContent),
275
591
  contentLength: handoffContent.length,
276
592
  verifiedAt: timestamp,
593
+ resumeReadyAt: "",
594
+ readBackSourceReviews: [],
277
595
  };
278
596
  nextPhase.handoffReadAt = "";
279
597
  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) {