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.
- package/ACTIVE_SLICES.md +43 -0
- package/CHANGELOG.md +8 -0
- package/auditoria-ux-ui-performance-documentacion.md +705 -0
- package/copys-landing-page.md +244 -0
- package/docs/CLI_UX_GUIDE.md +13 -3
- package/docs/TROUBLESHOOTING.md +94 -0
- package/docs/reference/commands.md +68 -4
- package/docs/workflows/existing-project-ai-quiver-setup.md +552 -0
- package/docs/workflows/existing-project.md +36 -0
- package/package.json +1 -1
- package/specs/quiver-v43-cli-i18n-audit-release-readiness/command-language-mode-matrix.json +5 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/EVIDENCE_REPORT.md +151 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/EXECUTION_PLAN.md +50 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/SPEC.md +207 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/STATUS.md +32 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/pr.md +73 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-00-analysis-run-contract/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-00-analysis-run-contract/EXECUTION_BRIEF.md +63 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-00-analysis-run-contract/slice.json +70 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-01-provider-fixture-harness/CLOSURE_BRIEF.md +38 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-01-provider-fixture-harness/EXECUTION_BRIEF.md +60 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-01-provider-fixture-harness/slice.json +74 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-02-safe-context-boundary/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-02-safe-context-boundary/EXECUTION_BRIEF.md +68 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-02-safe-context-boundary/slice.json +85 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-03-schema-error-grouping/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-03-schema-error-grouping/EXECUTION_BRIEF.md +65 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-03-schema-error-grouping/slice.json +80 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-04-safe-repair-layer/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-04-safe-repair-layer/EXECUTION_BRIEF.md +63 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-04-safe-repair-layer/slice.json +80 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-05-controlled-retry-layer/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-05-controlled-retry-layer/EXECUTION_BRIEF.md +65 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-05-controlled-retry-layer/slice.json +81 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-06-audit-review-transaction/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-06-audit-review-transaction/EXECUTION_BRIEF.md +71 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-06-audit-review-transaction/slice.json +88 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-07-semantic-validation-docs/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-07-semantic-validation-docs/EXECUTION_BRIEF.md +66 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-07-semantic-validation-docs/slice.json +86 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-08-structural-map-hardening/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-08-structural-map-hardening/EXECUTION_BRIEF.md +67 -0
- package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-08-structural-map-hardening/slice.json +81 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/EVIDENCE_REPORT.md +53 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/EXECUTION_PLAN.md +55 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/SPEC.md +184 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/STATUS.md +34 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-01-contract-regression-harness/CLOSURE_BRIEF.md +10 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-01-contract-regression-harness/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-01-contract-regression-harness/slice.json +72 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-02-safe-discovery-sampling/CLOSURE_BRIEF.md +11 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-02-safe-discovery-sampling/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-02-safe-discovery-sampling/slice.json +69 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-03-run-lifecycle-cli-io/CLOSURE_BRIEF.md +10 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-03-run-lifecycle-cli-io/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-03-run-lifecycle-cli-io/slice.json +69 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-04-artifact-store-redaction/CLOSURE_BRIEF.md +10 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-04-artifact-store-redaction/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-04-artifact-store-redaction/slice.json +73 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-05-path-aware-repair-engine/CLOSURE_BRIEF.md +10 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-05-path-aware-repair-engine/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-05-path-aware-repair-engine/slice.json +72 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-06-retry-controller/CLOSURE_BRIEF.md +9 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-06-retry-controller/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-06-retry-controller/slice.json +70 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-07-final-validation-review-write-gate/CLOSURE_BRIEF.md +10 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-07-final-validation-review-write-gate/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-07-final-validation-review-write-gate/slice.json +75 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-08-doctor-agents-guidance/CLOSURE_BRIEF.md +9 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-08-doctor-agents-guidance/EXECUTION_BRIEF.md +51 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-08-doctor-agents-guidance/slice.json +66 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-09-release-smoke-rollback-docs/CLOSURE_BRIEF.md +10 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-09-release-smoke-rollback-docs/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-09-release-smoke-rollback-docs/slice.json +69 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/EVIDENCE_REPORT.md +209 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/EXECUTION_PLAN.md +49 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/SPEC.md +276 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/STATUS.md +29 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/CLOSURE_BRIEF.md +47 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/EXECUTION_BRIEF.md +67 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/pr.md +129 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/slice.json +82 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/CLOSURE_BRIEF.md +39 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/EXECUTION_BRIEF.md +61 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/pr.md +116 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/slice.json +69 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/EXECUTION_BRIEF.md +62 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/pr.md +132 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/slice.json +76 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/CLOSURE_BRIEF.md +47 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/EXECUTION_BRIEF.md +70 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/pr.md +141 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/slice.json +72 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/CLOSURE_BRIEF.md +42 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/EXECUTION_BRIEF.md +61 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/pr.md +129 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/slice.json +67 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/CLOSURE_BRIEF.md +38 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/EXECUTION_BRIEF.md +64 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/pr.md +123 -0
- package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/slice.json +76 -0
- package/src/create-quiver/commands/ai.js +989 -136
- package/src/create-quiver/index.js +83 -1
- package/src/create-quiver/lib/ai/analyze-project-apply.js +269 -0
- package/src/create-quiver/lib/ai/analyze-project-discovery.js +213 -2
- package/src/create-quiver/lib/ai/analyze-project-docs.js +3 -1
- package/src/create-quiver/lib/ai/analyze-project-interactive.js +241 -0
- package/src/create-quiver/lib/ai/analyze-project-parser.js +1 -0
- package/src/create-quiver/lib/ai/analyze-project-prompts.js +130 -0
- package/src/create-quiver/lib/ai/analyze-project-proposal.js +582 -0
- package/src/create-quiver/lib/ai/analyze-project-repair.js +402 -0
- package/src/create-quiver/lib/ai/analyze-project-sampling.js +31 -4
- package/src/create-quiver/lib/ai/analyze-project-validation.js +178 -8
- package/src/create-quiver/lib/ai/artifacts.js +83 -2
- package/src/create-quiver/lib/cli/ux-flags.js +6 -0
- package/src/create-quiver/lib/doctor.js +107 -12
- package/src/create-quiver/lib/i18n/messages/en.js +33 -0
- 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', '
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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;
|