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.
Files changed (86) hide show
  1. package/ACTIVE_SLICES.md +43 -0
  2. package/CHANGELOG.md +8 -0
  3. package/auditoria-ux-ui-performance-documentacion.md +705 -0
  4. package/copys-landing-page.md +244 -0
  5. package/docs/TROUBLESHOOTING.md +53 -0
  6. package/docs/reference/commands.md +42 -0
  7. package/docs/workflows/existing-project-ai-quiver-setup.md +552 -0
  8. package/docs/workflows/existing-project.md +21 -0
  9. package/package.json +1 -1
  10. package/specs/quiver-v43-cli-i18n-audit-release-readiness/command-language-mode-matrix.json +1 -0
  11. package/specs/quiver-v53-reliable-deep-project-analysis/EVIDENCE_REPORT.md +151 -0
  12. package/specs/quiver-v53-reliable-deep-project-analysis/EXECUTION_PLAN.md +50 -0
  13. package/specs/quiver-v53-reliable-deep-project-analysis/SPEC.md +207 -0
  14. package/specs/quiver-v53-reliable-deep-project-analysis/STATUS.md +32 -0
  15. package/specs/quiver-v53-reliable-deep-project-analysis/pr.md +73 -0
  16. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-00-analysis-run-contract/CLOSURE_BRIEF.md +32 -0
  17. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-00-analysis-run-contract/EXECUTION_BRIEF.md +63 -0
  18. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-00-analysis-run-contract/slice.json +70 -0
  19. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-01-provider-fixture-harness/CLOSURE_BRIEF.md +38 -0
  20. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-01-provider-fixture-harness/EXECUTION_BRIEF.md +60 -0
  21. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-01-provider-fixture-harness/slice.json +74 -0
  22. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-02-safe-context-boundary/CLOSURE_BRIEF.md +33 -0
  23. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-02-safe-context-boundary/EXECUTION_BRIEF.md +68 -0
  24. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-02-safe-context-boundary/slice.json +85 -0
  25. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-03-schema-error-grouping/CLOSURE_BRIEF.md +34 -0
  26. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-03-schema-error-grouping/EXECUTION_BRIEF.md +65 -0
  27. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-03-schema-error-grouping/slice.json +80 -0
  28. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-04-safe-repair-layer/CLOSURE_BRIEF.md +34 -0
  29. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-04-safe-repair-layer/EXECUTION_BRIEF.md +63 -0
  30. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-04-safe-repair-layer/slice.json +80 -0
  31. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-05-controlled-retry-layer/CLOSURE_BRIEF.md +34 -0
  32. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-05-controlled-retry-layer/EXECUTION_BRIEF.md +65 -0
  33. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-05-controlled-retry-layer/slice.json +81 -0
  34. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-06-audit-review-transaction/CLOSURE_BRIEF.md +35 -0
  35. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-06-audit-review-transaction/EXECUTION_BRIEF.md +71 -0
  36. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-06-audit-review-transaction/slice.json +88 -0
  37. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-07-semantic-validation-docs/CLOSURE_BRIEF.md +32 -0
  38. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-07-semantic-validation-docs/EXECUTION_BRIEF.md +66 -0
  39. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-07-semantic-validation-docs/slice.json +86 -0
  40. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-08-structural-map-hardening/CLOSURE_BRIEF.md +34 -0
  41. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-08-structural-map-hardening/EXECUTION_BRIEF.md +67 -0
  42. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-08-structural-map-hardening/slice.json +81 -0
  43. package/specs/quiver-v54-deep-project-analysis-hardening/EVIDENCE_REPORT.md +53 -0
  44. package/specs/quiver-v54-deep-project-analysis-hardening/EXECUTION_PLAN.md +55 -0
  45. package/specs/quiver-v54-deep-project-analysis-hardening/SPEC.md +184 -0
  46. package/specs/quiver-v54-deep-project-analysis-hardening/STATUS.md +34 -0
  47. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-01-contract-regression-harness/CLOSURE_BRIEF.md +10 -0
  48. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-01-contract-regression-harness/EXECUTION_BRIEF.md +54 -0
  49. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-01-contract-regression-harness/slice.json +72 -0
  50. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-02-safe-discovery-sampling/CLOSURE_BRIEF.md +11 -0
  51. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-02-safe-discovery-sampling/EXECUTION_BRIEF.md +53 -0
  52. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-02-safe-discovery-sampling/slice.json +69 -0
  53. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-03-run-lifecycle-cli-io/CLOSURE_BRIEF.md +10 -0
  54. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-03-run-lifecycle-cli-io/EXECUTION_BRIEF.md +53 -0
  55. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-03-run-lifecycle-cli-io/slice.json +69 -0
  56. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-04-artifact-store-redaction/CLOSURE_BRIEF.md +10 -0
  57. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-04-artifact-store-redaction/EXECUTION_BRIEF.md +54 -0
  58. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-04-artifact-store-redaction/slice.json +73 -0
  59. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-05-path-aware-repair-engine/CLOSURE_BRIEF.md +10 -0
  60. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-05-path-aware-repair-engine/EXECUTION_BRIEF.md +57 -0
  61. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-05-path-aware-repair-engine/slice.json +72 -0
  62. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-06-retry-controller/CLOSURE_BRIEF.md +9 -0
  63. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-06-retry-controller/EXECUTION_BRIEF.md +52 -0
  64. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-06-retry-controller/slice.json +70 -0
  65. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-07-final-validation-review-write-gate/CLOSURE_BRIEF.md +10 -0
  66. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-07-final-validation-review-write-gate/EXECUTION_BRIEF.md +57 -0
  67. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-07-final-validation-review-write-gate/slice.json +75 -0
  68. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-08-doctor-agents-guidance/CLOSURE_BRIEF.md +9 -0
  69. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-08-doctor-agents-guidance/EXECUTION_BRIEF.md +51 -0
  70. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-08-doctor-agents-guidance/slice.json +66 -0
  71. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-09-release-smoke-rollback-docs/CLOSURE_BRIEF.md +10 -0
  72. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-09-release-smoke-rollback-docs/EXECUTION_BRIEF.md +54 -0
  73. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-09-release-smoke-rollback-docs/slice.json +69 -0
  74. package/src/create-quiver/commands/ai.js +383 -34
  75. package/src/create-quiver/index.js +3 -0
  76. package/src/create-quiver/lib/ai/analyze-project-discovery.js +213 -2
  77. package/src/create-quiver/lib/ai/analyze-project-docs.js +3 -1
  78. package/src/create-quiver/lib/ai/analyze-project-parser.js +1 -0
  79. package/src/create-quiver/lib/ai/analyze-project-prompts.js +130 -0
  80. package/src/create-quiver/lib/ai/analyze-project-repair.js +402 -0
  81. package/src/create-quiver/lib/ai/analyze-project-sampling.js +31 -4
  82. package/src/create-quiver/lib/ai/analyze-project-validation.js +168 -0
  83. package/src/create-quiver/lib/ai/artifacts.js +83 -2
  84. package/src/create-quiver/lib/doctor.js +107 -12
  85. package/src/create-quiver/lib/i18n/messages/en.js +6 -0
  86. 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 dirname(filePath) {
113
- return path.posix.dirname(toPosix(filePath));
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 (LOCKFILE_BASENAMES.has(base) || LOCKFILE_BASENAMES.has(baseLower)) {
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('config') || signals.includes('docs') || signals.includes('lockfile')) {
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
  };