create-quiver 0.17.1 → 0.17.3

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 (119) hide show
  1. package/ACTIVE_SLICES.md +43 -0
  2. package/CHANGELOG.md +8 -0
  3. package/auditoria-ux-ui-performance-documentacion.md +705 -0
  4. package/copys-landing-page.md +244 -0
  5. package/docs/CLI_UX_GUIDE.md +13 -3
  6. package/docs/TROUBLESHOOTING.md +94 -0
  7. package/docs/reference/commands.md +68 -4
  8. package/docs/workflows/existing-project-ai-quiver-setup.md +552 -0
  9. package/docs/workflows/existing-project.md +36 -0
  10. package/package.json +1 -1
  11. package/specs/quiver-v43-cli-i18n-audit-release-readiness/command-language-mode-matrix.json +5 -0
  12. package/specs/quiver-v53-reliable-deep-project-analysis/EVIDENCE_REPORT.md +151 -0
  13. package/specs/quiver-v53-reliable-deep-project-analysis/EXECUTION_PLAN.md +50 -0
  14. package/specs/quiver-v53-reliable-deep-project-analysis/SPEC.md +207 -0
  15. package/specs/quiver-v53-reliable-deep-project-analysis/STATUS.md +32 -0
  16. package/specs/quiver-v53-reliable-deep-project-analysis/pr.md +73 -0
  17. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-00-analysis-run-contract/CLOSURE_BRIEF.md +32 -0
  18. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-00-analysis-run-contract/EXECUTION_BRIEF.md +63 -0
  19. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-00-analysis-run-contract/slice.json +70 -0
  20. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-01-provider-fixture-harness/CLOSURE_BRIEF.md +38 -0
  21. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-01-provider-fixture-harness/EXECUTION_BRIEF.md +60 -0
  22. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-01-provider-fixture-harness/slice.json +74 -0
  23. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-02-safe-context-boundary/CLOSURE_BRIEF.md +33 -0
  24. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-02-safe-context-boundary/EXECUTION_BRIEF.md +68 -0
  25. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-02-safe-context-boundary/slice.json +85 -0
  26. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-03-schema-error-grouping/CLOSURE_BRIEF.md +34 -0
  27. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-03-schema-error-grouping/EXECUTION_BRIEF.md +65 -0
  28. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-03-schema-error-grouping/slice.json +80 -0
  29. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-04-safe-repair-layer/CLOSURE_BRIEF.md +34 -0
  30. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-04-safe-repair-layer/EXECUTION_BRIEF.md +63 -0
  31. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-04-safe-repair-layer/slice.json +80 -0
  32. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-05-controlled-retry-layer/CLOSURE_BRIEF.md +34 -0
  33. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-05-controlled-retry-layer/EXECUTION_BRIEF.md +65 -0
  34. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-05-controlled-retry-layer/slice.json +81 -0
  35. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-06-audit-review-transaction/CLOSURE_BRIEF.md +35 -0
  36. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-06-audit-review-transaction/EXECUTION_BRIEF.md +71 -0
  37. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-06-audit-review-transaction/slice.json +88 -0
  38. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-07-semantic-validation-docs/CLOSURE_BRIEF.md +32 -0
  39. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-07-semantic-validation-docs/EXECUTION_BRIEF.md +66 -0
  40. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-07-semantic-validation-docs/slice.json +86 -0
  41. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-08-structural-map-hardening/CLOSURE_BRIEF.md +34 -0
  42. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-08-structural-map-hardening/EXECUTION_BRIEF.md +67 -0
  43. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-08-structural-map-hardening/slice.json +81 -0
  44. package/specs/quiver-v54-deep-project-analysis-hardening/EVIDENCE_REPORT.md +53 -0
  45. package/specs/quiver-v54-deep-project-analysis-hardening/EXECUTION_PLAN.md +55 -0
  46. package/specs/quiver-v54-deep-project-analysis-hardening/SPEC.md +184 -0
  47. package/specs/quiver-v54-deep-project-analysis-hardening/STATUS.md +34 -0
  48. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-01-contract-regression-harness/CLOSURE_BRIEF.md +10 -0
  49. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-01-contract-regression-harness/EXECUTION_BRIEF.md +54 -0
  50. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-01-contract-regression-harness/slice.json +72 -0
  51. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-02-safe-discovery-sampling/CLOSURE_BRIEF.md +11 -0
  52. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-02-safe-discovery-sampling/EXECUTION_BRIEF.md +53 -0
  53. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-02-safe-discovery-sampling/slice.json +69 -0
  54. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-03-run-lifecycle-cli-io/CLOSURE_BRIEF.md +10 -0
  55. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-03-run-lifecycle-cli-io/EXECUTION_BRIEF.md +53 -0
  56. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-03-run-lifecycle-cli-io/slice.json +69 -0
  57. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-04-artifact-store-redaction/CLOSURE_BRIEF.md +10 -0
  58. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-04-artifact-store-redaction/EXECUTION_BRIEF.md +54 -0
  59. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-04-artifact-store-redaction/slice.json +73 -0
  60. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-05-path-aware-repair-engine/CLOSURE_BRIEF.md +10 -0
  61. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-05-path-aware-repair-engine/EXECUTION_BRIEF.md +57 -0
  62. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-05-path-aware-repair-engine/slice.json +72 -0
  63. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-06-retry-controller/CLOSURE_BRIEF.md +9 -0
  64. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-06-retry-controller/EXECUTION_BRIEF.md +52 -0
  65. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-06-retry-controller/slice.json +70 -0
  66. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-07-final-validation-review-write-gate/CLOSURE_BRIEF.md +10 -0
  67. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-07-final-validation-review-write-gate/EXECUTION_BRIEF.md +57 -0
  68. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-07-final-validation-review-write-gate/slice.json +75 -0
  69. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-08-doctor-agents-guidance/CLOSURE_BRIEF.md +9 -0
  70. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-08-doctor-agents-guidance/EXECUTION_BRIEF.md +51 -0
  71. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-08-doctor-agents-guidance/slice.json +66 -0
  72. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-09-release-smoke-rollback-docs/CLOSURE_BRIEF.md +10 -0
  73. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-09-release-smoke-rollback-docs/EXECUTION_BRIEF.md +54 -0
  74. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-09-release-smoke-rollback-docs/slice.json +69 -0
  75. package/specs/quiver-v55-analyze-project-doc-apply-ux/EVIDENCE_REPORT.md +209 -0
  76. package/specs/quiver-v55-analyze-project-doc-apply-ux/EXECUTION_PLAN.md +49 -0
  77. package/specs/quiver-v55-analyze-project-doc-apply-ux/SPEC.md +276 -0
  78. package/specs/quiver-v55-analyze-project-doc-apply-ux/STATUS.md +29 -0
  79. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/CLOSURE_BRIEF.md +47 -0
  80. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/EXECUTION_BRIEF.md +67 -0
  81. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/pr.md +129 -0
  82. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/slice.json +82 -0
  83. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/CLOSURE_BRIEF.md +39 -0
  84. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/EXECUTION_BRIEF.md +61 -0
  85. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/pr.md +116 -0
  86. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/slice.json +69 -0
  87. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/CLOSURE_BRIEF.md +36 -0
  88. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/EXECUTION_BRIEF.md +62 -0
  89. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/pr.md +132 -0
  90. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/slice.json +76 -0
  91. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/CLOSURE_BRIEF.md +47 -0
  92. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/EXECUTION_BRIEF.md +70 -0
  93. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/pr.md +141 -0
  94. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/slice.json +72 -0
  95. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/CLOSURE_BRIEF.md +42 -0
  96. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/EXECUTION_BRIEF.md +61 -0
  97. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/pr.md +129 -0
  98. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/slice.json +67 -0
  99. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/CLOSURE_BRIEF.md +38 -0
  100. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/EXECUTION_BRIEF.md +64 -0
  101. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/pr.md +123 -0
  102. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/slice.json +76 -0
  103. package/src/create-quiver/commands/ai.js +989 -136
  104. package/src/create-quiver/index.js +83 -1
  105. package/src/create-quiver/lib/ai/analyze-project-apply.js +269 -0
  106. package/src/create-quiver/lib/ai/analyze-project-discovery.js +213 -2
  107. package/src/create-quiver/lib/ai/analyze-project-docs.js +3 -1
  108. package/src/create-quiver/lib/ai/analyze-project-interactive.js +241 -0
  109. package/src/create-quiver/lib/ai/analyze-project-parser.js +1 -0
  110. package/src/create-quiver/lib/ai/analyze-project-prompts.js +130 -0
  111. package/src/create-quiver/lib/ai/analyze-project-proposal.js +582 -0
  112. package/src/create-quiver/lib/ai/analyze-project-repair.js +402 -0
  113. package/src/create-quiver/lib/ai/analyze-project-sampling.js +31 -4
  114. package/src/create-quiver/lib/ai/analyze-project-validation.js +178 -8
  115. package/src/create-quiver/lib/ai/artifacts.js +83 -2
  116. package/src/create-quiver/lib/cli/ux-flags.js +6 -0
  117. package/src/create-quiver/lib/doctor.js +107 -12
  118. package/src/create-quiver/lib/i18n/messages/en.js +33 -0
  119. package/src/create-quiver/lib/i18n/messages/es.js +34 -0
@@ -22,18 +22,43 @@ const {
22
22
  formatAnalyzeProjectDiffPreview,
23
23
  writeAnalyzeProjectDocs,
24
24
  } = require('../lib/ai/analyze-project-docs');
25
- const { parseAnalyzeProjectOutput } = require('../lib/ai/analyze-project-parser');
26
- const { buildAnalyzeProjectPrompt } = require('../lib/ai/analyze-project-prompts');
25
+ const {
26
+ buildAnalyzeProjectPrompt,
27
+ buildAnalyzeProjectRetryPrompt,
28
+ buildSelectedContextManifest,
29
+ writeSelectedContextManifest,
30
+ } = require('../lib/ai/analyze-project-prompts');
31
+ const {
32
+ parseAnalyzeProjectOutputWithRepair,
33
+ writeAnalyzeProjectRepairManifest,
34
+ } = require('../lib/ai/analyze-project-repair');
27
35
  const {
28
36
  confirmAnalyzeProjectWrites,
29
37
  reviewAnalyzeProjectDocProposal,
30
38
  } = require('../lib/ai/analyze-project-review');
39
+ const {
40
+ findLatestAnalyzeProjectSavedProposalRun,
41
+ readAnalyzeProjectSavedProposal,
42
+ writeAnalyzeProjectProposalArtifacts,
43
+ } = require('../lib/ai/analyze-project-proposal');
44
+ const {
45
+ applyAnalyzeProjectDocProposal,
46
+ } = require('../lib/ai/analyze-project-apply');
47
+ const {
48
+ canUseAnalyzeProjectInteractiveSelector,
49
+ runAnalyzeProjectInteractiveApplySelector,
50
+ } = require('../lib/ai/analyze-project-interactive');
31
51
  const {
32
52
  DEFAULT_MAX_BYTES: DEFAULT_ANALYZE_MAX_BYTES,
33
53
  DEFAULT_MAX_FILES: DEFAULT_ANALYZE_MAX_FILES,
34
54
  sampleProjectFiles,
35
55
  } = require('../lib/ai/analyze-project-sampling');
36
- const { validateAnalyzeProjectPostWrite } = require('../lib/ai/analyze-project-validation');
56
+ const {
57
+ groupAnalyzeProjectIssues,
58
+ validateAnalyzeProjectPostWrite,
59
+ writeAnalyzeProjectRetryManifest,
60
+ writeAnalyzeProjectValidationManifest,
61
+ } = require('../lib/ai/analyze-project-validation');
37
62
  const { openEditor } = require('../lib/cli/editor');
38
63
  const { selectOption, promptText } = require('../lib/cli/selectors');
39
64
  const { createUx } = require('../lib/cli/ux');
@@ -166,22 +191,23 @@ function limitList(items, maxItems = 30) {
166
191
  }
167
192
 
168
193
  function formatAnalyzeProjectIssues(issues = [], maxIssues = 8) {
169
- const list = Array.isArray(issues) ? issues : [];
170
- const visible = list.slice(0, maxIssues);
171
- const lines = visible.map((issue) => {
172
- const location = issue.path || 'analysis';
173
- const code = issue.issue || 'invalid';
174
- const message = issue.message || 'Invalid provider analysis output.';
175
- return `- ${location}: ${code} - ${message}`;
194
+ const groups = groupAnalyzeProjectIssues(issues, { maxExamplesPerGroup: 1 });
195
+ const visible = groups.slice(0, maxIssues);
196
+ const lines = visible.map((group) => {
197
+ const example = group.examples[0];
198
+ const exampleText = example
199
+ ? ` Example: ${example.path || 'analysis'} - ${example.message}`
200
+ : '';
201
+ return `- ${group.path_family}: ${group.type} (${group.count} issue${group.count === 1 ? '' : 's'}). Cause: ${group.cause_hint}${exampleText}`;
176
202
  });
177
- const hidden = list.length - visible.length;
203
+ const hidden = groups.length - visible.length;
178
204
  if (hidden > 0) {
179
- lines.push(`- ... ${hidden} more issue${hidden === 1 ? '' : 's'}`);
205
+ lines.push(`- ... ${hidden} more issue group${hidden === 1 ? '' : 's'}`);
180
206
  }
181
207
  return lines;
182
208
  }
183
209
 
184
- function enhanceAnalyzeProjectAnalysisError(error) {
210
+ function enhanceAnalyzeProjectAnalysisError(error, options = {}) {
185
211
  if (!error || error.code !== 'AI_ANALYZE_PROJECT_INVALID') {
186
212
  return error;
187
213
  }
@@ -195,13 +221,19 @@ function enhanceAnalyzeProjectAnalysisError(error) {
195
221
  error.message,
196
222
  'Issues:',
197
223
  ...issueLines,
224
+ options.repairManifest?.path ? `Repair manifest: ${options.repairManifest.path}` : '',
225
+ options.retryManifest?.path ? `Retry manifest: ${options.retryManifest.path}` : '',
226
+ options.validationManifest?.path ? `Validation manifest: ${options.validationManifest.path}` : '',
198
227
  'Next safe step: inspect the selected evidence with `npx create-quiver ai analyze-project --deep --dry-run --json`, then rerun live. If provider drift repeats, reduce --max-files or --max-bytes.',
199
- ].join('\n'));
228
+ ].filter(Boolean).join('\n'));
200
229
  wrapped.name = error.name;
201
230
  wrapped.code = error.code;
202
231
  wrapped.cause = error;
203
232
  wrapped.issues = error.issues;
204
233
  wrapped.details = error.issues;
234
+ wrapped.repair_manifest = options.repairManifest || null;
235
+ wrapped.retry_manifest = options.retryManifest || null;
236
+ wrapped.validation_manifest = options.validationManifest || null;
205
237
  return wrapped;
206
238
  }
207
239
 
@@ -251,6 +283,9 @@ function formatAnalyzeProjectReport(report) {
251
283
  lines.push(`Source roots: ${report.detected.source_roots.length > 0 ? report.detected.source_roots.join(', ') : 'none'}`);
252
284
  lines.push(`Entrypoints: ${report.detected.entrypoints.length > 0 ? report.detected.entrypoints.join(', ') : 'none'}`);
253
285
  lines.push(`Configs: ${report.detected.configs.length > 0 ? report.detected.configs.join(', ') : 'none'}`);
286
+ lines.push(`Lockfiles: ${(report.detected.lockfiles || []).length > 0
287
+ ? report.detected.lockfiles.map((file) => `${file.path} (${file.package_manager}; metadata only)`).join(', ')
288
+ : 'none'}`);
254
289
 
255
290
  lines.push('', 'Selected files:');
256
291
  for (const file of selected.items) {
@@ -397,6 +432,60 @@ function buildAnalyzeProjectProviderArtifact(result, clean, repoRoot, options =
397
432
  };
398
433
  }
399
434
 
435
+ function createAnalyzeProjectAuditRunId(now = new Date()) {
436
+ const date = now instanceof Date ? now : new Date(now || Date.now());
437
+ return `run-${date.toISOString()
438
+ .replace(/\.\d{3}Z$/, 'z')
439
+ .replace(/[^0-9a-z]+/gi, '-')
440
+ .toLowerCase()
441
+ .replace(/^-+|-+$/g, '')}`;
442
+ }
443
+
444
+ function writeAnalyzeProjectRunStatus(repoRoot, runId, status, details = {}) {
445
+ const statusPath = path.join(repoRoot, '.quiver', 'runs', runId, 'status.json');
446
+ fs.mkdirSync(path.dirname(statusPath), { recursive: true });
447
+ fs.writeFileSync(statusPath, `${JSON.stringify({
448
+ schema_version: 1,
449
+ kind: 'quiver-analyze-project-run-status',
450
+ run_id: runId,
451
+ status,
452
+ updated_at: (details.now instanceof Date ? details.now : new Date(details.now || Date.now())).toISOString(),
453
+ provider: details.provider || null,
454
+ attempts: details.attempts || [],
455
+ artifacts: details.artifacts || {},
456
+ }, null, 2)}\n`);
457
+ return path.relative(repoRoot, statusPath).split(path.sep).join('/');
458
+ }
459
+
460
+ function normalizeAnalyzeProjectMaxRetries(value) {
461
+ if (value === undefined || value === null || value === '') {
462
+ return 1;
463
+ }
464
+ const parsed = Number(value);
465
+ if (!Number.isFinite(parsed) || parsed < 0) {
466
+ return 1;
467
+ }
468
+ return Math.min(2, Math.floor(parsed));
469
+ }
470
+
471
+ function isAnalyzeProjectRetryableError(error) {
472
+ if (!error || error.code !== 'AI_ANALYZE_PROJECT_INVALID') {
473
+ return false;
474
+ }
475
+ const retryableIssues = new Set([
476
+ 'empty-output',
477
+ 'malformed-json',
478
+ 'invalid_type',
479
+ 'invalid_value',
480
+ 'unrecognized_keys',
481
+ 'missing-evidence',
482
+ 'evidence-not-selected',
483
+ 'unapproved-doc-update-path',
484
+ ]);
485
+ const issues = Array.isArray(error.issues) ? error.issues : [];
486
+ return issues.length > 0 && issues.every((issue) => retryableIssues.has(issue.issue || ''));
487
+ }
488
+
400
489
  function formatAnalyzeProjectLiveReport(report) {
401
490
  const warningCount = report.analysis_validation?.warnings?.length || 0;
402
491
  const docUpdatePaths = report.analysis_validation?.doc_update_paths || [];
@@ -405,6 +494,7 @@ function formatAnalyzeProjectLiveReport(report) {
405
494
  `Mode: ${report.mode}`,
406
495
  `Provider: ${report.provider}`,
407
496
  `Provider execution: ${report.provider_execution}`,
497
+ `Provider attempts: ${report.provider_attempts?.length || 1}`,
408
498
  `Writes: ${report.writes.length === 0 ? 'none' : report.writes.join(', ')}`,
409
499
  `Privacy preflight: ${report.privacy_preflight.ok ? 'passed' : 'failed'}`,
410
500
  `Prompt bytes: ${report.prompt.bytes}/${report.prompt.max_provider_prompt_bytes}`,
@@ -423,12 +513,46 @@ function formatAnalyzeProjectLiveReport(report) {
423
513
  }
424
514
 
425
515
  lines.push('', 'Next commands:');
516
+ lines.push('- npx create-quiver ai analyze-project --deep --save-proposal --provider <provider> --model <model>');
426
517
  lines.push('- npx create-quiver ai analyze-project --deep --review');
427
518
  lines.push('- npx create-quiver ai analyze-project --deep --json');
428
519
 
429
520
  return `${lines.join('\n')}\n`;
430
521
  }
431
522
 
523
+ function summarizeAnalyzeProjectWritePlan(writePlan = []) {
524
+ return writePlan.map((item) => ({
525
+ path: item.path,
526
+ action: item.action,
527
+ dirty: item.dirty,
528
+ before_sha256: item.before_sha256,
529
+ after_sha256: item.after_sha256,
530
+ reason: item.reason,
531
+ }));
532
+ }
533
+
534
+ function formatAnalyzeProjectSavedProposalReport(report = {}) {
535
+ const artifacts = report.proposal_artifacts || {};
536
+ const changed = report.write_plan?.filter((item) => item.action !== 'skip') || [];
537
+ const lines = [
538
+ 'AI analyze-project proposal saved',
539
+ `Run: ${report.run_id || 'unknown'}`,
540
+ `Provider: ${report.provider || 'unknown'}`,
541
+ `Writes: none (final docs were not modified)`,
542
+ `Proposed docs: ${report.doc_paths?.length || 0}`,
543
+ `Docs with changes: ${changed.length}`,
544
+ `Proposal JSON: ${artifacts.proposal_json || 'none'}`,
545
+ `Proposal summary: ${artifacts.proposal_markdown || 'none'}`,
546
+ `Proposal diff: ${artifacts.proposal_diff || 'none'}`,
547
+ `Manifest: ${artifacts.manifest || 'none'}`,
548
+ '',
549
+ 'Next commands:',
550
+ `- npx create-quiver ai analyze-project apply --run ${report.run_id || '<run-id>'}`,
551
+ '- npx create-quiver ai analyze-project --deep --apply-docs --provider <provider> --model <model>',
552
+ ];
553
+ return `${lines.join('\n')}\n`;
554
+ }
555
+
432
556
  function formatAnalyzeProjectPostWriteValidation(validation) {
433
557
  if (!validation) {
434
558
  return [];
@@ -475,6 +599,430 @@ function formatAnalyzeProjectReviewPlan({ writePlan, reviewPath, snapshot, writt
475
599
  return `${lines.join('\n')}\n`;
476
600
  }
477
601
 
602
+ function formatAnalyzeProjectApplyReport(report = {}) {
603
+ const artifacts = report.proposal_artifacts || {};
604
+ const writeManifest = report.write_manifest || {};
605
+ const changed = report.write_plan?.filter((item) => item.action !== 'skip') || [];
606
+ const dirty = changed.filter((item) => item.dirty);
607
+ const lines = [
608
+ 'AI analyze-project docs applied',
609
+ `Run: ${report.run_id || 'unknown'}`,
610
+ `Provider: ${report.provider || 'unknown'}`,
611
+ report.provider_execution ? `Provider execution: ${report.provider_execution}` : '',
612
+ report.apply_run ? `Saved proposal edited: ${report.saved_proposal?.proposal_edited ? 'yes' : 'no'}` : '',
613
+ `Writes: ${changed.length > 0 ? changed.map((item) => item.path).join(', ') : 'none'}`,
614
+ `Dirty target docs: ${dirty.length > 0 ? dirty.map((item) => item.path).join(', ') : 'none'}`,
615
+ `Written docs: ${report.written_docs?.length > 0 ? report.written_docs.join(', ') : 'none'}`,
616
+ `Snapshot: ${report.snapshot?.root || 'none'}`,
617
+ `Proposal manifest: ${artifacts.manifest || 'none'}`,
618
+ `Write manifest: ${writeManifest.path || 'none'}`,
619
+ ].filter(Boolean);
620
+ lines.push(...formatAnalyzeProjectPostWriteValidation(report.post_write_validation));
621
+ return `${lines.join('\n')}\n`;
622
+ }
623
+
624
+ function formatAnalyzeProjectApplyPreflightError(error) {
625
+ if (!error || error.code !== 'AI_ANALYZE_PROJECT_APPLY_BLOCKED') {
626
+ return error;
627
+ }
628
+
629
+ const issueLines = (error.issues || []).slice(0, 12).map((issue) => {
630
+ const hashes = issue.expected_sha256 || issue.current_sha256
631
+ ? ` expected=${issue.expected_sha256 || 'none'} current=${issue.current_sha256 || 'none'}`
632
+ : '';
633
+ return `- ${issue.path || 'docs'}: ${issue.issue}${hashes} - ${issue.message}`;
634
+ });
635
+ const wrapped = new Error([
636
+ error.message,
637
+ 'Issues:',
638
+ ...issueLines,
639
+ error.apply_run
640
+ ? 'Next safe step: inspect `.quiver/runs/<run-id>/proposal/analyze-project-doc-proposal.diff`, then rerun with an explicit run id. Add `--allow-dirty-docs` only if overwriting existing managed docs is intended.'
641
+ : 'Next safe step: rerun with `--review`, or inspect with `--save-proposal`, or add `--allow-dirty-docs` if overwriting existing managed docs is intended.',
642
+ ].join('\n'));
643
+ wrapped.name = error.name;
644
+ wrapped.code = error.code;
645
+ wrapped.cause = error;
646
+ wrapped.issues = error.issues;
647
+ return wrapped;
648
+ }
649
+
650
+ function resolveAnalyzeProjectSavedProposalRunId(repoRoot, options = {}) {
651
+ const requestedRunId = String(options.runId || '').trim();
652
+ if (requestedRunId !== 'latest') {
653
+ return { runId: requestedRunId, requestedRunId };
654
+ }
655
+
656
+ if (!canUseInteractiveTerminal(options)) {
657
+ throw analyzeProjectContractError(
658
+ 'ai analyze-project apply --run latest requires an interactive terminal.',
659
+ 'Use an explicit run id for automation: `npx create-quiver ai analyze-project apply --run <run-id>`.',
660
+ );
661
+ }
662
+
663
+ const latestRunId = findLatestAnalyzeProjectSavedProposalRun(repoRoot);
664
+ if (!latestRunId) {
665
+ throw analyzeProjectContractError(
666
+ 'ai analyze-project apply --run latest could not find a saved proposal.',
667
+ 'Create one first: `npx create-quiver ai analyze-project --deep --save-proposal --provider <provider> --model <model>`.',
668
+ );
669
+ }
670
+
671
+ if (options.json !== true) {
672
+ process.stdout.write(`Resolved latest analyze-project proposal: ${latestRunId}\n`);
673
+ }
674
+ return { runId: latestRunId, requestedRunId };
675
+ }
676
+
677
+ function analyzeProjectRawProviderArtifactPaths(rawProviderArtifacts = []) {
678
+ return (Array.isArray(rawProviderArtifacts) ? rawProviderArtifacts : [])
679
+ .map((artifact) => (typeof artifact === 'string' ? artifact : artifact?.path))
680
+ .filter(Boolean);
681
+ }
682
+
683
+ function summarizeAnalyzeProjectProposalArtifacts(proposalArtifacts = {}) {
684
+ return {
685
+ root: proposalArtifacts.root,
686
+ proposal_json: proposalArtifacts.proposal_json,
687
+ proposal_markdown: proposalArtifacts.proposal_markdown,
688
+ proposal_diff: proposalArtifacts.proposal_diff,
689
+ manifest: proposalArtifacts.manifest,
690
+ proposal_sha256: proposalArtifacts.proposal_sha256,
691
+ };
692
+ }
693
+
694
+ function listAnalyzeProjectProposalArtifactFiles(proposalArtifacts = {}) {
695
+ if (Array.isArray(proposalArtifacts.files)) {
696
+ return proposalArtifacts.files.filter(Boolean);
697
+ }
698
+ return [
699
+ proposalArtifacts.proposal_json,
700
+ proposalArtifacts.proposal_markdown,
701
+ proposalArtifacts.proposal_diff,
702
+ proposalArtifacts.manifest,
703
+ ].filter(Boolean);
704
+ }
705
+
706
+ function saveAnalyzeProjectDocProposalReport(repoRoot, options = {}) {
707
+ const proposalArtifacts = writeAnalyzeProjectProposalArtifacts(repoRoot, {
708
+ runId: options.auditRunId,
709
+ now: options.commandOptions?.now || new Date(),
710
+ provider: options.provider,
711
+ language: options.commandOptions?.language,
712
+ proposal: options.proposal,
713
+ writePlan: options.writePlan,
714
+ selectedContextManifest: options.selectedContextManifest,
715
+ repairManifest: options.repairManifest,
716
+ events: options.interactiveAction ? [{
717
+ type: 'interactive-action',
718
+ action: options.interactiveAction,
719
+ at: (options.commandOptions?.now instanceof Date
720
+ ? options.commandOptions.now
721
+ : new Date(options.commandOptions?.now || Date.now())).toISOString(),
722
+ }] : [],
723
+ });
724
+ const saveReport = {
725
+ ...options.completedReport,
726
+ save_proposal: true,
727
+ interactive_action: options.interactiveAction || undefined,
728
+ doc_proposal: options.proposal,
729
+ doc_paths: proposalArtifacts.doc_paths,
730
+ proposal_artifacts: summarizeAnalyzeProjectProposalArtifacts(proposalArtifacts),
731
+ write_plan: summarizeAnalyzeProjectWritePlan(options.writePlan),
732
+ };
733
+ writeAnalyzeProjectRunStatus(repoRoot, options.auditRunId, 'proposal-saved', {
734
+ now: options.commandOptions?.now,
735
+ provider: options.provider,
736
+ attempts: options.providerAttempts,
737
+ artifacts: {
738
+ selected_context: options.selectedContextManifest,
739
+ raw_provider: analyzeProjectRawProviderArtifactPaths(options.rawProviderArtifacts),
740
+ repair: options.repairManifest,
741
+ retry: options.retryManifest,
742
+ proposal_manifest: proposalArtifacts.manifest,
743
+ proposal_files: proposalArtifacts.files,
744
+ },
745
+ });
746
+ return saveReport;
747
+ }
748
+
749
+ function applyAnalyzeProjectDocProposalReport(repoRoot, options = {}) {
750
+ let applyReport;
751
+ try {
752
+ applyReport = applyAnalyzeProjectDocProposal(repoRoot, {
753
+ report: options.completedReport,
754
+ runId: options.auditRunId,
755
+ now: options.commandOptions?.now || new Date(),
756
+ provider: options.provider,
757
+ language: options.commandOptions?.language,
758
+ proposal: options.proposal,
759
+ providerArtifact: options.completedReport.provider_artifact,
760
+ selectedContextManifest: options.selectedContextManifest,
761
+ repairManifest: options.repairManifest,
762
+ strict: options.commandOptions?.strict === true,
763
+ allowDirtyDocs: options.allowDirtyDocs === true,
764
+ summarizeWritePlan: summarizeAnalyzeProjectWritePlan,
765
+ });
766
+ } catch (error) {
767
+ const proposalArtifacts = error.proposal_artifacts || {};
768
+ writeAnalyzeProjectRunStatus(repoRoot, options.auditRunId, error.code === 'AI_ANALYZE_PROJECT_APPLY_BLOCKED' ? 'apply-blocked' : 'apply-failed', {
769
+ now: options.commandOptions?.now,
770
+ provider: options.provider,
771
+ attempts: options.providerAttempts,
772
+ artifacts: {
773
+ selected_context: options.selectedContextManifest,
774
+ raw_provider: analyzeProjectRawProviderArtifactPaths(options.rawProviderArtifacts),
775
+ repair: options.repairManifest,
776
+ retry: options.retryManifest,
777
+ proposal_manifest: proposalArtifacts.manifest || null,
778
+ proposal_files: listAnalyzeProjectProposalArtifactFiles(proposalArtifacts),
779
+ write_manifest: error.write_manifest?.path || null,
780
+ snapshot_manifest: error.snapshot?.manifestPath || null,
781
+ },
782
+ });
783
+ throw formatAnalyzeProjectApplyPreflightError(error);
784
+ }
785
+
786
+ applyReport = {
787
+ ...applyReport,
788
+ interactive_action: options.interactiveAction || applyReport.interactive_action,
789
+ };
790
+ writeAnalyzeProjectRunStatus(repoRoot, options.auditRunId, applyReport.post_write_validation?.ok ? 'docs-applied' : 'apply-validation-failed', {
791
+ now: options.commandOptions?.now,
792
+ provider: options.provider,
793
+ attempts: options.providerAttempts,
794
+ artifacts: {
795
+ selected_context: options.selectedContextManifest,
796
+ raw_provider: analyzeProjectRawProviderArtifactPaths(options.rawProviderArtifacts),
797
+ repair: options.repairManifest,
798
+ retry: options.retryManifest,
799
+ proposal_manifest: applyReport.proposal_artifacts?.manifest,
800
+ proposal_files: listAnalyzeProjectProposalArtifactFiles(applyReport.proposal_artifacts),
801
+ write_manifest: applyReport.write_manifest?.path,
802
+ snapshot_manifest: applyReport.snapshot?.manifestPath,
803
+ },
804
+ });
805
+ return applyReport;
806
+ }
807
+
808
+ function applyAnalyzeProjectSavedProposalRun(repoRoot, options = {}) {
809
+ const { runId, requestedRunId } = resolveAnalyzeProjectSavedProposalRunId(repoRoot, options);
810
+ const now = options.now instanceof Date ? options.now : new Date(options.now || Date.now());
811
+ const saved = readAnalyzeProjectSavedProposal(repoRoot, runId);
812
+ const provider = saved.manifest.provider || null;
813
+ const runStatusPath = path.join('.quiver', 'runs', runId, 'status.json').split(path.sep).join('/');
814
+ const baseReport = {
815
+ schema_version: 1,
816
+ kind: 'quiver-project-analysis-apply-saved-proposal',
817
+ command: 'ai analyze-project apply',
818
+ mode: 'apply-saved-proposal',
819
+ apply_run: true,
820
+ requested_run_id: requestedRunId,
821
+ run_id: runId,
822
+ provider,
823
+ provider_execution: 'skipped',
824
+ writes: [],
825
+ doc_proposal: saved.proposal,
826
+ saved_proposal: {
827
+ requested_run_id: requestedRunId,
828
+ run_id: runId,
829
+ manifest: saved.artifacts.manifest,
830
+ proposal_json: saved.artifacts.proposal_json,
831
+ proposal_sha256: saved.proposal_sha256,
832
+ proposal_edited: saved.proposal_edited,
833
+ },
834
+ run_status_path: runStatusPath,
835
+ };
836
+ const events = [
837
+ {
838
+ type: 'saved-proposal-apply',
839
+ at: now.toISOString(),
840
+ requested_run_id: requestedRunId,
841
+ run_id: runId,
842
+ },
843
+ ];
844
+ if (saved.proposal_edited) {
845
+ events.push({
846
+ type: 'saved-proposal-edited',
847
+ at: now.toISOString(),
848
+ original_sha256: saved.manifest.proposal_sha256,
849
+ current_sha256: saved.proposal_sha256,
850
+ });
851
+ }
852
+
853
+ let applyReport;
854
+ try {
855
+ applyReport = applyAnalyzeProjectDocProposal(repoRoot, {
856
+ report: baseReport,
857
+ runId,
858
+ now,
859
+ provider,
860
+ language: saved.manifest.language,
861
+ proposal: saved.proposal,
862
+ proposalArtifacts: saved.artifacts,
863
+ proposalManifest: saved.manifest,
864
+ proposalEdited: saved.proposal_edited,
865
+ strict: options.strict === true,
866
+ allowDirtyDocs: options.allowDirtyDocs === true,
867
+ summarizeWritePlan: summarizeAnalyzeProjectWritePlan,
868
+ events,
869
+ });
870
+ } catch (error) {
871
+ error.apply_run = true;
872
+ const proposalArtifacts = error.proposal_artifacts || saved.artifacts || {};
873
+ writeAnalyzeProjectRunStatus(repoRoot, runId, error.code === 'AI_ANALYZE_PROJECT_APPLY_BLOCKED' ? 'apply-blocked' : 'apply-failed', {
874
+ now,
875
+ provider,
876
+ attempts: [],
877
+ artifacts: {
878
+ proposal_manifest: proposalArtifacts.manifest || saved.artifacts.manifest,
879
+ proposal_files: listAnalyzeProjectProposalArtifactFiles(proposalArtifacts),
880
+ write_manifest: error.write_manifest?.path || null,
881
+ snapshot_manifest: error.snapshot?.manifestPath || null,
882
+ },
883
+ });
884
+ throw formatAnalyzeProjectApplyPreflightError(error);
885
+ }
886
+
887
+ applyReport = {
888
+ ...applyReport,
889
+ apply_run: true,
890
+ requested_run_id: requestedRunId,
891
+ saved_proposal: baseReport.saved_proposal,
892
+ run_status_path: runStatusPath,
893
+ };
894
+ writeAnalyzeProjectRunStatus(repoRoot, runId, applyReport.post_write_validation?.ok ? 'docs-applied' : 'apply-validation-failed', {
895
+ now,
896
+ provider,
897
+ attempts: [],
898
+ artifacts: {
899
+ proposal_manifest: applyReport.proposal_artifacts?.manifest,
900
+ proposal_files: listAnalyzeProjectProposalArtifactFiles(applyReport.proposal_artifacts),
901
+ write_manifest: applyReport.write_manifest?.path,
902
+ snapshot_manifest: applyReport.snapshot?.manifestPath,
903
+ },
904
+ });
905
+ return emitAnalyzeProjectApplyReport(applyReport, options);
906
+ }
907
+
908
+ function emitAnalyzeProjectApplyReport(applyReport, options = {}) {
909
+ if (options.json === true) {
910
+ process.stdout.write(`${JSON.stringify(applyReport, null, 2)}\n`);
911
+ } else {
912
+ process.stdout.write(formatAnalyzeProjectApplyReport(applyReport));
913
+ }
914
+ if (!applyReport.post_write_validation?.ok) {
915
+ const error = new Error(formatError('ai analyze-project post-write validation failed.'));
916
+ error.code = 'AI_ANALYZE_PROJECT_POST_WRITE_VALIDATION_FAILED';
917
+ error.validation = applyReport.post_write_validation;
918
+ throw error;
919
+ }
920
+ return applyReport;
921
+ }
922
+
923
+ async function reviewAndWriteAnalyzeProjectDocs(repoRoot, options = {}) {
924
+ const commandOptions = options.commandOptions || {};
925
+ const reviewed = await reviewAnalyzeProjectDocProposal(repoRoot, options.initialProposal, commandOptions);
926
+ const writePlan = buildAnalyzeProjectWritePlan(repoRoot, reviewed.proposal);
927
+ process.stdout.write(formatAnalyzeProjectReviewPlan({
928
+ writePlan,
929
+ reviewPath: reviewed.reviewPath,
930
+ }));
931
+ await confirmAnalyzeProjectWrites(writePlan, commandOptions);
932
+ const lifecycleRun = ensureAiRun(repoRoot, {
933
+ command: 'ai analyze-project',
934
+ input: reviewed.reviewPath,
935
+ runId: commandOptions.runId,
936
+ phase: 'created',
937
+ });
938
+ const snapshot = createAnalyzeProjectSnapshot(repoRoot, lifecycleRun, writePlan, {
939
+ providerArtifact: options.completedReport.provider_artifact,
940
+ proposal: reviewed.proposal,
941
+ now: commandOptions.now || new Date(),
942
+ });
943
+ const writtenDocs = writeAnalyzeProjectDocs(writePlan);
944
+ let writeReport = {
945
+ ...options.completedReport,
946
+ review: true,
947
+ interactive_action: options.interactiveAction || undefined,
948
+ review_path: reviewed.reviewPath,
949
+ doc_proposal: reviewed.proposal,
950
+ write_plan: summarizeAnalyzeProjectWritePlan(writePlan),
951
+ snapshot,
952
+ written_docs: writtenDocs,
953
+ run_id: lifecycleRun.run_id,
954
+ };
955
+ const postWriteValidation = validateAnalyzeProjectPostWrite(repoRoot, writeReport, {
956
+ strict: commandOptions.strict === true,
957
+ });
958
+ writeReport = {
959
+ ...writeReport,
960
+ post_write_validation: postWriteValidation,
961
+ };
962
+
963
+ if (commandOptions.json === true) {
964
+ process.stdout.write(`${JSON.stringify(writeReport, null, 2)}\n`);
965
+ if (!postWriteValidation.ok) {
966
+ const error = new Error(formatError('ai analyze-project post-write validation failed.'));
967
+ error.code = 'AI_ANALYZE_PROJECT_POST_WRITE_VALIDATION_FAILED';
968
+ error.validation = postWriteValidation;
969
+ throw error;
970
+ }
971
+ return writeReport;
972
+ }
973
+
974
+ process.stdout.write(formatAnalyzeProjectReviewPlan({
975
+ writePlan,
976
+ reviewPath: reviewed.reviewPath,
977
+ snapshot,
978
+ writtenDocs,
979
+ validation: postWriteValidation,
980
+ completed: true,
981
+ }));
982
+ if (!postWriteValidation.ok) {
983
+ const error = new Error(formatError('ai analyze-project post-write validation failed.'));
984
+ error.code = 'AI_ANALYZE_PROJECT_POST_WRITE_VALIDATION_FAILED';
985
+ error.validation = postWriteValidation;
986
+ throw error;
987
+ }
988
+ return writeReport;
989
+ }
990
+
991
+ function cancelAnalyzeProjectInteractiveApplyReport(repoRoot, options = {}) {
992
+ const translator = createTranslator(options.commandOptions?.language);
993
+ const savedArtifacts = options.savedReport?.proposal_artifacts || null;
994
+ const cancelReport = {
995
+ ...options.completedReport,
996
+ apply_docs: true,
997
+ interactive_action: options.interactiveAction || 'cancel',
998
+ cancelled: true,
999
+ writes: [],
1000
+ written_docs: [],
1001
+ doc_proposal: options.proposal,
1002
+ proposal_artifacts: savedArtifacts || undefined,
1003
+ write_plan: summarizeAnalyzeProjectWritePlan(options.writePlan),
1004
+ };
1005
+ writeAnalyzeProjectRunStatus(repoRoot, options.auditRunId, 'apply-canceled', {
1006
+ now: options.commandOptions?.now,
1007
+ provider: options.provider,
1008
+ attempts: options.providerAttempts,
1009
+ artifacts: {
1010
+ selected_context: options.selectedContextManifest,
1011
+ raw_provider: analyzeProjectRawProviderArtifactPaths(options.rawProviderArtifacts),
1012
+ repair: options.repairManifest,
1013
+ retry: options.retryManifest,
1014
+ proposal_manifest: savedArtifacts?.manifest || null,
1015
+ proposal_files: savedArtifacts ? listAnalyzeProjectProposalArtifactFiles(savedArtifacts) : [],
1016
+ },
1017
+ });
1018
+ process.stdout.write([
1019
+ translator.t('ai.analyze_project.apply.cancelled'),
1020
+ translator.t('ai.analyze_project.apply.cancelled_writes'),
1021
+ '',
1022
+ ].join('\n'));
1023
+ return cancelReport;
1024
+ }
1025
+
478
1026
  function formatLocalizedActionableError({ failure, impact, fix, nextCommand } = {}, options = {}) {
479
1027
  const translator = createTranslator(options.language);
480
1028
  const lines = [`create-quiver: ${String(failure || 'operation failed').trim()}`];
@@ -492,6 +1040,84 @@ function formatLocalizedActionableError({ failure, impact, fix, nextCommand } =
492
1040
  return lines.join('\n');
493
1041
  }
494
1042
 
1043
+ function analyzeProjectContractError(message, nextCommand) {
1044
+ const error = new Error(formatError([
1045
+ message,
1046
+ nextCommand ? `Next safe step: ${nextCommand}` : '',
1047
+ ].filter(Boolean).join('\n')));
1048
+ error.code = 'AI_ANALYZE_PROJECT_CONTRACT_UNAVAILABLE';
1049
+ return error;
1050
+ }
1051
+
1052
+ function canUseInteractiveTerminal(options = {}) {
1053
+ return options.stdinIsTTY === true || (options.stdinIsTTY !== false && Boolean(process.stdin.isTTY));
1054
+ }
1055
+
1056
+ function assertAnalyzeProjectCommandContract(options = {}) {
1057
+ if (options.review === true && options.applyDocs === true) {
1058
+ throw analyzeProjectContractError(
1059
+ 'ai analyze-project --apply-docs cannot be combined with --review.',
1060
+ 'Use either `npx create-quiver ai analyze-project --deep --review` or `npx create-quiver ai analyze-project --deep --apply-docs`.',
1061
+ );
1062
+ }
1063
+
1064
+ if (options.dryRun === true && options.applyDocs === true) {
1065
+ throw analyzeProjectContractError(
1066
+ 'ai analyze-project --dry-run cannot be combined with --apply-docs.',
1067
+ 'Preview with `npx create-quiver ai analyze-project --deep --dry-run`, then rerun without --dry-run.',
1068
+ );
1069
+ }
1070
+
1071
+ if (options.dryRun === true && options.saveProposal === true) {
1072
+ throw analyzeProjectContractError(
1073
+ 'ai analyze-project --dry-run cannot be combined with --save-proposal.',
1074
+ 'Preview with `npx create-quiver ai analyze-project --deep --dry-run`, then rerun with --save-proposal.',
1075
+ );
1076
+ }
1077
+
1078
+ if (options.applyRun === true && options.dryRun === true) {
1079
+ throw analyzeProjectContractError(
1080
+ 'ai analyze-project apply --run cannot be combined with --dry-run.',
1081
+ 'Inspect the saved proposal under `.quiver/runs/<run-id>/proposal/`, then rerun without --dry-run.',
1082
+ );
1083
+ }
1084
+
1085
+ if (options.applyRun === true && (options.applyDocs === true || options.saveProposal === true || options.review === true)) {
1086
+ throw analyzeProjectContractError(
1087
+ 'ai analyze-project apply --run cannot be combined with --apply-docs, --save-proposal, or --review.',
1088
+ 'Use `npx create-quiver ai analyze-project apply --run <run-id>` to apply a saved proposal, or rerun `ai analyze-project --deep` for a fresh proposal.',
1089
+ );
1090
+ }
1091
+
1092
+ if (options.json === true && options.applyDocs === true && options.force !== true) {
1093
+ throw analyzeProjectContractError(
1094
+ 'ai analyze-project --json with --apply-docs requires --yes because JSON output cannot include an interactive selector.',
1095
+ 'Use `npx create-quiver ai analyze-project --deep --apply-docs --yes --json` for automation.',
1096
+ );
1097
+ }
1098
+
1099
+ if (options.applyRun === true && !String(options.runId || '').trim()) {
1100
+ throw analyzeProjectContractError(
1101
+ 'ai analyze-project apply requires --run <run-id>.',
1102
+ 'Use `npx create-quiver ai analyze-project apply --run <run-id>`.',
1103
+ );
1104
+ }
1105
+
1106
+ if (options.applyRun === true && String(options.runId || '').trim() === 'latest' && options.force === true) {
1107
+ throw analyzeProjectContractError(
1108
+ 'ai analyze-project apply --run latest cannot be combined with --yes.',
1109
+ 'Use an explicit run id for automation: `npx create-quiver ai analyze-project apply --run <run-id> --yes`.',
1110
+ );
1111
+ }
1112
+
1113
+ if (options.applyRun === true && String(options.runId || '').trim() === 'latest' && options.json === true) {
1114
+ throw analyzeProjectContractError(
1115
+ 'ai analyze-project apply --run latest cannot be combined with --json.',
1116
+ 'Use an explicit run id for JSON automation: `npx create-quiver ai analyze-project apply --run <run-id> --json`.',
1117
+ );
1118
+ }
1119
+ }
1120
+
495
1121
  function readTextFile(filePath, repoRoot) {
496
1122
  if (!filePath) {
497
1123
  return '';
@@ -609,7 +1235,8 @@ function shouldShowHumanProgress(ux, options = {}) {
609
1235
  return options.progress !== false
610
1236
  && options.dryRun !== true
611
1237
  && options.printPrompt !== true
612
- && ux?.mode?.decoration === true;
1238
+ && ux?.mode?.json !== true
1239
+ && (ux?.mode?.decoration === true || options.linearProgress === true);
613
1240
  }
614
1241
 
615
1242
  function plannerProgressTitle(action, runtimeProfile, options = {}) {
@@ -630,10 +1257,32 @@ function writeProgressChecks(ux, enabled, title, checks = []) {
630
1257
  }
631
1258
  }
632
1259
 
633
- async function runProviderWithProgress({ ux, enabled, message = 'Running agent...', successMessage = 'Agent finished', failureMessage = 'Agent failed', run }) {
1260
+ function writeProgressCheck(ux, enabled, check) {
1261
+ if (!enabled || !check) {
1262
+ return;
1263
+ }
1264
+ ux.check(check);
1265
+ }
1266
+
1267
+ function writeProgressInfo(ux, enabled, message) {
1268
+ if (!enabled || !message) {
1269
+ return;
1270
+ }
1271
+ ux.info(message);
1272
+ }
1273
+
1274
+ async function runProviderWithProgress({
1275
+ ux,
1276
+ enabled,
1277
+ message = 'Running agent...',
1278
+ successMessage = 'Agent finished',
1279
+ failureMessage = 'Agent failed',
1280
+ failOnProviderResult = true,
1281
+ run,
1282
+ }) {
634
1283
  async function runAndFailOnProviderResult() {
635
1284
  const result = await run();
636
- if (result && result.ok === false) {
1285
+ if (failOnProviderResult && result && result.ok === false) {
637
1286
  const error = new Error(result.error?.message || 'provider run failed');
638
1287
  error.code = result.error?.code || 'AI_PROVIDER_RUN_FAILED';
639
1288
  error.providerResult = result;
@@ -1909,6 +2558,33 @@ async function runPrepareContext(repoRoot, options = {}) {
1909
2558
  }
1910
2559
 
1911
2560
  async function runAnalyzeProject(repoRoot, options = {}) {
2561
+ assertAnalyzeProjectCommandContract(options);
2562
+ if (options.applyRun === true) {
2563
+ return applyAnalyzeProjectSavedProposalRun(repoRoot, options);
2564
+ }
2565
+ if (options.applyDocs === true && options.force !== true && !canUseAnalyzeProjectInteractiveSelector(options)) {
2566
+ throw analyzeProjectContractError(
2567
+ 'ai analyze-project --apply-docs requires an interactive TTY unless --yes is passed. No provider was run and no files were written.',
2568
+ 'Use `npx create-quiver ai analyze-project --deep --apply-docs --yes --provider <provider> --model <model>` for automation, or rerun from an interactive terminal.',
2569
+ );
2570
+ }
2571
+
2572
+ const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
2573
+ const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
2574
+ const provider = runtimeProfile.provider;
2575
+ const ux = createCommandUx(options);
2576
+ const showProgress = shouldShowHumanProgress(ux, { ...options, linearProgress: true });
2577
+ const progressTranslator = createTranslator(options.language);
2578
+ const progressTitle = plannerProgressTitle(
2579
+ progressTranslator.t('ai.planner.progress.analyze_project'),
2580
+ runtimeProfile,
2581
+ options,
2582
+ );
2583
+
2584
+ if (showProgress) {
2585
+ ux.heading(progressTitle);
2586
+ }
2587
+
1912
2588
  const report = buildAnalyzeProjectReport(repoRoot, options);
1913
2589
  if (options.dryRun === true) {
1914
2590
  if (options.json === true) {
@@ -1919,13 +2595,10 @@ async function runAnalyzeProject(repoRoot, options = {}) {
1919
2595
  return report;
1920
2596
  }
1921
2597
 
1922
- const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
1923
- const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
1924
- const provider = runtimeProfile.provider;
1925
2598
  const timeoutMs = normalizeTimeout(options.timeout);
1926
- const ux = createCommandUx(options);
1927
- const showProgress = shouldShowHumanProgress(ux, options);
1928
- const progressTranslator = createTranslator(options.language);
2599
+ writeProgressCheck(ux, showProgress, progressTranslator.t('ai.planner.progress.reading_base_docs'));
2600
+ writeProgressCheck(ux, showProgress, progressTranslator.t('ai.planner.progress.detecting_structure'));
2601
+ writeProgressCheck(ux, showProgress, progressTranslator.t('ai.planner.progress.selecting_sample'));
1929
2602
  const promptPackage = buildAnalyzeProjectPrompt({
1930
2603
  analysisPlan: report,
1931
2604
  repoRoot,
@@ -1935,17 +2608,8 @@ async function runAnalyzeProject(repoRoot, options = {}) {
1935
2608
  const prompt = promptPackage.prompt;
1936
2609
  const promptLimit = assertProviderPromptWithinLimit(prompt, options.promptLimitOptions || {});
1937
2610
  const privacyPreflight = promptPackage.privacyPreflight;
1938
-
1939
- writeProgressChecks(
1940
- ux,
1941
- showProgress,
1942
- plannerProgressTitle(progressTranslator.t('ai.planner.progress.analyze_project'), runtimeProfile, options),
1943
- [
1944
- progressTranslator.t('ai.planner.progress.reading_base_docs'),
1945
- progressTranslator.t('ai.planner.progress.detecting_structure'),
1946
- progressTranslator.t('ai.planner.progress.preparing_prompt'),
1947
- ],
1948
- );
2611
+ const auditRunId = options.runId || createAnalyzeProjectAuditRunId(options.now || new Date());
2612
+ writeProgressCheck(ux, showProgress, progressTranslator.t('ai.planner.progress.preparing_prompt'));
1949
2613
 
1950
2614
  if (!privacyPreflight.ok) {
1951
2615
  const error = new Error(formatError('ai analyze-project privacy preflight failed; provider execution was blocked before sending repository content.'));
@@ -1983,45 +2647,215 @@ async function runAnalyzeProject(repoRoot, options = {}) {
1983
2647
  };
1984
2648
  }
1985
2649
 
2650
+ const selectedContextManifest = writeSelectedContextManifest(
2651
+ repoRoot,
2652
+ buildSelectedContextManifest({
2653
+ analysisPlan: report,
2654
+ promptPackage,
2655
+ promptLimit,
2656
+ provider,
2657
+ runId: auditRunId,
2658
+ now: options.now,
2659
+ }),
2660
+ {
2661
+ runId: auditRunId,
2662
+ now: options.now,
2663
+ },
2664
+ );
2665
+
2666
+ const maxRetries = normalizeAnalyzeProjectMaxRetries(options.maxAnalyzeProjectRetries);
2667
+ const providerAttempts = [];
1986
2668
  let result;
1987
- try {
1988
- result = await runProviderWithProgress({
1989
- ux,
1990
- enabled: showProgress,
1991
- message: progressTranslator.t('ai.planner.progress.running_agent'),
1992
- successMessage: progressTranslator.t('ai.planner.progress.agent_finished'),
1993
- failureMessage: progressTranslator.t('ai.planner.progress.agent_failed'),
1994
- run: () => (options.runProviderFn || runProvider)(provider, {
1995
- prompt,
1996
- cwd: repoRoot,
1997
- timeoutMs,
1998
- dryRun: false,
1999
- probe: options.probe,
2000
- spawn: options.spawn,
2001
- tempRoot: options.tempRoot,
2002
- tempFileName: options.tempFileName,
2003
- tempFilePrefix: options.tempFilePrefix,
2004
- ...runtimeModelExecutionOptions(runtimeProfile, options),
2005
- enforceModelSelection: false,
2006
- }),
2007
- });
2008
- } catch (error) {
2009
- throw annotateProviderError(error, 'analyze-project');
2010
- }
2669
+ let clean;
2670
+ let parsed;
2671
+ let repairManifest = null;
2672
+ let retryManifest = null;
2673
+ let currentPrompt = prompt;
2674
+ const rawProviderArtifacts = [];
2011
2675
 
2012
- if (!result.ok) {
2013
- throw annotateProviderError(result.error || new Error('provider run failed'), 'analyze-project');
2014
- }
2676
+ for (let attemptIndex = 0; attemptIndex <= maxRetries; attemptIndex += 1) {
2677
+ try {
2678
+ result = await runProviderWithProgress({
2679
+ ux,
2680
+ enabled: showProgress,
2681
+ message: attemptIndex === 0
2682
+ ? progressTranslator.t('ai.planner.progress.running_agent')
2683
+ : `Retrying agent (${attemptIndex}/${maxRetries})...`,
2684
+ successMessage: progressTranslator.t('ai.planner.progress.agent_finished'),
2685
+ failureMessage: progressTranslator.t('ai.planner.progress.agent_failed'),
2686
+ failOnProviderResult: false,
2687
+ run: () => (options.runProviderFn || runProvider)(provider, {
2688
+ prompt: currentPrompt,
2689
+ cwd: repoRoot,
2690
+ timeoutMs,
2691
+ dryRun: false,
2692
+ probe: options.probe,
2693
+ spawn: options.spawn,
2694
+ tempRoot: options.tempRoot,
2695
+ tempFileName: options.tempFileName,
2696
+ tempFilePrefix: options.tempFilePrefix,
2697
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
2698
+ enforceModelSelection: false,
2699
+ }),
2700
+ });
2701
+ } catch (error) {
2702
+ throw annotateProviderError(error, 'analyze-project');
2703
+ }
2015
2704
 
2016
- const clean = extractCleanProviderOutput(result, { prompt, projectRoot: repoRoot });
2017
- let parsed;
2018
- try {
2019
- parsed = parseAnalyzeProjectOutput(clean.cleanOutput, {
2020
- selectedFiles: report.selected_files,
2021
- promptFiles: promptPackage.files,
2022
- });
2023
- } catch (error) {
2024
- throw enhanceAnalyzeProjectAnalysisError(error);
2705
+ rawProviderArtifacts.push(writeRawProviderArtifact(repoRoot, auditRunId, `analyze-project-provider-attempt-${attemptIndex + 1}`, result, {
2706
+ now: options.now,
2707
+ metadata: {
2708
+ attempt: attemptIndex + 1,
2709
+ retry: attemptIndex > 0,
2710
+ },
2711
+ }));
2712
+ writeProgressCheck(ux, showProgress, progressTranslator.t('ai.planner.progress.writing_artifacts'));
2713
+ if (!result.ok) {
2714
+ writeProgressInfo(ux, showProgress, progressTranslator.t('ai.planner.progress.agent_failed'));
2715
+ writeAnalyzeProjectRunStatus(repoRoot, auditRunId, 'failed', {
2716
+ now: options.now,
2717
+ provider,
2718
+ attempts: [{
2719
+ attempt: attemptIndex + 1,
2720
+ status: 'provider-failed',
2721
+ retry: attemptIndex > 0,
2722
+ }],
2723
+ artifacts: {
2724
+ selected_context: selectedContextManifest,
2725
+ raw_provider: rawProviderArtifacts.map((artifact) => artifact.path),
2726
+ },
2727
+ });
2728
+ throw annotateProviderError(result.error || new Error('provider run failed'), 'analyze-project');
2729
+ }
2730
+
2731
+ clean = extractCleanProviderOutput(result, { prompt: currentPrompt, projectRoot: repoRoot });
2732
+ try {
2733
+ parsed = parseAnalyzeProjectOutputWithRepair(clean.cleanOutput, {
2734
+ selectedFiles: report.selected_files,
2735
+ promptFiles: promptPackage.files,
2736
+ });
2737
+ writeProgressCheck(ux, showProgress, progressTranslator.t('ai.planner.progress.validating_schema'));
2738
+ if (parsed.repairManifest) {
2739
+ repairManifest = writeAnalyzeProjectRepairManifest(repoRoot, parsed.repairManifest, {
2740
+ runId: auditRunId,
2741
+ now: options.now,
2742
+ });
2743
+ writeProgressCheck(ux, showProgress, progressTranslator.t('ai.planner.progress.repairing_schema'));
2744
+ }
2745
+ providerAttempts.push({
2746
+ attempt: attemptIndex + 1,
2747
+ status: 'valid',
2748
+ retry: attemptIndex > 0,
2749
+ parse_source: parsed.parseSource,
2750
+ repaired: parsed.repaired === true,
2751
+ repair_manifest: repairManifest,
2752
+ });
2753
+ if (providerAttempts.length > 1) {
2754
+ retryManifest = writeAnalyzeProjectRetryManifest(repoRoot, {
2755
+ provider,
2756
+ command: 'ai analyze-project',
2757
+ runId: auditRunId,
2758
+ now: options.now,
2759
+ maxRetries,
2760
+ finalStatus: 'valid',
2761
+ attempts: providerAttempts,
2762
+ });
2763
+ }
2764
+ writeAnalyzeProjectRunStatus(repoRoot, auditRunId, 'completed', {
2765
+ now: options.now,
2766
+ provider,
2767
+ attempts: providerAttempts,
2768
+ artifacts: {
2769
+ selected_context: selectedContextManifest,
2770
+ raw_provider: rawProviderArtifacts.map((artifact) => artifact.path),
2771
+ repair: repairManifest,
2772
+ retry: retryManifest,
2773
+ },
2774
+ });
2775
+ break;
2776
+ } catch (error) {
2777
+ let attemptRepairManifest = null;
2778
+ if (error.repair_manifest) {
2779
+ try {
2780
+ attemptRepairManifest = writeAnalyzeProjectRepairManifest(repoRoot, error.repair_manifest, {
2781
+ runId: auditRunId,
2782
+ now: options.now,
2783
+ });
2784
+ } catch {
2785
+ attemptRepairManifest = null;
2786
+ }
2787
+ }
2788
+ const retryable = isAnalyzeProjectRetryableError(error);
2789
+ providerAttempts.push({
2790
+ attempt: attemptIndex + 1,
2791
+ status: 'invalid',
2792
+ retry: attemptIndex > 0,
2793
+ retryable,
2794
+ issue_count: Array.isArray(error.issues) ? error.issues.length : 0,
2795
+ groups: groupAnalyzeProjectIssues(error.issues, { maxExamplesPerGroup: 2 }),
2796
+ repair_manifest: attemptRepairManifest,
2797
+ });
2798
+
2799
+ if (retryable && attemptIndex < maxRetries) {
2800
+ writeProgressInfo(ux, showProgress, progressTranslator.t('ai.planner.progress.retrying_agent'));
2801
+ currentPrompt = buildAnalyzeProjectRetryPrompt({
2802
+ previousOutput: clean.cleanOutput,
2803
+ issueLines: formatAnalyzeProjectIssues(error.issues, 6),
2804
+ attempt: attemptIndex + 1,
2805
+ maxRetries,
2806
+ });
2807
+ continue;
2808
+ }
2809
+
2810
+ if (providerAttempts.length > 1) {
2811
+ retryManifest = writeAnalyzeProjectRetryManifest(repoRoot, {
2812
+ provider,
2813
+ command: 'ai analyze-project',
2814
+ runId: auditRunId,
2815
+ now: options.now,
2816
+ maxRetries,
2817
+ finalStatus: 'invalid',
2818
+ attempts: providerAttempts,
2819
+ });
2820
+ }
2821
+
2822
+ let validationManifest = null;
2823
+ try {
2824
+ validationManifest = writeAnalyzeProjectValidationManifest(repoRoot, {
2825
+ error,
2826
+ provider,
2827
+ command: 'ai analyze-project',
2828
+ runId: auditRunId,
2829
+ now: options.now,
2830
+ retry: {
2831
+ attempts: providerAttempts.length,
2832
+ max_retries: maxRetries,
2833
+ retryable,
2834
+ exhausted: retryable && attemptIndex >= maxRetries,
2835
+ manifest: retryManifest,
2836
+ },
2837
+ });
2838
+ } catch {
2839
+ validationManifest = null;
2840
+ }
2841
+ writeAnalyzeProjectRunStatus(repoRoot, auditRunId, 'failed', {
2842
+ now: options.now,
2843
+ provider,
2844
+ attempts: providerAttempts,
2845
+ artifacts: {
2846
+ selected_context: selectedContextManifest,
2847
+ raw_provider: rawProviderArtifacts.map((artifact) => artifact.path),
2848
+ repair: attemptRepairManifest,
2849
+ retry: retryManifest,
2850
+ validation: validationManifest,
2851
+ },
2852
+ });
2853
+ throw enhanceAnalyzeProjectAnalysisError(error, {
2854
+ validationManifest,
2855
+ repairManifest: attemptRepairManifest,
2856
+ retryManifest,
2857
+ });
2858
+ }
2025
2859
  }
2026
2860
  const completedReport = {
2027
2861
  ...report,
@@ -2040,82 +2874,101 @@ async function runAnalyzeProject(repoRoot, options = {}) {
2040
2874
  parse_source: parsed.parseSource,
2041
2875
  warnings: parsed.warnings,
2042
2876
  doc_update_paths: parsed.docUpdatePaths,
2877
+ repaired: parsed.repaired === true,
2878
+ repair_manifest: repairManifest,
2879
+ retry_count: Math.max(0, providerAttempts.length - 1),
2880
+ retry_manifest: retryManifest,
2043
2881
  },
2044
2882
  provider_artifact: buildAnalyzeProjectProviderArtifact(result, clean, repoRoot),
2883
+ raw_provider_artifacts: rawProviderArtifacts.map((artifact) => artifact.path),
2884
+ provider_attempts: providerAttempts,
2885
+ run_id: auditRunId,
2886
+ run_status_path: path.join('.quiver', 'runs', auditRunId, 'status.json').split(path.sep).join('/'),
2887
+ selected_context_manifest: selectedContextManifest,
2888
+ };
2889
+ const needsDocProposal = options.applyDocs === true || options.saveProposal === true || options.review === true;
2890
+ const docProposal = needsDocProposal ? buildAnalyzeProjectDocProposal(parsed.analysis) : null;
2891
+ const docWritePlan = docProposal ? buildAnalyzeProjectWritePlan(repoRoot, docProposal) : [];
2892
+ const docActionContext = {
2893
+ auditRunId,
2894
+ commandOptions: options,
2895
+ completedReport,
2896
+ proposal: docProposal,
2897
+ writePlan: docWritePlan,
2898
+ provider,
2899
+ providerAttempts,
2900
+ rawProviderArtifacts,
2901
+ selectedContextManifest,
2902
+ repairManifest,
2903
+ retryManifest,
2045
2904
  };
2046
2905
 
2047
- if (options.review === true) {
2048
- const initialProposal = buildAnalyzeProjectDocProposal(parsed.analysis);
2049
- const reviewed = await reviewAnalyzeProjectDocProposal(repoRoot, initialProposal, options);
2050
- const writePlan = buildAnalyzeProjectWritePlan(repoRoot, reviewed.proposal);
2051
- process.stdout.write(formatAnalyzeProjectReviewPlan({
2052
- writePlan,
2053
- reviewPath: reviewed.reviewPath,
2054
- }));
2055
- await confirmAnalyzeProjectWrites(writePlan, options);
2056
- const lifecycleRun = ensureAiRun(repoRoot, {
2057
- command: 'ai analyze-project',
2058
- input: reviewed.reviewPath,
2059
- runId: options.runId,
2060
- phase: 'created',
2061
- });
2062
- const snapshot = createAnalyzeProjectSnapshot(repoRoot, lifecycleRun, writePlan, {
2063
- providerArtifact: completedReport.provider_artifact,
2064
- proposal: reviewed.proposal,
2065
- now: options.now || new Date(),
2906
+ if (options.applyDocs === true && options.force === true) {
2907
+ const applyReport = applyAnalyzeProjectDocProposalReport(repoRoot, {
2908
+ ...docActionContext,
2909
+ allowDirtyDocs: options.allowDirtyDocs === true,
2066
2910
  });
2067
- const writtenDocs = writeAnalyzeProjectDocs(writePlan);
2068
- let writeReport = {
2069
- ...completedReport,
2070
- review: true,
2071
- review_path: reviewed.reviewPath,
2072
- doc_proposal: reviewed.proposal,
2073
- write_plan: writePlan.map((item) => ({
2074
- path: item.path,
2075
- action: item.action,
2076
- dirty: item.dirty,
2077
- before_sha256: item.before_sha256,
2078
- after_sha256: item.after_sha256,
2079
- reason: item.reason,
2080
- })),
2081
- snapshot,
2082
- written_docs: writtenDocs,
2083
- run_id: lifecycleRun.run_id,
2084
- };
2085
- const postWriteValidation = validateAnalyzeProjectPostWrite(repoRoot, writeReport, {
2086
- strict: options.strict === true,
2911
+ return emitAnalyzeProjectApplyReport(applyReport, options);
2912
+ }
2913
+
2914
+ if (options.applyDocs === true) {
2915
+ return runAnalyzeProjectInteractiveApplySelector({
2916
+ report: completedReport,
2917
+ proposal: docProposal,
2918
+ writePlan: docWritePlan,
2919
+ options,
2920
+ actions: {
2921
+ apply: ({ allowDirtyDocs, interactiveAction }) => {
2922
+ const applyReport = applyAnalyzeProjectDocProposalReport(repoRoot, {
2923
+ ...docActionContext,
2924
+ allowDirtyDocs,
2925
+ interactiveAction,
2926
+ });
2927
+ return emitAnalyzeProjectApplyReport(applyReport, options);
2928
+ },
2929
+ save: ({ interactiveAction, silent, savedReport } = {}) => {
2930
+ const saveReport = savedReport || saveAnalyzeProjectDocProposalReport(repoRoot, {
2931
+ ...docActionContext,
2932
+ interactiveAction,
2933
+ });
2934
+ if (silent !== true) {
2935
+ process.stdout.write(formatAnalyzeProjectSavedProposalReport(saveReport));
2936
+ }
2937
+ return saveReport;
2938
+ },
2939
+ edit: ({ interactiveAction } = {}) => reviewAndWriteAnalyzeProjectDocs(repoRoot, {
2940
+ commandOptions: options,
2941
+ completedReport,
2942
+ initialProposal: docProposal,
2943
+ interactiveAction,
2944
+ }),
2945
+ cancel: ({ interactiveAction, savedReport } = {}) => cancelAnalyzeProjectInteractiveApplyReport(repoRoot, {
2946
+ ...docActionContext,
2947
+ interactiveAction,
2948
+ savedReport,
2949
+ }),
2950
+ },
2087
2951
  });
2088
- writeReport = {
2089
- ...writeReport,
2090
- post_write_validation: postWriteValidation,
2091
- };
2952
+ }
2953
+
2954
+ if (options.saveProposal === true) {
2955
+ const saveReport = saveAnalyzeProjectDocProposalReport(repoRoot, docActionContext);
2092
2956
 
2093
2957
  if (options.json === true) {
2094
- process.stdout.write(`${JSON.stringify(writeReport, null, 2)}\n`);
2095
- if (!postWriteValidation.ok) {
2096
- const error = new Error(formatError('ai analyze-project post-write validation failed.'));
2097
- error.code = 'AI_ANALYZE_PROJECT_POST_WRITE_VALIDATION_FAILED';
2098
- error.validation = postWriteValidation;
2099
- throw error;
2100
- }
2101
- return writeReport;
2958
+ process.stdout.write(`${JSON.stringify(saveReport, null, 2)}\n`);
2959
+ return saveReport;
2102
2960
  }
2103
2961
 
2104
- process.stdout.write(formatAnalyzeProjectReviewPlan({
2105
- writePlan,
2106
- reviewPath: reviewed.reviewPath,
2107
- snapshot,
2108
- writtenDocs,
2109
- validation: postWriteValidation,
2110
- completed: true,
2111
- }));
2112
- if (!postWriteValidation.ok) {
2113
- const error = new Error(formatError('ai analyze-project post-write validation failed.'));
2114
- error.code = 'AI_ANALYZE_PROJECT_POST_WRITE_VALIDATION_FAILED';
2115
- error.validation = postWriteValidation;
2116
- throw error;
2117
- }
2118
- return writeReport;
2962
+ process.stdout.write(formatAnalyzeProjectSavedProposalReport(saveReport));
2963
+ return saveReport;
2964
+ }
2965
+
2966
+ if (options.review === true) {
2967
+ return reviewAndWriteAnalyzeProjectDocs(repoRoot, {
2968
+ commandOptions: options,
2969
+ completedReport,
2970
+ initialProposal: docProposal,
2971
+ });
2119
2972
  }
2120
2973
 
2121
2974
  if (options.json === true) {