create-quiver 0.17.3 → 0.17.5
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 +2 -0
- package/docs/CLI_UX_GUIDE.md +11 -4
- package/docs/TROUBLESHOOTING.md +17 -9
- package/docs/reference/commands.md +21 -9
- package/docs/workflows/existing-project.md +5 -5
- package/package.json +1 -1
- package/specs/quiver-v56-analyze-project-usable-doc-merge/EVIDENCE_REPORT.md +11 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/EXECUTION_PLAN.md +28 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/SPEC.md +96 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/STATUS.md +26 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/pr.md +56 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-01-document-classification-merge-engine/CLOSURE_BRIEF.md +61 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-01-document-classification-merge-engine/EXECUTION_BRIEF.md +59 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-01-document-classification-merge-engine/pr.md +61 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-01-document-classification-merge-engine/slice.json +71 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-02-apply-integration-validation-contract/CLOSURE_BRIEF.md +30 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-02-apply-integration-validation-contract/EXECUTION_BRIEF.md +39 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-02-apply-integration-validation-contract/pr.md +62 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-02-apply-integration-validation-contract/slice.json +80 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-03-cli-docs-real-fixture-smoke/CLOSURE_BRIEF.md +29 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-03-cli-docs-real-fixture-smoke/EXECUTION_BRIEF.md +32 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-03-cli-docs-real-fixture-smoke/pr.md +61 -0
- package/specs/quiver-v56-analyze-project-usable-doc-merge/slices/slice-03-cli-docs-real-fixture-smoke/slice.json +79 -0
- package/src/create-quiver/commands/ai.js +50 -2
- package/src/create-quiver/index.js +1 -1
- package/src/create-quiver/lib/ai/analyze-project-docs.js +300 -5
- package/src/create-quiver/lib/ai/analyze-project-proposal.js +11 -0
- package/src/create-quiver/lib/ai/analyze-project-validation.js +26 -0
- package/src/create-quiver/lib/i18n/messages/es.js +1 -1
|
@@ -15,6 +15,43 @@ const ANALYZE_DOC_PROPOSAL_SCHEMA_VERSION = 1;
|
|
|
15
15
|
const ANALYZE_DOC_PROPOSAL_KIND = 'quiver-analyze-project-doc-proposal';
|
|
16
16
|
const ANALYZE_MANAGED_START = '<!-- quiver:analyze-project:start -->';
|
|
17
17
|
const ANALYZE_MANAGED_END = '<!-- quiver:analyze-project:end -->';
|
|
18
|
+
const CONTEXT_PREP_MANAGED_START = '<!-- quiver:context-prep:start -->';
|
|
19
|
+
const CONTEXT_PREP_MANAGED_END = '<!-- quiver:context-prep:end -->';
|
|
20
|
+
|
|
21
|
+
const CRITICAL_SCAFFOLD_PLACEHOLDER_PATTERNS = [
|
|
22
|
+
/\[Uno o dos parrafos que expliquen el proyecto\.\]/i,
|
|
23
|
+
/\[Frase principal del proyecto\]/i,
|
|
24
|
+
/\[Describe el usuario principal\.\]/i,
|
|
25
|
+
/\[Objetivo actual \d+\]/i,
|
|
26
|
+
/\[Datos sensibles que nunca se guardan\]/i,
|
|
27
|
+
/\[Datos que si se guardan\]/i,
|
|
28
|
+
/\[Medida de seguridad importante\]/i,
|
|
29
|
+
/\[One or two paragraphs? that explain the project\.\]/i,
|
|
30
|
+
/\[Project tagline\]/i,
|
|
31
|
+
/\[Describe the primary user\.\]/i,
|
|
32
|
+
/\[Current goal \d+\]/i,
|
|
33
|
+
/\[Sensitive data that is never stored\]/i,
|
|
34
|
+
/\[Data that is stored\]/i,
|
|
35
|
+
/\[Important security measure\]/i,
|
|
36
|
+
/\[Short project summary\.\]/i,
|
|
37
|
+
/\[Description\]/i,
|
|
38
|
+
/\[TODO: confirm [^\]\n]+\]/i,
|
|
39
|
+
/\[TODO: confirmar [^\]\n]+\]/i,
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const SCAFFOLD_BOILERPLATE_PATTERNS = [
|
|
43
|
+
/^purpose:\s*"Human-readable project overview"$/i,
|
|
44
|
+
/^applies_when:\s*"onboarding, review"$/i,
|
|
45
|
+
/^supersedes:\s*null$/i,
|
|
46
|
+
/^El stack, package manager y comandos se generan en `docs\/PROJECT_MAP\.md` despues de ejecutar `analyze`\.$/i,
|
|
47
|
+
/^The stack, package manager, and command surface are generated in `docs\/PROJECT_MAP\.md` after analysis\.$/i,
|
|
48
|
+
/^If sos agente IA, lee `AI_CONTEXT\.md`\./i,
|
|
49
|
+
/^Read `AI_CONTEXT\.md` first if you are an AI agent\./i,
|
|
50
|
+
/^Use `npx create-quiver ai prepare-context --dry-run`/i,
|
|
51
|
+
/^TODO: confirm any repo fact/i,
|
|
52
|
+
/^Assumption: missing README_FOR_AI\.md/i,
|
|
53
|
+
/^Pending confirmation:/i,
|
|
54
|
+
];
|
|
18
55
|
|
|
19
56
|
const docProposalEntrySchema = z.object({
|
|
20
57
|
path: z.string().trim().min(1),
|
|
@@ -67,6 +104,10 @@ function normalizeDocContent(content) {
|
|
|
67
104
|
return `${String(content || '').replace(/\s+$/g, '')}\n`;
|
|
68
105
|
}
|
|
69
106
|
|
|
107
|
+
function normalizeNewlines(content) {
|
|
108
|
+
return String(content || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
109
|
+
}
|
|
110
|
+
|
|
70
111
|
function managedBlock(content) {
|
|
71
112
|
return [
|
|
72
113
|
ANALYZE_MANAGED_START,
|
|
@@ -76,8 +117,240 @@ function managedBlock(content) {
|
|
|
76
117
|
].join('\n');
|
|
77
118
|
}
|
|
78
119
|
|
|
120
|
+
function findManagedBlockRange(content, startMarker, endMarker) {
|
|
121
|
+
const text = normalizeNewlines(content);
|
|
122
|
+
const start = text.indexOf(startMarker);
|
|
123
|
+
const end = text.indexOf(endMarker);
|
|
124
|
+
if (start < 0 || end <= start) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
start,
|
|
129
|
+
end: end + endMarker.length,
|
|
130
|
+
content: text.slice(start + startMarker.length, end),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function removeManagedBlock(content, startMarker, endMarker) {
|
|
135
|
+
const text = normalizeNewlines(content);
|
|
136
|
+
const range = findManagedBlockRange(text, startMarker, endMarker);
|
|
137
|
+
if (!range) {
|
|
138
|
+
return {
|
|
139
|
+
content: text,
|
|
140
|
+
removed: false,
|
|
141
|
+
block: '',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
content: `${text.slice(0, range.start)}${text.slice(range.end)}`.replace(/\n{3,}/g, '\n\n').trimEnd(),
|
|
146
|
+
removed: true,
|
|
147
|
+
block: range.content,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function splitFrontmatter(content) {
|
|
152
|
+
const text = normalizeNewlines(content);
|
|
153
|
+
if (!text.startsWith('---\n')) {
|
|
154
|
+
return {
|
|
155
|
+
frontmatter: '',
|
|
156
|
+
body: text,
|
|
157
|
+
valid: false,
|
|
158
|
+
present: false,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const match = text.slice(4).match(/\n---\n/);
|
|
162
|
+
if (!match || match.index === undefined) {
|
|
163
|
+
return {
|
|
164
|
+
frontmatter: '',
|
|
165
|
+
body: text,
|
|
166
|
+
valid: false,
|
|
167
|
+
present: true,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const endIndex = 4 + match.index + '\n---\n'.length;
|
|
171
|
+
return {
|
|
172
|
+
frontmatter: text.slice(0, endIndex),
|
|
173
|
+
body: text.slice(endIndex),
|
|
174
|
+
valid: true,
|
|
175
|
+
present: true,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function collectCriticalPlaceholders(content) {
|
|
180
|
+
const findings = [];
|
|
181
|
+
const text = normalizeNewlines(content);
|
|
182
|
+
for (const pattern of CRITICAL_SCAFFOLD_PLACEHOLDER_PATTERNS) {
|
|
183
|
+
const match = text.match(pattern);
|
|
184
|
+
if (match) {
|
|
185
|
+
findings.push(match[0]);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return [...new Set(findings)].sort();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function isCriticalPlaceholderLine(line) {
|
|
192
|
+
const trimmed = String(line || '').trim();
|
|
193
|
+
return CRITICAL_SCAFFOLD_PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isScaffoldBoilerplateLine(line) {
|
|
197
|
+
const trimmed = String(line || '')
|
|
198
|
+
.replace(/^[-*]\s+/, '')
|
|
199
|
+
.replace(/^>\s*/, '')
|
|
200
|
+
.trim();
|
|
201
|
+
if (!trimmed) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
if (/^#+\s+/.test(trimmed)) {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
if (/^last_updated:\s*"/i.test(trimmed) || /^token_cost:\s*\d+/i.test(trimmed)) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
if (/^Files Considered$|^Assumptions$|^Risks$|^Contradictions$|^Omitted Paths$/i.test(trimmed.replace(/^#+\s*/, ''))) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
return SCAFFOLD_BOILERPLATE_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function significantHumanLines(content) {
|
|
217
|
+
return normalizeNewlines(content)
|
|
218
|
+
.split('\n')
|
|
219
|
+
.map((line) => line.trim())
|
|
220
|
+
.filter((line) => line.length >= 12)
|
|
221
|
+
.filter((line) => !isCriticalPlaceholderLine(line))
|
|
222
|
+
.filter((line) => !isScaffoldBoilerplateLine(line));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function classifyAnalyzeProjectDoc(currentContent) {
|
|
226
|
+
const current = normalizeNewlines(currentContent);
|
|
227
|
+
const frontmatter = splitFrontmatter(current);
|
|
228
|
+
const withoutAnalyze = removeManagedBlock(frontmatter.body, ANALYZE_MANAGED_START, ANALYZE_MANAGED_END);
|
|
229
|
+
const withoutContextPrep = removeManagedBlock(withoutAnalyze.content, CONTEXT_PREP_MANAGED_START, CONTEXT_PREP_MANAGED_END);
|
|
230
|
+
const visibleBody = withoutContextPrep.content.trim();
|
|
231
|
+
const visiblePlaceholders = collectCriticalPlaceholders(visibleBody);
|
|
232
|
+
const contextPrepPlaceholders = collectCriticalPlaceholders(withoutContextPrep.block);
|
|
233
|
+
const humanLines = significantHumanLines(visibleBody);
|
|
234
|
+
const contextPrepHumanLines = significantHumanLines(withoutContextPrep.block);
|
|
235
|
+
const hasManagedOnly = !visibleBody && (withoutAnalyze.removed || withoutContextPrep.removed);
|
|
236
|
+
|
|
237
|
+
let classification = 'unknown';
|
|
238
|
+
if (!current.trim()) {
|
|
239
|
+
classification = 'managed_only';
|
|
240
|
+
} else if (hasManagedOnly) {
|
|
241
|
+
classification = 'managed_only';
|
|
242
|
+
} else if (visiblePlaceholders.length > 0 && humanLines.length === 0) {
|
|
243
|
+
classification = 'scaffold';
|
|
244
|
+
} else if (visiblePlaceholders.length > 0 && humanLines.length > 0) {
|
|
245
|
+
classification = 'partial_scaffold';
|
|
246
|
+
} else if (withoutAnalyze.removed || withoutContextPrep.removed) {
|
|
247
|
+
classification = humanLines.length > 0 ? 'mixed' : 'managed_only';
|
|
248
|
+
} else if (humanLines.length > 0) {
|
|
249
|
+
classification = 'human_content';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
classification,
|
|
254
|
+
frontmatter,
|
|
255
|
+
visible_body: visibleBody,
|
|
256
|
+
has_analyze_project_block: withoutAnalyze.removed,
|
|
257
|
+
has_context_prep_block: withoutContextPrep.removed,
|
|
258
|
+
context_prep_is_scaffold: withoutContextPrep.removed && contextPrepHumanLines.length === 0 && contextPrepPlaceholders.length > 0,
|
|
259
|
+
critical_placeholders: visiblePlaceholders,
|
|
260
|
+
context_prep_placeholders: contextPrepPlaceholders,
|
|
261
|
+
human_line_count: humanLines.length,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function composeWithFrontmatter(frontmatter, body) {
|
|
266
|
+
const prefix = frontmatter?.valid ? `${frontmatter.frontmatter.trimEnd()}\n\n` : '';
|
|
267
|
+
return `${prefix}${normalizeDocContent(body)}`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function visibleAnalyzeProjectContent(proposedContent) {
|
|
271
|
+
return normalizeDocContent(proposedContent).trimEnd();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function buildPreservedContentSection(content) {
|
|
275
|
+
const body = normalizeNewlines(content).trim();
|
|
276
|
+
if (!body) {
|
|
277
|
+
return '';
|
|
278
|
+
}
|
|
279
|
+
return `## Existing Content Preserved\n\n${body}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function normalizedMarkdownEquivalent(left, right) {
|
|
283
|
+
return normalizeNewlines(left).trim() === normalizeNewlines(right).trim();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function mergeAnalyzeProjectDoc(currentContent, proposedContent) {
|
|
287
|
+
const current = normalizeNewlines(currentContent);
|
|
288
|
+
const proposedVisible = visibleAnalyzeProjectContent(proposedContent);
|
|
289
|
+
const classification = classifyAnalyzeProjectDoc(current);
|
|
290
|
+
const currentWithoutAnalyze = removeManagedBlock(classification.frontmatter.body, ANALYZE_MANAGED_START, ANALYZE_MANAGED_END);
|
|
291
|
+
const currentWithoutContextPrep = removeManagedBlock(currentWithoutAnalyze.content, CONTEXT_PREP_MANAGED_START, CONTEXT_PREP_MANAGED_END);
|
|
292
|
+
const bodyWithoutManaged = currentWithoutContextPrep.content.trim();
|
|
293
|
+
const report = {
|
|
294
|
+
classification: classification.classification,
|
|
295
|
+
strategy: 'preserve-and-update-managed-block',
|
|
296
|
+
scaffold_replaced: false,
|
|
297
|
+
human_content_preserved: false,
|
|
298
|
+
analyze_project_block_replaced: classification.has_analyze_project_block,
|
|
299
|
+
context_prep_removed: false,
|
|
300
|
+
critical_placeholders: classification.critical_placeholders,
|
|
301
|
+
warnings: [],
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
let visibleBody;
|
|
305
|
+
if (['scaffold', 'managed_only'].includes(classification.classification)) {
|
|
306
|
+
visibleBody = proposedVisible;
|
|
307
|
+
report.strategy = classification.classification === 'scaffold'
|
|
308
|
+
? 'replace-scaffold-primary-content'
|
|
309
|
+
: 'replace-managed-only-content';
|
|
310
|
+
report.scaffold_replaced = classification.classification === 'scaffold';
|
|
311
|
+
} else if (['partial_scaffold', 'mixed'].includes(classification.classification)) {
|
|
312
|
+
const cleaned = bodyWithoutManaged
|
|
313
|
+
.split('\n')
|
|
314
|
+
.filter((line) => !isCriticalPlaceholderLine(line))
|
|
315
|
+
.join('\n')
|
|
316
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
317
|
+
.trim();
|
|
318
|
+
const preserved = normalizedMarkdownEquivalent(cleaned, proposedVisible)
|
|
319
|
+
? ''
|
|
320
|
+
: buildPreservedContentSection(cleaned);
|
|
321
|
+
visibleBody = preserved ? `${proposedVisible}\n\n${preserved}` : proposedVisible;
|
|
322
|
+
report.strategy = 'replace-placeholders-preserve-human-content';
|
|
323
|
+
report.scaffold_replaced = classification.critical_placeholders.length > 0;
|
|
324
|
+
report.human_content_preserved = Boolean(preserved);
|
|
325
|
+
} else {
|
|
326
|
+
visibleBody = bodyWithoutManaged
|
|
327
|
+
? `${bodyWithoutManaged}\n\n${managedBlock(proposedContent).trimEnd()}`
|
|
328
|
+
: managedBlock(proposedContent).trimEnd();
|
|
329
|
+
report.human_content_preserved = bodyWithoutManaged.trim().length > 0;
|
|
330
|
+
if (classification.classification === 'unknown') {
|
|
331
|
+
report.warnings.push('document classification is unknown; preserved existing content and updated managed block only');
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
content: composeWithFrontmatter(classification.frontmatter, visibleBody),
|
|
335
|
+
report,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (classification.has_context_prep_block && (classification.context_prep_is_scaffold || classification.context_prep_placeholders.length > 0)) {
|
|
340
|
+
report.context_prep_removed = true;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
content: composeWithFrontmatter(
|
|
345
|
+
classification.frontmatter,
|
|
346
|
+
`${visibleBody}\n\n${managedBlock(proposedContent).trimEnd()}`,
|
|
347
|
+
),
|
|
348
|
+
report,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
79
352
|
function mergeManagedBlock(currentContent, proposedContent) {
|
|
80
|
-
const current =
|
|
353
|
+
const current = normalizeNewlines(currentContent);
|
|
81
354
|
const block = managedBlock(proposedContent);
|
|
82
355
|
const startIndex = current.indexOf(ANALYZE_MANAGED_START);
|
|
83
356
|
const endIndex = current.indexOf(ANALYZE_MANAGED_END);
|
|
@@ -93,6 +366,21 @@ function mergeManagedBlock(currentContent, proposedContent) {
|
|
|
93
366
|
return `${current.replace(/\s+$/g, '')}\n\n${block}`;
|
|
94
367
|
}
|
|
95
368
|
|
|
369
|
+
function redactSnapshotValue(value, repoRoot) {
|
|
370
|
+
if (typeof value === 'string') {
|
|
371
|
+
return redactSensitiveLocalValues(value, { projectRoot: repoRoot });
|
|
372
|
+
}
|
|
373
|
+
if (Array.isArray(value)) {
|
|
374
|
+
return value.map((item) => redactSnapshotValue(item, repoRoot));
|
|
375
|
+
}
|
|
376
|
+
if (value && typeof value === 'object') {
|
|
377
|
+
return Object.fromEntries(
|
|
378
|
+
Object.entries(value).map(([key, item]) => [key, redactSnapshotValue(item, repoRoot)]),
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
return value;
|
|
382
|
+
}
|
|
383
|
+
|
|
96
384
|
function validateDocPath(docPath) {
|
|
97
385
|
let normalized;
|
|
98
386
|
try {
|
|
@@ -238,9 +526,10 @@ function buildAnalyzeProjectWritePlan(repoRoot, proposal) {
|
|
|
238
526
|
const destinationPath = path.join(repoRoot, doc.path);
|
|
239
527
|
const exists = fs.existsSync(destinationPath);
|
|
240
528
|
const currentContent = exists ? fs.readFileSync(destinationPath, 'utf8') : '';
|
|
241
|
-
const
|
|
242
|
-
? currentContent
|
|
243
|
-
:
|
|
529
|
+
const merged = doc.action === 'skip'
|
|
530
|
+
? { content: currentContent, report: { classification: 'skipped', strategy: 'skip' } }
|
|
531
|
+
: mergeAnalyzeProjectDoc(currentContent, doc.content);
|
|
532
|
+
const proposedContent = merged.content;
|
|
244
533
|
const changed = currentContent.replace(/\r\n/g, '\n') !== proposedContent.replace(/\r\n/g, '\n');
|
|
245
534
|
const action = doc.action === 'skip' || !changed ? 'skip' : exists ? 'update' : 'create';
|
|
246
535
|
|
|
@@ -256,6 +545,7 @@ function buildAnalyzeProjectWritePlan(repoRoot, proposal) {
|
|
|
256
545
|
before_sha256: exists ? sha256(currentContent) : null,
|
|
257
546
|
after_sha256: action === 'skip' ? (exists ? sha256(currentContent) : null) : sha256(proposedContent),
|
|
258
547
|
diff: buildDiffSnippet(doc.path, currentContent, proposedContent),
|
|
548
|
+
merge_report: merged.report,
|
|
259
549
|
};
|
|
260
550
|
});
|
|
261
551
|
}
|
|
@@ -303,7 +593,7 @@ function createAnalyzeProjectSnapshot(repoRoot, run, writePlan, options = {}) {
|
|
|
303
593
|
|
|
304
594
|
const providerArtifactPath = path.join(rawRoot, 'analyze-project-provider-artifact.json');
|
|
305
595
|
const providerArtifact = options.providerArtifact
|
|
306
|
-
?
|
|
596
|
+
? redactSnapshotValue(options.providerArtifact, repoRoot)
|
|
307
597
|
: null;
|
|
308
598
|
fs.writeFileSync(providerArtifactPath, `${JSON.stringify(providerArtifact, null, 2)}\n`);
|
|
309
599
|
|
|
@@ -353,12 +643,17 @@ module.exports = {
|
|
|
353
643
|
ANALYZE_DOC_PROPOSAL_SCHEMA_VERSION,
|
|
354
644
|
ANALYZE_MANAGED_END,
|
|
355
645
|
ANALYZE_MANAGED_START,
|
|
646
|
+
CONTEXT_PREP_MANAGED_END,
|
|
647
|
+
CONTEXT_PREP_MANAGED_START,
|
|
356
648
|
AnalyzeProjectDocsError,
|
|
357
649
|
analyzeProjectDocProposalSchema,
|
|
358
650
|
buildAnalyzeProjectDocProposal,
|
|
359
651
|
buildAnalyzeProjectWritePlan,
|
|
652
|
+
classifyAnalyzeProjectDoc,
|
|
653
|
+
collectCriticalPlaceholders,
|
|
360
654
|
createAnalyzeProjectSnapshot,
|
|
361
655
|
formatAnalyzeProjectDiffPreview,
|
|
656
|
+
mergeAnalyzeProjectDoc,
|
|
362
657
|
mergeManagedBlock,
|
|
363
658
|
normalizeAnalyzeProjectDocProposal,
|
|
364
659
|
parseAnalyzeProjectDocProposal,
|
|
@@ -127,6 +127,7 @@ const proposalManifestSchema = z.object({
|
|
|
127
127
|
repair_manifest: relativePathSchema('repair_manifest').nullable(),
|
|
128
128
|
doc_paths: z.array(relativePathSchema('doc_paths')).default([]),
|
|
129
129
|
doc_before_hashes: z.record(z.string().trim().min(1), z.string().trim().min(1).nullable()).default({}),
|
|
130
|
+
merge_plan: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
130
131
|
proposal_sha256: z.string().trim().min(1),
|
|
131
132
|
events: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
132
133
|
}).strict();
|
|
@@ -139,6 +140,7 @@ const writeManifestActionSchema = z.object({
|
|
|
139
140
|
snapshot_path: relativePathSchema('actions.snapshot_path').nullable(),
|
|
140
141
|
dirty: z.boolean().default(false),
|
|
141
142
|
status: z.enum(['planned', 'written', 'skipped', 'failed']).default('planned'),
|
|
143
|
+
merge_report: z.record(z.string(), z.unknown()).optional(),
|
|
142
144
|
}).strict();
|
|
143
145
|
|
|
144
146
|
const writeManifestSchema = z.object({
|
|
@@ -419,9 +421,16 @@ function writeAnalyzeProjectProposalArtifacts(repoRoot, options = {}) {
|
|
|
419
421
|
const proposalJson = `${JSON.stringify(proposal, null, 2)}\n`;
|
|
420
422
|
const proposalHash = sha256(proposalJson);
|
|
421
423
|
const docBeforeHashes = {};
|
|
424
|
+
const mergePlan = [];
|
|
422
425
|
|
|
423
426
|
for (const item of writePlan) {
|
|
424
427
|
docBeforeHashes[item.path] = item.before_sha256 || null;
|
|
428
|
+
mergePlan.push({
|
|
429
|
+
path: item.path,
|
|
430
|
+
action: item.action,
|
|
431
|
+
dirty: item.dirty === true,
|
|
432
|
+
merge_report: item.merge_report || null,
|
|
433
|
+
});
|
|
425
434
|
}
|
|
426
435
|
|
|
427
436
|
const selectedContextPath = artifactPathFromRef(options.selectedContextManifest)
|
|
@@ -441,6 +450,7 @@ function writeAnalyzeProjectProposalArtifacts(repoRoot, options = {}) {
|
|
|
441
450
|
repair_manifest: repairPath,
|
|
442
451
|
doc_paths: writePlan.map((item) => item.path),
|
|
443
452
|
doc_before_hashes: docBeforeHashes,
|
|
453
|
+
merge_plan: mergePlan,
|
|
444
454
|
proposal_sha256: proposalHash,
|
|
445
455
|
events: [
|
|
446
456
|
{
|
|
@@ -524,6 +534,7 @@ function writeAnalyzeProjectWriteManifest(repoRoot, options = {}) {
|
|
|
524
534
|
snapshot_path: snapshotEntry?.snapshot_path || null,
|
|
525
535
|
dirty: item.dirty === true,
|
|
526
536
|
status,
|
|
537
|
+
merge_report: item.merge_report || undefined,
|
|
527
538
|
};
|
|
528
539
|
});
|
|
529
540
|
const validation = options.validation || {
|
|
@@ -4,6 +4,8 @@ const path = require('node:path');
|
|
|
4
4
|
const {
|
|
5
5
|
ANALYZE_MANAGED_END,
|
|
6
6
|
ANALYZE_MANAGED_START,
|
|
7
|
+
classifyAnalyzeProjectDoc,
|
|
8
|
+
collectCriticalPlaceholders,
|
|
7
9
|
normalizeAnalyzeProjectDocProposal,
|
|
8
10
|
} = require('./analyze-project-docs');
|
|
9
11
|
const { normalizeAnalyzeProjectAnalysis } = require('./analyze-project-parser');
|
|
@@ -326,6 +328,29 @@ function addPlaceholderValidation(docPath, managedContent, errors) {
|
|
|
326
328
|
}
|
|
327
329
|
}
|
|
328
330
|
|
|
331
|
+
function addVisiblePlaceholderValidation(docPath, content, warnings, errors, options = {}) {
|
|
332
|
+
const classification = classifyAnalyzeProjectDoc(content);
|
|
333
|
+
const placeholders = collectCriticalPlaceholders(classification.visible_body || '');
|
|
334
|
+
if (placeholders.length === 0) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const issue = makeIssue(
|
|
339
|
+
docPath,
|
|
340
|
+
'visible-critical-placeholder',
|
|
341
|
+
`critical scaffold placeholder remains in primary visible content: ${placeholders.slice(0, 3).join(', ')}`,
|
|
342
|
+
{
|
|
343
|
+
classification: classification.classification,
|
|
344
|
+
placeholders,
|
|
345
|
+
},
|
|
346
|
+
);
|
|
347
|
+
if (options.strict === true) {
|
|
348
|
+
errors.push(issue);
|
|
349
|
+
} else {
|
|
350
|
+
warnings.push(issue);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
329
354
|
function normalizeFactValue(value) {
|
|
330
355
|
return String(value || '')
|
|
331
356
|
.replace(/[`*_]/g, '')
|
|
@@ -446,6 +471,7 @@ function validateAnalyzeProjectPostWrite(repoRoot, report, options = {}) {
|
|
|
446
471
|
content: file.content,
|
|
447
472
|
managedBlock,
|
|
448
473
|
});
|
|
474
|
+
addVisiblePlaceholderValidation(docPath, file.content, warnings, errors, { strict: options.strict === true });
|
|
449
475
|
if (!managedBlock.ok) {
|
|
450
476
|
errors.push(makeIssue(docPath, managedBlock.issue, `Quiver managed block is missing or unbalanced in ${docPath}.`));
|
|
451
477
|
continue;
|
|
@@ -37,7 +37,7 @@ module.exports = {
|
|
|
37
37
|
'ai active-slice status|reconcile': 'Inspecciona o simula reconciliar el estado local de active-slice desde todas las fuentes soportadas.',
|
|
38
38
|
'ai status': 'Muestra fase actual del ciclo de vida de IA, versiones aprobadas, bloqueos y proximo comando.',
|
|
39
39
|
'ai resume': 'Reanuda la guia desde la ultima fase valida del ciclo de vida sin memoria de chat.',
|
|
40
|
-
'ai analyze-project': 'Analiza una muestra acotada del proyecto y
|
|
40
|
+
'ai analyze-project': 'Analiza una muestra acotada del proyecto y aplica actualizaciones documentales validadas.',
|
|
41
41
|
'ai onboard': 'Ejecuta o imprime el prompt de onboarding del planner con contexto optimizado por tokens.',
|
|
42
42
|
'ai prepare-context': 'Previsualiza o escribe actualizaciones docs-only del contexto de IA con supuestos y riesgos.',
|
|
43
43
|
'ai agent set|list|show|doctor|repair': 'Gestiona, diagnostica y simula reparaciones de perfiles planner, executor, reviewer y doctor sin secretos.',
|