create-quiver 0.16.0 → 0.17.0
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/CHANGELOG.md +4 -0
- package/README.md +7 -1
- package/README_FOR_AI.md +4 -1
- package/docs/CLI_UX_GUIDE.md +11 -0
- package/docs/INDEX.md +5 -1
- package/docs/reference/commands.md +34 -0
- package/package.json +1 -1
- package/specs/quiver-v43-cli-i18n-audit-release-readiness/command-language-mode-matrix.json +31 -1
- package/specs/quiver-v46-deep-project-analysis/EVIDENCE_REPORT.md +94 -0
- package/specs/quiver-v46-deep-project-analysis/EXECUTION_PLAN.md +26 -0
- package/specs/quiver-v46-deep-project-analysis/SPEC.md +157 -0
- package/specs/quiver-v46-deep-project-analysis/STATUS.md +26 -0
- package/specs/quiver-v46-deep-project-analysis/pr.md +131 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-00-analysis-contract-foundation/CLOSURE_BRIEF.md +14 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-00-analysis-contract-foundation/EXECUTION_BRIEF.md +41 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-00-analysis-contract-foundation/slice.json +61 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-01-stack-agnostic-discovery-sampling/CLOSURE_BRIEF.md +22 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-01-stack-agnostic-discovery-sampling/EXECUTION_BRIEF.md +33 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-01-stack-agnostic-discovery-sampling/slice.json +80 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-02-provider-analysis-json-contract/CLOSURE_BRIEF.md +21 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-02-provider-analysis-json-contract/EXECUTION_BRIEF.md +34 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-02-provider-analysis-json-contract/slice.json +79 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-03-doc-proposal-review-safe-writes/CLOSURE_BRIEF.md +19 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-03-doc-proposal-review-safe-writes/EXECUTION_BRIEF.md +33 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-03-doc-proposal-review-safe-writes/slice.json +79 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-04-validation-docs-release-readiness/CLOSURE_BRIEF.md +20 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-04-validation-docs-release-readiness/EXECUTION_BRIEF.md +32 -0
- package/specs/quiver-v46-deep-project-analysis/slices/slice-04-validation-docs-release-readiness/slice.json +85 -0
- package/src/create-quiver/commands/ai.js +521 -0
- package/src/create-quiver/index.js +113 -3
- package/src/create-quiver/lib/ai/analyze-project-discovery.js +387 -0
- package/src/create-quiver/lib/ai/analyze-project-docs.js +364 -0
- package/src/create-quiver/lib/ai/analyze-project-parser.js +277 -0
- package/src/create-quiver/lib/ai/analyze-project-prompts.js +214 -0
- package/src/create-quiver/lib/ai/analyze-project-review.js +99 -0
- package/src/create-quiver/lib/ai/analyze-project-sampling.js +402 -0
- package/src/create-quiver/lib/ai/analyze-project-schema.js +92 -0
- package/src/create-quiver/lib/ai/analyze-project-validation.js +309 -0
- package/src/create-quiver/lib/ai/artifacts.js +4 -1
- package/src/create-quiver/lib/cli/command-registry.js +1 -0
- package/.quiver/runs/run-2026-06-09t19-14-39z/approvals.json +0 -5
- package/.quiver/runs/run-2026-06-09t19-14-39z/decisions.md +0 -2
- package/.quiver/runs/run-2026-06-09t19-14-39z/requirement.md +0 -0
- package/.quiver/runs/run-2026-06-09t19-14-39z/snapshots/20260609T191439Z/docs/INDEX.md +0 -97
- package/.quiver/runs/run-2026-06-09t19-14-39z/snapshots/20260609T191439Z/manifest.json +0 -61
- package/.quiver/runs/run-2026-06-09t19-14-39z/state.json +0 -28
- package/ACTIVE_SLICES.md +0 -43
- package/auditoria-ux-ui-performance-documentacion.md +0 -705
- package/copys-landing-page.md +0 -244
- package/docs/ai/ACTIVE_SLICE.md +0 -61
- package/pr.md +0 -154
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# CLOSURE_BRIEF - slice-04 Validation, docs, fixtures, and release readiness
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Implemented the release-readiness layer for `ai analyze-project`: post-write validation, public docs, fixture coverage, command matrix updates, and package readiness hardening. Validation now checks analysis/doc proposal schemas, evidence paths, managed-block presence, critical placeholders, snapshot manifest entries, and deterministic conflicts between `docs/PROJECT_MAP.md` and context docs. `--strict` turns important deterministic doc conflicts into errors. Public docs now describe read-only defaults, privacy exclusions, provider/review gates, budgets, scope, JSON output, limitations, and safe write behavior.
|
|
6
|
+
|
|
7
|
+
## Validation
|
|
8
|
+
|
|
9
|
+
- [x] `node --test`
|
|
10
|
+
- [x] `npm run smoke:create-quiver`
|
|
11
|
+
- [x] `npm run package:quiver`
|
|
12
|
+
- [x] `node bin/create-quiver.js spec validate specs/quiver-v46-deep-project-analysis --strict`
|
|
13
|
+
- [x] `git diff --check`
|
|
14
|
+
|
|
15
|
+
## Closure Notes
|
|
16
|
+
|
|
17
|
+
- Fixture coverage is documented in `tests/fixtures/ai-analyze-project/matrix.json` and exercised by discovery, parser, provider, review, and validation tests.
|
|
18
|
+
- The package smoke initially failed because local `.quiver/runs` state was included in the npm tarball. `.npmignore` now excludes `.quiver/`, and package smoke passes.
|
|
19
|
+
- Deferred follow-up: full human-output i18n for `ai analyze-project`; this is recorded as an accepted exception in the v43 command language matrix. JSON output remains stable.
|
|
20
|
+
- Deferred follow-up: stack-specific adapters such as Rails, Django, Laravel, NestJS, Spring, and mobile can improve sampling later without changing the v46 schema.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# EXECUTION_BRIEF - slice-04 Validation, docs, fixtures, and release readiness
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
The feature can discover, analyze, review, and safely write docs after slices 01-03. This final slice makes the behavior releasable and documented.
|
|
6
|
+
|
|
7
|
+
## Objective
|
|
8
|
+
|
|
9
|
+
Add post-write validation, public docs, fixtures, smokes, and final release-readiness evidence.
|
|
10
|
+
|
|
11
|
+
## Acceptance Criteria
|
|
12
|
+
|
|
13
|
+
- Post-write validation checks schema, placeholders, evidence paths, deterministic-doc conflicts, and strict-mode behavior.
|
|
14
|
+
- `--strict` turns important conflicts into errors.
|
|
15
|
+
- Public docs explain read-only default, privacy preflight, review/write gates, budgets, scope, limitations, and JSON output.
|
|
16
|
+
- Fixtures cover Next/Supabase, unknown stack, monorepo, secrets, binaries, large repo, no package manager, symlinks, truncation, and invalid provider JSON.
|
|
17
|
+
- Package and smoke validation pass.
|
|
18
|
+
- v46 evidence report and status are updated.
|
|
19
|
+
|
|
20
|
+
## Production Guardrails
|
|
21
|
+
|
|
22
|
+
- Do not claim perfect project understanding.
|
|
23
|
+
- Document insufficient-evidence behavior and `--input <requirements.md>` usage.
|
|
24
|
+
- Preserve clean automation output.
|
|
25
|
+
|
|
26
|
+
## Completion Checklist
|
|
27
|
+
|
|
28
|
+
- [ ] Validation module added.
|
|
29
|
+
- [ ] Docs updated.
|
|
30
|
+
- [ ] Fixtures and smokes added.
|
|
31
|
+
- [ ] Final evidence captured.
|
|
32
|
+
- [ ] Spec marked ready/completed as appropriate.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"slice_id": "slice-04-validation-docs-release-readiness",
|
|
3
|
+
"ticket": "QUIVER-46-04",
|
|
4
|
+
"type": "release-readiness",
|
|
5
|
+
"title": "Validation, docs, fixtures, and release readiness",
|
|
6
|
+
"objective": "Complete post-write validation, public documentation, fixtures, smokes, and release evidence for ai analyze-project.",
|
|
7
|
+
"description": "Adds consistency validation, placeholder checks, docs updates, command reference updates, fixture coverage, smoke tests, and final package readiness evidence.",
|
|
8
|
+
"git": {
|
|
9
|
+
"branch_type": "feature",
|
|
10
|
+
"base_branch": "main",
|
|
11
|
+
"branch_slug": "v46-validation-docs-readiness",
|
|
12
|
+
"branch_name": "feature/QUIVER-46-04-v46-validation-docs-readiness"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src/create-quiver/commands/ai.js",
|
|
16
|
+
"src/create-quiver/lib/ai/analyze-project-validation.js",
|
|
17
|
+
".npmignore",
|
|
18
|
+
"docs/**",
|
|
19
|
+
"README.md",
|
|
20
|
+
"README_FOR_AI.md",
|
|
21
|
+
"tests/**",
|
|
22
|
+
"scripts/ci/**",
|
|
23
|
+
"specs/quiver-v46-deep-project-analysis/**"
|
|
24
|
+
],
|
|
25
|
+
"expected_read_paths": [
|
|
26
|
+
"docs/CLI_UX_GUIDE.md",
|
|
27
|
+
"docs/reference/commands.md",
|
|
28
|
+
"README.md",
|
|
29
|
+
"README_FOR_AI.md",
|
|
30
|
+
"scripts/ci",
|
|
31
|
+
"tests"
|
|
32
|
+
],
|
|
33
|
+
"allowed_write_paths": [
|
|
34
|
+
"src/create-quiver/commands/ai.js",
|
|
35
|
+
"src/create-quiver/lib/ai/analyze-project-validation.js",
|
|
36
|
+
".npmignore",
|
|
37
|
+
"docs/**",
|
|
38
|
+
"README.md",
|
|
39
|
+
"README_FOR_AI.md",
|
|
40
|
+
"tests/**",
|
|
41
|
+
"scripts/ci/**",
|
|
42
|
+
"specs/quiver-v46-deep-project-analysis/**"
|
|
43
|
+
],
|
|
44
|
+
"depends_on": [
|
|
45
|
+
"slice-03-doc-proposal-review-safe-writes"
|
|
46
|
+
],
|
|
47
|
+
"parallel_safe": "no",
|
|
48
|
+
"parallel_safe_reason": "Final validation and docs depend on the completed runtime behavior.",
|
|
49
|
+
"must": [
|
|
50
|
+
"Validate post-write schema and evidence paths.",
|
|
51
|
+
"Check placeholders and doc conflicts, with --strict converting important conflicts to errors.",
|
|
52
|
+
"Document command usage, safe defaults, privacy preflight, review flow, JSON output, budgets, scope, and limitations.",
|
|
53
|
+
"Add fixture coverage for Next/Supabase, unknown stack, monorepo, secrets, binary files, large repo, no package manager, symlinks, truncation, and invalid provider JSON.",
|
|
54
|
+
"Update command reference and AI guidance.",
|
|
55
|
+
"Capture final validation evidence."
|
|
56
|
+
],
|
|
57
|
+
"not_included": [
|
|
58
|
+
"New product feature implementation in target projects.",
|
|
59
|
+
"Full language-specific AST indexing.",
|
|
60
|
+
"Provider-specific account setup docs beyond existing Quiver provider guidance."
|
|
61
|
+
],
|
|
62
|
+
"acceptance": [
|
|
63
|
+
"Post-write validation reports schema, placeholders, evidence path issues, and doc conflicts.",
|
|
64
|
+
"--strict turns important conflicts into failures.",
|
|
65
|
+
"Public docs describe default read-only behavior and explicit write gates.",
|
|
66
|
+
"Fixtures cover production-risk edge cases.",
|
|
67
|
+
"Package and smoke validation pass.",
|
|
68
|
+
"Spec status and evidence report are updated with final commands."
|
|
69
|
+
],
|
|
70
|
+
"tests": [
|
|
71
|
+
"node --test tests/**/*.test.js",
|
|
72
|
+
"npm run smoke:create-quiver",
|
|
73
|
+
"npm run package:quiver",
|
|
74
|
+
"node bin/create-quiver.js spec validate specs/quiver-v46-deep-project-analysis --strict",
|
|
75
|
+
"git diff --check"
|
|
76
|
+
],
|
|
77
|
+
"validation_hints": [
|
|
78
|
+
"Run docs command reference checks if available.",
|
|
79
|
+
"Verify --json output remains clean in every tested mode."
|
|
80
|
+
],
|
|
81
|
+
"estimated_hours": 8,
|
|
82
|
+
"status": "completed",
|
|
83
|
+
"completed_at": "2026-06-10",
|
|
84
|
+
"blocked_reason": null
|
|
85
|
+
}
|
|
@@ -6,12 +6,34 @@ const { redactSecrets } = require('../lib/evidence');
|
|
|
6
6
|
const { formatActionableError } = require('../lib/actionable-error');
|
|
7
7
|
const {
|
|
8
8
|
assertProviderPromptWithinLimit,
|
|
9
|
+
byteLength,
|
|
9
10
|
compactRevisionInput,
|
|
10
11
|
extractCleanProviderOutput,
|
|
12
|
+
redactSensitiveLocalValues,
|
|
11
13
|
writeRawProviderArtifact,
|
|
12
14
|
} = require('../lib/ai/artifacts');
|
|
13
15
|
const { buildContextPackMetadata, normalizeRole } = require('../lib/ai/context-packs');
|
|
14
16
|
const { parseContextProposalOutput } = require('../lib/ai/context-proposal');
|
|
17
|
+
const { discoverProjectFiles } = require('../lib/ai/analyze-project-discovery');
|
|
18
|
+
const {
|
|
19
|
+
buildAnalyzeProjectDocProposal,
|
|
20
|
+
buildAnalyzeProjectWritePlan,
|
|
21
|
+
createAnalyzeProjectSnapshot,
|
|
22
|
+
formatAnalyzeProjectDiffPreview,
|
|
23
|
+
writeAnalyzeProjectDocs,
|
|
24
|
+
} = require('../lib/ai/analyze-project-docs');
|
|
25
|
+
const { parseAnalyzeProjectOutput } = require('../lib/ai/analyze-project-parser');
|
|
26
|
+
const { buildAnalyzeProjectPrompt } = require('../lib/ai/analyze-project-prompts');
|
|
27
|
+
const {
|
|
28
|
+
confirmAnalyzeProjectWrites,
|
|
29
|
+
reviewAnalyzeProjectDocProposal,
|
|
30
|
+
} = require('../lib/ai/analyze-project-review');
|
|
31
|
+
const {
|
|
32
|
+
DEFAULT_MAX_BYTES: DEFAULT_ANALYZE_MAX_BYTES,
|
|
33
|
+
DEFAULT_MAX_FILES: DEFAULT_ANALYZE_MAX_FILES,
|
|
34
|
+
sampleProjectFiles,
|
|
35
|
+
} = require('../lib/ai/analyze-project-sampling');
|
|
36
|
+
const { validateAnalyzeProjectPostWrite } = require('../lib/ai/analyze-project-validation');
|
|
15
37
|
const { openEditor } = require('../lib/cli/editor');
|
|
16
38
|
const { selectOption, promptText } = require('../lib/cli/selectors');
|
|
17
39
|
const { createUx } = require('../lib/cli/ux');
|
|
@@ -103,11 +125,316 @@ const DEFAULT_PLAN_CONTEXT = 'planning';
|
|
|
103
125
|
const DEFAULT_PLAN_PHASE = 'acceptance';
|
|
104
126
|
const CONTEXT_PREP_START = '<!-- quiver:context-prep:start -->';
|
|
105
127
|
const CONTEXT_PREP_END = '<!-- quiver:context-prep:end -->';
|
|
128
|
+
const ANALYZE_PROJECT_KIND = 'quiver-project-analysis-plan';
|
|
106
129
|
|
|
107
130
|
function formatError(message) {
|
|
108
131
|
return `create-quiver: ${message}`;
|
|
109
132
|
}
|
|
110
133
|
|
|
134
|
+
function normalizeAnalyzeBudget(value, fallback, flagName) {
|
|
135
|
+
if (typeof value === 'undefined' || value === null || value === '') {
|
|
136
|
+
return fallback;
|
|
137
|
+
}
|
|
138
|
+
const parsed = Number.parseInt(value, 10);
|
|
139
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
140
|
+
throw new Error(formatError(`invalid value for ${flagName}`));
|
|
141
|
+
}
|
|
142
|
+
return parsed;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function mergeReasonSummaries(...summaries) {
|
|
146
|
+
const merged = {};
|
|
147
|
+
for (const summary of summaries) {
|
|
148
|
+
for (const [reason, count] of Object.entries(summary || {})) {
|
|
149
|
+
merged[reason] = (merged[reason] || 0) + count;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return Object.keys(merged)
|
|
153
|
+
.sort()
|
|
154
|
+
.reduce((acc, reason) => {
|
|
155
|
+
acc[reason] = merged[reason];
|
|
156
|
+
return acc;
|
|
157
|
+
}, {});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function limitList(items, maxItems = 30) {
|
|
161
|
+
const list = Array.isArray(items) ? items : [];
|
|
162
|
+
return {
|
|
163
|
+
items: list.slice(0, maxItems),
|
|
164
|
+
hidden: Math.max(0, list.length - maxItems),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function formatAnalyzeProjectFileLine(file) {
|
|
169
|
+
const details = [];
|
|
170
|
+
if (Array.isArray(file.signals) && file.signals.length > 0) {
|
|
171
|
+
details.push(file.signals.join(', '));
|
|
172
|
+
}
|
|
173
|
+
if (typeof file.bytes === 'number') {
|
|
174
|
+
details.push(`${file.bytes} bytes`);
|
|
175
|
+
}
|
|
176
|
+
if (file.reason) {
|
|
177
|
+
details.push(file.reason);
|
|
178
|
+
}
|
|
179
|
+
return `- ${file.path}${details.length > 0 ? ` (${details.join('; ')})` : ''}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function formatAnalyzeProjectReport(report) {
|
|
183
|
+
const selected = limitList(report.selected_files, 80);
|
|
184
|
+
const omitted = limitList(report.omitted_files, 30);
|
|
185
|
+
const safety = limitList(report.safety_exclusions, 30);
|
|
186
|
+
const workspaces = limitList(report.roots.workspaces, 20);
|
|
187
|
+
const lines = [
|
|
188
|
+
'AI analyze-project read-only analysis',
|
|
189
|
+
`Mode: ${report.mode}`,
|
|
190
|
+
`Dry-run: ${report.dry_run ? 'yes' : 'no'} (dry-run never writes)`,
|
|
191
|
+
`Provider execution: ${report.provider_execution}`,
|
|
192
|
+
`Writes: ${report.writes.length === 0 ? 'none' : report.writes.join(', ')}`,
|
|
193
|
+
`Project: ${report.project.name}`,
|
|
194
|
+
`Scope: ${report.options.scope}`,
|
|
195
|
+
`Budgets: ${report.budgets.selected_files}/${report.budgets.max_files} files, ${report.budgets.selected_bytes}/${report.budgets.max_bytes} bytes`,
|
|
196
|
+
`Selected files: ${report.selected_files.length}`,
|
|
197
|
+
`Omitted files: ${report.omitted_files.length}`,
|
|
198
|
+
`Safety exclusions: ${report.safety_exclusions.length}`,
|
|
199
|
+
'',
|
|
200
|
+
'Workspace roots:',
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
for (const workspace of workspaces.items) {
|
|
204
|
+
lines.push(`- ${workspace.path} (${workspace.name}; ${workspace.source})`);
|
|
205
|
+
}
|
|
206
|
+
if (workspaces.hidden > 0) {
|
|
207
|
+
lines.push(`- ... ${workspaces.hidden} more`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
lines.push('', `Detected stack: ${report.detected.stack.length > 0 ? report.detected.stack.join(', ') : 'unknown'}`);
|
|
211
|
+
lines.push(`Source roots: ${report.detected.source_roots.length > 0 ? report.detected.source_roots.join(', ') : 'none'}`);
|
|
212
|
+
lines.push(`Entrypoints: ${report.detected.entrypoints.length > 0 ? report.detected.entrypoints.join(', ') : 'none'}`);
|
|
213
|
+
lines.push(`Configs: ${report.detected.configs.length > 0 ? report.detected.configs.join(', ') : 'none'}`);
|
|
214
|
+
|
|
215
|
+
lines.push('', 'Selected files:');
|
|
216
|
+
for (const file of selected.items) {
|
|
217
|
+
lines.push(formatAnalyzeProjectFileLine(file));
|
|
218
|
+
}
|
|
219
|
+
if (selected.hidden > 0) {
|
|
220
|
+
lines.push(`- ... ${selected.hidden} more`);
|
|
221
|
+
}
|
|
222
|
+
if (selected.items.length === 0) {
|
|
223
|
+
lines.push('- none');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
lines.push('', 'Omitted files:');
|
|
227
|
+
for (const file of omitted.items) {
|
|
228
|
+
lines.push(formatAnalyzeProjectFileLine(file));
|
|
229
|
+
}
|
|
230
|
+
if (omitted.hidden > 0) {
|
|
231
|
+
lines.push(`- ... ${omitted.hidden} more`);
|
|
232
|
+
}
|
|
233
|
+
if (omitted.items.length === 0) {
|
|
234
|
+
lines.push('- none');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
lines.push('', 'Safety exclusions:');
|
|
238
|
+
for (const file of safety.items) {
|
|
239
|
+
lines.push(`- ${file.path} (${file.reason})`);
|
|
240
|
+
}
|
|
241
|
+
if (safety.hidden > 0) {
|
|
242
|
+
lines.push(`- ... ${safety.hidden} more`);
|
|
243
|
+
}
|
|
244
|
+
if (safety.items.length === 0) {
|
|
245
|
+
lines.push('- none');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
lines.push('', 'Next commands:');
|
|
249
|
+
for (const command of report.next_commands) {
|
|
250
|
+
lines.push(`- ${command}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return `${lines.join('\n')}\n`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function buildAnalyzeProjectReport(repoRoot, options = {}) {
|
|
257
|
+
const deep = options.deep === true;
|
|
258
|
+
const includeSource = options.includeSource === true || deep;
|
|
259
|
+
const includeDb = options.includeDb === true || deep;
|
|
260
|
+
const includeTests = options.includeTests === true;
|
|
261
|
+
const maxFiles = normalizeAnalyzeBudget(options.maxFiles, DEFAULT_ANALYZE_MAX_FILES, '--max-files');
|
|
262
|
+
const maxBytes = normalizeAnalyzeBudget(options.maxBytes, DEFAULT_ANALYZE_MAX_BYTES, '--max-bytes');
|
|
263
|
+
const discovery = discoverProjectFiles(repoRoot, { scope: options.scope || '' });
|
|
264
|
+
const sample = sampleProjectFiles(discovery.files, {
|
|
265
|
+
includeDb,
|
|
266
|
+
includeSource,
|
|
267
|
+
includeTests,
|
|
268
|
+
maxBytes,
|
|
269
|
+
maxFiles,
|
|
270
|
+
});
|
|
271
|
+
const omittedFiles = [
|
|
272
|
+
...sample.omittedFiles,
|
|
273
|
+
...discovery.skippedFiles.map((file) => ({
|
|
274
|
+
path: file.path,
|
|
275
|
+
reason: file.reason,
|
|
276
|
+
})),
|
|
277
|
+
].sort((a, b) => a.path.localeCompare(b.path));
|
|
278
|
+
const omittedSummary = mergeReasonSummaries(
|
|
279
|
+
sample.omittedSummary,
|
|
280
|
+
discovery.skippedSummary,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
schema_version: 1,
|
|
285
|
+
kind: ANALYZE_PROJECT_KIND,
|
|
286
|
+
command: 'ai analyze-project',
|
|
287
|
+
mode: 'read-only',
|
|
288
|
+
dry_run: options.dryRun === true,
|
|
289
|
+
read_only: true,
|
|
290
|
+
provider_execution: 'skipped',
|
|
291
|
+
writes: [],
|
|
292
|
+
project: discovery.project,
|
|
293
|
+
options: {
|
|
294
|
+
deep,
|
|
295
|
+
scope: discovery.roots.analysis_root,
|
|
296
|
+
max_files: maxFiles,
|
|
297
|
+
max_bytes: maxBytes,
|
|
298
|
+
include_source: includeSource,
|
|
299
|
+
include_tests: includeTests,
|
|
300
|
+
include_db: includeDb,
|
|
301
|
+
},
|
|
302
|
+
roots: discovery.roots,
|
|
303
|
+
detected: discovery.detected,
|
|
304
|
+
budgets: sample.budgets,
|
|
305
|
+
selected_files: sample.selectedFiles,
|
|
306
|
+
omitted_files: omittedFiles,
|
|
307
|
+
omitted_summary: omittedSummary,
|
|
308
|
+
safety_exclusions: discovery.safetyExclusions,
|
|
309
|
+
safety_summary: discovery.safetySummary,
|
|
310
|
+
next_commands: [
|
|
311
|
+
'npx create-quiver ai analyze-project --deep --dry-run --json',
|
|
312
|
+
'npx create-quiver ai analyze-project --deep --review',
|
|
313
|
+
],
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function limitProviderArtifactText(text, maxBytes = 12_000) {
|
|
318
|
+
let value = String(text || '');
|
|
319
|
+
const redacted = value;
|
|
320
|
+
if (byteLength(redacted) <= maxBytes) {
|
|
321
|
+
return {
|
|
322
|
+
text: redacted,
|
|
323
|
+
truncated: false,
|
|
324
|
+
bytes: byteLength(redacted),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
value = redacted;
|
|
329
|
+
while (byteLength(value) > maxBytes && value.length > 0) {
|
|
330
|
+
value = value.slice(0, Math.max(0, value.length - Math.ceil((byteLength(value) - maxBytes) / 2) - 16));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
text: `${value.trimEnd()}\n[TRUNCATED BY QUIVER]\n`,
|
|
335
|
+
truncated: true,
|
|
336
|
+
bytes: byteLength(redacted),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function buildAnalyzeProjectProviderArtifact(result, clean, repoRoot, options = {}) {
|
|
341
|
+
const rawOutput = clean?.cleanOutput || result?.stdout || result?.stderr || '';
|
|
342
|
+
const redactedOutput = redactSensitiveLocalValues(rawOutput, { projectRoot: repoRoot });
|
|
343
|
+
const limited = limitProviderArtifactText(redactedOutput, options.maxBytes || 12_000);
|
|
344
|
+
return {
|
|
345
|
+
schema_version: 1,
|
|
346
|
+
kind: 'quiver-analyze-project-provider-artifact',
|
|
347
|
+
persisted: false,
|
|
348
|
+
redacted: true,
|
|
349
|
+
size_limited: true,
|
|
350
|
+
provider: result?.provider || null,
|
|
351
|
+
command: result?.command || null,
|
|
352
|
+
exit_code: typeof result?.exitCode === 'number' ? result.exitCode : null,
|
|
353
|
+
output_source: clean?.source || 'unknown',
|
|
354
|
+
output_bytes: limited.bytes,
|
|
355
|
+
output_truncated: limited.truncated,
|
|
356
|
+
output: limited.text,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function formatAnalyzeProjectLiveReport(report) {
|
|
361
|
+
const warningCount = report.analysis_validation?.warnings?.length || 0;
|
|
362
|
+
const docUpdatePaths = report.analysis_validation?.doc_update_paths || [];
|
|
363
|
+
const lines = [
|
|
364
|
+
'AI analyze-project provider analysis',
|
|
365
|
+
`Mode: ${report.mode}`,
|
|
366
|
+
`Provider: ${report.provider}`,
|
|
367
|
+
`Provider execution: ${report.provider_execution}`,
|
|
368
|
+
`Writes: ${report.writes.length === 0 ? 'none' : report.writes.join(', ')}`,
|
|
369
|
+
`Privacy preflight: ${report.privacy_preflight.ok ? 'passed' : 'failed'}`,
|
|
370
|
+
`Prompt bytes: ${report.prompt.bytes}/${report.prompt.max_provider_prompt_bytes}`,
|
|
371
|
+
`Selected files: ${report.selected_files.length}`,
|
|
372
|
+
`Omitted files: ${report.omitted_files.length}`,
|
|
373
|
+
`Safety exclusions: ${report.safety_exclusions.length}`,
|
|
374
|
+
`Analysis validation: passed (${warningCount} warning${warningCount === 1 ? '' : 's'})`,
|
|
375
|
+
`Doc update proposals: ${docUpdatePaths.length > 0 ? docUpdatePaths.join(', ') : 'none'}`,
|
|
376
|
+
];
|
|
377
|
+
|
|
378
|
+
if (warningCount > 0) {
|
|
379
|
+
lines.push('', 'Warnings:');
|
|
380
|
+
for (const warning of report.analysis_validation.warnings.slice(0, 20)) {
|
|
381
|
+
lines.push(`- ${warning.path}: ${warning.issue}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
lines.push('', 'Next commands:');
|
|
386
|
+
lines.push('- npx create-quiver ai analyze-project --deep --review');
|
|
387
|
+
lines.push('- npx create-quiver ai analyze-project --deep --json');
|
|
388
|
+
|
|
389
|
+
return `${lines.join('\n')}\n`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function formatAnalyzeProjectPostWriteValidation(validation) {
|
|
393
|
+
if (!validation) {
|
|
394
|
+
return [];
|
|
395
|
+
}
|
|
396
|
+
const warningCount = validation.warnings?.length || 0;
|
|
397
|
+
const errorCount = validation.errors?.length || 0;
|
|
398
|
+
const lines = [
|
|
399
|
+
`Post-write validation: ${validation.ok ? 'passed' : 'failed'} (${errorCount} error${errorCount === 1 ? '' : 's'}, ${warningCount} warning${warningCount === 1 ? '' : 's'})`,
|
|
400
|
+
];
|
|
401
|
+
for (const issue of [...(validation.errors || []), ...(validation.warnings || [])].slice(0, 20)) {
|
|
402
|
+
lines.push(`- ${issue.path || 'analysis'}: ${issue.issue} - ${issue.message}`);
|
|
403
|
+
}
|
|
404
|
+
return lines;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function formatAnalyzeProjectReviewPlan({ writePlan, reviewPath, snapshot, writtenDocs, completed = false, validation = null } = {}) {
|
|
408
|
+
const changed = (writePlan || []).filter((item) => item.action !== 'skip');
|
|
409
|
+
const dirty = changed.filter((item) => item.dirty);
|
|
410
|
+
const lines = [
|
|
411
|
+
completed ? 'AI analyze-project docs written' : 'AI analyze-project review write plan',
|
|
412
|
+
`Review artifact: ${reviewPath || 'none'}`,
|
|
413
|
+
`Writes: ${changed.length > 0 ? changed.map((item) => item.path).join(', ') : 'none'}`,
|
|
414
|
+
`Dirty target docs: ${dirty.length > 0 ? dirty.map((item) => item.path).join(', ') : 'none'}`,
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
if (snapshot) {
|
|
418
|
+
lines.push(`Snapshot: ${snapshot.root}`);
|
|
419
|
+
lines.push(`Manifest: ${snapshot.manifestPath}`);
|
|
420
|
+
}
|
|
421
|
+
if (completed) {
|
|
422
|
+
lines.push(`Written docs: ${writtenDocs && writtenDocs.length > 0 ? writtenDocs.join(', ') : 'none'}`);
|
|
423
|
+
lines.push(...formatAnalyzeProjectPostWriteValidation(validation));
|
|
424
|
+
return `${lines.join('\n')}\n`;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
lines.push('', 'Proposed changes:');
|
|
428
|
+
for (const item of writePlan || []) {
|
|
429
|
+
lines.push(`- ${item.path}: ${item.action}${item.reason ? ` (${item.reason})` : ''}`);
|
|
430
|
+
}
|
|
431
|
+
lines.push('', 'Final diff:');
|
|
432
|
+
lines.push(...formatAnalyzeProjectDiffPreview(writePlan || []));
|
|
433
|
+
lines.push('', 'Confirmation required before writing.');
|
|
434
|
+
|
|
435
|
+
return `${lines.join('\n')}\n`;
|
|
436
|
+
}
|
|
437
|
+
|
|
111
438
|
function formatLocalizedActionableError({ failure, impact, fix, nextCommand } = {}, options = {}) {
|
|
112
439
|
const translator = createTranslator(options.language);
|
|
113
440
|
const lines = [`create-quiver: ${String(failure || 'operation failed').trim()}`];
|
|
@@ -1541,6 +1868,199 @@ async function runPrepareContext(repoRoot, options = {}) {
|
|
|
1541
1868
|
};
|
|
1542
1869
|
}
|
|
1543
1870
|
|
|
1871
|
+
async function runAnalyzeProject(repoRoot, options = {}) {
|
|
1872
|
+
const report = buildAnalyzeProjectReport(repoRoot, options);
|
|
1873
|
+
if (options.dryRun === true) {
|
|
1874
|
+
if (options.json === true) {
|
|
1875
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
1876
|
+
return report;
|
|
1877
|
+
}
|
|
1878
|
+
process.stdout.write(formatAnalyzeProjectReport(report));
|
|
1879
|
+
return report;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
|
|
1883
|
+
const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
|
|
1884
|
+
const provider = runtimeProfile.provider;
|
|
1885
|
+
const timeoutMs = normalizeTimeout(options.timeout);
|
|
1886
|
+
const promptPackage = buildAnalyzeProjectPrompt({
|
|
1887
|
+
analysisPlan: report,
|
|
1888
|
+
repoRoot,
|
|
1889
|
+
maxFileBytes: options.maxPromptFileBytes,
|
|
1890
|
+
maxTotalFileBytes: options.maxPromptTotalBytes,
|
|
1891
|
+
});
|
|
1892
|
+
const prompt = promptPackage.prompt;
|
|
1893
|
+
const promptLimit = assertProviderPromptWithinLimit(prompt, options.promptLimitOptions || {});
|
|
1894
|
+
const privacyPreflight = promptPackage.privacyPreflight;
|
|
1895
|
+
|
|
1896
|
+
if (!privacyPreflight.ok) {
|
|
1897
|
+
const error = new Error(formatError('ai analyze-project privacy preflight failed; provider execution was blocked before sending repository content.'));
|
|
1898
|
+
error.code = 'AI_ANALYZE_PROJECT_PRIVACY_PREFLIGHT_FAILED';
|
|
1899
|
+
error.details = privacyPreflight;
|
|
1900
|
+
throw error;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
let invocation;
|
|
1904
|
+
try {
|
|
1905
|
+
invocation = buildProviderInvocation(provider, {
|
|
1906
|
+
prompt,
|
|
1907
|
+
cwd: repoRoot,
|
|
1908
|
+
timeoutMs,
|
|
1909
|
+
...runtimeModelExecutionOptions(runtimeProfile, options),
|
|
1910
|
+
enforceModelSelection: false,
|
|
1911
|
+
});
|
|
1912
|
+
} catch (error) {
|
|
1913
|
+
throw annotateProviderError(error, 'analyze-project');
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
if (options.printPrompt) {
|
|
1917
|
+
process.stdout.write(prompt.endsWith('\n') ? prompt : `${prompt}\n`);
|
|
1918
|
+
return {
|
|
1919
|
+
...report,
|
|
1920
|
+
provider,
|
|
1921
|
+
role,
|
|
1922
|
+
invocation,
|
|
1923
|
+
privacy_preflight: privacyPreflight,
|
|
1924
|
+
prompt: {
|
|
1925
|
+
bytes: promptLimit.bytes,
|
|
1926
|
+
max_provider_prompt_bytes: promptLimit.maxProviderPromptBytes,
|
|
1927
|
+
files: promptPackage.files,
|
|
1928
|
+
},
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
let result;
|
|
1933
|
+
try {
|
|
1934
|
+
result = await (options.runProviderFn || runProvider)(provider, {
|
|
1935
|
+
prompt,
|
|
1936
|
+
cwd: repoRoot,
|
|
1937
|
+
timeoutMs,
|
|
1938
|
+
dryRun: false,
|
|
1939
|
+
probe: options.probe,
|
|
1940
|
+
spawn: options.spawn,
|
|
1941
|
+
tempRoot: options.tempRoot,
|
|
1942
|
+
tempFileName: options.tempFileName,
|
|
1943
|
+
tempFilePrefix: options.tempFilePrefix,
|
|
1944
|
+
...runtimeModelExecutionOptions(runtimeProfile, options),
|
|
1945
|
+
enforceModelSelection: false,
|
|
1946
|
+
});
|
|
1947
|
+
} catch (error) {
|
|
1948
|
+
throw annotateProviderError(error, 'analyze-project');
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
if (!result.ok) {
|
|
1952
|
+
throw annotateProviderError(result.error || new Error('provider run failed'), 'analyze-project');
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
const clean = extractCleanProviderOutput(result, { prompt, projectRoot: repoRoot });
|
|
1956
|
+
const parsed = parseAnalyzeProjectOutput(clean.cleanOutput, {
|
|
1957
|
+
selectedFiles: report.selected_files,
|
|
1958
|
+
promptFiles: promptPackage.files,
|
|
1959
|
+
});
|
|
1960
|
+
const completedReport = {
|
|
1961
|
+
...report,
|
|
1962
|
+
provider,
|
|
1963
|
+
role,
|
|
1964
|
+
provider_execution: 'completed',
|
|
1965
|
+
invocation,
|
|
1966
|
+
privacy_preflight: privacyPreflight,
|
|
1967
|
+
prompt: {
|
|
1968
|
+
bytes: promptLimit.bytes,
|
|
1969
|
+
max_provider_prompt_bytes: promptLimit.maxProviderPromptBytes,
|
|
1970
|
+
files: promptPackage.files,
|
|
1971
|
+
},
|
|
1972
|
+
analysis: parsed.analysis,
|
|
1973
|
+
analysis_validation: {
|
|
1974
|
+
parse_source: parsed.parseSource,
|
|
1975
|
+
warnings: parsed.warnings,
|
|
1976
|
+
doc_update_paths: parsed.docUpdatePaths,
|
|
1977
|
+
},
|
|
1978
|
+
provider_artifact: buildAnalyzeProjectProviderArtifact(result, clean, repoRoot),
|
|
1979
|
+
};
|
|
1980
|
+
|
|
1981
|
+
if (options.review === true) {
|
|
1982
|
+
const initialProposal = buildAnalyzeProjectDocProposal(parsed.analysis);
|
|
1983
|
+
const reviewed = await reviewAnalyzeProjectDocProposal(repoRoot, initialProposal, options);
|
|
1984
|
+
const writePlan = buildAnalyzeProjectWritePlan(repoRoot, reviewed.proposal);
|
|
1985
|
+
process.stdout.write(formatAnalyzeProjectReviewPlan({
|
|
1986
|
+
writePlan,
|
|
1987
|
+
reviewPath: reviewed.reviewPath,
|
|
1988
|
+
}));
|
|
1989
|
+
await confirmAnalyzeProjectWrites(writePlan, options);
|
|
1990
|
+
const lifecycleRun = ensureAiRun(repoRoot, {
|
|
1991
|
+
command: 'ai analyze-project',
|
|
1992
|
+
input: reviewed.reviewPath,
|
|
1993
|
+
runId: options.runId,
|
|
1994
|
+
phase: 'created',
|
|
1995
|
+
});
|
|
1996
|
+
const snapshot = createAnalyzeProjectSnapshot(repoRoot, lifecycleRun, writePlan, {
|
|
1997
|
+
providerArtifact: completedReport.provider_artifact,
|
|
1998
|
+
proposal: reviewed.proposal,
|
|
1999
|
+
now: options.now || new Date(),
|
|
2000
|
+
});
|
|
2001
|
+
const writtenDocs = writeAnalyzeProjectDocs(writePlan);
|
|
2002
|
+
let writeReport = {
|
|
2003
|
+
...completedReport,
|
|
2004
|
+
review: true,
|
|
2005
|
+
review_path: reviewed.reviewPath,
|
|
2006
|
+
doc_proposal: reviewed.proposal,
|
|
2007
|
+
write_plan: writePlan.map((item) => ({
|
|
2008
|
+
path: item.path,
|
|
2009
|
+
action: item.action,
|
|
2010
|
+
dirty: item.dirty,
|
|
2011
|
+
before_sha256: item.before_sha256,
|
|
2012
|
+
after_sha256: item.after_sha256,
|
|
2013
|
+
reason: item.reason,
|
|
2014
|
+
})),
|
|
2015
|
+
snapshot,
|
|
2016
|
+
written_docs: writtenDocs,
|
|
2017
|
+
run_id: lifecycleRun.run_id,
|
|
2018
|
+
};
|
|
2019
|
+
const postWriteValidation = validateAnalyzeProjectPostWrite(repoRoot, writeReport, {
|
|
2020
|
+
strict: options.strict === true,
|
|
2021
|
+
});
|
|
2022
|
+
writeReport = {
|
|
2023
|
+
...writeReport,
|
|
2024
|
+
post_write_validation: postWriteValidation,
|
|
2025
|
+
};
|
|
2026
|
+
|
|
2027
|
+
if (options.json === true) {
|
|
2028
|
+
process.stdout.write(`${JSON.stringify(writeReport, null, 2)}\n`);
|
|
2029
|
+
if (!postWriteValidation.ok) {
|
|
2030
|
+
const error = new Error(formatError('ai analyze-project post-write validation failed.'));
|
|
2031
|
+
error.code = 'AI_ANALYZE_PROJECT_POST_WRITE_VALIDATION_FAILED';
|
|
2032
|
+
error.validation = postWriteValidation;
|
|
2033
|
+
throw error;
|
|
2034
|
+
}
|
|
2035
|
+
return writeReport;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
process.stdout.write(formatAnalyzeProjectReviewPlan({
|
|
2039
|
+
writePlan,
|
|
2040
|
+
reviewPath: reviewed.reviewPath,
|
|
2041
|
+
snapshot,
|
|
2042
|
+
writtenDocs,
|
|
2043
|
+
validation: postWriteValidation,
|
|
2044
|
+
completed: true,
|
|
2045
|
+
}));
|
|
2046
|
+
if (!postWriteValidation.ok) {
|
|
2047
|
+
const error = new Error(formatError('ai analyze-project post-write validation failed.'));
|
|
2048
|
+
error.code = 'AI_ANALYZE_PROJECT_POST_WRITE_VALIDATION_FAILED';
|
|
2049
|
+
error.validation = postWriteValidation;
|
|
2050
|
+
throw error;
|
|
2051
|
+
}
|
|
2052
|
+
return writeReport;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
if (options.json === true) {
|
|
2056
|
+
process.stdout.write(`${JSON.stringify(completedReport, null, 2)}\n`);
|
|
2057
|
+
return completedReport;
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
process.stdout.write(formatAnalyzeProjectLiveReport(completedReport));
|
|
2061
|
+
return completedReport;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
1544
2064
|
async function runPrepareContextWithPlanner(repoRoot, options = {}) {
|
|
1545
2065
|
const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
|
|
1546
2066
|
const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
|
|
@@ -3412,6 +3932,7 @@ module.exports = {
|
|
|
3412
3932
|
resolveInteractiveAgentSetOptions,
|
|
3413
3933
|
runAgent,
|
|
3414
3934
|
runActiveSlice,
|
|
3935
|
+
runAnalyzeProject,
|
|
3415
3936
|
runDoctor,
|
|
3416
3937
|
runExecutePlan,
|
|
3417
3938
|
runExecuteSlice,
|