create-quiver 0.17.0 → 0.17.2
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/TROUBLESHOOTING.md +53 -0
- package/docs/reference/commands.md +42 -0
- package/docs/workflows/existing-project-ai-quiver-setup.md +552 -0
- package/docs/workflows/existing-project.md +21 -0
- package/package.json +1 -1
- package/specs/quiver-v43-cli-i18n-audit-release-readiness/command-language-mode-matrix.json +1 -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/src/create-quiver/commands/ai.js +383 -34
- package/src/create-quiver/index.js +3 -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-parser.js +1 -0
- package/src/create-quiver/lib/ai/analyze-project-prompts.js +130 -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 +168 -0
- package/src/create-quiver/lib/ai/artifacts.js +83 -2
- package/src/create-quiver/lib/doctor.js +107 -12
- package/src/create-quiver/lib/i18n/messages/en.js +6 -0
- package/src/create-quiver/lib/i18n/messages/es.js +6 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
AnalyzeProjectAnalysisError,
|
|
6
|
+
normalizeAnalyzeProjectAnalysis,
|
|
7
|
+
parseProviderAnalysisJson,
|
|
8
|
+
} = require('./analyze-project-parser');
|
|
9
|
+
const {
|
|
10
|
+
buildQuiverInternalGitignore,
|
|
11
|
+
quiverInternalPaths,
|
|
12
|
+
} = require('../init-layout');
|
|
13
|
+
|
|
14
|
+
const REPAIR_MANIFEST_KIND = 'quiver-analyze-project-repair-manifest';
|
|
15
|
+
const SAFE_ADDITIONAL_PROPERTY_KEYS = ['claim', 'confidence', 'notes'];
|
|
16
|
+
const DEFAULT_REPAIR_LIMITS = {
|
|
17
|
+
maxEntries: 80,
|
|
18
|
+
maxPerPathFamily: 40,
|
|
19
|
+
};
|
|
20
|
+
const NAMED_FINDING_PATH_PATTERNS = [
|
|
21
|
+
/^product\.(?:name|type)$/,
|
|
22
|
+
/^domain\.(?:roles|entities|actions|flows|incomplete_or_suspicious)\.\d+$/,
|
|
23
|
+
/^architecture\.(?:frontend|backend|auth|persistence|integrations|state|api|testing|deploy|risks)\.\d+$/,
|
|
24
|
+
/^(?:features|risks)\.\d+$/,
|
|
25
|
+
];
|
|
26
|
+
const QUESTION_PATH_PATTERN = /^questions\.\d+$/;
|
|
27
|
+
|
|
28
|
+
function toPosix(filePath) {
|
|
29
|
+
return String(filePath || '').replace(/\\/g, '/');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function cloneJson(value) {
|
|
33
|
+
return JSON.parse(JSON.stringify(value));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeRunId(value, now = new Date()) {
|
|
37
|
+
const raw = value || `run-${now.toISOString()
|
|
38
|
+
.replace(/\.\d{3}Z$/, 'z')
|
|
39
|
+
.replace(/[^0-9a-z]+/gi, '-')
|
|
40
|
+
.toLowerCase()
|
|
41
|
+
.replace(/^-+|-+$/g, '')}`;
|
|
42
|
+
return String(raw)
|
|
43
|
+
.trim()
|
|
44
|
+
.toLowerCase()
|
|
45
|
+
.replace(/[^a-z0-9._-]+/g, '-')
|
|
46
|
+
.replace(/^-+|-+$/g, '') || 'run-analyze-project-repair';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function ensureQuiverRunsIgnored(repoRoot) {
|
|
50
|
+
const internalPaths = quiverInternalPaths(repoRoot);
|
|
51
|
+
if (!fs.existsSync(internalPaths.root)) {
|
|
52
|
+
fs.mkdirSync(internalPaths.root, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
if (!fs.existsSync(internalPaths.gitignorePath)) {
|
|
55
|
+
fs.writeFileSync(internalPaths.gitignorePath, buildQuiverInternalGitignore());
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getValueAtPath(root, pathName) {
|
|
60
|
+
if (!pathName) {
|
|
61
|
+
return root;
|
|
62
|
+
}
|
|
63
|
+
const parts = String(pathName).split('.').filter(Boolean);
|
|
64
|
+
let current = root;
|
|
65
|
+
for (const part of parts) {
|
|
66
|
+
if (current === null || typeof current !== 'object') {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const key = /^\d+$/.test(part) ? Number(part) : part;
|
|
70
|
+
current = current[key];
|
|
71
|
+
}
|
|
72
|
+
return current;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function extractUnrecognizedKeys(issue) {
|
|
76
|
+
if (Array.isArray(issue?.keys)) {
|
|
77
|
+
return issue.keys.map(String);
|
|
78
|
+
}
|
|
79
|
+
const message = String(issue?.message || '');
|
|
80
|
+
return Array.from(message.matchAll(/"([^"]+)"/g)).map((match) => match[1]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeIssuePath(issue) {
|
|
84
|
+
return issue?.path ? String(issue.path) : '';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isNamedFindingPath(pathName) {
|
|
88
|
+
return NAMED_FINDING_PATH_PATTERNS.some((pattern) => pattern.test(String(pathName || '')));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isQuestionPath(pathName) {
|
|
92
|
+
return QUESTION_PATH_PATTERN.test(String(pathName || ''));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isMissingNameIssue(issue) {
|
|
96
|
+
const pathName = normalizeIssuePath(issue);
|
|
97
|
+
return issue?.issue === 'invalid_type'
|
|
98
|
+
&& pathName.endsWith('.name')
|
|
99
|
+
&& /(?:expected string|required|received undefined)/i.test(String(issue?.message || ''));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getParentPath(pathName) {
|
|
103
|
+
return String(pathName || '').replace(/\.[^.]+$/, '');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getPathFamily(pathName) {
|
|
107
|
+
return String(pathName || 'analysis').replace(/\.\d+(?=\.|$)/g, '.*');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function resolveRepairLimits(options = {}) {
|
|
111
|
+
const maxEntries = Number(options.maxEntries ?? DEFAULT_REPAIR_LIMITS.maxEntries);
|
|
112
|
+
const maxPerPathFamily = Number(options.maxPerPathFamily ?? DEFAULT_REPAIR_LIMITS.maxPerPathFamily);
|
|
113
|
+
return {
|
|
114
|
+
maxEntries: Number.isFinite(maxEntries) && maxEntries > 0
|
|
115
|
+
? Math.floor(maxEntries)
|
|
116
|
+
: DEFAULT_REPAIR_LIMITS.maxEntries,
|
|
117
|
+
maxPerPathFamily: Number.isFinite(maxPerPathFamily) && maxPerPathFamily > 0
|
|
118
|
+
? Math.floor(maxPerPathFamily)
|
|
119
|
+
: DEFAULT_REPAIR_LIMITS.maxPerPathFamily,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function makeNotRepaired({ value, issues, reason }) {
|
|
124
|
+
return {
|
|
125
|
+
repaired: false,
|
|
126
|
+
value,
|
|
127
|
+
manifest: makeRepairManifest({
|
|
128
|
+
status: 'not-repaired',
|
|
129
|
+
issues,
|
|
130
|
+
reason,
|
|
131
|
+
}),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function makeRepairManifest({ status, entries = [], issues = [], reason = '' } = {}) {
|
|
136
|
+
return {
|
|
137
|
+
schema_version: 1,
|
|
138
|
+
kind: REPAIR_MANIFEST_KIND,
|
|
139
|
+
status,
|
|
140
|
+
strategy: 'path-aware-safe-schema-drift-repair',
|
|
141
|
+
safe_keys: SAFE_ADDITIONAL_PROPERTY_KEYS,
|
|
142
|
+
reason,
|
|
143
|
+
entry_count: entries.length,
|
|
144
|
+
entries,
|
|
145
|
+
issue_count: issues.length,
|
|
146
|
+
issues,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function assertRepairBudget(entries, issues, value, limits) {
|
|
151
|
+
if (entries.length > limits.maxEntries) {
|
|
152
|
+
return makeNotRepaired({
|
|
153
|
+
value,
|
|
154
|
+
issues,
|
|
155
|
+
reason: `repair budget exceeded: ${entries.length} entries exceeds max ${limits.maxEntries}`,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const familyCounts = new Map();
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
const family = getPathFamily(entry.path);
|
|
162
|
+
familyCounts.set(family, (familyCounts.get(family) || 0) + 1);
|
|
163
|
+
if (familyCounts.get(family) > limits.maxPerPathFamily) {
|
|
164
|
+
return makeNotRepaired({
|
|
165
|
+
value,
|
|
166
|
+
issues,
|
|
167
|
+
reason: `repair budget exceeded for ${family}: ${familyCounts.get(family)} entries exceeds max ${limits.maxPerPathFamily}`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function repairAnalyzeProjectValue(value, issues = [], options = {}) {
|
|
176
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
177
|
+
return makeNotRepaired({
|
|
178
|
+
value,
|
|
179
|
+
issues,
|
|
180
|
+
reason: 'analysis value is not an object',
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const issueList = Array.isArray(issues) ? issues : [];
|
|
185
|
+
const repairIssues = issueList.filter((issue) => issue.issue === 'unrecognized_keys');
|
|
186
|
+
const missingNameIssues = issueList.filter(isMissingNameIssue);
|
|
187
|
+
const limits = resolveRepairLimits(options);
|
|
188
|
+
|
|
189
|
+
if (repairIssues.length === 0) {
|
|
190
|
+
return makeNotRepaired({
|
|
191
|
+
value,
|
|
192
|
+
issues,
|
|
193
|
+
reason: 'no safe repairable schema drift issues were found',
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const keysByIssue = repairIssues.map((issue) => ({
|
|
198
|
+
issue,
|
|
199
|
+
keys: extractUnrecognizedKeys(issue),
|
|
200
|
+
}));
|
|
201
|
+
const allKeys = keysByIssue.flatMap((item) => item.keys);
|
|
202
|
+
if (allKeys.length === 0 || !allKeys.every((key) => SAFE_ADDITIONAL_PROPERTY_KEYS.includes(key))) {
|
|
203
|
+
return makeNotRepaired({
|
|
204
|
+
value,
|
|
205
|
+
issues,
|
|
206
|
+
reason: 'additional-property keys are not in the safe repair allowlist',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const repairedValue = cloneJson(value);
|
|
211
|
+
const entries = [];
|
|
212
|
+
const coveredIssueIndexes = new Set();
|
|
213
|
+
const claimMappedPaths = new Set();
|
|
214
|
+
const missingNamePaths = new Set(missingNameIssues.map((issue) => getParentPath(issue.path)));
|
|
215
|
+
for (const { issue, keys } of keysByIssue) {
|
|
216
|
+
const target = getValueAtPath(repairedValue, issue.path);
|
|
217
|
+
if (!target || typeof target !== 'object' || Array.isArray(target)) {
|
|
218
|
+
return makeNotRepaired({
|
|
219
|
+
value,
|
|
220
|
+
issues,
|
|
221
|
+
reason: `repair target is not an object at ${issue.path || 'analysis'}`,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for (const key of keys) {
|
|
226
|
+
if (!Object.hasOwn(target, key)) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (key === 'claim') {
|
|
231
|
+
if (!isNamedFindingPath(issue.path)) {
|
|
232
|
+
return makeNotRepaired({
|
|
233
|
+
value,
|
|
234
|
+
issues,
|
|
235
|
+
reason: `claim can only be mapped to name on named finding paths, got ${issue.path || 'analysis'}`,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (Object.hasOwn(target, 'name')) {
|
|
239
|
+
return makeNotRepaired({
|
|
240
|
+
value,
|
|
241
|
+
issues,
|
|
242
|
+
reason: `claim cannot be mapped because name already exists at ${issue.path || 'analysis'}`,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
if (!missingNamePaths.has(issue.path)) {
|
|
246
|
+
return makeNotRepaired({
|
|
247
|
+
value,
|
|
248
|
+
issues,
|
|
249
|
+
reason: `claim cannot be mapped because schema did not report a missing name at ${issue.path || 'analysis'}`,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const claim = String(target.claim || '').trim();
|
|
253
|
+
if (!claim) {
|
|
254
|
+
return makeNotRepaired({
|
|
255
|
+
value,
|
|
256
|
+
issues,
|
|
257
|
+
reason: `claim cannot be mapped because it is empty at ${issue.path || 'analysis'}`,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
target.name = claim;
|
|
262
|
+
delete target.claim;
|
|
263
|
+
claimMappedPaths.add(issue.path);
|
|
264
|
+
entries.push({
|
|
265
|
+
path: issue.path || null,
|
|
266
|
+
source_key: 'claim',
|
|
267
|
+
target_key: 'name',
|
|
268
|
+
action: 'mapped',
|
|
269
|
+
reason: 'named-finding-name-missing-and-claim-is-safe-string',
|
|
270
|
+
});
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (key === 'confidence' && !isQuestionPath(issue.path)) {
|
|
275
|
+
return makeNotRepaired({
|
|
276
|
+
value,
|
|
277
|
+
issues,
|
|
278
|
+
reason: `confidence can only be removed from unsupported question paths, got ${issue.path || 'analysis'}`,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
delete target[key];
|
|
283
|
+
entries.push({
|
|
284
|
+
path: issue.path || null,
|
|
285
|
+
key,
|
|
286
|
+
action: 'removed',
|
|
287
|
+
reason: key === 'confidence'
|
|
288
|
+
? 'unsupported-question-confidence-property'
|
|
289
|
+
: 'unsupported-additional-property',
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (entries.length === 0) {
|
|
295
|
+
return makeNotRepaired({
|
|
296
|
+
value,
|
|
297
|
+
issues,
|
|
298
|
+
reason: 'no safe additional properties were present on target objects',
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
for (const [index, issue] of issueList.entries()) {
|
|
303
|
+
if (issue.issue === 'unrecognized_keys') {
|
|
304
|
+
coveredIssueIndexes.add(index);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (isMissingNameIssue(issue) && claimMappedPaths.has(getParentPath(issue.path))) {
|
|
308
|
+
coveredIssueIndexes.add(index);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (coveredIssueIndexes.size !== issueList.length) {
|
|
313
|
+
return makeNotRepaired({
|
|
314
|
+
value,
|
|
315
|
+
issues,
|
|
316
|
+
reason: 'one or more issues are not covered by safe schema drift repair rules',
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const budgetError = assertRepairBudget(entries, issues, value, limits);
|
|
321
|
+
if (budgetError) {
|
|
322
|
+
return budgetError;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
repaired: true,
|
|
327
|
+
value: repairedValue,
|
|
328
|
+
manifest: makeRepairManifest({
|
|
329
|
+
status: 'repaired',
|
|
330
|
+
entries,
|
|
331
|
+
issues,
|
|
332
|
+
reason: 'applied only safe path-aware schema drift repairs',
|
|
333
|
+
}),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function parseAnalyzeProjectOutputWithRepair(text, options = {}) {
|
|
338
|
+
const parsed = parseProviderAnalysisJson(text);
|
|
339
|
+
try {
|
|
340
|
+
const normalized = normalizeAnalyzeProjectAnalysis(parsed.value, options);
|
|
341
|
+
return {
|
|
342
|
+
...normalized,
|
|
343
|
+
parseSource: parsed.source,
|
|
344
|
+
repaired: false,
|
|
345
|
+
repairManifest: null,
|
|
346
|
+
};
|
|
347
|
+
} catch (error) {
|
|
348
|
+
if (!(error instanceof AnalyzeProjectAnalysisError)) {
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const repair = repairAnalyzeProjectValue(parsed.value, error.issues);
|
|
353
|
+
if (!repair.repaired) {
|
|
354
|
+
error.repair_manifest = repair.manifest;
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
const normalized = normalizeAnalyzeProjectAnalysis(repair.value, options);
|
|
360
|
+
return {
|
|
361
|
+
...normalized,
|
|
362
|
+
parseSource: parsed.source,
|
|
363
|
+
repaired: true,
|
|
364
|
+
repairManifest: repair.manifest,
|
|
365
|
+
};
|
|
366
|
+
} catch (repairError) {
|
|
367
|
+
repairError.repair_manifest = repair.manifest;
|
|
368
|
+
throw repairError;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function writeAnalyzeProjectRepairManifest(repoRoot, manifest, options = {}) {
|
|
374
|
+
const now = options.now instanceof Date ? options.now : new Date(options.now || Date.now());
|
|
375
|
+
const runId = normalizeRunId(options.runId, now);
|
|
376
|
+
const internalPaths = quiverInternalPaths(repoRoot);
|
|
377
|
+
const manifestPath = path.join(internalPaths.runsDir, runId, 'repair', 'analyze-project-repair.json');
|
|
378
|
+
const value = {
|
|
379
|
+
...manifest,
|
|
380
|
+
created_at: manifest?.created_at || now.toISOString(),
|
|
381
|
+
run_id: runId,
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
ensureQuiverRunsIgnored(repoRoot);
|
|
385
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
386
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(value, null, 2)}\n`);
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
run_id: runId,
|
|
390
|
+
path: toPosix(path.relative(repoRoot, manifestPath)),
|
|
391
|
+
status: value.status,
|
|
392
|
+
entry_count: value.entry_count,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
module.exports = {
|
|
397
|
+
REPAIR_MANIFEST_KIND,
|
|
398
|
+
SAFE_ADDITIONAL_PROPERTY_KEYS,
|
|
399
|
+
parseAnalyzeProjectOutputWithRepair,
|
|
400
|
+
repairAnalyzeProjectValue,
|
|
401
|
+
writeAnalyzeProjectRepairManifest,
|
|
402
|
+
};
|
|
@@ -96,6 +96,15 @@ const DOC_BASENAMES = new Set([
|
|
|
96
96
|
'readme.md',
|
|
97
97
|
]);
|
|
98
98
|
|
|
99
|
+
const QUIVER_GENERATED_DOC_PATHS = new Set([
|
|
100
|
+
'docs/AI_CONTEXT.md',
|
|
101
|
+
'docs/ARCHITECTURE.md',
|
|
102
|
+
'docs/CONTEXTO.md',
|
|
103
|
+
'docs/DECISIONS.md',
|
|
104
|
+
'docs/PROJECT_MAP.md',
|
|
105
|
+
'docs/STATUS.md',
|
|
106
|
+
]);
|
|
107
|
+
|
|
99
108
|
function toPosix(filePath) {
|
|
100
109
|
return String(filePath || '').replace(/\\/g, '/');
|
|
101
110
|
}
|
|
@@ -109,8 +118,14 @@ function basename(filePath) {
|
|
|
109
118
|
return path.posix.basename(toPosix(filePath));
|
|
110
119
|
}
|
|
111
120
|
|
|
112
|
-
function
|
|
113
|
-
|
|
121
|
+
function isLockfilePath(filePath) {
|
|
122
|
+
const base = basename(filePath);
|
|
123
|
+
const baseLower = base.toLowerCase();
|
|
124
|
+
return LOCKFILE_BASENAMES.has(base) || LOCKFILE_BASENAMES.has(baseLower);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isQuiverGeneratedDocPath(filePath) {
|
|
128
|
+
return QUIVER_GENERATED_DOC_PATHS.has(toPosix(filePath));
|
|
114
129
|
}
|
|
115
130
|
|
|
116
131
|
function hasPathSegment(filePath, segments) {
|
|
@@ -210,7 +225,7 @@ function classifyProjectFile(filePath) {
|
|
|
210
225
|
score += 95;
|
|
211
226
|
}
|
|
212
227
|
|
|
213
|
-
if (
|
|
228
|
+
if (isLockfilePath(normalized)) {
|
|
214
229
|
signals.push('lockfile');
|
|
215
230
|
score += 20;
|
|
216
231
|
}
|
|
@@ -220,6 +235,11 @@ function classifyProjectFile(filePath) {
|
|
|
220
235
|
score += 45;
|
|
221
236
|
}
|
|
222
237
|
|
|
238
|
+
if (isQuiverGeneratedDocPath(normalized)) {
|
|
239
|
+
signals.push('quiver-doc');
|
|
240
|
+
score -= 35;
|
|
241
|
+
}
|
|
242
|
+
|
|
223
243
|
if (matchesEntrypoint(normalized)) {
|
|
224
244
|
signals.push('entrypoint');
|
|
225
245
|
score += 45;
|
|
@@ -269,7 +289,11 @@ function classifyProjectFile(filePath) {
|
|
|
269
289
|
|
|
270
290
|
function isAllowedByOptions(classification, options) {
|
|
271
291
|
const signals = classification.signals;
|
|
272
|
-
if (signals.includes('
|
|
292
|
+
if (signals.includes('lockfile') && options.includeLockfiles !== true) {
|
|
293
|
+
return { allowed: false, reason: 'sampling:lockfile-metadata' };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (signals.includes('config') || signals.includes('docs')) {
|
|
273
297
|
return { allowed: true, reason: '' };
|
|
274
298
|
}
|
|
275
299
|
|
|
@@ -319,6 +343,7 @@ function sampleProjectFiles(files, options = {}) {
|
|
|
319
343
|
const maxBytes = normalizePositiveInteger(options.maxBytes, DEFAULT_MAX_BYTES);
|
|
320
344
|
const normalizedOptions = {
|
|
321
345
|
includeDb: options.includeDb === true,
|
|
346
|
+
includeLockfiles: options.includeLockfiles === true,
|
|
322
347
|
includeSource: options.includeSource === true,
|
|
323
348
|
includeTests: options.includeTests === true,
|
|
324
349
|
maxBytes,
|
|
@@ -397,6 +422,8 @@ module.exports = {
|
|
|
397
422
|
DEFAULT_MAX_BYTES,
|
|
398
423
|
DEFAULT_MAX_FILES,
|
|
399
424
|
classifyProjectFile,
|
|
425
|
+
isLockfilePath,
|
|
426
|
+
isQuiverGeneratedDocPath,
|
|
400
427
|
sampleProjectFiles,
|
|
401
428
|
summarizeByReason,
|
|
402
429
|
};
|
|
@@ -7,8 +7,14 @@ const {
|
|
|
7
7
|
normalizeAnalyzeProjectDocProposal,
|
|
8
8
|
} = require('./analyze-project-docs');
|
|
9
9
|
const { normalizeAnalyzeProjectAnalysis } = require('./analyze-project-parser');
|
|
10
|
+
const {
|
|
11
|
+
buildQuiverInternalGitignore,
|
|
12
|
+
quiverInternalPaths,
|
|
13
|
+
} = require('../init-layout');
|
|
10
14
|
|
|
11
15
|
const VALIDATION_KIND = 'quiver-analyze-project-post-write-validation';
|
|
16
|
+
const ANALYZE_PROJECT_VALIDATION_MANIFEST_KIND = 'quiver-analyze-project-validation-manifest';
|
|
17
|
+
const ANALYZE_PROJECT_RETRY_MANIFEST_KIND = 'quiver-analyze-project-retry-manifest';
|
|
12
18
|
|
|
13
19
|
const PLACEHOLDER_PATTERNS = [
|
|
14
20
|
{ issue: 'placeholder-todo', pattern: /\bTODO\b/i },
|
|
@@ -50,6 +56,163 @@ function makeIssue(pathName, issue, message, extra = {}) {
|
|
|
50
56
|
};
|
|
51
57
|
}
|
|
52
58
|
|
|
59
|
+
function normalizeManifestRunId(value, now = new Date()) {
|
|
60
|
+
const raw = value || `run-${now.toISOString()
|
|
61
|
+
.replace(/\.\d{3}Z$/, 'z')
|
|
62
|
+
.replace(/[^0-9a-z]+/gi, '-')
|
|
63
|
+
.toLowerCase()
|
|
64
|
+
.replace(/^-+|-+$/g, '')}`;
|
|
65
|
+
return String(raw)
|
|
66
|
+
.trim()
|
|
67
|
+
.toLowerCase()
|
|
68
|
+
.replace(/[^a-z0-9._-]+/g, '-')
|
|
69
|
+
.replace(/^-+|-+$/g, '') || 'run-analyze-project-validation';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function issuePathFamily(pathName) {
|
|
73
|
+
const normalized = String(pathName || 'analysis').replace(/\\/g, '/');
|
|
74
|
+
return normalized
|
|
75
|
+
.replace(/(?:^|\.)\d+(?=\.|$)/g, (segment) => segment.startsWith('.') ? '.*' : '*')
|
|
76
|
+
.replace(/\[\d+\]/g, '[*]');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function issueCauseHint(issue) {
|
|
80
|
+
switch (issue) {
|
|
81
|
+
case 'unrecognized_keys':
|
|
82
|
+
return 'Provider returned fields that are not allowed by the schema.';
|
|
83
|
+
case 'invalid_type':
|
|
84
|
+
return 'Provider omitted a required value or returned the wrong type.';
|
|
85
|
+
case 'invalid_value':
|
|
86
|
+
return 'Provider returned a value outside the allowed schema values.';
|
|
87
|
+
case 'malformed-json':
|
|
88
|
+
return 'Provider did not return one parseable JSON object.';
|
|
89
|
+
case 'empty-output':
|
|
90
|
+
return 'Provider returned no analysis JSON.';
|
|
91
|
+
case 'missing-evidence':
|
|
92
|
+
return 'Provider made a non-unknown claim without evidence.';
|
|
93
|
+
case 'evidence-not-selected':
|
|
94
|
+
return 'Provider cited a path outside the selected context sample.';
|
|
95
|
+
case 'unapproved-doc-update-path':
|
|
96
|
+
return 'Provider proposed writing outside the approved docs allowlist.';
|
|
97
|
+
default:
|
|
98
|
+
return 'Provider output drifted from the required analysis contract.';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function groupAnalyzeProjectIssues(issues = [], options = {}) {
|
|
103
|
+
const maxExamplesPerGroup = Number.isInteger(options.maxExamplesPerGroup)
|
|
104
|
+
? Math.max(1, options.maxExamplesPerGroup)
|
|
105
|
+
: 2;
|
|
106
|
+
const grouped = new Map();
|
|
107
|
+
|
|
108
|
+
for (const issue of Array.isArray(issues) ? issues : []) {
|
|
109
|
+
const type = issue.issue || issue.code || 'invalid';
|
|
110
|
+
const pathFamily = issuePathFamily(issue.path);
|
|
111
|
+
const key = `${type}\0${pathFamily}`;
|
|
112
|
+
if (!grouped.has(key)) {
|
|
113
|
+
grouped.set(key, {
|
|
114
|
+
type,
|
|
115
|
+
path_family: pathFamily,
|
|
116
|
+
count: 0,
|
|
117
|
+
cause_hint: issueCauseHint(type),
|
|
118
|
+
examples: [],
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const group = grouped.get(key);
|
|
122
|
+
group.count += 1;
|
|
123
|
+
if (group.examples.length < maxExamplesPerGroup) {
|
|
124
|
+
group.examples.push({
|
|
125
|
+
path: issue.path || null,
|
|
126
|
+
type,
|
|
127
|
+
message: issue.message || 'Invalid provider analysis output.',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return Array.from(grouped.values())
|
|
133
|
+
.sort((a, b) => {
|
|
134
|
+
const byType = a.type.localeCompare(b.type);
|
|
135
|
+
return byType || a.path_family.localeCompare(b.path_family);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function ensureQuiverRunsIgnored(repoRoot) {
|
|
140
|
+
const internalPaths = quiverInternalPaths(repoRoot);
|
|
141
|
+
if (!fs.existsSync(internalPaths.root)) {
|
|
142
|
+
fs.mkdirSync(internalPaths.root, { recursive: true });
|
|
143
|
+
}
|
|
144
|
+
if (!fs.existsSync(internalPaths.gitignorePath)) {
|
|
145
|
+
fs.writeFileSync(internalPaths.gitignorePath, buildQuiverInternalGitignore());
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function writeAnalyzeProjectValidationManifest(repoRoot, options = {}) {
|
|
150
|
+
const now = options.now instanceof Date ? options.now : new Date(options.now || Date.now());
|
|
151
|
+
const runId = normalizeManifestRunId(options.runId, now);
|
|
152
|
+
const internalPaths = quiverInternalPaths(repoRoot);
|
|
153
|
+
const manifestPath = path.join(internalPaths.runsDir, runId, 'validation', 'analyze-project-validation.json');
|
|
154
|
+
const issues = Array.isArray(options.error?.issues) ? options.error.issues : [];
|
|
155
|
+
const manifest = {
|
|
156
|
+
schema_version: 1,
|
|
157
|
+
kind: ANALYZE_PROJECT_VALIDATION_MANIFEST_KIND,
|
|
158
|
+
created_at: now.toISOString(),
|
|
159
|
+
run_id: runId,
|
|
160
|
+
command: options.command || 'ai analyze-project',
|
|
161
|
+
provider: options.provider || null,
|
|
162
|
+
status: 'invalid',
|
|
163
|
+
error: {
|
|
164
|
+
code: options.error?.code || null,
|
|
165
|
+
message: options.error?.message || 'provider analysis validation failed',
|
|
166
|
+
},
|
|
167
|
+
issue_count: issues.length,
|
|
168
|
+
groups: groupAnalyzeProjectIssues(issues, { maxExamplesPerGroup: 5 }),
|
|
169
|
+
retry: options.retry || null,
|
|
170
|
+
issues,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
ensureQuiverRunsIgnored(repoRoot);
|
|
174
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
175
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
run_id: runId,
|
|
179
|
+
path: toPosix(path.relative(repoRoot, manifestPath)),
|
|
180
|
+
issue_count: issues.length,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function writeAnalyzeProjectRetryManifest(repoRoot, options = {}) {
|
|
185
|
+
const now = options.now instanceof Date ? options.now : new Date(options.now || Date.now());
|
|
186
|
+
const runId = normalizeManifestRunId(options.runId, now);
|
|
187
|
+
const internalPaths = quiverInternalPaths(repoRoot);
|
|
188
|
+
const manifestPath = path.join(internalPaths.runsDir, runId, 'retry', 'analyze-project-retry.json');
|
|
189
|
+
const attempts = Array.isArray(options.attempts) ? options.attempts : [];
|
|
190
|
+
const manifest = {
|
|
191
|
+
schema_version: 1,
|
|
192
|
+
kind: ANALYZE_PROJECT_RETRY_MANIFEST_KIND,
|
|
193
|
+
created_at: now.toISOString(),
|
|
194
|
+
run_id: runId,
|
|
195
|
+
command: options.command || 'ai analyze-project',
|
|
196
|
+
provider: options.provider || null,
|
|
197
|
+
retry_count: Math.max(0, attempts.length - 1),
|
|
198
|
+
max_retries: options.maxRetries || 0,
|
|
199
|
+
final_status: options.finalStatus || 'unknown',
|
|
200
|
+
attempts,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
ensureQuiverRunsIgnored(repoRoot);
|
|
204
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
205
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
run_id: runId,
|
|
209
|
+
path: toPosix(path.relative(repoRoot, manifestPath)),
|
|
210
|
+
retry_count: manifest.retry_count,
|
|
211
|
+
max_retries: manifest.max_retries,
|
|
212
|
+
final_status: manifest.final_status,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
53
216
|
function extractManagedBlock(content) {
|
|
54
217
|
const text = String(content || '');
|
|
55
218
|
const start = text.indexOf(ANALYZE_MANAGED_START);
|
|
@@ -302,8 +465,13 @@ function validateAnalyzeProjectPostWrite(repoRoot, report, options = {}) {
|
|
|
302
465
|
}
|
|
303
466
|
|
|
304
467
|
module.exports = {
|
|
468
|
+
ANALYZE_PROJECT_RETRY_MANIFEST_KIND,
|
|
469
|
+
ANALYZE_PROJECT_VALIDATION_MANIFEST_KIND,
|
|
305
470
|
VALIDATION_KIND,
|
|
306
471
|
extractFactEntries,
|
|
307
472
|
extractManagedBlock,
|
|
473
|
+
groupAnalyzeProjectIssues,
|
|
308
474
|
validateAnalyzeProjectPostWrite,
|
|
475
|
+
writeAnalyzeProjectRetryManifest,
|
|
476
|
+
writeAnalyzeProjectValidationManifest,
|
|
309
477
|
};
|