create-quiver 0.17.4 → 0.17.6

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 (53) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/docs/CLI_UX_GUIDE.md +7 -0
  3. package/docs/TROUBLESHOOTING.md +62 -0
  4. package/docs/reference/commands.md +16 -0
  5. package/docs/workflows/existing-project-ai-quiver-setup.md +0 -1
  6. package/docs/workflows/existing-project.md +1 -0
  7. package/package.json +1 -1
  8. package/specs/quiver-v43-cli-i18n-audit-release-readiness/command-language-mode-matrix.json +1 -0
  9. package/specs/quiver-v56-analyze-project-usable-doc-merge/EVIDENCE_REPORT.md +11 -0
  10. package/specs/quiver-v56-analyze-project-usable-doc-merge/EXECUTION_PLAN.md +28 -0
  11. package/specs/quiver-v56-analyze-project-usable-doc-merge/SPEC.md +96 -0
  12. package/specs/quiver-v56-analyze-project-usable-doc-merge/STATUS.md +26 -0
  13. package/specs/quiver-v56-analyze-project-usable-doc-merge/pr.md +56 -0
  14. package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-01-document-classification-merge-engine/CLOSURE_BRIEF.md +61 -0
  15. package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-01-document-classification-merge-engine/EXECUTION_BRIEF.md +59 -0
  16. package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-01-document-classification-merge-engine/pr.md +61 -0
  17. package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-01-document-classification-merge-engine/slice.json +71 -0
  18. package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-02-apply-integration-validation-contract/CLOSURE_BRIEF.md +30 -0
  19. package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-02-apply-integration-validation-contract/EXECUTION_BRIEF.md +39 -0
  20. package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-02-apply-integration-validation-contract/pr.md +62 -0
  21. package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-02-apply-integration-validation-contract/slice.json +80 -0
  22. package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-03-cli-docs-real-fixture-smoke/CLOSURE_BRIEF.md +29 -0
  23. package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-03-cli-docs-real-fixture-smoke/EXECUTION_BRIEF.md +32 -0
  24. package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-03-cli-docs-real-fixture-smoke/pr.md +61 -0
  25. package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-03-cli-docs-real-fixture-smoke/slice.json +79 -0
  26. package/specs/quiver-v57-evidence-budget-recovery-ux/EVIDENCE_REPORT.md +27 -0
  27. package/specs/quiver-v57-evidence-budget-recovery-ux/EXECUTION_PLAN.md +41 -0
  28. package/specs/quiver-v57-evidence-budget-recovery-ux/SPEC.md +139 -0
  29. package/specs/quiver-v57-evidence-budget-recovery-ux/STATUS.md +20 -0
  30. package/specs/quiver-v57-evidence-budget-recovery-ux/pr.md +124 -0
  31. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-01-recovery-contract-security-classifier/CLOSURE_BRIEF.md +22 -0
  32. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-01-recovery-contract-security-classifier/EXECUTION_BRIEF.md +58 -0
  33. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-01-recovery-contract-security-classifier/pr.md +12 -0
  34. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-01-recovery-contract-security-classifier/slice.json +60 -0
  35. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-02-budget-command-recommendation/CLOSURE_BRIEF.md +9 -0
  36. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-02-budget-command-recommendation/EXECUTION_BRIEF.md +54 -0
  37. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-02-budget-command-recommendation/pr.md +12 -0
  38. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-02-budget-command-recommendation/slice.json +60 -0
  39. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-03-cli-json-i18n-output/CLOSURE_BRIEF.md +10 -0
  40. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-03-cli-json-i18n-output/EXECUTION_BRIEF.md +58 -0
  41. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-03-cli-json-i18n-output/pr.md +13 -0
  42. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-03-cli-json-i18n-output/slice.json +68 -0
  43. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-04-integration-fixtures-docs-release-smoke/CLOSURE_BRIEF.md +14 -0
  44. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-04-integration-fixtures-docs-release-smoke/EXECUTION_BRIEF.md +55 -0
  45. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-04-integration-fixtures-docs-release-smoke/pr.md +14 -0
  46. package/specs/quiver-v57-evidence-budget-recovery-ux/slices/slice-04-integration-fixtures-docs-release-smoke/slice.json +65 -0
  47. package/src/create-quiver/commands/ai.js +132 -2
  48. package/src/create-quiver/lib/ai/analyze-project-docs.js +284 -4
  49. package/src/create-quiver/lib/ai/analyze-project-proposal.js +11 -0
  50. package/src/create-quiver/lib/ai/analyze-project-recovery.js +736 -0
  51. package/src/create-quiver/lib/ai/analyze-project-validation.js +27 -0
  52. package/src/create-quiver/lib/i18n/messages/en.js +6 -0
  53. package/src/create-quiver/lib/i18n/messages/es.js +6 -0
@@ -15,6 +15,43 @@ const ANALYZE_DOC_PROPOSAL_SCHEMA_VERSION = 1;
15
15
  const ANALYZE_DOC_PROPOSAL_KIND = 'quiver-analyze-project-doc-proposal';
16
16
  const ANALYZE_MANAGED_START = '<!-- quiver:analyze-project:start -->';
17
17
  const ANALYZE_MANAGED_END = '<!-- quiver:analyze-project:end -->';
18
+ const CONTEXT_PREP_MANAGED_START = '<!-- quiver:context-prep:start -->';
19
+ const CONTEXT_PREP_MANAGED_END = '<!-- quiver:context-prep:end -->';
20
+
21
+ const CRITICAL_SCAFFOLD_PLACEHOLDER_PATTERNS = [
22
+ /\[Uno o dos parrafos que expliquen el proyecto\.\]/i,
23
+ /\[Frase principal del proyecto\]/i,
24
+ /\[Describe el usuario principal\.\]/i,
25
+ /\[Objetivo actual \d+\]/i,
26
+ /\[Datos sensibles que nunca se guardan\]/i,
27
+ /\[Datos que si se guardan\]/i,
28
+ /\[Medida de seguridad importante\]/i,
29
+ /\[One or two paragraphs? that explain the project\.\]/i,
30
+ /\[Project tagline\]/i,
31
+ /\[Describe the primary user\.\]/i,
32
+ /\[Current goal \d+\]/i,
33
+ /\[Sensitive data that is never stored\]/i,
34
+ /\[Data that is stored\]/i,
35
+ /\[Important security measure\]/i,
36
+ /\[Short project summary\.\]/i,
37
+ /\[Description\]/i,
38
+ /\[TODO: confirm [^\]\n]+\]/i,
39
+ /\[TODO: confirmar [^\]\n]+\]/i,
40
+ ];
41
+
42
+ const SCAFFOLD_BOILERPLATE_PATTERNS = [
43
+ /^purpose:\s*"Human-readable project overview"$/i,
44
+ /^applies_when:\s*"onboarding, review"$/i,
45
+ /^supersedes:\s*null$/i,
46
+ /^El stack, package manager y comandos se generan en `docs\/PROJECT_MAP\.md` despues de ejecutar `analyze`\.$/i,
47
+ /^The stack, package manager, and command surface are generated in `docs\/PROJECT_MAP\.md` after analysis\.$/i,
48
+ /^If sos agente IA, lee `AI_CONTEXT\.md`\./i,
49
+ /^Read `AI_CONTEXT\.md` first if you are an AI agent\./i,
50
+ /^Use `npx create-quiver ai prepare-context --dry-run`/i,
51
+ /^TODO: confirm any repo fact/i,
52
+ /^Assumption: missing README_FOR_AI\.md/i,
53
+ /^Pending confirmation:/i,
54
+ ];
18
55
 
19
56
  const docProposalEntrySchema = z.object({
20
57
  path: z.string().trim().min(1),
@@ -67,6 +104,10 @@ function normalizeDocContent(content) {
67
104
  return `${String(content || '').replace(/\s+$/g, '')}\n`;
68
105
  }
69
106
 
107
+ function normalizeNewlines(content) {
108
+ return String(content || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
109
+ }
110
+
70
111
  function managedBlock(content) {
71
112
  return [
72
113
  ANALYZE_MANAGED_START,
@@ -76,8 +117,240 @@ function managedBlock(content) {
76
117
  ].join('\n');
77
118
  }
78
119
 
120
+ function findManagedBlockRange(content, startMarker, endMarker) {
121
+ const text = normalizeNewlines(content);
122
+ const start = text.indexOf(startMarker);
123
+ const end = text.indexOf(endMarker);
124
+ if (start < 0 || end <= start) {
125
+ return null;
126
+ }
127
+ return {
128
+ start,
129
+ end: end + endMarker.length,
130
+ content: text.slice(start + startMarker.length, end),
131
+ };
132
+ }
133
+
134
+ function removeManagedBlock(content, startMarker, endMarker) {
135
+ const text = normalizeNewlines(content);
136
+ const range = findManagedBlockRange(text, startMarker, endMarker);
137
+ if (!range) {
138
+ return {
139
+ content: text,
140
+ removed: false,
141
+ block: '',
142
+ };
143
+ }
144
+ return {
145
+ content: `${text.slice(0, range.start)}${text.slice(range.end)}`.replace(/\n{3,}/g, '\n\n').trimEnd(),
146
+ removed: true,
147
+ block: range.content,
148
+ };
149
+ }
150
+
151
+ function splitFrontmatter(content) {
152
+ const text = normalizeNewlines(content);
153
+ if (!text.startsWith('---\n')) {
154
+ return {
155
+ frontmatter: '',
156
+ body: text,
157
+ valid: false,
158
+ present: false,
159
+ };
160
+ }
161
+ const match = text.slice(4).match(/\n---\n/);
162
+ if (!match || match.index === undefined) {
163
+ return {
164
+ frontmatter: '',
165
+ body: text,
166
+ valid: false,
167
+ present: true,
168
+ };
169
+ }
170
+ const endIndex = 4 + match.index + '\n---\n'.length;
171
+ return {
172
+ frontmatter: text.slice(0, endIndex),
173
+ body: text.slice(endIndex),
174
+ valid: true,
175
+ present: true,
176
+ };
177
+ }
178
+
179
+ function collectCriticalPlaceholders(content) {
180
+ const findings = [];
181
+ const text = normalizeNewlines(content);
182
+ for (const pattern of CRITICAL_SCAFFOLD_PLACEHOLDER_PATTERNS) {
183
+ const match = text.match(pattern);
184
+ if (match) {
185
+ findings.push(match[0]);
186
+ }
187
+ }
188
+ return [...new Set(findings)].sort();
189
+ }
190
+
191
+ function isCriticalPlaceholderLine(line) {
192
+ const trimmed = String(line || '').trim();
193
+ return CRITICAL_SCAFFOLD_PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(trimmed));
194
+ }
195
+
196
+ function isScaffoldBoilerplateLine(line) {
197
+ const trimmed = String(line || '')
198
+ .replace(/^[-*]\s+/, '')
199
+ .replace(/^>\s*/, '')
200
+ .trim();
201
+ if (!trimmed) {
202
+ return true;
203
+ }
204
+ if (/^#+\s+/.test(trimmed)) {
205
+ return true;
206
+ }
207
+ if (/^last_updated:\s*"/i.test(trimmed) || /^token_cost:\s*\d+/i.test(trimmed)) {
208
+ return true;
209
+ }
210
+ if (/^Files Considered$|^Assumptions$|^Risks$|^Contradictions$|^Omitted Paths$/i.test(trimmed.replace(/^#+\s*/, ''))) {
211
+ return true;
212
+ }
213
+ return SCAFFOLD_BOILERPLATE_PATTERNS.some((pattern) => pattern.test(trimmed));
214
+ }
215
+
216
+ function significantHumanLines(content) {
217
+ return normalizeNewlines(content)
218
+ .split('\n')
219
+ .map((line) => line.trim())
220
+ .filter((line) => line.length >= 12)
221
+ .filter((line) => !isCriticalPlaceholderLine(line))
222
+ .filter((line) => !isScaffoldBoilerplateLine(line));
223
+ }
224
+
225
+ function classifyAnalyzeProjectDoc(currentContent) {
226
+ const current = normalizeNewlines(currentContent);
227
+ const frontmatter = splitFrontmatter(current);
228
+ const withoutAnalyze = removeManagedBlock(frontmatter.body, ANALYZE_MANAGED_START, ANALYZE_MANAGED_END);
229
+ const withoutContextPrep = removeManagedBlock(withoutAnalyze.content, CONTEXT_PREP_MANAGED_START, CONTEXT_PREP_MANAGED_END);
230
+ const visibleBody = withoutContextPrep.content.trim();
231
+ const visiblePlaceholders = collectCriticalPlaceholders(visibleBody);
232
+ const contextPrepPlaceholders = collectCriticalPlaceholders(withoutContextPrep.block);
233
+ const humanLines = significantHumanLines(visibleBody);
234
+ const contextPrepHumanLines = significantHumanLines(withoutContextPrep.block);
235
+ const hasManagedOnly = !visibleBody && (withoutAnalyze.removed || withoutContextPrep.removed);
236
+
237
+ let classification = 'unknown';
238
+ if (!current.trim()) {
239
+ classification = 'managed_only';
240
+ } else if (hasManagedOnly) {
241
+ classification = 'managed_only';
242
+ } else if (visiblePlaceholders.length > 0 && humanLines.length === 0) {
243
+ classification = 'scaffold';
244
+ } else if (visiblePlaceholders.length > 0 && humanLines.length > 0) {
245
+ classification = 'partial_scaffold';
246
+ } else if (withoutAnalyze.removed || withoutContextPrep.removed) {
247
+ classification = humanLines.length > 0 ? 'mixed' : 'managed_only';
248
+ } else if (humanLines.length > 0) {
249
+ classification = 'human_content';
250
+ }
251
+
252
+ return {
253
+ classification,
254
+ frontmatter,
255
+ visible_body: visibleBody,
256
+ has_analyze_project_block: withoutAnalyze.removed,
257
+ has_context_prep_block: withoutContextPrep.removed,
258
+ context_prep_is_scaffold: withoutContextPrep.removed && contextPrepHumanLines.length === 0 && contextPrepPlaceholders.length > 0,
259
+ critical_placeholders: visiblePlaceholders,
260
+ context_prep_placeholders: contextPrepPlaceholders,
261
+ human_line_count: humanLines.length,
262
+ };
263
+ }
264
+
265
+ function composeWithFrontmatter(frontmatter, body) {
266
+ const prefix = frontmatter?.valid ? `${frontmatter.frontmatter.trimEnd()}\n\n` : '';
267
+ return `${prefix}${normalizeDocContent(body)}`;
268
+ }
269
+
270
+ function visibleAnalyzeProjectContent(proposedContent) {
271
+ return normalizeDocContent(proposedContent).trimEnd();
272
+ }
273
+
274
+ function buildPreservedContentSection(content) {
275
+ const body = normalizeNewlines(content).trim();
276
+ if (!body) {
277
+ return '';
278
+ }
279
+ return `## Existing Content Preserved\n\n${body}`;
280
+ }
281
+
282
+ function normalizedMarkdownEquivalent(left, right) {
283
+ return normalizeNewlines(left).trim() === normalizeNewlines(right).trim();
284
+ }
285
+
286
+ function mergeAnalyzeProjectDoc(currentContent, proposedContent) {
287
+ const current = normalizeNewlines(currentContent);
288
+ const proposedVisible = visibleAnalyzeProjectContent(proposedContent);
289
+ const classification = classifyAnalyzeProjectDoc(current);
290
+ const currentWithoutAnalyze = removeManagedBlock(classification.frontmatter.body, ANALYZE_MANAGED_START, ANALYZE_MANAGED_END);
291
+ const currentWithoutContextPrep = removeManagedBlock(currentWithoutAnalyze.content, CONTEXT_PREP_MANAGED_START, CONTEXT_PREP_MANAGED_END);
292
+ const bodyWithoutManaged = currentWithoutContextPrep.content.trim();
293
+ const report = {
294
+ classification: classification.classification,
295
+ strategy: 'preserve-and-update-managed-block',
296
+ scaffold_replaced: false,
297
+ human_content_preserved: false,
298
+ analyze_project_block_replaced: classification.has_analyze_project_block,
299
+ context_prep_removed: false,
300
+ critical_placeholders: classification.critical_placeholders,
301
+ warnings: [],
302
+ };
303
+
304
+ let visibleBody;
305
+ if (['scaffold', 'managed_only'].includes(classification.classification)) {
306
+ visibleBody = proposedVisible;
307
+ report.strategy = classification.classification === 'scaffold'
308
+ ? 'replace-scaffold-primary-content'
309
+ : 'replace-managed-only-content';
310
+ report.scaffold_replaced = classification.classification === 'scaffold';
311
+ } else if (['partial_scaffold', 'mixed'].includes(classification.classification)) {
312
+ const cleaned = bodyWithoutManaged
313
+ .split('\n')
314
+ .filter((line) => !isCriticalPlaceholderLine(line))
315
+ .join('\n')
316
+ .replace(/\n{3,}/g, '\n\n')
317
+ .trim();
318
+ const preserved = normalizedMarkdownEquivalent(cleaned, proposedVisible)
319
+ ? ''
320
+ : buildPreservedContentSection(cleaned);
321
+ visibleBody = preserved ? `${proposedVisible}\n\n${preserved}` : proposedVisible;
322
+ report.strategy = 'replace-placeholders-preserve-human-content';
323
+ report.scaffold_replaced = classification.critical_placeholders.length > 0;
324
+ report.human_content_preserved = Boolean(preserved);
325
+ } else {
326
+ visibleBody = bodyWithoutManaged
327
+ ? `${bodyWithoutManaged}\n\n${managedBlock(proposedContent).trimEnd()}`
328
+ : managedBlock(proposedContent).trimEnd();
329
+ report.human_content_preserved = bodyWithoutManaged.trim().length > 0;
330
+ if (classification.classification === 'unknown') {
331
+ report.warnings.push('document classification is unknown; preserved existing content and updated managed block only');
332
+ }
333
+ return {
334
+ content: composeWithFrontmatter(classification.frontmatter, visibleBody),
335
+ report,
336
+ };
337
+ }
338
+
339
+ if (classification.has_context_prep_block && (classification.context_prep_is_scaffold || classification.context_prep_placeholders.length > 0)) {
340
+ report.context_prep_removed = true;
341
+ }
342
+
343
+ return {
344
+ content: composeWithFrontmatter(
345
+ classification.frontmatter,
346
+ `${visibleBody}\n\n${managedBlock(proposedContent).trimEnd()}`,
347
+ ),
348
+ report,
349
+ };
350
+ }
351
+
79
352
  function mergeManagedBlock(currentContent, proposedContent) {
80
- const current = String(currentContent || '');
353
+ const current = normalizeNewlines(currentContent);
81
354
  const block = managedBlock(proposedContent);
82
355
  const startIndex = current.indexOf(ANALYZE_MANAGED_START);
83
356
  const endIndex = current.indexOf(ANALYZE_MANAGED_END);
@@ -253,9 +526,10 @@ function buildAnalyzeProjectWritePlan(repoRoot, proposal) {
253
526
  const destinationPath = path.join(repoRoot, doc.path);
254
527
  const exists = fs.existsSync(destinationPath);
255
528
  const currentContent = exists ? fs.readFileSync(destinationPath, 'utf8') : '';
256
- const proposedContent = doc.action === 'skip'
257
- ? currentContent
258
- : mergeManagedBlock(currentContent, doc.content);
529
+ const merged = doc.action === 'skip'
530
+ ? { content: currentContent, report: { classification: 'skipped', strategy: 'skip' } }
531
+ : mergeAnalyzeProjectDoc(currentContent, doc.content);
532
+ const proposedContent = merged.content;
259
533
  const changed = currentContent.replace(/\r\n/g, '\n') !== proposedContent.replace(/\r\n/g, '\n');
260
534
  const action = doc.action === 'skip' || !changed ? 'skip' : exists ? 'update' : 'create';
261
535
 
@@ -271,6 +545,7 @@ function buildAnalyzeProjectWritePlan(repoRoot, proposal) {
271
545
  before_sha256: exists ? sha256(currentContent) : null,
272
546
  after_sha256: action === 'skip' ? (exists ? sha256(currentContent) : null) : sha256(proposedContent),
273
547
  diff: buildDiffSnippet(doc.path, currentContent, proposedContent),
548
+ merge_report: merged.report,
274
549
  };
275
550
  });
276
551
  }
@@ -368,12 +643,17 @@ module.exports = {
368
643
  ANALYZE_DOC_PROPOSAL_SCHEMA_VERSION,
369
644
  ANALYZE_MANAGED_END,
370
645
  ANALYZE_MANAGED_START,
646
+ CONTEXT_PREP_MANAGED_END,
647
+ CONTEXT_PREP_MANAGED_START,
371
648
  AnalyzeProjectDocsError,
372
649
  analyzeProjectDocProposalSchema,
373
650
  buildAnalyzeProjectDocProposal,
374
651
  buildAnalyzeProjectWritePlan,
652
+ classifyAnalyzeProjectDoc,
653
+ collectCriticalPlaceholders,
375
654
  createAnalyzeProjectSnapshot,
376
655
  formatAnalyzeProjectDiffPreview,
656
+ mergeAnalyzeProjectDoc,
377
657
  mergeManagedBlock,
378
658
  normalizeAnalyzeProjectDocProposal,
379
659
  parseAnalyzeProjectDocProposal,
@@ -127,6 +127,7 @@ const proposalManifestSchema = z.object({
127
127
  repair_manifest: relativePathSchema('repair_manifest').nullable(),
128
128
  doc_paths: z.array(relativePathSchema('doc_paths')).default([]),
129
129
  doc_before_hashes: z.record(z.string().trim().min(1), z.string().trim().min(1).nullable()).default({}),
130
+ merge_plan: z.array(z.record(z.string(), z.unknown())).default([]),
130
131
  proposal_sha256: z.string().trim().min(1),
131
132
  events: z.array(z.record(z.string(), z.unknown())).default([]),
132
133
  }).strict();
@@ -139,6 +140,7 @@ const writeManifestActionSchema = z.object({
139
140
  snapshot_path: relativePathSchema('actions.snapshot_path').nullable(),
140
141
  dirty: z.boolean().default(false),
141
142
  status: z.enum(['planned', 'written', 'skipped', 'failed']).default('planned'),
143
+ merge_report: z.record(z.string(), z.unknown()).optional(),
142
144
  }).strict();
143
145
 
144
146
  const writeManifestSchema = z.object({
@@ -419,9 +421,16 @@ function writeAnalyzeProjectProposalArtifacts(repoRoot, options = {}) {
419
421
  const proposalJson = `${JSON.stringify(proposal, null, 2)}\n`;
420
422
  const proposalHash = sha256(proposalJson);
421
423
  const docBeforeHashes = {};
424
+ const mergePlan = [];
422
425
 
423
426
  for (const item of writePlan) {
424
427
  docBeforeHashes[item.path] = item.before_sha256 || null;
428
+ mergePlan.push({
429
+ path: item.path,
430
+ action: item.action,
431
+ dirty: item.dirty === true,
432
+ merge_report: item.merge_report || null,
433
+ });
425
434
  }
426
435
 
427
436
  const selectedContextPath = artifactPathFromRef(options.selectedContextManifest)
@@ -441,6 +450,7 @@ function writeAnalyzeProjectProposalArtifacts(repoRoot, options = {}) {
441
450
  repair_manifest: repairPath,
442
451
  doc_paths: writePlan.map((item) => item.path),
443
452
  doc_before_hashes: docBeforeHashes,
453
+ merge_plan: mergePlan,
444
454
  proposal_sha256: proposalHash,
445
455
  events: [
446
456
  {
@@ -524,6 +534,7 @@ function writeAnalyzeProjectWriteManifest(repoRoot, options = {}) {
524
534
  snapshot_path: snapshotEntry?.snapshot_path || null,
525
535
  dirty: item.dirty === true,
526
536
  status,
537
+ merge_report: item.merge_report || undefined,
527
538
  };
528
539
  });
529
540
  const validation = options.validation || {