create-quiver 0.16.0 → 0.17.1
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 +8 -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 +587 -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/src/create-quiver/lib/i18n/messages/en.js +1 -0
- package/src/create-quiver/lib/i18n/messages/es.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
|
@@ -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,356 @@ 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 formatAnalyzeProjectIssues(issues = [], maxIssues = 8) {
|
|
169
|
+
const list = Array.isArray(issues) ? issues : [];
|
|
170
|
+
const visible = list.slice(0, maxIssues);
|
|
171
|
+
const lines = visible.map((issue) => {
|
|
172
|
+
const location = issue.path || 'analysis';
|
|
173
|
+
const code = issue.issue || 'invalid';
|
|
174
|
+
const message = issue.message || 'Invalid provider analysis output.';
|
|
175
|
+
return `- ${location}: ${code} - ${message}`;
|
|
176
|
+
});
|
|
177
|
+
const hidden = list.length - visible.length;
|
|
178
|
+
if (hidden > 0) {
|
|
179
|
+
lines.push(`- ... ${hidden} more issue${hidden === 1 ? '' : 's'}`);
|
|
180
|
+
}
|
|
181
|
+
return lines;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function enhanceAnalyzeProjectAnalysisError(error) {
|
|
185
|
+
if (!error || error.code !== 'AI_ANALYZE_PROJECT_INVALID') {
|
|
186
|
+
return error;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const issueLines = formatAnalyzeProjectIssues(error.issues);
|
|
190
|
+
if (issueLines.length === 0) {
|
|
191
|
+
return error;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const wrapped = new Error([
|
|
195
|
+
error.message,
|
|
196
|
+
'Issues:',
|
|
197
|
+
...issueLines,
|
|
198
|
+
'Next safe step: inspect the selected evidence with `npx create-quiver ai analyze-project --deep --dry-run --json`, then rerun live. If provider drift repeats, reduce --max-files or --max-bytes.',
|
|
199
|
+
].join('\n'));
|
|
200
|
+
wrapped.name = error.name;
|
|
201
|
+
wrapped.code = error.code;
|
|
202
|
+
wrapped.cause = error;
|
|
203
|
+
wrapped.issues = error.issues;
|
|
204
|
+
wrapped.details = error.issues;
|
|
205
|
+
return wrapped;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function formatAnalyzeProjectFileLine(file) {
|
|
209
|
+
const details = [];
|
|
210
|
+
if (Array.isArray(file.signals) && file.signals.length > 0) {
|
|
211
|
+
details.push(file.signals.join(', '));
|
|
212
|
+
}
|
|
213
|
+
if (typeof file.bytes === 'number') {
|
|
214
|
+
details.push(`${file.bytes} bytes`);
|
|
215
|
+
}
|
|
216
|
+
if (file.reason) {
|
|
217
|
+
details.push(file.reason);
|
|
218
|
+
}
|
|
219
|
+
return `- ${file.path}${details.length > 0 ? ` (${details.join('; ')})` : ''}`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function formatAnalyzeProjectReport(report) {
|
|
223
|
+
const selected = limitList(report.selected_files, 80);
|
|
224
|
+
const omitted = limitList(report.omitted_files, 30);
|
|
225
|
+
const safety = limitList(report.safety_exclusions, 30);
|
|
226
|
+
const workspaces = limitList(report.roots.workspaces, 20);
|
|
227
|
+
const lines = [
|
|
228
|
+
'AI analyze-project read-only analysis',
|
|
229
|
+
`Mode: ${report.mode}`,
|
|
230
|
+
`Dry-run: ${report.dry_run ? 'yes' : 'no'} (dry-run never writes)`,
|
|
231
|
+
`Provider execution: ${report.provider_execution}`,
|
|
232
|
+
`Writes: ${report.writes.length === 0 ? 'none' : report.writes.join(', ')}`,
|
|
233
|
+
`Project: ${report.project.name}`,
|
|
234
|
+
`Scope: ${report.options.scope}`,
|
|
235
|
+
`Budgets: ${report.budgets.selected_files}/${report.budgets.max_files} files, ${report.budgets.selected_bytes}/${report.budgets.max_bytes} bytes`,
|
|
236
|
+
`Selected files: ${report.selected_files.length}`,
|
|
237
|
+
`Omitted files: ${report.omitted_files.length}`,
|
|
238
|
+
`Safety exclusions: ${report.safety_exclusions.length}`,
|
|
239
|
+
'',
|
|
240
|
+
'Workspace roots:',
|
|
241
|
+
];
|
|
242
|
+
|
|
243
|
+
for (const workspace of workspaces.items) {
|
|
244
|
+
lines.push(`- ${workspace.path} (${workspace.name}; ${workspace.source})`);
|
|
245
|
+
}
|
|
246
|
+
if (workspaces.hidden > 0) {
|
|
247
|
+
lines.push(`- ... ${workspaces.hidden} more`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
lines.push('', `Detected stack: ${report.detected.stack.length > 0 ? report.detected.stack.join(', ') : 'unknown'}`);
|
|
251
|
+
lines.push(`Source roots: ${report.detected.source_roots.length > 0 ? report.detected.source_roots.join(', ') : 'none'}`);
|
|
252
|
+
lines.push(`Entrypoints: ${report.detected.entrypoints.length > 0 ? report.detected.entrypoints.join(', ') : 'none'}`);
|
|
253
|
+
lines.push(`Configs: ${report.detected.configs.length > 0 ? report.detected.configs.join(', ') : 'none'}`);
|
|
254
|
+
|
|
255
|
+
lines.push('', 'Selected files:');
|
|
256
|
+
for (const file of selected.items) {
|
|
257
|
+
lines.push(formatAnalyzeProjectFileLine(file));
|
|
258
|
+
}
|
|
259
|
+
if (selected.hidden > 0) {
|
|
260
|
+
lines.push(`- ... ${selected.hidden} more`);
|
|
261
|
+
}
|
|
262
|
+
if (selected.items.length === 0) {
|
|
263
|
+
lines.push('- none');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
lines.push('', 'Omitted files:');
|
|
267
|
+
for (const file of omitted.items) {
|
|
268
|
+
lines.push(formatAnalyzeProjectFileLine(file));
|
|
269
|
+
}
|
|
270
|
+
if (omitted.hidden > 0) {
|
|
271
|
+
lines.push(`- ... ${omitted.hidden} more`);
|
|
272
|
+
}
|
|
273
|
+
if (omitted.items.length === 0) {
|
|
274
|
+
lines.push('- none');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
lines.push('', 'Safety exclusions:');
|
|
278
|
+
for (const file of safety.items) {
|
|
279
|
+
lines.push(`- ${file.path} (${file.reason})`);
|
|
280
|
+
}
|
|
281
|
+
if (safety.hidden > 0) {
|
|
282
|
+
lines.push(`- ... ${safety.hidden} more`);
|
|
283
|
+
}
|
|
284
|
+
if (safety.items.length === 0) {
|
|
285
|
+
lines.push('- none');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
lines.push('', 'Next commands:');
|
|
289
|
+
for (const command of report.next_commands) {
|
|
290
|
+
lines.push(`- ${command}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return `${lines.join('\n')}\n`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function buildAnalyzeProjectReport(repoRoot, options = {}) {
|
|
297
|
+
const deep = options.deep === true;
|
|
298
|
+
const includeSource = options.includeSource === true || deep;
|
|
299
|
+
const includeDb = options.includeDb === true || deep;
|
|
300
|
+
const includeTests = options.includeTests === true;
|
|
301
|
+
const maxFiles = normalizeAnalyzeBudget(options.maxFiles, DEFAULT_ANALYZE_MAX_FILES, '--max-files');
|
|
302
|
+
const maxBytes = normalizeAnalyzeBudget(options.maxBytes, DEFAULT_ANALYZE_MAX_BYTES, '--max-bytes');
|
|
303
|
+
const discovery = discoverProjectFiles(repoRoot, { scope: options.scope || '' });
|
|
304
|
+
const sample = sampleProjectFiles(discovery.files, {
|
|
305
|
+
includeDb,
|
|
306
|
+
includeSource,
|
|
307
|
+
includeTests,
|
|
308
|
+
maxBytes,
|
|
309
|
+
maxFiles,
|
|
310
|
+
});
|
|
311
|
+
const omittedFiles = [
|
|
312
|
+
...sample.omittedFiles,
|
|
313
|
+
...discovery.skippedFiles.map((file) => ({
|
|
314
|
+
path: file.path,
|
|
315
|
+
reason: file.reason,
|
|
316
|
+
})),
|
|
317
|
+
].sort((a, b) => a.path.localeCompare(b.path));
|
|
318
|
+
const omittedSummary = mergeReasonSummaries(
|
|
319
|
+
sample.omittedSummary,
|
|
320
|
+
discovery.skippedSummary,
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
schema_version: 1,
|
|
325
|
+
kind: ANALYZE_PROJECT_KIND,
|
|
326
|
+
command: 'ai analyze-project',
|
|
327
|
+
mode: 'read-only',
|
|
328
|
+
dry_run: options.dryRun === true,
|
|
329
|
+
read_only: true,
|
|
330
|
+
provider_execution: 'skipped',
|
|
331
|
+
writes: [],
|
|
332
|
+
project: discovery.project,
|
|
333
|
+
options: {
|
|
334
|
+
deep,
|
|
335
|
+
scope: discovery.roots.analysis_root,
|
|
336
|
+
max_files: maxFiles,
|
|
337
|
+
max_bytes: maxBytes,
|
|
338
|
+
include_source: includeSource,
|
|
339
|
+
include_tests: includeTests,
|
|
340
|
+
include_db: includeDb,
|
|
341
|
+
},
|
|
342
|
+
roots: discovery.roots,
|
|
343
|
+
detected: discovery.detected,
|
|
344
|
+
budgets: sample.budgets,
|
|
345
|
+
selected_files: sample.selectedFiles,
|
|
346
|
+
omitted_files: omittedFiles,
|
|
347
|
+
omitted_summary: omittedSummary,
|
|
348
|
+
safety_exclusions: discovery.safetyExclusions,
|
|
349
|
+
safety_summary: discovery.safetySummary,
|
|
350
|
+
next_commands: [
|
|
351
|
+
'npx create-quiver ai analyze-project --deep --dry-run --json',
|
|
352
|
+
'npx create-quiver ai analyze-project --deep --review',
|
|
353
|
+
],
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function limitProviderArtifactText(text, maxBytes = 12_000) {
|
|
358
|
+
let value = String(text || '');
|
|
359
|
+
const redacted = value;
|
|
360
|
+
if (byteLength(redacted) <= maxBytes) {
|
|
361
|
+
return {
|
|
362
|
+
text: redacted,
|
|
363
|
+
truncated: false,
|
|
364
|
+
bytes: byteLength(redacted),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
value = redacted;
|
|
369
|
+
while (byteLength(value) > maxBytes && value.length > 0) {
|
|
370
|
+
value = value.slice(0, Math.max(0, value.length - Math.ceil((byteLength(value) - maxBytes) / 2) - 16));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
text: `${value.trimEnd()}\n[TRUNCATED BY QUIVER]\n`,
|
|
375
|
+
truncated: true,
|
|
376
|
+
bytes: byteLength(redacted),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function buildAnalyzeProjectProviderArtifact(result, clean, repoRoot, options = {}) {
|
|
381
|
+
const rawOutput = clean?.cleanOutput || result?.stdout || result?.stderr || '';
|
|
382
|
+
const redactedOutput = redactSensitiveLocalValues(rawOutput, { projectRoot: repoRoot });
|
|
383
|
+
const limited = limitProviderArtifactText(redactedOutput, options.maxBytes || 12_000);
|
|
384
|
+
return {
|
|
385
|
+
schema_version: 1,
|
|
386
|
+
kind: 'quiver-analyze-project-provider-artifact',
|
|
387
|
+
persisted: false,
|
|
388
|
+
redacted: true,
|
|
389
|
+
size_limited: true,
|
|
390
|
+
provider: result?.provider || null,
|
|
391
|
+
command: result?.command || null,
|
|
392
|
+
exit_code: typeof result?.exitCode === 'number' ? result.exitCode : null,
|
|
393
|
+
output_source: clean?.source || 'unknown',
|
|
394
|
+
output_bytes: limited.bytes,
|
|
395
|
+
output_truncated: limited.truncated,
|
|
396
|
+
output: limited.text,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function formatAnalyzeProjectLiveReport(report) {
|
|
401
|
+
const warningCount = report.analysis_validation?.warnings?.length || 0;
|
|
402
|
+
const docUpdatePaths = report.analysis_validation?.doc_update_paths || [];
|
|
403
|
+
const lines = [
|
|
404
|
+
'AI analyze-project provider analysis',
|
|
405
|
+
`Mode: ${report.mode}`,
|
|
406
|
+
`Provider: ${report.provider}`,
|
|
407
|
+
`Provider execution: ${report.provider_execution}`,
|
|
408
|
+
`Writes: ${report.writes.length === 0 ? 'none' : report.writes.join(', ')}`,
|
|
409
|
+
`Privacy preflight: ${report.privacy_preflight.ok ? 'passed' : 'failed'}`,
|
|
410
|
+
`Prompt bytes: ${report.prompt.bytes}/${report.prompt.max_provider_prompt_bytes}`,
|
|
411
|
+
`Selected files: ${report.selected_files.length}`,
|
|
412
|
+
`Omitted files: ${report.omitted_files.length}`,
|
|
413
|
+
`Safety exclusions: ${report.safety_exclusions.length}`,
|
|
414
|
+
`Analysis validation: passed (${warningCount} warning${warningCount === 1 ? '' : 's'})`,
|
|
415
|
+
`Doc update proposals: ${docUpdatePaths.length > 0 ? docUpdatePaths.join(', ') : 'none'}`,
|
|
416
|
+
];
|
|
417
|
+
|
|
418
|
+
if (warningCount > 0) {
|
|
419
|
+
lines.push('', 'Warnings:');
|
|
420
|
+
for (const warning of report.analysis_validation.warnings.slice(0, 20)) {
|
|
421
|
+
lines.push(`- ${warning.path}: ${warning.issue}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
lines.push('', 'Next commands:');
|
|
426
|
+
lines.push('- npx create-quiver ai analyze-project --deep --review');
|
|
427
|
+
lines.push('- npx create-quiver ai analyze-project --deep --json');
|
|
428
|
+
|
|
429
|
+
return `${lines.join('\n')}\n`;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function formatAnalyzeProjectPostWriteValidation(validation) {
|
|
433
|
+
if (!validation) {
|
|
434
|
+
return [];
|
|
435
|
+
}
|
|
436
|
+
const warningCount = validation.warnings?.length || 0;
|
|
437
|
+
const errorCount = validation.errors?.length || 0;
|
|
438
|
+
const lines = [
|
|
439
|
+
`Post-write validation: ${validation.ok ? 'passed' : 'failed'} (${errorCount} error${errorCount === 1 ? '' : 's'}, ${warningCount} warning${warningCount === 1 ? '' : 's'})`,
|
|
440
|
+
];
|
|
441
|
+
for (const issue of [...(validation.errors || []), ...(validation.warnings || [])].slice(0, 20)) {
|
|
442
|
+
lines.push(`- ${issue.path || 'analysis'}: ${issue.issue} - ${issue.message}`);
|
|
443
|
+
}
|
|
444
|
+
return lines;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function formatAnalyzeProjectReviewPlan({ writePlan, reviewPath, snapshot, writtenDocs, completed = false, validation = null } = {}) {
|
|
448
|
+
const changed = (writePlan || []).filter((item) => item.action !== 'skip');
|
|
449
|
+
const dirty = changed.filter((item) => item.dirty);
|
|
450
|
+
const lines = [
|
|
451
|
+
completed ? 'AI analyze-project docs written' : 'AI analyze-project review write plan',
|
|
452
|
+
`Review artifact: ${reviewPath || 'none'}`,
|
|
453
|
+
`Writes: ${changed.length > 0 ? changed.map((item) => item.path).join(', ') : 'none'}`,
|
|
454
|
+
`Dirty target docs: ${dirty.length > 0 ? dirty.map((item) => item.path).join(', ') : 'none'}`,
|
|
455
|
+
];
|
|
456
|
+
|
|
457
|
+
if (snapshot) {
|
|
458
|
+
lines.push(`Snapshot: ${snapshot.root}`);
|
|
459
|
+
lines.push(`Manifest: ${snapshot.manifestPath}`);
|
|
460
|
+
}
|
|
461
|
+
if (completed) {
|
|
462
|
+
lines.push(`Written docs: ${writtenDocs && writtenDocs.length > 0 ? writtenDocs.join(', ') : 'none'}`);
|
|
463
|
+
lines.push(...formatAnalyzeProjectPostWriteValidation(validation));
|
|
464
|
+
return `${lines.join('\n')}\n`;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
lines.push('', 'Proposed changes:');
|
|
468
|
+
for (const item of writePlan || []) {
|
|
469
|
+
lines.push(`- ${item.path}: ${item.action}${item.reason ? ` (${item.reason})` : ''}`);
|
|
470
|
+
}
|
|
471
|
+
lines.push('', 'Final diff:');
|
|
472
|
+
lines.push(...formatAnalyzeProjectDiffPreview(writePlan || []));
|
|
473
|
+
lines.push('', 'Confirmation required before writing.');
|
|
474
|
+
|
|
475
|
+
return `${lines.join('\n')}\n`;
|
|
476
|
+
}
|
|
477
|
+
|
|
111
478
|
function formatLocalizedActionableError({ failure, impact, fix, nextCommand } = {}, options = {}) {
|
|
112
479
|
const translator = createTranslator(options.language);
|
|
113
480
|
const lines = [`create-quiver: ${String(failure || 'operation failed').trim()}`];
|
|
@@ -1541,6 +1908,225 @@ async function runPrepareContext(repoRoot, options = {}) {
|
|
|
1541
1908
|
};
|
|
1542
1909
|
}
|
|
1543
1910
|
|
|
1911
|
+
async function runAnalyzeProject(repoRoot, options = {}) {
|
|
1912
|
+
const report = buildAnalyzeProjectReport(repoRoot, options);
|
|
1913
|
+
if (options.dryRun === true) {
|
|
1914
|
+
if (options.json === true) {
|
|
1915
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
1916
|
+
return report;
|
|
1917
|
+
}
|
|
1918
|
+
process.stdout.write(formatAnalyzeProjectReport(report));
|
|
1919
|
+
return report;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
|
|
1923
|
+
const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
|
|
1924
|
+
const provider = runtimeProfile.provider;
|
|
1925
|
+
const timeoutMs = normalizeTimeout(options.timeout);
|
|
1926
|
+
const ux = createCommandUx(options);
|
|
1927
|
+
const showProgress = shouldShowHumanProgress(ux, options);
|
|
1928
|
+
const progressTranslator = createTranslator(options.language);
|
|
1929
|
+
const promptPackage = buildAnalyzeProjectPrompt({
|
|
1930
|
+
analysisPlan: report,
|
|
1931
|
+
repoRoot,
|
|
1932
|
+
maxFileBytes: options.maxPromptFileBytes,
|
|
1933
|
+
maxTotalFileBytes: options.maxPromptTotalBytes,
|
|
1934
|
+
});
|
|
1935
|
+
const prompt = promptPackage.prompt;
|
|
1936
|
+
const promptLimit = assertProviderPromptWithinLimit(prompt, options.promptLimitOptions || {});
|
|
1937
|
+
const privacyPreflight = promptPackage.privacyPreflight;
|
|
1938
|
+
|
|
1939
|
+
writeProgressChecks(
|
|
1940
|
+
ux,
|
|
1941
|
+
showProgress,
|
|
1942
|
+
plannerProgressTitle(progressTranslator.t('ai.planner.progress.analyze_project'), runtimeProfile, options),
|
|
1943
|
+
[
|
|
1944
|
+
progressTranslator.t('ai.planner.progress.reading_base_docs'),
|
|
1945
|
+
progressTranslator.t('ai.planner.progress.detecting_structure'),
|
|
1946
|
+
progressTranslator.t('ai.planner.progress.preparing_prompt'),
|
|
1947
|
+
],
|
|
1948
|
+
);
|
|
1949
|
+
|
|
1950
|
+
if (!privacyPreflight.ok) {
|
|
1951
|
+
const error = new Error(formatError('ai analyze-project privacy preflight failed; provider execution was blocked before sending repository content.'));
|
|
1952
|
+
error.code = 'AI_ANALYZE_PROJECT_PRIVACY_PREFLIGHT_FAILED';
|
|
1953
|
+
error.details = privacyPreflight;
|
|
1954
|
+
throw error;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
let invocation;
|
|
1958
|
+
try {
|
|
1959
|
+
invocation = buildProviderInvocation(provider, {
|
|
1960
|
+
prompt,
|
|
1961
|
+
cwd: repoRoot,
|
|
1962
|
+
timeoutMs,
|
|
1963
|
+
...runtimeModelExecutionOptions(runtimeProfile, options),
|
|
1964
|
+
enforceModelSelection: false,
|
|
1965
|
+
});
|
|
1966
|
+
} catch (error) {
|
|
1967
|
+
throw annotateProviderError(error, 'analyze-project');
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
if (options.printPrompt) {
|
|
1971
|
+
process.stdout.write(prompt.endsWith('\n') ? prompt : `${prompt}\n`);
|
|
1972
|
+
return {
|
|
1973
|
+
...report,
|
|
1974
|
+
provider,
|
|
1975
|
+
role,
|
|
1976
|
+
invocation,
|
|
1977
|
+
privacy_preflight: privacyPreflight,
|
|
1978
|
+
prompt: {
|
|
1979
|
+
bytes: promptLimit.bytes,
|
|
1980
|
+
max_provider_prompt_bytes: promptLimit.maxProviderPromptBytes,
|
|
1981
|
+
files: promptPackage.files,
|
|
1982
|
+
},
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
let result;
|
|
1987
|
+
try {
|
|
1988
|
+
result = await runProviderWithProgress({
|
|
1989
|
+
ux,
|
|
1990
|
+
enabled: showProgress,
|
|
1991
|
+
message: progressTranslator.t('ai.planner.progress.running_agent'),
|
|
1992
|
+
successMessage: progressTranslator.t('ai.planner.progress.agent_finished'),
|
|
1993
|
+
failureMessage: progressTranslator.t('ai.planner.progress.agent_failed'),
|
|
1994
|
+
run: () => (options.runProviderFn || runProvider)(provider, {
|
|
1995
|
+
prompt,
|
|
1996
|
+
cwd: repoRoot,
|
|
1997
|
+
timeoutMs,
|
|
1998
|
+
dryRun: false,
|
|
1999
|
+
probe: options.probe,
|
|
2000
|
+
spawn: options.spawn,
|
|
2001
|
+
tempRoot: options.tempRoot,
|
|
2002
|
+
tempFileName: options.tempFileName,
|
|
2003
|
+
tempFilePrefix: options.tempFilePrefix,
|
|
2004
|
+
...runtimeModelExecutionOptions(runtimeProfile, options),
|
|
2005
|
+
enforceModelSelection: false,
|
|
2006
|
+
}),
|
|
2007
|
+
});
|
|
2008
|
+
} catch (error) {
|
|
2009
|
+
throw annotateProviderError(error, 'analyze-project');
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
if (!result.ok) {
|
|
2013
|
+
throw annotateProviderError(result.error || new Error('provider run failed'), 'analyze-project');
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
const clean = extractCleanProviderOutput(result, { prompt, projectRoot: repoRoot });
|
|
2017
|
+
let parsed;
|
|
2018
|
+
try {
|
|
2019
|
+
parsed = parseAnalyzeProjectOutput(clean.cleanOutput, {
|
|
2020
|
+
selectedFiles: report.selected_files,
|
|
2021
|
+
promptFiles: promptPackage.files,
|
|
2022
|
+
});
|
|
2023
|
+
} catch (error) {
|
|
2024
|
+
throw enhanceAnalyzeProjectAnalysisError(error);
|
|
2025
|
+
}
|
|
2026
|
+
const completedReport = {
|
|
2027
|
+
...report,
|
|
2028
|
+
provider,
|
|
2029
|
+
role,
|
|
2030
|
+
provider_execution: 'completed',
|
|
2031
|
+
invocation,
|
|
2032
|
+
privacy_preflight: privacyPreflight,
|
|
2033
|
+
prompt: {
|
|
2034
|
+
bytes: promptLimit.bytes,
|
|
2035
|
+
max_provider_prompt_bytes: promptLimit.maxProviderPromptBytes,
|
|
2036
|
+
files: promptPackage.files,
|
|
2037
|
+
},
|
|
2038
|
+
analysis: parsed.analysis,
|
|
2039
|
+
analysis_validation: {
|
|
2040
|
+
parse_source: parsed.parseSource,
|
|
2041
|
+
warnings: parsed.warnings,
|
|
2042
|
+
doc_update_paths: parsed.docUpdatePaths,
|
|
2043
|
+
},
|
|
2044
|
+
provider_artifact: buildAnalyzeProjectProviderArtifact(result, clean, repoRoot),
|
|
2045
|
+
};
|
|
2046
|
+
|
|
2047
|
+
if (options.review === true) {
|
|
2048
|
+
const initialProposal = buildAnalyzeProjectDocProposal(parsed.analysis);
|
|
2049
|
+
const reviewed = await reviewAnalyzeProjectDocProposal(repoRoot, initialProposal, options);
|
|
2050
|
+
const writePlan = buildAnalyzeProjectWritePlan(repoRoot, reviewed.proposal);
|
|
2051
|
+
process.stdout.write(formatAnalyzeProjectReviewPlan({
|
|
2052
|
+
writePlan,
|
|
2053
|
+
reviewPath: reviewed.reviewPath,
|
|
2054
|
+
}));
|
|
2055
|
+
await confirmAnalyzeProjectWrites(writePlan, options);
|
|
2056
|
+
const lifecycleRun = ensureAiRun(repoRoot, {
|
|
2057
|
+
command: 'ai analyze-project',
|
|
2058
|
+
input: reviewed.reviewPath,
|
|
2059
|
+
runId: options.runId,
|
|
2060
|
+
phase: 'created',
|
|
2061
|
+
});
|
|
2062
|
+
const snapshot = createAnalyzeProjectSnapshot(repoRoot, lifecycleRun, writePlan, {
|
|
2063
|
+
providerArtifact: completedReport.provider_artifact,
|
|
2064
|
+
proposal: reviewed.proposal,
|
|
2065
|
+
now: options.now || new Date(),
|
|
2066
|
+
});
|
|
2067
|
+
const writtenDocs = writeAnalyzeProjectDocs(writePlan);
|
|
2068
|
+
let writeReport = {
|
|
2069
|
+
...completedReport,
|
|
2070
|
+
review: true,
|
|
2071
|
+
review_path: reviewed.reviewPath,
|
|
2072
|
+
doc_proposal: reviewed.proposal,
|
|
2073
|
+
write_plan: writePlan.map((item) => ({
|
|
2074
|
+
path: item.path,
|
|
2075
|
+
action: item.action,
|
|
2076
|
+
dirty: item.dirty,
|
|
2077
|
+
before_sha256: item.before_sha256,
|
|
2078
|
+
after_sha256: item.after_sha256,
|
|
2079
|
+
reason: item.reason,
|
|
2080
|
+
})),
|
|
2081
|
+
snapshot,
|
|
2082
|
+
written_docs: writtenDocs,
|
|
2083
|
+
run_id: lifecycleRun.run_id,
|
|
2084
|
+
};
|
|
2085
|
+
const postWriteValidation = validateAnalyzeProjectPostWrite(repoRoot, writeReport, {
|
|
2086
|
+
strict: options.strict === true,
|
|
2087
|
+
});
|
|
2088
|
+
writeReport = {
|
|
2089
|
+
...writeReport,
|
|
2090
|
+
post_write_validation: postWriteValidation,
|
|
2091
|
+
};
|
|
2092
|
+
|
|
2093
|
+
if (options.json === true) {
|
|
2094
|
+
process.stdout.write(`${JSON.stringify(writeReport, null, 2)}\n`);
|
|
2095
|
+
if (!postWriteValidation.ok) {
|
|
2096
|
+
const error = new Error(formatError('ai analyze-project post-write validation failed.'));
|
|
2097
|
+
error.code = 'AI_ANALYZE_PROJECT_POST_WRITE_VALIDATION_FAILED';
|
|
2098
|
+
error.validation = postWriteValidation;
|
|
2099
|
+
throw error;
|
|
2100
|
+
}
|
|
2101
|
+
return writeReport;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
process.stdout.write(formatAnalyzeProjectReviewPlan({
|
|
2105
|
+
writePlan,
|
|
2106
|
+
reviewPath: reviewed.reviewPath,
|
|
2107
|
+
snapshot,
|
|
2108
|
+
writtenDocs,
|
|
2109
|
+
validation: postWriteValidation,
|
|
2110
|
+
completed: true,
|
|
2111
|
+
}));
|
|
2112
|
+
if (!postWriteValidation.ok) {
|
|
2113
|
+
const error = new Error(formatError('ai analyze-project post-write validation failed.'));
|
|
2114
|
+
error.code = 'AI_ANALYZE_PROJECT_POST_WRITE_VALIDATION_FAILED';
|
|
2115
|
+
error.validation = postWriteValidation;
|
|
2116
|
+
throw error;
|
|
2117
|
+
}
|
|
2118
|
+
return writeReport;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
if (options.json === true) {
|
|
2122
|
+
process.stdout.write(`${JSON.stringify(completedReport, null, 2)}\n`);
|
|
2123
|
+
return completedReport;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
process.stdout.write(formatAnalyzeProjectLiveReport(completedReport));
|
|
2127
|
+
return completedReport;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
1544
2130
|
async function runPrepareContextWithPlanner(repoRoot, options = {}) {
|
|
1545
2131
|
const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
|
|
1546
2132
|
const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
|
|
@@ -3412,6 +3998,7 @@ module.exports = {
|
|
|
3412
3998
|
resolveInteractiveAgentSetOptions,
|
|
3413
3999
|
runAgent,
|
|
3414
4000
|
runActiveSlice,
|
|
4001
|
+
runAnalyzeProject,
|
|
3415
4002
|
runDoctor,
|
|
3416
4003
|
runExecutePlan,
|
|
3417
4004
|
runExecuteSlice,
|