@vibecompass/vibecompass 0.10.0 → 0.10.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/CHANGELOG.md +18 -0
- package/README.md +4 -0
- package/package.json +3 -2
- package/src/cli.js +16 -0
- package/src/docs-review.js +348 -7
- package/src/generators/agent-files/template.js +1 -0
- package/src/scaffold.js +1 -0
- package/src/source-inventory.js +915 -0
- package/src/workflows/registry.js +5 -3
package/src/docs-review.js
CHANGED
|
@@ -6,15 +6,27 @@ import { sha256Text } from './hash.js';
|
|
|
6
6
|
import { writeStateManifest } from './manifest.js';
|
|
7
7
|
import { scanProjectMemory } from './project-memory.js';
|
|
8
8
|
import { parseSimpleYaml } from './simple-yaml.js';
|
|
9
|
+
import {
|
|
10
|
+
buildDocsReviewSourceInventory,
|
|
11
|
+
docsReviewSourceInventoryPath,
|
|
12
|
+
readDocsReviewSourceInventory,
|
|
13
|
+
reconcileCoverageWithSourceInventory,
|
|
14
|
+
summarizeSourceInventory,
|
|
15
|
+
writeDocsReviewSourceInventory,
|
|
16
|
+
} from './source-inventory.js';
|
|
9
17
|
import { readSyncCursor, resolveSyncBinding } from './sync-binding.js';
|
|
10
18
|
import { PACKAGE_VERSION } from './version.js';
|
|
11
19
|
|
|
12
|
-
const DOCS_REVIEW_PROMPT_VERSION = 'VibeCompass Docs Review Prompt
|
|
20
|
+
const DOCS_REVIEW_PROMPT_VERSION = 'VibeCompass Docs Review Prompt v6';
|
|
21
|
+
const DOCS_REVIEW_PARSER_VERSION = 'docs-review-parser-v1';
|
|
22
|
+
const DOCS_REVIEW_DOCUMENTATION_PLAN_PROJECTION_VERSION = 'docs-review-documentation-plan-v1';
|
|
23
|
+
const DOCUMENTATION_PLAN_STATE_VERSION = 1;
|
|
13
24
|
const ARCHITECTURE_DOC_SOFT_SIZE_LIMIT_BYTES = 12000;
|
|
14
25
|
const COVERAGE_PLAN_OUTPUT_VERSION = 1;
|
|
15
26
|
const COVERAGE_PLAN_STATUSES = new Set(['accepted', 'deferred', 'missing']);
|
|
16
27
|
const COVERAGE_LEVELS = new Set(['comprehensive', 'partial', 'initial', 'missing']);
|
|
17
28
|
const ANCHOR_ACTIONS = new Set(['reuse', 'update', 'split', 'merge', 'defer', 'replace', 'new']);
|
|
29
|
+
const DOCUMENTATION_PLAN_RUN_SCOPES = new Set(['baseline', 'deepening']);
|
|
18
30
|
const REBUILD_STALE_POLICIES = new Set(['keep', 'archive']);
|
|
19
31
|
const PROJECT_MAP_PATH = 'architecture/overview/project-shape.md';
|
|
20
32
|
|
|
@@ -93,6 +105,12 @@ export async function preflightDocsReview(options = {}, environment = {}) {
|
|
|
93
105
|
anthropicEnvVar: options.anthropicEnvVar ?? 'ANTHROPIC_API_KEY',
|
|
94
106
|
syncTarget: options.syncTarget ?? null,
|
|
95
107
|
});
|
|
108
|
+
const sourceInventory = await buildDocsReviewSourceInventory(project, {
|
|
109
|
+
rootDir,
|
|
110
|
+
cwd,
|
|
111
|
+
sourceRootOverrides: options.sourceRootOverrides,
|
|
112
|
+
});
|
|
113
|
+
await writeDocsReviewSourceInventory(rootDir, sourceInventory);
|
|
96
114
|
const reviewPrompt = renderReviewPrompt({
|
|
97
115
|
project,
|
|
98
116
|
rootDir,
|
|
@@ -100,6 +118,7 @@ export async function preflightDocsReview(options = {}, environment = {}) {
|
|
|
100
118
|
model: reviewConfig.model,
|
|
101
119
|
executionMode: promptExecutionMode,
|
|
102
120
|
anchors: await buildDocsReviewAnchorContext(rootDir),
|
|
121
|
+
sourceInventory,
|
|
103
122
|
});
|
|
104
123
|
|
|
105
124
|
const status = options.submitHosted
|
|
@@ -138,6 +157,7 @@ export async function preflightDocsReview(options = {}, environment = {}) {
|
|
|
138
157
|
llm: reviewConfig.llm,
|
|
139
158
|
model: reviewConfig.model,
|
|
140
159
|
runtime,
|
|
160
|
+
source_inventory: summarizeSourceInventory(sourceInventory),
|
|
141
161
|
...(hosted ? { hosted } : {}),
|
|
142
162
|
...(localReview ? { local_review: localReview } : {}),
|
|
143
163
|
completed_at: null,
|
|
@@ -154,8 +174,10 @@ export async function preflightDocsReview(options = {}, environment = {}) {
|
|
|
154
174
|
status,
|
|
155
175
|
hosted,
|
|
156
176
|
localReview,
|
|
177
|
+
sourceInventory,
|
|
157
178
|
reviewPrompt,
|
|
158
179
|
warnings: [
|
|
180
|
+
...(sourceInventory.warnings ?? []).map((warning) => warning.message ?? `${warning.code}: ${warning.repo_id ?? warning.path ?? ''}`.trim()),
|
|
159
181
|
...(localReview?.truncated ? [`Local ${localReview.provider} docs-review stopped at max_tokens; saved output may be partial.`] : []),
|
|
160
182
|
],
|
|
161
183
|
message: renderDocsReviewMessage(options),
|
|
@@ -242,14 +264,40 @@ async function applyDocsReviewOutput(options) {
|
|
|
242
264
|
}
|
|
243
265
|
|
|
244
266
|
validateArchitectureDocBlocks(blocks);
|
|
267
|
+
const sourceInventory = await readDocsReviewSourceInventory(options.rootDir);
|
|
245
268
|
const coverageProjection = buildCoverageProjection(coverageBlocks, {
|
|
246
269
|
outputPath,
|
|
247
270
|
outputHash: sha256Text(source),
|
|
271
|
+
sourceInventory,
|
|
248
272
|
});
|
|
249
273
|
const anchorContext = coverageProjection ? await buildDocsReviewAnchorContext(options.rootDir) : null;
|
|
274
|
+
const sourceInventoryReconciliation = reconcileCoverageWithSourceInventory(coverageProjection, sourceInventory);
|
|
275
|
+
if (coverageProjection && sourceInventory) {
|
|
276
|
+
coverageProjection.source_inventory_summary = summarizeSourceInventory(sourceInventory);
|
|
277
|
+
coverageProjection.reconciliation_summary = sourceInventoryReconciliation
|
|
278
|
+
? {
|
|
279
|
+
scanned_count: sourceInventoryReconciliation.scanned_count,
|
|
280
|
+
declared_count: sourceInventoryReconciliation.declared_count,
|
|
281
|
+
accounted_count: sourceInventoryReconciliation.accounted_count,
|
|
282
|
+
unaccounted_ids: sourceInventoryReconciliation.unaccounted_ids,
|
|
283
|
+
unknown_declared_ids: sourceInventoryReconciliation.unknown_declared_ids,
|
|
284
|
+
source_unavailable_repo_ids: sourceInventoryReconciliation.source_unavailable_repo_ids,
|
|
285
|
+
}
|
|
286
|
+
: null;
|
|
287
|
+
coverageProjection.warnings = sourceInventoryReconciliation?.warnings ?? [];
|
|
288
|
+
}
|
|
289
|
+
const documentationPlanProjection = coverageProjection
|
|
290
|
+
? buildDocumentationPlanProjection(coverageProjection, sourceInventory, {
|
|
291
|
+
generatedAt: options.generatedAt,
|
|
292
|
+
})
|
|
293
|
+
: null;
|
|
294
|
+
if (coverageProjection && documentationPlanProjection) {
|
|
295
|
+
coverageProjection.documentation_plan_summary = summarizeDocumentationPlan(documentationPlanProjection);
|
|
296
|
+
}
|
|
250
297
|
const decisionRecommendations = validateDecisionRecommendationBlocks(decisionRecommendationBlocks);
|
|
251
298
|
const warnings = [
|
|
252
299
|
...createCoveragePlanQualityWarnings(coverageProjection, anchorContext),
|
|
300
|
+
...(sourceInventoryReconciliation?.warnings ?? []).map((warning) => `${warning.code}: ${warning.message}`),
|
|
253
301
|
...createArchitectureDocSizeWarnings(blocks),
|
|
254
302
|
...createArchitectureDocQualityWarnings(blocks),
|
|
255
303
|
];
|
|
@@ -264,6 +312,7 @@ async function applyDocsReviewOutput(options) {
|
|
|
264
312
|
}
|
|
265
313
|
|
|
266
314
|
let appliedCoverage = null;
|
|
315
|
+
let appliedDocumentationPlan = null;
|
|
267
316
|
if (coverageProjection) {
|
|
268
317
|
const coveragePath = path.join(path.dirname(options.statePath), 'docs-review-coverage.json');
|
|
269
318
|
await writeFile(coveragePath, `${JSON.stringify(coverageProjection, null, 2)}\n`, 'utf8');
|
|
@@ -274,6 +323,16 @@ async function applyDocsReviewOutput(options) {
|
|
|
274
323
|
statuses: coverageProjection.statuses,
|
|
275
324
|
};
|
|
276
325
|
}
|
|
326
|
+
if (documentationPlanProjection) {
|
|
327
|
+
const documentationPlanPath = path.join(path.dirname(options.statePath), 'docs-review-documentation-plan.json');
|
|
328
|
+
await writeFile(documentationPlanPath, `${JSON.stringify(documentationPlanProjection, null, 2)}\n`, 'utf8');
|
|
329
|
+
appliedDocumentationPlan = {
|
|
330
|
+
path: documentationPlanPath,
|
|
331
|
+
item_count: documentationPlanProjection.summary.item_count,
|
|
332
|
+
by_status: documentationPlanProjection.summary.by_status,
|
|
333
|
+
by_run_scope: documentationPlanProjection.summary.by_run_scope,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
277
336
|
|
|
278
337
|
const appliedDecisionCandidates = decisionRecommendations.length > 0
|
|
279
338
|
? await applyDecisionRecommendations({
|
|
@@ -300,6 +359,7 @@ async function applyDocsReviewOutput(options) {
|
|
|
300
359
|
manifest_hash: manifestResult.manifest.canonical.manifest_hash,
|
|
301
360
|
architecture_docs: applied,
|
|
302
361
|
...(appliedCoverage ? { coverage: appliedCoverage } : {}),
|
|
362
|
+
...(appliedDocumentationPlan ? { documentation_plan: appliedDocumentationPlan } : {}),
|
|
303
363
|
...(appliedDecisionCandidates.applied.length > 0 ? { decision_candidates: appliedDecisionCandidates.applied } : {}),
|
|
304
364
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
305
365
|
applied_at: appliedAt,
|
|
@@ -921,13 +981,24 @@ function buildCoverageProjection(blocks, options) {
|
|
|
921
981
|
|
|
922
982
|
const ids = new Set();
|
|
923
983
|
const areas = plan.areas.map((area, index) => normalizeCoverageArea(area, index, ids));
|
|
984
|
+
const areaById = new Map(areas.map((area) => [area.id, area]));
|
|
985
|
+
const hasCompletenessInventory = Object.prototype.hasOwnProperty.call(plan, 'completeness_inventory');
|
|
986
|
+
const completenessInventory = normalizeCompletenessInventory(plan.completeness_inventory, areaById);
|
|
924
987
|
const statuses = countBy(areas, 'status');
|
|
925
988
|
const coverage_levels = countBy(areas, 'coverage');
|
|
926
|
-
const
|
|
927
|
-
const acceptedCount = statuses.accepted ?? 0;
|
|
989
|
+
const inventory_statuses = countBy(completenessInventory, 'status');
|
|
928
990
|
|
|
929
991
|
return {
|
|
930
992
|
version: COVERAGE_PLAN_OUTPUT_VERSION,
|
|
993
|
+
producer: {
|
|
994
|
+
package_version: PACKAGE_VERSION,
|
|
995
|
+
prompt_version: DOCS_REVIEW_PROMPT_VERSION,
|
|
996
|
+
parser_version: DOCS_REVIEW_PARSER_VERSION,
|
|
997
|
+
coverage_projection_version: COVERAGE_PLAN_OUTPUT_VERSION,
|
|
998
|
+
...(options.sourceInventory?.producer?.scanner_version
|
|
999
|
+
? { scanner_version: options.sourceInventory.producer.scanner_version }
|
|
1000
|
+
: {}),
|
|
1001
|
+
},
|
|
931
1002
|
source: {
|
|
932
1003
|
output_path: options.outputPath,
|
|
933
1004
|
output_hash: options.outputHash,
|
|
@@ -937,13 +1008,107 @@ function buildCoverageProjection(blocks, options) {
|
|
|
937
1008
|
...(normalizeOptionalString(plan.topology) ? { topology: normalizeOptionalString(plan.topology) } : {}),
|
|
938
1009
|
...(normalizeCoverageTaxonomy(plan.taxonomy) ? { taxonomy: normalizeCoverageTaxonomy(plan.taxonomy) } : {}),
|
|
939
1010
|
area_count: areas.length,
|
|
940
|
-
|
|
1011
|
+
...(hasCompletenessInventory ? {
|
|
1012
|
+
completeness_inventory: completenessInventory,
|
|
1013
|
+
inventory_count: completenessInventory.length,
|
|
1014
|
+
inventory_statuses,
|
|
1015
|
+
} : {}),
|
|
1016
|
+
score_basis: hasCompletenessInventory ? 'model_declared_inventory' : 'area_statuses',
|
|
1017
|
+
coverage_score: calculateCoverageScore({ areas, statuses, completenessInventory, hasCompletenessInventory }),
|
|
941
1018
|
statuses,
|
|
942
1019
|
coverage_levels,
|
|
943
1020
|
areas,
|
|
944
1021
|
};
|
|
945
1022
|
}
|
|
946
1023
|
|
|
1024
|
+
function buildDocumentationPlanProjection(coverageProjection, sourceInventory, options = {}) {
|
|
1025
|
+
const generatedAt = options.generatedAt instanceof Date ? options.generatedAt : new Date();
|
|
1026
|
+
const inventoryIdsByArea = new Map();
|
|
1027
|
+
for (const item of coverageProjection.completeness_inventory ?? []) {
|
|
1028
|
+
for (const areaId of item.coverage_area_ids ?? []) {
|
|
1029
|
+
const ids = inventoryIdsByArea.get(areaId) ?? [];
|
|
1030
|
+
ids.push(item.id);
|
|
1031
|
+
inventoryIdsByArea.set(areaId, ids);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const items = coverageProjection.areas.map((area) => {
|
|
1036
|
+
const linkedInventoryIds = uniqueStrings([
|
|
1037
|
+
...(area.linked_inventory_ids ?? []),
|
|
1038
|
+
...(inventoryIdsByArea.get(area.id) ?? []),
|
|
1039
|
+
]);
|
|
1040
|
+
return {
|
|
1041
|
+
id: area.id,
|
|
1042
|
+
title: area.title ?? buildCoverageAreaTitle(area),
|
|
1043
|
+
status: area.status,
|
|
1044
|
+
run_scope: area.run_scope ?? 'baseline',
|
|
1045
|
+
expected_coverage: area.coverage,
|
|
1046
|
+
...(area.proposed_path ? { target_path: area.proposed_path } : {}),
|
|
1047
|
+
...(area.purpose ? { purpose: area.purpose } : {}),
|
|
1048
|
+
...(area.parent ? { parent: area.parent } : {}),
|
|
1049
|
+
...(linkedInventoryIds.length > 0 ? { linked_inventory_ids: linkedInventoryIds } : {}),
|
|
1050
|
+
evidence: area.evidence ?? [],
|
|
1051
|
+
blindspots: area.blindspots ?? [],
|
|
1052
|
+
...(area.anchor_action ? { anchor_action: area.anchor_action } : {}),
|
|
1053
|
+
...(area.anchor_paths?.length > 0 ? { anchor_paths: area.anchor_paths } : {}),
|
|
1054
|
+
...(area.anchor_reason ? { anchor_reason: area.anchor_reason } : {}),
|
|
1055
|
+
};
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
return {
|
|
1059
|
+
version: DOCUMENTATION_PLAN_STATE_VERSION,
|
|
1060
|
+
producer: {
|
|
1061
|
+
package_version: PACKAGE_VERSION,
|
|
1062
|
+
prompt_version: DOCS_REVIEW_PROMPT_VERSION,
|
|
1063
|
+
parser_version: DOCS_REVIEW_PARSER_VERSION,
|
|
1064
|
+
documentation_plan_projection_version: DOCS_REVIEW_DOCUMENTATION_PLAN_PROJECTION_VERSION,
|
|
1065
|
+
coverage_projection_version: coverageProjection.version,
|
|
1066
|
+
...(sourceInventory?.producer?.scanner_version
|
|
1067
|
+
? { scanner_version: sourceInventory.producer.scanner_version }
|
|
1068
|
+
: {}),
|
|
1069
|
+
generated_at: generatedAt.toISOString(),
|
|
1070
|
+
},
|
|
1071
|
+
source: coverageProjection.source,
|
|
1072
|
+
summary: {
|
|
1073
|
+
item_count: items.length,
|
|
1074
|
+
by_status: countBy(items, 'status'),
|
|
1075
|
+
by_run_scope: countBy(items, 'run_scope'),
|
|
1076
|
+
linked_inventory_item_count: new Set(items.flatMap((item) => item.linked_inventory_ids ?? [])).size,
|
|
1077
|
+
unlinked_item_count: items.filter((item) => !item.linked_inventory_ids || item.linked_inventory_ids.length === 0).length,
|
|
1078
|
+
},
|
|
1079
|
+
...(sourceInventory ? { source_inventory_summary: summarizeSourceInventory(sourceInventory) } : {}),
|
|
1080
|
+
items,
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function summarizeDocumentationPlan(documentationPlan) {
|
|
1085
|
+
return {
|
|
1086
|
+
path: 'state/docs-review-documentation-plan.json',
|
|
1087
|
+
projection_version: documentationPlan.producer.documentation_plan_projection_version,
|
|
1088
|
+
item_count: documentationPlan.summary.item_count,
|
|
1089
|
+
by_status: documentationPlan.summary.by_status,
|
|
1090
|
+
by_run_scope: documentationPlan.summary.by_run_scope,
|
|
1091
|
+
linked_inventory_item_count: documentationPlan.summary.linked_inventory_item_count,
|
|
1092
|
+
unlinked_item_count: documentationPlan.summary.unlinked_item_count,
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
function buildCoverageAreaTitle(area) {
|
|
1097
|
+
return [area.domain, area.feature, area.component].filter(Boolean).join(' / ') || area.id;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function calculateCoverageScore({ areas, statuses, completenessInventory, hasCompletenessInventory }) {
|
|
1101
|
+
if (hasCompletenessInventory) {
|
|
1102
|
+
const denominator = completenessInventory.length;
|
|
1103
|
+
const acceptedCount = completenessInventory.filter((item) => item.status === 'accepted').length;
|
|
1104
|
+
return denominator === 0 ? null : Number((acceptedCount / denominator).toFixed(4));
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
const denominator = areas.length;
|
|
1108
|
+
const acceptedCount = statuses.accepted ?? 0;
|
|
1109
|
+
return denominator === 0 ? null : Number((acceptedCount / denominator).toFixed(4));
|
|
1110
|
+
}
|
|
1111
|
+
|
|
947
1112
|
function normalizeCoverageTaxonomy(value) {
|
|
948
1113
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
949
1114
|
return null;
|
|
@@ -961,6 +1126,73 @@ function normalizeCoverageTaxonomy(value) {
|
|
|
961
1126
|
};
|
|
962
1127
|
}
|
|
963
1128
|
|
|
1129
|
+
function normalizeCompletenessInventory(value, areaById) {
|
|
1130
|
+
if (value === undefined || value === null) {
|
|
1131
|
+
return [];
|
|
1132
|
+
}
|
|
1133
|
+
if (!Array.isArray(value)) {
|
|
1134
|
+
throw new Error('Coverage plan completeness_inventory must be an array when present.');
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
const ids = new Set();
|
|
1138
|
+
const unknownCoverageAreaReferences = [];
|
|
1139
|
+
const normalized = value.map((item, index) => normalizeCompletenessInventoryItem(
|
|
1140
|
+
item,
|
|
1141
|
+
index,
|
|
1142
|
+
ids,
|
|
1143
|
+
areaById,
|
|
1144
|
+
unknownCoverageAreaReferences,
|
|
1145
|
+
));
|
|
1146
|
+
if (unknownCoverageAreaReferences.length > 0) {
|
|
1147
|
+
throw new Error(formatUnknownCoverageAreaReferences(unknownCoverageAreaReferences));
|
|
1148
|
+
}
|
|
1149
|
+
return normalized;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
function normalizeCompletenessInventoryItem(item, index, ids, areaById, unknownCoverageAreaReferences) {
|
|
1153
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
1154
|
+
throw new Error(`Coverage plan completeness_inventory item at index ${index} must be an object.`);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
const id = normalizeOptionalString(item.id);
|
|
1158
|
+
if (!id) {
|
|
1159
|
+
throw new Error(`Coverage plan completeness_inventory item at index ${index} requires non-empty "id".`);
|
|
1160
|
+
}
|
|
1161
|
+
if (ids.has(id)) {
|
|
1162
|
+
throw new Error(`Coverage plan completeness_inventory contains duplicate item id: ${id}`);
|
|
1163
|
+
}
|
|
1164
|
+
ids.add(id);
|
|
1165
|
+
|
|
1166
|
+
const status = normalizeOptionalString(item.status);
|
|
1167
|
+
if (!COVERAGE_PLAN_STATUSES.has(status)) {
|
|
1168
|
+
throw new Error(`Coverage plan completeness_inventory item "${id}" requires status to be accepted, deferred, or missing.`);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
const coverageAreaIds = normalizeStringArray(item.coverage_area_ids);
|
|
1172
|
+
for (const areaId of coverageAreaIds) {
|
|
1173
|
+
if (!areaById.has(areaId)) {
|
|
1174
|
+
unknownCoverageAreaReferences.push({ inventoryId: id, areaId });
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
return {
|
|
1179
|
+
id,
|
|
1180
|
+
...(normalizeOptionalString(item.kind) ? { kind: normalizeOptionalString(item.kind) } : {}),
|
|
1181
|
+
...(normalizeOptionalString(item.label) ? { label: normalizeOptionalString(item.label) } : {}),
|
|
1182
|
+
status,
|
|
1183
|
+
...(coverageAreaIds.length > 0 ? { coverage_area_ids: coverageAreaIds } : {}),
|
|
1184
|
+
evidence: normalizeStringArray(item.evidence),
|
|
1185
|
+
blindspots: normalizeStringArray(item.blindspots),
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function formatUnknownCoverageAreaReferences(references) {
|
|
1190
|
+
const formatted = references
|
|
1191
|
+
.map((reference) => `"${reference.inventoryId}" -> "${reference.areaId}"`)
|
|
1192
|
+
.join(', ');
|
|
1193
|
+
return `Coverage plan completeness_inventory references unknown coverage area ids: ${formatted}`;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
964
1196
|
function normalizeCoverageArea(area, index, ids) {
|
|
965
1197
|
if (!area || typeof area !== 'object' || Array.isArray(area)) {
|
|
966
1198
|
throw new Error(`Coverage plan area at index ${index} must be an object.`);
|
|
@@ -1002,6 +1234,10 @@ function normalizeCoverageArea(area, index, ids) {
|
|
|
1002
1234
|
if (anchorAction && !ANCHOR_ACTIONS.has(anchorAction)) {
|
|
1003
1235
|
throw new Error(`Coverage plan area "${id}" has invalid anchor_action: ${anchorAction}`);
|
|
1004
1236
|
}
|
|
1237
|
+
const runScope = normalizeOptionalString(area.run_scope);
|
|
1238
|
+
if (runScope && !DOCUMENTATION_PLAN_RUN_SCOPES.has(runScope)) {
|
|
1239
|
+
throw new Error(`Coverage plan area "${id}" has invalid run_scope: ${runScope}`);
|
|
1240
|
+
}
|
|
1005
1241
|
const anchorPaths = normalizeStringArray(area.anchor_paths).map(normalizeBlockPath);
|
|
1006
1242
|
for (const anchorPath of anchorPaths) {
|
|
1007
1243
|
if (
|
|
@@ -1021,7 +1257,14 @@ function normalizeCoverageArea(area, index, ids) {
|
|
|
1021
1257
|
component: normalizeOptionalString(area.component),
|
|
1022
1258
|
status,
|
|
1023
1259
|
coverage,
|
|
1260
|
+
...(normalizeOptionalString(area.title) ? { title: normalizeOptionalString(area.title) } : {}),
|
|
1261
|
+
...(normalizeOptionalString(area.purpose) ? { purpose: normalizeOptionalString(area.purpose) } : {}),
|
|
1262
|
+
...(normalizeOptionalString(area.parent) ? { parent: normalizeOptionalString(area.parent) } : {}),
|
|
1263
|
+
...(runScope ? { run_scope: runScope } : {}),
|
|
1024
1264
|
...(proposedPath ? { proposed_path: normalizeBlockPath(proposedPath) } : {}),
|
|
1265
|
+
...(normalizeStringArray(area.linked_inventory_ids).length > 0
|
|
1266
|
+
? { linked_inventory_ids: normalizeStringArray(area.linked_inventory_ids) }
|
|
1267
|
+
: {}),
|
|
1025
1268
|
evidence: normalizeStringArray(area.evidence),
|
|
1026
1269
|
blindspots: normalizeStringArray(area.blindspots),
|
|
1027
1270
|
...(normalizeOptionalString(area.reason) ? { reason: normalizeOptionalString(area.reason) } : {}),
|
|
@@ -1044,6 +1287,10 @@ function normalizeStringArray(value) {
|
|
|
1044
1287
|
: [];
|
|
1045
1288
|
}
|
|
1046
1289
|
|
|
1290
|
+
function uniqueStrings(values) {
|
|
1291
|
+
return [...new Set(values.filter((value) => typeof value === 'string' && value.trim() !== '').map((value) => value.trim()))];
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1047
1294
|
function normalizeBlockPath(value) {
|
|
1048
1295
|
return String(value ?? '').trim().replaceAll('\\', '/');
|
|
1049
1296
|
}
|
|
@@ -1134,15 +1381,30 @@ function createCoveragePlanQualityWarnings(coverageProjection, anchors) {
|
|
|
1134
1381
|
return [];
|
|
1135
1382
|
}
|
|
1136
1383
|
|
|
1384
|
+
const warnings = [];
|
|
1385
|
+
const hasModernCoverageMetadata = Boolean(coverageProjection.topology || coverageProjection.taxonomy);
|
|
1386
|
+
const hasCompletenessInventory = Array.isArray(coverageProjection.completeness_inventory);
|
|
1387
|
+
if (hasModernCoverageMetadata && !hasCompletenessInventory) {
|
|
1388
|
+
warnings.push('missing_completeness_inventory: coverage plan should enumerate discovered subsystems so deferred or missing areas cannot be silently omitted.');
|
|
1389
|
+
} else if (hasCompletenessInventory) {
|
|
1390
|
+
for (const item of coverageProjection.completeness_inventory) {
|
|
1391
|
+
if (item.status === 'accepted' && (!Array.isArray(item.coverage_area_ids) || item.coverage_area_ids.length === 0)) {
|
|
1392
|
+
warnings.push(`accepted_inventory_without_area: completeness_inventory item "${item.id}" is accepted but does not reference coverage_area_ids.`);
|
|
1393
|
+
}
|
|
1394
|
+
if (item.status !== 'accepted' && (!Array.isArray(item.blindspots) || item.blindspots.length === 0)) {
|
|
1395
|
+
warnings.push(`incomplete_inventory_blindspot: completeness_inventory item "${item.id}" is ${item.status} but does not explain the gap in blindspots.`);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1137
1400
|
const hasPriorAnchors =
|
|
1138
1401
|
(anchors?.architectureDocs?.length ?? 0) > 0 ||
|
|
1139
1402
|
(anchors?.projectMapFeatures?.length ?? 0) > 0 ||
|
|
1140
1403
|
Boolean(anchors?.coverage);
|
|
1141
1404
|
if (!hasPriorAnchors) {
|
|
1142
|
-
return
|
|
1405
|
+
return warnings;
|
|
1143
1406
|
}
|
|
1144
1407
|
|
|
1145
|
-
const warnings = [];
|
|
1146
1408
|
for (const area of coverageProjection.areas) {
|
|
1147
1409
|
if (!area.anchor_action) {
|
|
1148
1410
|
warnings.push(`missing_anchor_action: coverage area "${area.id}" should classify prior docs or project-map identities when re-review anchors exist.`);
|
|
@@ -1950,6 +2212,55 @@ function renderAnchorContext(anchors) {
|
|
|
1950
2212
|
return lines.join('\n');
|
|
1951
2213
|
}
|
|
1952
2214
|
|
|
2215
|
+
function renderSourceInventoryContext(inventory) {
|
|
2216
|
+
const lines = [
|
|
2217
|
+
'## Package Source Inventory',
|
|
2218
|
+
'Use this package-scanned inventory as the source-evidence denominator for coverage planning. Account for each scanned item as accepted, deferred, or missing in `completeness_inventory` when evidence is sufficient.',
|
|
2219
|
+
'',
|
|
2220
|
+
];
|
|
2221
|
+
|
|
2222
|
+
if (!inventory) {
|
|
2223
|
+
lines.push('No package source inventory was available.');
|
|
2224
|
+
return lines.join('\n');
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
const itemCount = Array.isArray(inventory.items) ? inventory.items.length : 0;
|
|
2228
|
+
lines.push(`Scanner: ${inventory.producer?.scanner_version ?? 'unknown'}; items=${itemCount}; warnings=${inventory.warnings?.length ?? 0}.`);
|
|
2229
|
+
if (Array.isArray(inventory.source_roots) && inventory.source_roots.length > 0) {
|
|
2230
|
+
lines.push('Source roots:');
|
|
2231
|
+
for (const sourceRoot of inventory.source_roots) {
|
|
2232
|
+
const details = [
|
|
2233
|
+
`kind=${sourceRoot.kind}`,
|
|
2234
|
+
`status=${sourceRoot.status}`,
|
|
2235
|
+
sourceRoot.repo_root_path ? `repo_root_path=${sourceRoot.repo_root_path}` : null,
|
|
2236
|
+
].filter(Boolean).join('; ');
|
|
2237
|
+
lines.push(`- ${sourceRoot.repo_id}: ${details}`);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
if (Array.isArray(inventory.warnings) && inventory.warnings.length > 0) {
|
|
2242
|
+
lines.push('Source inventory warnings:');
|
|
2243
|
+
for (const warning of inventory.warnings.slice(0, 20)) {
|
|
2244
|
+
lines.push(`- ${warning.code}: ${warning.message}`);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
if (itemCount > 0) {
|
|
2249
|
+
lines.push('Scanned inventory items:');
|
|
2250
|
+
for (const item of inventory.items.slice(0, 80)) {
|
|
2251
|
+
const evidence = item.evidence?.slice(0, 3).map((entry) => entry.path).join(', ');
|
|
2252
|
+
lines.push(`- ${item.id} (${item.kind}, ${item.confidence}): ${item.label}${evidence ? `; evidence=${evidence}` : ''}`);
|
|
2253
|
+
}
|
|
2254
|
+
if (itemCount > 80) {
|
|
2255
|
+
lines.push(`- ... ${itemCount - 80} additional scanned inventory items omitted from prompt summary.`);
|
|
2256
|
+
}
|
|
2257
|
+
} else {
|
|
2258
|
+
lines.push('Scanned inventory items: none. If source roots are unavailable, say coverage is not source-backed for those repos.');
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
return lines.join('\n');
|
|
2262
|
+
}
|
|
2263
|
+
|
|
1953
2264
|
function renderReviewPrompt(options) {
|
|
1954
2265
|
const outputPath = path.join(options.rootDir, 'state', 'docs-review-output.md');
|
|
1955
2266
|
const applyCommand = `vibecompass docs-review --root ${shellQuote(options.rootDir)} --apply-output --output ${shellQuote(outputPath)}`;
|
|
@@ -2021,22 +2332,31 @@ function renderReviewPrompt(options) {
|
|
|
2021
2332
|
'',
|
|
2022
2333
|
renderAnchorContext(options.anchors),
|
|
2023
2334
|
'',
|
|
2335
|
+
renderSourceInventoryContext(options.sourceInventory),
|
|
2336
|
+
'',
|
|
2024
2337
|
'## Staged Review Protocol',
|
|
2025
2338
|
'Stage 1 — Evidence inventory:',
|
|
2026
2339
|
'- Build a compact repo inventory from file paths, manifests/config files, route/job/test directories, and existing architecture frontmatter before reading large file bodies.',
|
|
2027
2340
|
'- Identify the project topology before choosing docs: single-repo/monolith, multi-repo, multi-surface, package/CLI, workers/services, mobile app, or mixed. State the topology in the coverage plan.',
|
|
2341
|
+
'- Build a completeness inventory of discovered apps/repos, routes/screens, API endpoints, tables/storage, jobs/cron/tasks, auth/session flows, external integrations, platform surfaces, and product features. Keep it subsystem-level, not a raw file dump.',
|
|
2028
2342
|
'- Mine finalized session notes for decision candidates and product constraints, but cap proposed decisions to the five highest-value architecture choices.',
|
|
2029
2343
|
'- Identify candidate domains, features, components, ownership boundaries, user journey entry points, feature-to-feature relationships, and system-layer connections with concrete `repo:path` evidence.',
|
|
2344
|
+
'- Separate product features from runtime surfaces. Web, mobile, backend/API, database/storage, jobs, external integrations, and deployment/runtime are surfaces or systems that participate in features; do not treat them as competing feature categories unless the project itself is a platform/tooling project.',
|
|
2030
2345
|
'- Do not fetch or summarize broad source bodies until the coverage plan says a file is needed.',
|
|
2031
2346
|
'',
|
|
2032
2347
|
'Stage 2 — Coverage plan:',
|
|
2033
2348
|
'- Propose the smallest useful architecture-doc set that will let future agents understand the project and retrieve targeted context.',
|
|
2034
2349
|
'- The minimum useful set must cover: user journey, project/system map (frontend/backend/API/data/integration structure and connections), and summaries of the core journey-facing features.',
|
|
2350
|
+
'- Treat Stage 2 as a documentation-plan gate: each proposed doc needs a title, target path, purpose, parent/backbone grouping, linked inventory ids, evidence set, expected coverage level, prior-anchor action, and `baseline` or `deepening` run scope.',
|
|
2351
|
+
'- Keep baseline docs breadth-first and compact. Use `deepening` only for scoped follow-up docs that should not block the first project-understanding baseline.',
|
|
2035
2352
|
'- Prioritize entry points, user-flow/gating relationships, data ownership, external integrations, jobs/async boundaries, auth/security boundaries, and test surfaces.',
|
|
2353
|
+
'- Account for each completeness-inventory item in the coverage plan as accepted, deferred, or missing. Do not inflate coverage by omitting large discovered subsystems; honest deferred/missing entries are expected when the first pass cannot cover everything.',
|
|
2354
|
+
'- Before generating prose, self-check that every `completeness_inventory[].coverage_area_ids[]` value points to an `areas[].id` in the same accepted plan. Fix all dangling ids together at the plan gate.',
|
|
2036
2355
|
'- Choose one primary taxonomy axis from topology evidence and keep it stable: single-repo/monolith may default domain-first; multi-repo or multi-surface projects may use platform/repo-first or domain-first with per-surface components. Do not mix taxonomy axes at the same tier unless you explicitly propose and justify a restructuring.',
|
|
2037
2356
|
'- For every prior architecture doc or project-map feature identity in Re-review Anchors, classify the plan action as `reuse`, `update`, `split`, `merge`, `defer`, or `replace`. Use `new` only for areas not represented by the prior tree.',
|
|
2038
2357
|
'- Every `split`, `merge`, or `replace` action requires an evidence-backed anchor reason before prose generation. No unexplained slug rename is allowed.',
|
|
2039
2358
|
'- Treat the accepted Stage-2 plan as the taxonomy and feature-list freeze for this docs-review run.',
|
|
2359
|
+
'- The user can revise the Stage-2 plan by merging, splitting, demoting, deferring, or requesting deepening before prose generation.',
|
|
2040
2360
|
'- Mark each proposed doc as `comprehensive`, `partial`, or `initial`, with the reason and blindspots.',
|
|
2041
2361
|
...coveragePlanStageLines,
|
|
2042
2362
|
'',
|
|
@@ -2047,6 +2367,7 @@ function renderReviewPrompt(options) {
|
|
|
2047
2367
|
'- Keep docs concise and retrieval-oriented: explain contracts, flows, invariants, and ownership; do not rewrite source code or produce file-by-file walkthroughs.',
|
|
2048
2368
|
`- Soft size budget: keep each generated architecture doc under ${ARCHITECTURE_DOC_SOFT_SIZE_LIMIT_BYTES} bytes unless the extra detail is necessary and called out in Blindspots or Retrieval guidance.`,
|
|
2049
2369
|
'- Include only the most important involved files; prefer representative entry points and owned contracts over exhaustive file lists.',
|
|
2370
|
+
'- Do not put transient apply-state instructions inside architecture docs, such as "this artifact has not been applied". Apply state belongs in the chat response and package marker, not canonical docs.',
|
|
2050
2371
|
'',
|
|
2051
2372
|
'Stage 4 — Apply and verify:',
|
|
2052
2373
|
stage4WriteLine,
|
|
@@ -2058,6 +2379,8 @@ function renderReviewPrompt(options) {
|
|
|
2058
2379
|
'- frontmatter fields: `domain`, `feature`, `component`, `status`, plus `repo` or `repos` when implementation-scoped',
|
|
2059
2380
|
'- body sections: `## Description`, `## Review metadata`, `## Details`, `## Retrieval guidance`, `## Next steps`, `## Involved files`',
|
|
2060
2381
|
'- overview docs may add compact peer sections for Stage 1 backbone outputs when useful: `## Topology`, `## Core feature inventory`, `## User journey map`, `## Project systems map`, and `## Coverage and quality summary`',
|
|
2382
|
+
'- feature docs may add a compact `## Surface matrix` prose table when multiple surfaces participate. Keep it evidence-sized: include participating surfaces with concrete `repo:path` evidence and explicit N/A rows only when an absence is important to retrieval. Do not emit a new machine-readable surface block.',
|
|
2383
|
+
'- surface/platform ownership docs should stay inside the existing contract, typically as `architecture/platform/<surface>/<component>.md`; do not introduce a top-level `systems/` tree.',
|
|
2061
2384
|
'',
|
|
2062
2385
|
'Use this exact Review metadata sub-header in every generated architecture doc:',
|
|
2063
2386
|
'',
|
|
@@ -2121,6 +2444,7 @@ function renderReviewPrompt(options) {
|
|
|
2121
2444
|
'',
|
|
2122
2445
|
'Only include relationships supported by evidence. Prefer `navigates_to` and `gates` for the main user journey; use `depends_on` and `writes_to` for supporting system relationships. If useful journey links cannot be inferred, state that as a coverage gap rather than inventing links.',
|
|
2123
2446
|
'`systems[]` is optional derived overview metadata, not a new canonical taxonomy, relationship schema, or projection source. Include it only when evidence identifies runtime/deployable units or external systems; treat `owns_features` as documentation-only grouping, put user-journey links in `relationships[]`, and record uncertain systems or system-to-system connections as blindspots.',
|
|
2447
|
+
'Per-feature surface matrices are derived prose. `systems[].owns_features` remains the only machine-readable system-to-feature hint in this release; do not duplicate that graph in another parsed block.',
|
|
2124
2448
|
'',
|
|
2125
2449
|
'## Output Contract',
|
|
2126
2450
|
'First provide a concise findings summary with:',
|
|
@@ -2129,10 +2453,11 @@ function renderReviewPrompt(options) {
|
|
|
2129
2453
|
'- user journey and project/system map coverage',
|
|
2130
2454
|
'- gaps/blindspots',
|
|
2131
2455
|
'- evidence inventory summary',
|
|
2456
|
+
'- completeness inventory summary with accepted/deferred/missing accounting',
|
|
2132
2457
|
'- topology and chosen taxonomy axis',
|
|
2133
2458
|
'- coverage plan and proposed architecture docs',
|
|
2134
2459
|
'- prior-doc anchor classifications and reasons',
|
|
2135
|
-
'- coverage/quality report, including missing/generic evidence, unmapped core features, oversized-doc candidates, and
|
|
2460
|
+
'- coverage/quality report, including missing/generic evidence, unmapped core features, oversized-doc candidates, unresolved blindspots, and a note that the score is over the evidence/completeness inventory when present, not the number of doc files',
|
|
2136
2461
|
'- token-budget risks or oversized-doc candidates',
|
|
2137
2462
|
'- proposed decisions, if any',
|
|
2138
2463
|
'',
|
|
@@ -2146,15 +2471,31 @@ function renderReviewPrompt(options) {
|
|
|
2146
2471
|
' "primary_axis": "domain-first | platform-first | repo-first | other",',
|
|
2147
2472
|
' "rationale": "Why this axis matches the evidence and how it avoids mixed tiers"',
|
|
2148
2473
|
' },',
|
|
2474
|
+
' "completeness_inventory": [',
|
|
2475
|
+
' {',
|
|
2476
|
+
' "id": "stable-subsystem-id",',
|
|
2477
|
+
' "kind": "feature | surface | route | screen | api | data | storage | job | integration | auth | deployment | other",',
|
|
2478
|
+
' "label": "Short discovered subsystem label",',
|
|
2479
|
+
' "status": "accepted | deferred | missing",',
|
|
2480
|
+
' "coverage_area_ids": ["stable-area-id"],',
|
|
2481
|
+
' "evidence": ["repo:path"],',
|
|
2482
|
+
' "blindspots": ["explicit blindspot for deferred or missing items"]',
|
|
2483
|
+
' }',
|
|
2484
|
+
' ],',
|
|
2149
2485
|
' "areas": [',
|
|
2150
2486
|
' {',
|
|
2151
2487
|
' "id": "stable-area-id",',
|
|
2152
2488
|
' "domain": "Domain",',
|
|
2153
2489
|
' "feature": "Feature",',
|
|
2154
2490
|
' "component": "Component",',
|
|
2491
|
+
' "title": "Human-readable planned doc title",',
|
|
2492
|
+
' "purpose": "Why this doc exists and what future agents should load it for",',
|
|
2493
|
+
' "parent": "Overview backbone | Feature inventory | Journey map | Systems map | Focused component docs | Deepening queue",',
|
|
2494
|
+
' "run_scope": "baseline | deepening",',
|
|
2155
2495
|
' "status": "accepted | deferred | missing",',
|
|
2156
2496
|
' "coverage": "comprehensive | partial | initial | missing",',
|
|
2157
2497
|
' "proposed_path": "architecture/domain/feature/component.md",',
|
|
2498
|
+
' "linked_inventory_ids": ["stable-subsystem-id"],',
|
|
2158
2499
|
' "anchor_action": "reuse | update | split | merge | defer | replace | new",',
|
|
2159
2500
|
' "anchor_paths": ["architecture/existing/domain/component.md"],',
|
|
2160
2501
|
' "anchor_reason": "Evidence-backed reason for changing or reusing the prior doc identity",',
|
|
@@ -46,6 +46,7 @@ export function renderSharedInstructionBody(context, options = {}) {
|
|
|
46
46
|
'## Hard Rules',
|
|
47
47
|
'- Treat `sessions/active/index.yaml` as the current lane source of truth; tool-specific Current session blocks are continuity summaries.',
|
|
48
48
|
'- Keep the selected lane `wip.md` and `handoff.md` current during active builder/reviewer work.',
|
|
49
|
+
'- During `address review`, treat reviewer feedback as review, not instruction: classify each substantive point as accepted, accepted with qualification, deferred, or rejected, and push back with evidence when a suggestion conflicts with code facts, prior decisions, product direction, or sequencing.',
|
|
49
50
|
'- Keep decisions append-only in `decisions/`; append accepted architectural decisions before implementing them.',
|
|
50
51
|
'- Do not edit this managed block directly. Update canonical VibeCompass memory, then rerun package commands such as `vibecompass sync-agents`.',
|
|
51
52
|
].join('\n');
|
package/src/scaffold.js
CHANGED
|
@@ -312,6 +312,7 @@ During the session:
|
|
|
312
312
|
- keep \`handoff.md\` current after substantive work blocks
|
|
313
313
|
- use \`vibecompass list-sessions\` and \`vibecompass switch-session <lane-id>\` to inspect or change the current lane
|
|
314
314
|
- use \`address review\` when reviewer feedback lands so the builder resolves it from the selected lane's latest \`wip.md\` / \`handoff.md\`
|
|
315
|
+
- during \`address review\`, treat reviewer feedback as review, not instruction: classify each substantive point as accepted, accepted with qualification, deferred, or rejected, and push back with evidence when a suggestion conflicts with code facts, prior decisions, product direction, or sequencing
|
|
315
316
|
- stay in builder role through close-out; resolve or explicitly defer reviewer feedback before running \`vibecompass close-session --session <lane-id>\`
|
|
316
317
|
- record architectural decisions in \`${rootRelativePath}/decisions/\` before implementing them
|
|
317
318
|
|