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
@@ -230,7 +230,7 @@ const COMMAND_HELP_GROUPS = [
230
230
  ['ai active-slice status|reconcile', 'Inspect or dry-run reconcile local active-slice state from every supported source.'],
231
231
  ['ai status', 'Show current AI lifecycle phase, approved versions, blockers, and next command.'],
232
232
  ['ai resume', 'Resume guidance from the last valid lifecycle phase without chat memory.'],
233
- ['ai analyze-project', 'Read and explain a bounded project sample without provider execution or writes.'],
233
+ ['ai analyze-project', 'Analyze a bounded project sample and generate audited documentation proposals.'],
234
234
  ['ai onboard', 'Run or print the planner onboarding prompt with a token-aware context pack.'],
235
235
  ['ai prepare-context', 'Preview or write docs-only AI context updates with assumptions and risks.'],
236
236
  ['ai agent set|list|show|doctor|repair', 'Manage, diagnose, and dry-run repair planner, executor, reviewer, and doctor provider profiles without secrets.'],
@@ -330,6 +330,7 @@ function printUsage(language = DEFAULT_LANGUAGE) {
330
330
  npx create-quiver ai status [options]
331
331
  npx create-quiver ai resume [options]
332
332
  npx create-quiver ai analyze-project [--deep] [--dry-run] [--json]
333
+ npx create-quiver ai analyze-project apply --run <run-id>
333
334
  npx create-quiver ai inspect [options]
334
335
  npx create-quiver ai export [--format json|markdown]
335
336
  npx create-quiver ai specs list [--json]
@@ -396,6 +397,10 @@ ${helpText(help, 'headings', 'options', 'Options:')}
396
397
  --include-source ${optionDescription(help, 'For ai analyze-project, include source files without requiring --deep')}
397
398
  --include-tests ${optionDescription(help, 'For ai analyze-project, include test files in the sample')}
398
399
  --include-db ${optionDescription(help, 'For ai analyze-project, include schema, migration, and DB files in the sample')}
400
+ --apply-docs ${optionDescription(help, 'For ai analyze-project, apply validated documentation proposals through the safe docs workflow')}
401
+ --save-proposal ${optionDescription(help, 'For ai analyze-project, save a validated documentation proposal without writing final docs')}
402
+ --diff ${optionDescription(help, 'For ai analyze-project, show or save the proposed documentation diff')}
403
+ --allow-dirty-docs ${optionDescription(help, 'For ai analyze-project, allow dirty target docs checks where the safe apply workflow supports it')}
399
404
  --scope <path|name> ${optionDescription(help, 'For ai analyze-project, restrict discovery to a repo path or workspace name')}
400
405
  --print-prompt ${optionDescription(help, 'Print the exact AI prompt and exit without executing provider CLIs')}
401
406
  --with-planner ${optionDescription(help, 'Enable planner-assisted behavior on commands that explicitly support it')}
@@ -579,6 +584,10 @@ function parseArgs(argv, options = {}) {
579
584
  aiAnalyzeIncludeDb: false,
580
585
  aiAnalyzeIncludeSource: false,
581
586
  aiAnalyzeIncludeTests: false,
587
+ aiAnalyzeApplyDocs: false,
588
+ aiAnalyzeSaveProposal: false,
589
+ aiAnalyzeDiff: false,
590
+ aiAnalyzeAllowDirtyDocs: false,
582
591
  aiAnalyzeMaxBytes: null,
583
592
  aiAnalyzeMaxFiles: null,
584
593
  aiAnalyzeScope: '',
@@ -763,6 +772,30 @@ function parseArgs(argv, options = {}) {
763
772
  continue;
764
773
  }
765
774
 
775
+ if (arg === '--apply-docs') {
776
+ result.aiAnalyzeApplyDocs = true;
777
+ result.aiAnalyzeOptionUsed = true;
778
+ continue;
779
+ }
780
+
781
+ if (arg === '--save-proposal') {
782
+ result.aiAnalyzeSaveProposal = true;
783
+ result.aiAnalyzeOptionUsed = true;
784
+ continue;
785
+ }
786
+
787
+ if (arg === '--diff') {
788
+ result.aiAnalyzeDiff = true;
789
+ result.aiAnalyzeOptionUsed = true;
790
+ continue;
791
+ }
792
+
793
+ if (arg === '--allow-dirty-docs') {
794
+ result.aiAnalyzeAllowDirtyDocs = true;
795
+ result.aiAnalyzeOptionUsed = true;
796
+ continue;
797
+ }
798
+
766
799
  if (arg === '--max-files') {
767
800
  const value = args[++index];
768
801
  if (typeof value === 'undefined') {
@@ -1332,6 +1365,15 @@ function parseArgs(argv, options = {}) {
1332
1365
  if (result.aiCommand === 'run' && !result.aiRunCommand && positional.length > 0) {
1333
1366
  result.aiRunCommand = positional.shift();
1334
1367
  }
1368
+ if (result.aiCommand === 'analyze-project' && !result.aiSecondaryCommand && positional.length > 0) {
1369
+ result.aiSecondaryCommand = positional.shift();
1370
+ }
1371
+ if (result.aiCommand === 'analyze-project' && result.aiSecondaryCommand && result.aiSecondaryCommand !== 'apply') {
1372
+ throw new Error(formatError(`unsupported ai analyze-project subcommand: ${result.aiSecondaryCommand}. Supported tasks: apply`));
1373
+ }
1374
+ if (result.aiCommand === 'analyze-project' && result.aiSecondaryCommand === 'apply' && !result.aiRunId) {
1375
+ throw new Error(formatError('ai analyze-project apply requires --run <run-id>'));
1376
+ }
1335
1377
  if ((result.aiCommand === 'specs' || result.aiCommand === 'slices' || result.aiCommand === 'models' || result.aiCommand === 'trace' || result.aiCommand === 'active-slice') && !result.aiSecondaryCommand && positional.length > 0) {
1336
1378
  result.aiSecondaryCommand = positional.shift();
1337
1379
  }
@@ -1470,6 +1512,33 @@ function parseArgs(argv, options = {}) {
1470
1512
  throw new Error(formatError('--global is only supported by config language set. Use: npx create-quiver config language set <en|es> --global'));
1471
1513
  }
1472
1514
 
1515
+ if (result.mode === 'ai' && result.aiCommand === 'analyze-project') {
1516
+ if (result.review && result.aiAnalyzeApplyDocs) {
1517
+ throw new Error(formatError('ai analyze-project --apply-docs cannot be combined with --review'));
1518
+ }
1519
+ if (result.dryRun && result.aiAnalyzeApplyDocs) {
1520
+ throw new Error(formatError('ai analyze-project --dry-run cannot be combined with --apply-docs'));
1521
+ }
1522
+ if (result.dryRun && result.aiAnalyzeSaveProposal) {
1523
+ throw new Error(formatError('ai analyze-project --dry-run cannot be combined with --save-proposal'));
1524
+ }
1525
+ if (result.dryRun && result.aiSecondaryCommand === 'apply') {
1526
+ throw new Error(formatError('ai analyze-project apply --run cannot be combined with --dry-run'));
1527
+ }
1528
+ if (result.aiSecondaryCommand === 'apply' && (result.aiAnalyzeApplyDocs || result.aiAnalyzeSaveProposal || result.review)) {
1529
+ throw new Error(formatError('ai analyze-project apply --run cannot be combined with --apply-docs, --save-proposal, or --review'));
1530
+ }
1531
+ if (result.aiSecondaryCommand === 'apply' && result.aiRunId === 'latest' && result.force) {
1532
+ throw new Error(formatError('ai analyze-project apply --run latest cannot be combined with --yes'));
1533
+ }
1534
+ if (result.aiSecondaryCommand === 'apply' && result.aiRunId === 'latest' && result.json) {
1535
+ throw new Error(formatError('ai analyze-project apply --run latest cannot be combined with --json'));
1536
+ }
1537
+ if (result.json && result.aiAnalyzeApplyDocs && !result.force) {
1538
+ throw new Error(formatError('ai analyze-project --json with --apply-docs requires --yes'));
1539
+ }
1540
+ }
1541
+
1473
1542
  return result;
1474
1543
  }
1475
1544
 
@@ -2862,6 +2931,9 @@ function buildDoctorCommandReport(projectRoot) {
2862
2931
 
2863
2932
  const suggestedFixes = [
2864
2933
  ...doctorReport.recommendations,
2934
+ doctorReport.warnings.some((warning) => String(warning).includes('AGENTS.md'))
2935
+ ? 'Repair AGENTS.md contract: npx create-quiver doctor --fix --dry-run, then npx create-quiver doctor --fix'
2936
+ : '',
2865
2937
  !hasQuiverState
2866
2938
  ? 'Run migration first: npx create-quiver migrate'
2867
2939
  : !hasScanArtifacts
@@ -3516,6 +3588,10 @@ async function run(argv) {
3516
3588
  await runAiAnalyzeProject(process.cwd(), {
3517
3589
  deep: args.aiAnalyzeDeep,
3518
3590
  dryRun: args.dryRun,
3591
+ allowDirtyDocs: args.aiAnalyzeAllowDirtyDocs,
3592
+ applyDocs: args.aiAnalyzeApplyDocs,
3593
+ applyRun: args.aiSecondaryCommand === 'apply',
3594
+ diff: args.aiAnalyzeDiff,
3519
3595
  force: args.force,
3520
3596
  includeDb: args.aiAnalyzeIncludeDb,
3521
3597
  includeSource: args.aiAnalyzeIncludeSource,
@@ -3525,10 +3601,16 @@ async function run(argv) {
3525
3601
  language: args.language,
3526
3602
  maxBytes: args.aiAnalyzeMaxBytes || undefined,
3527
3603
  maxFiles: args.aiAnalyzeMaxFiles || undefined,
3604
+ model: args.aiModel || undefined,
3528
3605
  printPrompt: args.aiPrintPrompt,
3606
+ provider: args.aiProvider || undefined,
3607
+ providerExplicit: args.aiProviderExplicit,
3529
3608
  review: args.review,
3609
+ runId: args.aiRunId || undefined,
3610
+ saveProposal: args.aiAnalyzeSaveProposal,
3530
3611
  scope: args.aiAnalyzeScope || undefined,
3531
3612
  strict: args.strict,
3613
+ timeout: args.aiTimeout || undefined,
3532
3614
  });
3533
3615
  return;
3534
3616
  }
@@ -0,0 +1,269 @@
1
+ const crypto = require('node:crypto');
2
+ const fs = require('node:fs');
3
+
4
+ const {
5
+ buildAnalyzeProjectWritePlan,
6
+ createAnalyzeProjectSnapshot,
7
+ writeAnalyzeProjectDocs,
8
+ } = require('./analyze-project-docs');
9
+ const {
10
+ buildAnalyzeProjectProposalArtifactPaths,
11
+ normalizeAnalyzeProjectProposalManifest,
12
+ writeAnalyzeProjectProposalArtifacts,
13
+ writeAnalyzeProjectWriteManifest,
14
+ } = require('./analyze-project-proposal');
15
+ const { validateAnalyzeProjectPostWrite } = require('./analyze-project-validation');
16
+
17
+ function formatError(message) {
18
+ return `create-quiver: ${message}`;
19
+ }
20
+
21
+ class AnalyzeProjectApplyError extends Error {
22
+ constructor(message, issues = []) {
23
+ super(formatError(message));
24
+ this.name = 'AnalyzeProjectApplyError';
25
+ this.code = 'AI_ANALYZE_PROJECT_APPLY_BLOCKED';
26
+ this.issues = issues;
27
+ }
28
+ }
29
+
30
+ function sha256(contents) {
31
+ return crypto.createHash('sha256').update(String(contents || ''), 'utf8').digest('hex');
32
+ }
33
+
34
+ function readCurrentHash(item) {
35
+ if (!fs.existsSync(item.destinationPath)) {
36
+ return null;
37
+ }
38
+ return sha256(fs.readFileSync(item.destinationPath, 'utf8'));
39
+ }
40
+
41
+ function collectWrittenDocs(writePlan) {
42
+ const written = [];
43
+ for (const item of writePlan) {
44
+ if (item.action === 'skip') {
45
+ continue;
46
+ }
47
+ if (readCurrentHash(item) === item.after_sha256) {
48
+ written.push(item.path);
49
+ }
50
+ }
51
+ return written;
52
+ }
53
+
54
+ function assertAnalyzeProjectApplyPreflight(writePlan, proposalManifest, options = {}) {
55
+ const issues = [];
56
+ const beforeHashes = proposalManifest?.doc_before_hashes || {};
57
+
58
+ for (const item of writePlan) {
59
+ if (item.action === 'skip') {
60
+ continue;
61
+ }
62
+ if (item.dirty === true && options.allowDirtyDocs !== true) {
63
+ issues.push({
64
+ path: item.path,
65
+ issue: 'dirty-target-doc',
66
+ message: 'target documentation already has human content; --yes requires --allow-dirty-docs or interactive review',
67
+ });
68
+ }
69
+
70
+ if (!Object.prototype.hasOwnProperty.call(beforeHashes, item.path)) {
71
+ issues.push({
72
+ path: item.path,
73
+ issue: 'missing-before-hash',
74
+ message: 'proposal manifest is missing the current doc hash for this target path',
75
+ });
76
+ continue;
77
+ }
78
+
79
+ const currentHash = readCurrentHash(item);
80
+ const expectedHash = beforeHashes[item.path] || null;
81
+ if (currentHash !== expectedHash) {
82
+ issues.push({
83
+ path: item.path,
84
+ issue: 'stale-target-doc',
85
+ message: 'target documentation changed after the proposal was built',
86
+ expected_sha256: expectedHash,
87
+ current_sha256: currentHash,
88
+ });
89
+ }
90
+ }
91
+
92
+ if (issues.length > 0) {
93
+ throw new AnalyzeProjectApplyError(
94
+ 'ai analyze-project --apply-docs --yes blocked before writing final docs',
95
+ issues,
96
+ );
97
+ }
98
+
99
+ return { ok: true, issues: [] };
100
+ }
101
+
102
+ function validationFromWriteError(error, options = {}) {
103
+ return {
104
+ ok: false,
105
+ strict: options.strict === true,
106
+ errors: [{
107
+ path: null,
108
+ issue: 'write-failed',
109
+ message: error?.message || 'analyze-project doc write failed',
110
+ }],
111
+ warnings: [],
112
+ };
113
+ }
114
+
115
+ function normalizeLoadedProposalArtifacts(runId, proposalArtifacts = {}, proposalManifest = null) {
116
+ const paths = buildAnalyzeProjectProposalArtifactPaths(runId);
117
+ const manifestData = normalizeAnalyzeProjectProposalManifest(proposalManifest || proposalArtifacts.manifest_data || {});
118
+ return {
119
+ root: proposalArtifacts.root || paths.root,
120
+ proposal_json: proposalArtifacts.proposal_json || manifestData.proposal_json,
121
+ proposal_markdown: proposalArtifacts.proposal_markdown || manifestData.proposal_markdown,
122
+ proposal_diff: proposalArtifacts.proposal_diff || manifestData.proposal_diff,
123
+ manifest: proposalArtifacts.manifest || paths.manifest,
124
+ proposal_sha256: proposalArtifacts.proposal_sha256 || manifestData.proposal_sha256,
125
+ doc_paths: proposalArtifacts.doc_paths || manifestData.doc_paths || [],
126
+ changed_docs: proposalArtifacts.changed_docs || manifestData.doc_paths || [],
127
+ manifest_data: manifestData,
128
+ files: Array.isArray(proposalArtifacts.files)
129
+ ? proposalArtifacts.files
130
+ : [
131
+ manifestData.proposal_json,
132
+ manifestData.proposal_markdown,
133
+ manifestData.proposal_diff,
134
+ paths.manifest,
135
+ ],
136
+ };
137
+ }
138
+
139
+ function applyAnalyzeProjectDocProposal(repoRoot, options = {}) {
140
+ const runId = String(options.runId || '').trim();
141
+ const proposal = options.proposal;
142
+ const now = options.now instanceof Date ? options.now : new Date(options.now || Date.now());
143
+ const writePlan = buildAnalyzeProjectWritePlan(repoRoot, proposal);
144
+ const applyEvents = Array.isArray(options.events) ? options.events : [];
145
+ const proposalArtifacts = options.proposalArtifacts
146
+ ? normalizeLoadedProposalArtifacts(runId, options.proposalArtifacts, options.proposalManifest)
147
+ : writeAnalyzeProjectProposalArtifacts(repoRoot, {
148
+ runId,
149
+ now,
150
+ provider: options.provider,
151
+ language: options.language,
152
+ proposal,
153
+ writePlan,
154
+ selectedContextManifest: options.selectedContextManifest,
155
+ repairManifest: options.repairManifest,
156
+ events: [{ type: 'proposal-saved-before-apply', at: now.toISOString() }],
157
+ });
158
+
159
+ try {
160
+ assertAnalyzeProjectApplyPreflight(writePlan, proposalArtifacts.manifest_data, {
161
+ allowDirtyDocs: options.allowDirtyDocs === true,
162
+ });
163
+ } catch (error) {
164
+ error.proposal_artifacts = proposalArtifacts;
165
+ error.write_plan = writePlan;
166
+ throw error;
167
+ }
168
+
169
+ const snapshot = createAnalyzeProjectSnapshot(repoRoot, { run_id: runId }, writePlan, {
170
+ providerArtifact: options.providerArtifact,
171
+ proposal,
172
+ now,
173
+ });
174
+ const plannedManifest = writeAnalyzeProjectWriteManifest(repoRoot, {
175
+ runId,
176
+ now,
177
+ proposalManifest: proposalArtifacts.manifest,
178
+ snapshot,
179
+ writePlan,
180
+ strict: options.strict === true,
181
+ status: 'planned',
182
+ events: [
183
+ ...applyEvents,
184
+ { type: 'write-planned', at: now.toISOString() },
185
+ ],
186
+ });
187
+
188
+ let writtenDocs = [];
189
+ try {
190
+ writtenDocs = writeAnalyzeProjectDocs(writePlan);
191
+ } catch (error) {
192
+ const partialDocs = collectWrittenDocs(writePlan);
193
+ const failedManifest = writeAnalyzeProjectWriteManifest(repoRoot, {
194
+ runId,
195
+ now: new Date(),
196
+ proposalManifest: proposalArtifacts.manifest,
197
+ snapshot,
198
+ writePlan,
199
+ writtenDocs: partialDocs,
200
+ validation: validationFromWriteError(error, { strict: options.strict === true }),
201
+ partialWrite: partialDocs.length > 0,
202
+ status: 'completed',
203
+ events: [
204
+ ...applyEvents,
205
+ { type: 'write-planned', at: now.toISOString() },
206
+ { type: 'write-failed', at: new Date().toISOString(), message: error?.message || 'write failed' },
207
+ ],
208
+ });
209
+ error.write_manifest = failedManifest;
210
+ error.snapshot = snapshot;
211
+ error.written_docs = partialDocs;
212
+ throw error;
213
+ }
214
+
215
+ const writeReport = {
216
+ ...options.report,
217
+ apply_docs: true,
218
+ writes: writtenDocs,
219
+ doc_proposal: proposal,
220
+ write_plan: options.summarizeWritePlan
221
+ ? options.summarizeWritePlan(writePlan)
222
+ : writePlan,
223
+ snapshot,
224
+ written_docs: writtenDocs,
225
+ proposal_artifacts: {
226
+ root: proposalArtifacts.root,
227
+ proposal_json: proposalArtifacts.proposal_json,
228
+ proposal_markdown: proposalArtifacts.proposal_markdown,
229
+ proposal_diff: proposalArtifacts.proposal_diff,
230
+ manifest: proposalArtifacts.manifest,
231
+ proposal_sha256: proposalArtifacts.proposal_sha256,
232
+ proposal_edited: options.proposalEdited === true,
233
+ },
234
+ write_manifest: {
235
+ path: plannedManifest.path,
236
+ },
237
+ };
238
+ const postWriteValidation = validateAnalyzeProjectPostWrite(repoRoot, writeReport, {
239
+ strict: options.strict === true,
240
+ });
241
+ const finalManifest = writeAnalyzeProjectWriteManifest(repoRoot, {
242
+ runId,
243
+ now: new Date(),
244
+ proposalManifest: proposalArtifacts.manifest,
245
+ snapshot,
246
+ writePlan,
247
+ writtenDocs,
248
+ validation: postWriteValidation,
249
+ partialWrite: false,
250
+ status: 'completed',
251
+ events: [
252
+ ...applyEvents,
253
+ { type: 'write-planned', at: now.toISOString() },
254
+ { type: 'write-completed', at: new Date().toISOString(), written_docs: writtenDocs },
255
+ ],
256
+ });
257
+
258
+ return {
259
+ ...writeReport,
260
+ post_write_validation: postWriteValidation,
261
+ write_manifest: finalManifest,
262
+ };
263
+ }
264
+
265
+ module.exports = {
266
+ AnalyzeProjectApplyError,
267
+ applyAnalyzeProjectDocProposal,
268
+ assertAnalyzeProjectApplyPreflight,
269
+ };
@@ -2,7 +2,11 @@ const fs = require('node:fs');
2
2
  const path = require('node:path');
3
3
 
4
4
  const { getContextPathExclusionReason } = require('./safety');
5
- const { classifyProjectFile, summarizeByReason } = require('./analyze-project-sampling');
5
+ const {
6
+ classifyProjectFile,
7
+ isLockfilePath,
8
+ summarizeByReason,
9
+ } = require('./analyze-project-sampling');
6
10
 
7
11
  const BINARY_EXTENSIONS = new Set([
8
12
  '.7z',
@@ -38,6 +42,20 @@ const BINARY_EXTENSIONS = new Set([
38
42
  '.woff2',
39
43
  '.zip',
40
44
  ]);
45
+ const STRUCTURAL_TEXT_EXTENSIONS = new Set([
46
+ '.cjs',
47
+ '.cts',
48
+ '.js',
49
+ '.jsx',
50
+ '.mjs',
51
+ '.mts',
52
+ '.ts',
53
+ '.tsx',
54
+ '.vue',
55
+ '.svelte',
56
+ ]);
57
+ const MAX_STRUCTURAL_FILES = 80;
58
+ const MAX_STRUCTURAL_FILE_BYTES = 20_000;
41
59
 
42
60
  function toPosix(filePath) {
43
61
  return String(filePath || '').replace(/\\/g, '/');
@@ -80,12 +98,95 @@ function detectPackageManager(repoRoot) {
80
98
  return 'unknown';
81
99
  }
82
100
 
101
+ function packageManagerFromLockfile(filePath) {
102
+ const base = path.posix.basename(toPosix(filePath)).toLowerCase();
103
+ if (base === 'package-lock.json') return 'npm';
104
+ if (base === 'pnpm-lock.yaml') return 'pnpm';
105
+ if (base === 'yarn.lock') return 'yarn';
106
+ if (base === 'bun.lockb') return 'bun';
107
+ if (base === 'cargo.lock') return 'cargo';
108
+ if (base === 'gemfile.lock') return 'bundler';
109
+ if (base === 'composer.lock') return 'composer';
110
+ if (base === 'poetry.lock') return 'poetry';
111
+ if (base === 'go.sum') return 'go';
112
+ return 'unknown';
113
+ }
114
+
83
115
  function readPackageJson(repoRoot, relativeDir = '.') {
84
116
  const packagePath = path.join(repoRoot, relativeDir, 'package.json');
85
117
  const pkg = fs.existsSync(packagePath) ? safeReadJson(packagePath) : null;
86
118
  return pkg && typeof pkg === 'object' ? pkg : null;
87
119
  }
88
120
 
121
+ function dependencyNamesFromPackageJson(packageJson) {
122
+ const names = [
123
+ ...Object.keys(packageJson?.dependencies || {}),
124
+ ...Object.keys(packageJson?.devDependencies || {}),
125
+ ...Object.keys(packageJson?.peerDependencies || {}),
126
+ ...Object.keys(packageJson?.optionalDependencies || {}),
127
+ ];
128
+ return uniqueSorted(names);
129
+ }
130
+
131
+ function summarizeNpmLockfile(lockfilePath) {
132
+ const lock = safeReadJson(lockfilePath);
133
+ if (!lock || typeof lock !== 'object') {
134
+ return {
135
+ dependency_count: null,
136
+ dependency_count_source: 'unreadable-package-lock',
137
+ };
138
+ }
139
+
140
+ if (lock.packages && typeof lock.packages === 'object') {
141
+ return {
142
+ dependency_count: Object.keys(lock.packages).filter(Boolean).length,
143
+ dependency_count_source: 'package-lock.packages',
144
+ };
145
+ }
146
+
147
+ if (lock.dependencies && typeof lock.dependencies === 'object') {
148
+ return {
149
+ dependency_count: Object.keys(lock.dependencies).length,
150
+ dependency_count_source: 'package-lock.dependencies',
151
+ };
152
+ }
153
+
154
+ return {
155
+ dependency_count: 0,
156
+ dependency_count_source: 'package-lock.empty',
157
+ };
158
+ }
159
+
160
+ function summarizeLockfiles(repoRoot, files) {
161
+ const packageJson = readPackageJson(repoRoot);
162
+ const topDependencies = dependencyNamesFromPackageJson(packageJson).slice(0, 30);
163
+
164
+ return files
165
+ .filter((file) => isLockfilePath(file.path))
166
+ .sort((a, b) => a.path.localeCompare(b.path))
167
+ .map((file) => {
168
+ const manager = packageManagerFromLockfile(file.path);
169
+ const lockfilePath = path.join(repoRoot, file.path);
170
+ const details = manager === 'npm'
171
+ ? summarizeNpmLockfile(lockfilePath)
172
+ : {
173
+ dependency_count: null,
174
+ dependency_count_source: 'not-estimated',
175
+ };
176
+
177
+ return {
178
+ path: file.path,
179
+ package_manager: manager,
180
+ bytes: file.bytes || 0,
181
+ content_included: false,
182
+ dependency_count: details.dependency_count,
183
+ dependency_count_source: details.dependency_count_source,
184
+ top_dependencies: topDependencies,
185
+ reason: 'lockfile summarized as metadata; full content excluded from provider sample by default',
186
+ };
187
+ });
188
+ }
189
+
89
190
  function expandWorkspacePattern(repoRoot, pattern) {
90
191
  const normalized = toPosix(pattern).trim();
91
192
  if (!normalized || normalized.startsWith('!')) {
@@ -352,12 +453,122 @@ function detectStack(repoRoot, files) {
352
453
  };
353
454
  }
354
455
 
456
+ function uniqueSorted(values) {
457
+ return [...new Set(values.filter(Boolean))].sort();
458
+ }
459
+
460
+ function extractImportSpecifiers(content) {
461
+ const imports = [];
462
+ const patterns = [
463
+ /\bimport\s+[^'"]*from\s+['"]([^'"]+)['"]/g,
464
+ /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
465
+ /\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
466
+ ];
467
+ for (const pattern of patterns) {
468
+ for (const match of content.matchAll(pattern)) {
469
+ imports.push(match[1]);
470
+ }
471
+ }
472
+ return uniqueSorted(imports).slice(0, 12);
473
+ }
474
+
475
+ function extractExportNames(content) {
476
+ const exports = [];
477
+ const patterns = [
478
+ /\bexport\s+(?:async\s+)?function\s+([A-Za-z0-9_$]+)/g,
479
+ /\bexport\s+(?:const|let|var|class)\s+([A-Za-z0-9_$]+)/g,
480
+ /\bexport\s+default\s+(?:function\s+)?([A-Za-z0-9_$]+)?/g,
481
+ ];
482
+ for (const pattern of patterns) {
483
+ for (const match of content.matchAll(pattern)) {
484
+ exports.push(match[1] || 'default');
485
+ }
486
+ }
487
+ return uniqueSorted(exports).slice(0, 12);
488
+ }
489
+
490
+ function isRoutePath(filePath) {
491
+ return /(^|\/)(routes?|controllers?|pages\/api|app\/api)(\/|$)/i.test(filePath)
492
+ || /(^|\/)app\/.+\/(page|route)\.[jt]sx?$/i.test(filePath)
493
+ || /(^|\/)pages\/.+\.[jt]sx?$/i.test(filePath);
494
+ }
495
+
496
+ function isComponentPath(filePath) {
497
+ const basename = path.posix.basename(filePath).replace(/\.[^.]+$/, '');
498
+ return /(^|\/)components?\//i.test(filePath) || /^[A-Z][A-Za-z0-9]+$/.test(basename);
499
+ }
500
+
501
+ function isContextPath(filePath) {
502
+ return /(^|\/)(contexts?|providers?)\//i.test(filePath) || /(?:context|provider)\.[jt]sx?$/i.test(filePath);
503
+ }
504
+
505
+ function buildStructuralMap(repoRoot, files, detected) {
506
+ const warnings = [];
507
+ const routeFiles = [];
508
+ const componentFiles = [];
509
+ const contextFiles = [];
510
+ const importEntries = [];
511
+ const exportEntries = [];
512
+ let scannedFiles = 0;
513
+
514
+ for (const file of files.slice().sort((a, b) => a.path.localeCompare(b.path))) {
515
+ if (isRoutePath(file.path)) routeFiles.push(file.path);
516
+ if (isComponentPath(file.path)) componentFiles.push(file.path);
517
+ if (isContextPath(file.path)) contextFiles.push(file.path);
518
+
519
+ const extension = path.extname(file.path).toLowerCase();
520
+ if (!STRUCTURAL_TEXT_EXTENSIONS.has(extension)) {
521
+ continue;
522
+ }
523
+ if (scannedFiles >= MAX_STRUCTURAL_FILES) {
524
+ warnings.push({ path: file.path, issue: 'structural-file-budget-exhausted' });
525
+ continue;
526
+ }
527
+ if (file.bytes > MAX_STRUCTURAL_FILE_BYTES) {
528
+ warnings.push({ path: file.path, issue: 'structural-file-too-large' });
529
+ continue;
530
+ }
531
+
532
+ try {
533
+ const content = fs.readFileSync(path.join(repoRoot, file.path), 'utf8');
534
+ const imports = extractImportSpecifiers(content);
535
+ const exports = extractExportNames(content);
536
+ if (imports.length > 0) {
537
+ importEntries.push({ path: file.path, imports });
538
+ }
539
+ if (exports.length > 0) {
540
+ exportEntries.push({ path: file.path, exports });
541
+ }
542
+ scannedFiles += 1;
543
+ } catch (error) {
544
+ warnings.push({ path: file.path, issue: error.code || 'structural-read-failed' });
545
+ }
546
+ }
547
+
548
+ return {
549
+ schema_version: 1,
550
+ files_scanned: scannedFiles,
551
+ files_considered: files.length,
552
+ warnings: warnings.slice(0, 40),
553
+ routes: uniqueSorted(routeFiles).slice(0, 80),
554
+ components: uniqueSorted(componentFiles).slice(0, 80),
555
+ contexts: uniqueSorted(contextFiles).slice(0, 40),
556
+ configs: (detected.configs || []).slice(0, 80),
557
+ scripts: Object.keys(detected.scripts || {}).sort(),
558
+ imports: importEntries.slice(0, 80),
559
+ exports: exportEntries.slice(0, 80),
560
+ };
561
+ }
562
+
355
563
  function discoverProjectFiles(repoRoot, options = {}) {
356
564
  const resolvedRepoRoot = path.resolve(repoRoot);
357
565
  const workspaceRoots = discoverWorkspaceRoots(resolvedRepoRoot);
358
566
  const analysisRoot = resolveAnalysisRoot(resolvedRepoRoot, options.scope, workspaceRoots);
359
567
  const walked = walkFiles(resolvedRepoRoot, analysisRoot.absolutePath);
360
568
  const packageJson = readPackageJson(resolvedRepoRoot);
569
+ const detected = detectStack(resolvedRepoRoot, walked.files);
570
+ detected.lockfiles = summarizeLockfiles(resolvedRepoRoot, walked.files);
571
+ detected.structural_map = buildStructuralMap(resolvedRepoRoot, walked.files, detected);
361
572
 
362
573
  return {
363
574
  project: {
@@ -376,7 +587,7 @@ function discoverProjectFiles(repoRoot, options = {}) {
376
587
  skippedSummary: summarizeByReason(walked.skipped),
377
588
  safetyExclusions: walked.safetyExclusions,
378
589
  safetySummary: summarizeByReason(walked.safetyExclusions),
379
- detected: detectStack(resolvedRepoRoot, walked.files),
590
+ detected,
380
591
  };
381
592
  }
382
593
 
@@ -329,7 +329,9 @@ function writeAnalyzeProjectDocs(writePlan) {
329
329
  continue;
330
330
  }
331
331
  fs.mkdirSync(path.dirname(item.destinationPath), { recursive: true });
332
- fs.writeFileSync(item.destinationPath, item.proposedContent);
332
+ const tempPath = `${item.destinationPath}.quiver-tmp-${process.pid}-${Date.now()}`;
333
+ fs.writeFileSync(tempPath, item.proposedContent);
334
+ fs.renameSync(tempPath, item.destinationPath);
333
335
  writtenDocs.push(item.path);
334
336
  }
335
337
  return writtenDocs;