@vibecompass/vibecompass 0.10.1 → 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 +11 -0
- package/README.md +4 -0
- package/package.json +3 -2
- package/src/cli.js +16 -0
- package/src/docs-review.js +244 -4
- 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 +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.2 - 2026-06-19
|
|
4
|
+
|
|
5
|
+
- Add a package-owned docs-review source inventory scanner that writes `state/docs-review-source-inventory.json`, includes scanned subsystem inventory in the review prompt, and records `scanned_unaccounted` / `source_unavailable` reconciliation warnings without changing the primary coverage score basis.
|
|
6
|
+
- Add an accepted docs-review documentation-plan projection at `state/docs-review-documentation-plan.json`, preserving plan titles, purposes, parent groups, baseline/deepening scope, linked inventory ids, evidence, anchors, and producer stamps as derived state.
|
|
7
|
+
- Add `docs-review --source-root <id=path>` so Git-backed repos can be scanned from local checkouts for a run without committing machine-local paths to `project.yaml`.
|
|
8
|
+
- Add a source-inventory concurrency stress test script and run it during `prepublishOnly` to guard symlink root-containment regressions.
|
|
9
|
+
- Ignore common OS metadata files such as `.DS_Store` during source inventory scans so package evidence does not create false route/API items.
|
|
10
|
+
- Collapse nested `config/<group>/**` source-inventory evidence into one platform subsystem per config group instead of fragmenting every leaf config file into its own item.
|
|
11
|
+
- Report all dangling `completeness_inventory[].coverage_area_ids[]` references in one docs-review apply error and prompt agents to fix them at the plan gate.
|
|
12
|
+
- Clarify docs-review coverage reporting so scores are described as evidence/completeness-inventory accounting when present, not doc-count accounting.
|
|
13
|
+
|
|
3
14
|
## 0.10.1 - 2026-06-14
|
|
4
15
|
|
|
5
16
|
- Add completeness-inventory guidance to docs-review so coverage plans account for accepted, deferred, and missing subsystems instead of silently omitting large areas.
|
package/README.md
CHANGED
|
@@ -134,6 +134,7 @@ npx -y @vibecompass/vibecompass@latest sync-agents --root .compass
|
|
|
134
134
|
|
|
135
135
|
# Architecture docs review
|
|
136
136
|
npx -y @vibecompass/vibecompass@latest docs-review --root .compass --guided
|
|
137
|
+
npx -y @vibecompass/vibecompass@latest docs-review --root .compass --guided --source-root app=../app
|
|
137
138
|
npx -y @vibecompass/vibecompass@latest docs-review --root .compass --apply-output
|
|
138
139
|
|
|
139
140
|
# Rerun docs review from an archived architecture-doc slate
|
|
@@ -153,6 +154,9 @@ npx -y @vibecompass/vibecompass@latest docs-review --root .compass --rebuild --a
|
|
|
153
154
|
state/
|
|
154
155
|
```
|
|
155
156
|
|
|
157
|
+
Docs review writes derived state under `state/`, including scanned source
|
|
158
|
+
inventory, accepted coverage, and the accepted documentation-plan projection.
|
|
159
|
+
|
|
156
160
|
Generated agent files can also include managed VibeCompass blocks:
|
|
157
161
|
|
|
158
162
|
```text
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibecompass/vibecompass",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Local-first project-memory core for VibeCompass",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
20
|
"test": "node --test src/tests/*.test.js",
|
|
21
|
-
"
|
|
21
|
+
"test:source-inventory-stress": "node --test --test-concurrency=8 src/tests/source-inventory.test.js",
|
|
22
|
+
"prepublishOnly": "npm test && npm run test:source-inventory-stress"
|
|
22
23
|
},
|
|
23
24
|
"keywords": [
|
|
24
25
|
"vibecompass",
|
package/src/cli.js
CHANGED
|
@@ -286,6 +286,13 @@ export async function runCli(argv, io = createDefaultIo(), runtime = {}) {
|
|
|
286
286
|
if (result.localReview) {
|
|
287
287
|
io.stdout.write(`Local review output: ${result.localReview.output_path}\n`);
|
|
288
288
|
}
|
|
289
|
+
if (result.sourceInventory) {
|
|
290
|
+
io.stdout.write(`Source inventory: ${result.sourceInventory.summary.item_count} items`);
|
|
291
|
+
if (result.sourceInventory.summary.warning_count > 0) {
|
|
292
|
+
io.stdout.write(` (${result.sourceInventory.summary.warning_count} warnings)`);
|
|
293
|
+
}
|
|
294
|
+
io.stdout.write('\n');
|
|
295
|
+
}
|
|
289
296
|
if (result.rebuild) {
|
|
290
297
|
io.stdout.write(`Rebuild scope: ${result.rebuild.scope_path}\n`);
|
|
291
298
|
io.stdout.write(`Stale policy: ${result.rebuild.stale_policy}\n`);
|
|
@@ -1170,6 +1177,14 @@ function parseDocsReviewArgs(argv) {
|
|
|
1170
1177
|
case '--sync-target':
|
|
1171
1178
|
parsed.syncTarget = value;
|
|
1172
1179
|
break;
|
|
1180
|
+
case '--source-root': {
|
|
1181
|
+
const [repoId, sourceRootPath] = splitAssignment(value, '--source-root');
|
|
1182
|
+
parsed.sourceRootOverrides = [
|
|
1183
|
+
...(parsed.sourceRootOverrides ?? []),
|
|
1184
|
+
{ repoId, path: sourceRootPath },
|
|
1185
|
+
];
|
|
1186
|
+
break;
|
|
1187
|
+
}
|
|
1173
1188
|
case '--max-tokens':
|
|
1174
1189
|
parsed.maxTokens = value;
|
|
1175
1190
|
break;
|
|
@@ -1605,6 +1620,7 @@ function usageText() {
|
|
|
1605
1620
|
' --poll-hosted Poll hosted docs-review run status and update state/docs-review.json',
|
|
1606
1621
|
' --complete Mark accepted docs-review changes as completed locally',
|
|
1607
1622
|
' --run-local Run the generated review request with a local provider',
|
|
1623
|
+
' --source-root <id=path> Repeatable run-local source root override for Git-backed repos',
|
|
1608
1624
|
' --provider <name> Local provider for --run-local. Currently: anthropic',
|
|
1609
1625
|
' --run-local-anthropic Compatibility alias for --run-local --provider anthropic',
|
|
1610
1626
|
' --apply-output Apply accepted architecture doc blocks from review output',
|
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
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,
|
|
@@ -930,6 +990,15 @@ function buildCoverageProjection(blocks, options) {
|
|
|
930
990
|
|
|
931
991
|
return {
|
|
932
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
|
+
},
|
|
933
1002
|
source: {
|
|
934
1003
|
output_path: options.outputPath,
|
|
935
1004
|
output_hash: options.outputHash,
|
|
@@ -944,6 +1013,7 @@ function buildCoverageProjection(blocks, options) {
|
|
|
944
1013
|
inventory_count: completenessInventory.length,
|
|
945
1014
|
inventory_statuses,
|
|
946
1015
|
} : {}),
|
|
1016
|
+
score_basis: hasCompletenessInventory ? 'model_declared_inventory' : 'area_statuses',
|
|
947
1017
|
coverage_score: calculateCoverageScore({ areas, statuses, completenessInventory, hasCompletenessInventory }),
|
|
948
1018
|
statuses,
|
|
949
1019
|
coverage_levels,
|
|
@@ -951,6 +1021,82 @@ function buildCoverageProjection(blocks, options) {
|
|
|
951
1021
|
};
|
|
952
1022
|
}
|
|
953
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
|
+
|
|
954
1100
|
function calculateCoverageScore({ areas, statuses, completenessInventory, hasCompletenessInventory }) {
|
|
955
1101
|
if (hasCompletenessInventory) {
|
|
956
1102
|
const denominator = completenessInventory.length;
|
|
@@ -989,10 +1135,21 @@ function normalizeCompletenessInventory(value, areaById) {
|
|
|
989
1135
|
}
|
|
990
1136
|
|
|
991
1137
|
const ids = new Set();
|
|
992
|
-
|
|
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;
|
|
993
1150
|
}
|
|
994
1151
|
|
|
995
|
-
function normalizeCompletenessInventoryItem(item, index, ids, areaById) {
|
|
1152
|
+
function normalizeCompletenessInventoryItem(item, index, ids, areaById, unknownCoverageAreaReferences) {
|
|
996
1153
|
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
997
1154
|
throw new Error(`Coverage plan completeness_inventory item at index ${index} must be an object.`);
|
|
998
1155
|
}
|
|
@@ -1014,7 +1171,7 @@ function normalizeCompletenessInventoryItem(item, index, ids, areaById) {
|
|
|
1014
1171
|
const coverageAreaIds = normalizeStringArray(item.coverage_area_ids);
|
|
1015
1172
|
for (const areaId of coverageAreaIds) {
|
|
1016
1173
|
if (!areaById.has(areaId)) {
|
|
1017
|
-
|
|
1174
|
+
unknownCoverageAreaReferences.push({ inventoryId: id, areaId });
|
|
1018
1175
|
}
|
|
1019
1176
|
}
|
|
1020
1177
|
|
|
@@ -1029,6 +1186,13 @@ function normalizeCompletenessInventoryItem(item, index, ids, areaById) {
|
|
|
1029
1186
|
};
|
|
1030
1187
|
}
|
|
1031
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
|
+
|
|
1032
1196
|
function normalizeCoverageArea(area, index, ids) {
|
|
1033
1197
|
if (!area || typeof area !== 'object' || Array.isArray(area)) {
|
|
1034
1198
|
throw new Error(`Coverage plan area at index ${index} must be an object.`);
|
|
@@ -1070,6 +1234,10 @@ function normalizeCoverageArea(area, index, ids) {
|
|
|
1070
1234
|
if (anchorAction && !ANCHOR_ACTIONS.has(anchorAction)) {
|
|
1071
1235
|
throw new Error(`Coverage plan area "${id}" has invalid anchor_action: ${anchorAction}`);
|
|
1072
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
|
+
}
|
|
1073
1241
|
const anchorPaths = normalizeStringArray(area.anchor_paths).map(normalizeBlockPath);
|
|
1074
1242
|
for (const anchorPath of anchorPaths) {
|
|
1075
1243
|
if (
|
|
@@ -1089,7 +1257,14 @@ function normalizeCoverageArea(area, index, ids) {
|
|
|
1089
1257
|
component: normalizeOptionalString(area.component),
|
|
1090
1258
|
status,
|
|
1091
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 } : {}),
|
|
1092
1264
|
...(proposedPath ? { proposed_path: normalizeBlockPath(proposedPath) } : {}),
|
|
1265
|
+
...(normalizeStringArray(area.linked_inventory_ids).length > 0
|
|
1266
|
+
? { linked_inventory_ids: normalizeStringArray(area.linked_inventory_ids) }
|
|
1267
|
+
: {}),
|
|
1093
1268
|
evidence: normalizeStringArray(area.evidence),
|
|
1094
1269
|
blindspots: normalizeStringArray(area.blindspots),
|
|
1095
1270
|
...(normalizeOptionalString(area.reason) ? { reason: normalizeOptionalString(area.reason) } : {}),
|
|
@@ -1112,6 +1287,10 @@ function normalizeStringArray(value) {
|
|
|
1112
1287
|
: [];
|
|
1113
1288
|
}
|
|
1114
1289
|
|
|
1290
|
+
function uniqueStrings(values) {
|
|
1291
|
+
return [...new Set(values.filter((value) => typeof value === 'string' && value.trim() !== '').map((value) => value.trim()))];
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1115
1294
|
function normalizeBlockPath(value) {
|
|
1116
1295
|
return String(value ?? '').trim().replaceAll('\\', '/');
|
|
1117
1296
|
}
|
|
@@ -2033,6 +2212,55 @@ function renderAnchorContext(anchors) {
|
|
|
2033
2212
|
return lines.join('\n');
|
|
2034
2213
|
}
|
|
2035
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
|
+
|
|
2036
2264
|
function renderReviewPrompt(options) {
|
|
2037
2265
|
const outputPath = path.join(options.rootDir, 'state', 'docs-review-output.md');
|
|
2038
2266
|
const applyCommand = `vibecompass docs-review --root ${shellQuote(options.rootDir)} --apply-output --output ${shellQuote(outputPath)}`;
|
|
@@ -2104,6 +2332,8 @@ function renderReviewPrompt(options) {
|
|
|
2104
2332
|
'',
|
|
2105
2333
|
renderAnchorContext(options.anchors),
|
|
2106
2334
|
'',
|
|
2335
|
+
renderSourceInventoryContext(options.sourceInventory),
|
|
2336
|
+
'',
|
|
2107
2337
|
'## Staged Review Protocol',
|
|
2108
2338
|
'Stage 1 — Evidence inventory:',
|
|
2109
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.',
|
|
@@ -2117,12 +2347,16 @@ function renderReviewPrompt(options) {
|
|
|
2117
2347
|
'Stage 2 — Coverage plan:',
|
|
2118
2348
|
'- Propose the smallest useful architecture-doc set that will let future agents understand the project and retrieve targeted context.',
|
|
2119
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.',
|
|
2120
2352
|
'- Prioritize entry points, user-flow/gating relationships, data ownership, external integrations, jobs/async boundaries, auth/security boundaries, and test surfaces.',
|
|
2121
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.',
|
|
2122
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.',
|
|
2123
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.',
|
|
2124
2357
|
'- Every `split`, `merge`, or `replace` action requires an evidence-backed anchor reason before prose generation. No unexplained slug rename is allowed.',
|
|
2125
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.',
|
|
2126
2360
|
'- Mark each proposed doc as `comprehensive`, `partial`, or `initial`, with the reason and blindspots.',
|
|
2127
2361
|
...coveragePlanStageLines,
|
|
2128
2362
|
'',
|
|
@@ -2133,6 +2367,7 @@ function renderReviewPrompt(options) {
|
|
|
2133
2367
|
'- Keep docs concise and retrieval-oriented: explain contracts, flows, invariants, and ownership; do not rewrite source code or produce file-by-file walkthroughs.',
|
|
2134
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.`,
|
|
2135
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.',
|
|
2136
2371
|
'',
|
|
2137
2372
|
'Stage 4 — Apply and verify:',
|
|
2138
2373
|
stage4WriteLine,
|
|
@@ -2222,7 +2457,7 @@ function renderReviewPrompt(options) {
|
|
|
2222
2457
|
'- topology and chosen taxonomy axis',
|
|
2223
2458
|
'- coverage plan and proposed architecture docs',
|
|
2224
2459
|
'- prior-doc anchor classifications and reasons',
|
|
2225
|
-
'- 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',
|
|
2226
2461
|
'- token-budget risks or oversized-doc candidates',
|
|
2227
2462
|
'- proposed decisions, if any',
|
|
2228
2463
|
'',
|
|
@@ -2253,9 +2488,14 @@ function renderReviewPrompt(options) {
|
|
|
2253
2488
|
' "domain": "Domain",',
|
|
2254
2489
|
' "feature": "Feature",',
|
|
2255
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",',
|
|
2256
2495
|
' "status": "accepted | deferred | missing",',
|
|
2257
2496
|
' "coverage": "comprehensive | partial | initial | missing",',
|
|
2258
2497
|
' "proposed_path": "architecture/domain/feature/component.md",',
|
|
2498
|
+
' "linked_inventory_ids": ["stable-subsystem-id"],',
|
|
2259
2499
|
' "anchor_action": "reuse | update | split | merge | defer | replace | new",',
|
|
2260
2500
|
' "anchor_paths": ["architecture/existing/domain/component.md"],',
|
|
2261
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
|
|