create-quiver 0.17.2 → 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 (44) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/docs/CLI_UX_GUIDE.md +13 -3
  3. package/docs/TROUBLESHOOTING.md +43 -2
  4. package/docs/reference/commands.md +31 -9
  5. package/docs/workflows/existing-project.md +18 -3
  6. package/package.json +1 -1
  7. package/specs/quiver-v43-cli-i18n-audit-release-readiness/command-language-mode-matrix.json +4 -0
  8. package/specs/quiver-v55-analyze-project-doc-apply-ux/EVIDENCE_REPORT.md +209 -0
  9. package/specs/quiver-v55-analyze-project-doc-apply-ux/EXECUTION_PLAN.md +49 -0
  10. package/specs/quiver-v55-analyze-project-doc-apply-ux/SPEC.md +276 -0
  11. package/specs/quiver-v55-analyze-project-doc-apply-ux/STATUS.md +29 -0
  12. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/CLOSURE_BRIEF.md +47 -0
  13. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/EXECUTION_BRIEF.md +67 -0
  14. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/pr.md +129 -0
  15. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/slice.json +82 -0
  16. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/CLOSURE_BRIEF.md +39 -0
  17. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/EXECUTION_BRIEF.md +61 -0
  18. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/pr.md +116 -0
  19. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/slice.json +69 -0
  20. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/CLOSURE_BRIEF.md +36 -0
  21. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/EXECUTION_BRIEF.md +62 -0
  22. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/pr.md +132 -0
  23. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/slice.json +76 -0
  24. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/CLOSURE_BRIEF.md +47 -0
  25. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/EXECUTION_BRIEF.md +70 -0
  26. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/pr.md +141 -0
  27. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/slice.json +72 -0
  28. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/CLOSURE_BRIEF.md +42 -0
  29. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/EXECUTION_BRIEF.md +61 -0
  30. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/pr.md +129 -0
  31. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/slice.json +67 -0
  32. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/CLOSURE_BRIEF.md +38 -0
  33. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/EXECUTION_BRIEF.md +64 -0
  34. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/pr.md +123 -0
  35. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/slice.json +76 -0
  36. package/src/create-quiver/commands/ai.js +636 -66
  37. package/src/create-quiver/index.js +80 -1
  38. package/src/create-quiver/lib/ai/analyze-project-apply.js +269 -0
  39. package/src/create-quiver/lib/ai/analyze-project-interactive.js +241 -0
  40. package/src/create-quiver/lib/ai/analyze-project-proposal.js +582 -0
  41. package/src/create-quiver/lib/ai/analyze-project-validation.js +10 -8
  42. package/src/create-quiver/lib/cli/ux-flags.js +6 -0
  43. package/src/create-quiver/lib/i18n/messages/en.js +28 -0
  44. package/src/create-quiver/lib/i18n/messages/es.js +29 -0
@@ -36,6 +36,18 @@ const {
36
36
  confirmAnalyzeProjectWrites,
37
37
  reviewAnalyzeProjectDocProposal,
38
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');
39
51
  const {
40
52
  DEFAULT_MAX_BYTES: DEFAULT_ANALYZE_MAX_BYTES,
41
53
  DEFAULT_MAX_FILES: DEFAULT_ANALYZE_MAX_FILES,
@@ -501,12 +513,46 @@ function formatAnalyzeProjectLiveReport(report) {
501
513
  }
502
514
 
503
515
  lines.push('', 'Next commands:');
516
+ lines.push('- npx create-quiver ai analyze-project --deep --save-proposal --provider <provider> --model <model>');
504
517
  lines.push('- npx create-quiver ai analyze-project --deep --review');
505
518
  lines.push('- npx create-quiver ai analyze-project --deep --json');
506
519
 
507
520
  return `${lines.join('\n')}\n`;
508
521
  }
509
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
+
510
556
  function formatAnalyzeProjectPostWriteValidation(validation) {
511
557
  if (!validation) {
512
558
  return [];
@@ -553,6 +599,430 @@ function formatAnalyzeProjectReviewPlan({ writePlan, reviewPath, snapshot, writt
553
599
  return `${lines.join('\n')}\n`;
554
600
  }
555
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
+
556
1026
  function formatLocalizedActionableError({ failure, impact, fix, nextCommand } = {}, options = {}) {
557
1027
  const translator = createTranslator(options.language);
558
1028
  const lines = [`create-quiver: ${String(failure || 'operation failed').trim()}`];
@@ -570,6 +1040,84 @@ function formatLocalizedActionableError({ failure, impact, fix, nextCommand } =
570
1040
  return lines.join('\n');
571
1041
  }
572
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
+
573
1121
  function readTextFile(filePath, repoRoot) {
574
1122
  if (!filePath) {
575
1123
  return '';
@@ -2010,6 +2558,17 @@ async function runPrepareContext(repoRoot, options = {}) {
2010
2558
  }
2011
2559
 
2012
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
+
2013
2572
  const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
2014
2573
  const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
2015
2574
  const provider = runtimeProfile.provider;
@@ -2323,82 +2882,93 @@ async function runAnalyzeProject(repoRoot, options = {}) {
2323
2882
  provider_artifact: buildAnalyzeProjectProviderArtifact(result, clean, repoRoot),
2324
2883
  raw_provider_artifacts: rawProviderArtifacts.map((artifact) => artifact.path),
2325
2884
  provider_attempts: providerAttempts,
2885
+ run_id: auditRunId,
2326
2886
  run_status_path: path.join('.quiver', 'runs', auditRunId, 'status.json').split(path.sep).join('/'),
2327
2887
  selected_context_manifest: selectedContextManifest,
2328
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,
2904
+ };
2329
2905
 
2330
- if (options.review === true) {
2331
- const initialProposal = buildAnalyzeProjectDocProposal(parsed.analysis);
2332
- const reviewed = await reviewAnalyzeProjectDocProposal(repoRoot, initialProposal, options);
2333
- const writePlan = buildAnalyzeProjectWritePlan(repoRoot, reviewed.proposal);
2334
- process.stdout.write(formatAnalyzeProjectReviewPlan({
2335
- writePlan,
2336
- reviewPath: reviewed.reviewPath,
2337
- }));
2338
- await confirmAnalyzeProjectWrites(writePlan, options);
2339
- const lifecycleRun = ensureAiRun(repoRoot, {
2340
- command: 'ai analyze-project',
2341
- input: reviewed.reviewPath,
2342
- runId: options.runId,
2343
- phase: 'created',
2344
- });
2345
- const snapshot = createAnalyzeProjectSnapshot(repoRoot, lifecycleRun, writePlan, {
2346
- providerArtifact: completedReport.provider_artifact,
2347
- proposal: reviewed.proposal,
2348
- 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,
2349
2910
  });
2350
- const writtenDocs = writeAnalyzeProjectDocs(writePlan);
2351
- let writeReport = {
2352
- ...completedReport,
2353
- review: true,
2354
- review_path: reviewed.reviewPath,
2355
- doc_proposal: reviewed.proposal,
2356
- write_plan: writePlan.map((item) => ({
2357
- path: item.path,
2358
- action: item.action,
2359
- dirty: item.dirty,
2360
- before_sha256: item.before_sha256,
2361
- after_sha256: item.after_sha256,
2362
- reason: item.reason,
2363
- })),
2364
- snapshot,
2365
- written_docs: writtenDocs,
2366
- run_id: lifecycleRun.run_id,
2367
- };
2368
- const postWriteValidation = validateAnalyzeProjectPostWrite(repoRoot, writeReport, {
2369
- 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
+ },
2370
2951
  });
2371
- writeReport = {
2372
- ...writeReport,
2373
- post_write_validation: postWriteValidation,
2374
- };
2952
+ }
2953
+
2954
+ if (options.saveProposal === true) {
2955
+ const saveReport = saveAnalyzeProjectDocProposalReport(repoRoot, docActionContext);
2375
2956
 
2376
2957
  if (options.json === true) {
2377
- process.stdout.write(`${JSON.stringify(writeReport, null, 2)}\n`);
2378
- if (!postWriteValidation.ok) {
2379
- const error = new Error(formatError('ai analyze-project post-write validation failed.'));
2380
- error.code = 'AI_ANALYZE_PROJECT_POST_WRITE_VALIDATION_FAILED';
2381
- error.validation = postWriteValidation;
2382
- throw error;
2383
- }
2384
- return writeReport;
2958
+ process.stdout.write(`${JSON.stringify(saveReport, null, 2)}\n`);
2959
+ return saveReport;
2385
2960
  }
2386
2961
 
2387
- process.stdout.write(formatAnalyzeProjectReviewPlan({
2388
- writePlan,
2389
- reviewPath: reviewed.reviewPath,
2390
- snapshot,
2391
- writtenDocs,
2392
- validation: postWriteValidation,
2393
- completed: true,
2394
- }));
2395
- if (!postWriteValidation.ok) {
2396
- const error = new Error(formatError('ai analyze-project post-write validation failed.'));
2397
- error.code = 'AI_ANALYZE_PROJECT_POST_WRITE_VALIDATION_FAILED';
2398
- error.validation = postWriteValidation;
2399
- throw error;
2400
- }
2401
- 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
+ });
2402
2972
  }
2403
2973
 
2404
2974
  if (options.json === true) {