@vibecompass/vibecompass 0.9.3 → 0.10.1
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 +13 -2
- package/package.json +1 -1
- package/src/docs-review.js +422 -15
- package/src/workflows/registry.js +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.10.1 - 2026-06-14
|
|
4
|
+
|
|
5
|
+
- Add completeness-inventory guidance to docs-review so coverage plans account for accepted, deferred, and missing subsystems instead of silently omitting large areas.
|
|
6
|
+
- Score accepted coverage against the optional completeness inventory denominator when present, with a backwards-compatible fallback for older coverage plans.
|
|
7
|
+
- Add soft parser warnings for modern coverage plans that omit completeness inventory or under-explain accepted/deferred/missing inventory items.
|
|
8
|
+
- Add prose-only surface matrix guidance for feature docs while keeping `systems[]` as derived overview metadata rather than a second projected graph.
|
|
9
|
+
|
|
10
|
+
## 0.10.0 - 2026-06-14
|
|
4
11
|
|
|
5
12
|
- Make docs-review prompts mode-aware: interactive AI sessions stop at the coverage-plan approval gate, while `--run-local` and hosted single-turn runs emit fenced proposal material for later apply/proposal review.
|
|
6
13
|
- Clarify that pre-approval coverage plans are proposed scope, not accepted output, so agents should not label proposed areas `accepted` before user approval.
|
|
7
|
-
-
|
|
14
|
+
- Add docs-review Stage 0 quality guidance: topology-aware taxonomy rules, re-review anchoring, prior-doc reuse/update/split/merge/defer/replace classifications, and concrete evidence requirements.
|
|
15
|
+
- Preserve optional topology, taxonomy, and anchor classification fields in accepted coverage-plan projections.
|
|
16
|
+
- Surface quality warnings for architecture docs that keep generic evidence metadata instead of concrete repo:path references.
|
|
17
|
+
- Add Stage 1 backbone guidance for topology, core feature inventory, user journey map, project systems map, coverage/quality summary, and minimal optional derived project-map `systems[]` metadata.
|
|
18
|
+
- Update generated docs-review workflow guidance and prompt regression coverage for the interactive vs single-turn split, Stage 0 anchoring contract, and Stage 1 backbone outputs.
|
|
8
19
|
|
|
9
20
|
## 0.9.2 - 2026-06-14
|
|
10
21
|
|
package/package.json
CHANGED
package/src/docs-review.js
CHANGED
|
@@ -4,16 +4,19 @@ import { createInterface } from 'node:readline/promises';
|
|
|
4
4
|
import { parseFrontmatter } from './frontmatter.js';
|
|
5
5
|
import { sha256Text } from './hash.js';
|
|
6
6
|
import { writeStateManifest } from './manifest.js';
|
|
7
|
+
import { scanProjectMemory } from './project-memory.js';
|
|
7
8
|
import { parseSimpleYaml } from './simple-yaml.js';
|
|
8
9
|
import { readSyncCursor, resolveSyncBinding } from './sync-binding.js';
|
|
9
10
|
import { PACKAGE_VERSION } from './version.js';
|
|
10
11
|
|
|
11
|
-
const DOCS_REVIEW_PROMPT_VERSION = 'VibeCompass Docs Review Prompt
|
|
12
|
+
const DOCS_REVIEW_PROMPT_VERSION = 'VibeCompass Docs Review Prompt v6';
|
|
12
13
|
const ARCHITECTURE_DOC_SOFT_SIZE_LIMIT_BYTES = 12000;
|
|
13
14
|
const COVERAGE_PLAN_OUTPUT_VERSION = 1;
|
|
14
15
|
const COVERAGE_PLAN_STATUSES = new Set(['accepted', 'deferred', 'missing']);
|
|
15
16
|
const COVERAGE_LEVELS = new Set(['comprehensive', 'partial', 'initial', 'missing']);
|
|
17
|
+
const ANCHOR_ACTIONS = new Set(['reuse', 'update', 'split', 'merge', 'defer', 'replace', 'new']);
|
|
16
18
|
const REBUILD_STALE_POLICIES = new Set(['keep', 'archive']);
|
|
19
|
+
const PROJECT_MAP_PATH = 'architecture/overview/project-shape.md';
|
|
17
20
|
|
|
18
21
|
export async function preflightDocsReview(options = {}, environment = {}) {
|
|
19
22
|
validateDocsReviewMode(options);
|
|
@@ -96,6 +99,7 @@ export async function preflightDocsReview(options = {}, environment = {}) {
|
|
|
96
99
|
llm: reviewConfig.llm,
|
|
97
100
|
model: reviewConfig.model,
|
|
98
101
|
executionMode: promptExecutionMode,
|
|
102
|
+
anchors: await buildDocsReviewAnchorContext(rootDir),
|
|
99
103
|
});
|
|
100
104
|
|
|
101
105
|
const status = options.submitHosted
|
|
@@ -242,8 +246,10 @@ async function applyDocsReviewOutput(options) {
|
|
|
242
246
|
outputPath,
|
|
243
247
|
outputHash: sha256Text(source),
|
|
244
248
|
});
|
|
249
|
+
const anchorContext = coverageProjection ? await buildDocsReviewAnchorContext(options.rootDir) : null;
|
|
245
250
|
const decisionRecommendations = validateDecisionRecommendationBlocks(decisionRecommendationBlocks);
|
|
246
251
|
const warnings = [
|
|
252
|
+
...createCoveragePlanQualityWarnings(coverageProjection, anchorContext),
|
|
247
253
|
...createArchitectureDocSizeWarnings(blocks),
|
|
248
254
|
...createArchitectureDocQualityWarnings(blocks),
|
|
249
255
|
];
|
|
@@ -448,12 +454,13 @@ async function rebuildDocsReview(options) {
|
|
|
448
454
|
rebuild: {
|
|
449
455
|
scope_path: scopePath,
|
|
450
456
|
stale_policy: stalePolicy,
|
|
457
|
+
anchor_reconcile_required: true,
|
|
451
458
|
architecture_docs: rebuildEntries,
|
|
452
459
|
architecture_doc_count: rebuildEntries.length,
|
|
453
460
|
archive_root: archiveRoot,
|
|
454
461
|
},
|
|
455
462
|
warnings: [],
|
|
456
|
-
message: 'Docs-review rebuild preview complete. Re-run with --apply to record the rebuild marker and perform the selected stale-doc action.',
|
|
463
|
+
message: 'Docs-review rebuild preview complete. Re-run with --apply to record the rebuild marker and perform the selected stale-doc action. The next docs-review run must anchor to the prior tree and reconcile taxonomy changes instead of cold-regenerating from a blank slate.',
|
|
457
464
|
};
|
|
458
465
|
}
|
|
459
466
|
|
|
@@ -479,6 +486,7 @@ async function rebuildDocsReview(options) {
|
|
|
479
486
|
rebuild: {
|
|
480
487
|
scope_path: scopePath,
|
|
481
488
|
stale_policy: stalePolicy,
|
|
489
|
+
anchor_reconcile_required: true,
|
|
482
490
|
architecture_docs: rebuildEntries,
|
|
483
491
|
architecture_doc_count: rebuildEntries.length,
|
|
484
492
|
...(archiveRoot ? { archive_root: path.join(options.rootDir, archiveRoot) } : {}),
|
|
@@ -501,8 +509,8 @@ async function rebuildDocsReview(options) {
|
|
|
501
509
|
rebuild: marker.rebuild,
|
|
502
510
|
warnings: [],
|
|
503
511
|
message: stalePolicy === 'archive'
|
|
504
|
-
? 'Archived selected architecture docs and recorded a rebuild marker. Run docs-review again
|
|
505
|
-
: 'Recorded a rebuild marker without moving architecture docs. Run docs-review again
|
|
512
|
+
? 'Archived selected architecture docs and recorded a rebuild marker. Run docs-review again; the next coverage plan must reconcile against the prior tree and explain reuse/update/split/merge/defer/replace actions.'
|
|
513
|
+
: 'Recorded a rebuild marker without moving architecture docs. Run docs-review again; the next coverage plan must reconcile against the prior tree and explain reuse/update/split/merge/defer/replace actions.',
|
|
506
514
|
};
|
|
507
515
|
}
|
|
508
516
|
|
|
@@ -913,10 +921,12 @@ function buildCoverageProjection(blocks, options) {
|
|
|
913
921
|
|
|
914
922
|
const ids = new Set();
|
|
915
923
|
const areas = plan.areas.map((area, index) => normalizeCoverageArea(area, index, ids));
|
|
924
|
+
const areaById = new Map(areas.map((area) => [area.id, area]));
|
|
925
|
+
const hasCompletenessInventory = Object.prototype.hasOwnProperty.call(plan, 'completeness_inventory');
|
|
926
|
+
const completenessInventory = normalizeCompletenessInventory(plan.completeness_inventory, areaById);
|
|
916
927
|
const statuses = countBy(areas, 'status');
|
|
917
928
|
const coverage_levels = countBy(areas, 'coverage');
|
|
918
|
-
const
|
|
919
|
-
const acceptedCount = statuses.accepted ?? 0;
|
|
929
|
+
const inventory_statuses = countBy(completenessInventory, 'status');
|
|
920
930
|
|
|
921
931
|
return {
|
|
922
932
|
version: COVERAGE_PLAN_OUTPUT_VERSION,
|
|
@@ -926,14 +936,99 @@ function buildCoverageProjection(blocks, options) {
|
|
|
926
936
|
},
|
|
927
937
|
projected_at: new Date().toISOString(),
|
|
928
938
|
summary: normalizeOptionalString(plan.summary),
|
|
939
|
+
...(normalizeOptionalString(plan.topology) ? { topology: normalizeOptionalString(plan.topology) } : {}),
|
|
940
|
+
...(normalizeCoverageTaxonomy(plan.taxonomy) ? { taxonomy: normalizeCoverageTaxonomy(plan.taxonomy) } : {}),
|
|
929
941
|
area_count: areas.length,
|
|
930
|
-
|
|
942
|
+
...(hasCompletenessInventory ? {
|
|
943
|
+
completeness_inventory: completenessInventory,
|
|
944
|
+
inventory_count: completenessInventory.length,
|
|
945
|
+
inventory_statuses,
|
|
946
|
+
} : {}),
|
|
947
|
+
coverage_score: calculateCoverageScore({ areas, statuses, completenessInventory, hasCompletenessInventory }),
|
|
931
948
|
statuses,
|
|
932
949
|
coverage_levels,
|
|
933
950
|
areas,
|
|
934
951
|
};
|
|
935
952
|
}
|
|
936
953
|
|
|
954
|
+
function calculateCoverageScore({ areas, statuses, completenessInventory, hasCompletenessInventory }) {
|
|
955
|
+
if (hasCompletenessInventory) {
|
|
956
|
+
const denominator = completenessInventory.length;
|
|
957
|
+
const acceptedCount = completenessInventory.filter((item) => item.status === 'accepted').length;
|
|
958
|
+
return denominator === 0 ? null : Number((acceptedCount / denominator).toFixed(4));
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const denominator = areas.length;
|
|
962
|
+
const acceptedCount = statuses.accepted ?? 0;
|
|
963
|
+
return denominator === 0 ? null : Number((acceptedCount / denominator).toFixed(4));
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function normalizeCoverageTaxonomy(value) {
|
|
967
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
968
|
+
return null;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
const primaryAxis = normalizeOptionalString(value.primary_axis);
|
|
972
|
+
const rationale = normalizeOptionalString(value.rationale);
|
|
973
|
+
if (!primaryAxis && !rationale) {
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
return {
|
|
978
|
+
...(primaryAxis ? { primary_axis: primaryAxis } : {}),
|
|
979
|
+
...(rationale ? { rationale } : {}),
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function normalizeCompletenessInventory(value, areaById) {
|
|
984
|
+
if (value === undefined || value === null) {
|
|
985
|
+
return [];
|
|
986
|
+
}
|
|
987
|
+
if (!Array.isArray(value)) {
|
|
988
|
+
throw new Error('Coverage plan completeness_inventory must be an array when present.');
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const ids = new Set();
|
|
992
|
+
return value.map((item, index) => normalizeCompletenessInventoryItem(item, index, ids, areaById));
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
function normalizeCompletenessInventoryItem(item, index, ids, areaById) {
|
|
996
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
997
|
+
throw new Error(`Coverage plan completeness_inventory item at index ${index} must be an object.`);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const id = normalizeOptionalString(item.id);
|
|
1001
|
+
if (!id) {
|
|
1002
|
+
throw new Error(`Coverage plan completeness_inventory item at index ${index} requires non-empty "id".`);
|
|
1003
|
+
}
|
|
1004
|
+
if (ids.has(id)) {
|
|
1005
|
+
throw new Error(`Coverage plan completeness_inventory contains duplicate item id: ${id}`);
|
|
1006
|
+
}
|
|
1007
|
+
ids.add(id);
|
|
1008
|
+
|
|
1009
|
+
const status = normalizeOptionalString(item.status);
|
|
1010
|
+
if (!COVERAGE_PLAN_STATUSES.has(status)) {
|
|
1011
|
+
throw new Error(`Coverage plan completeness_inventory item "${id}" requires status to be accepted, deferred, or missing.`);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const coverageAreaIds = normalizeStringArray(item.coverage_area_ids);
|
|
1015
|
+
for (const areaId of coverageAreaIds) {
|
|
1016
|
+
if (!areaById.has(areaId)) {
|
|
1017
|
+
throw new Error(`Coverage plan completeness_inventory item "${id}" references unknown coverage area id: ${areaId}`);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
return {
|
|
1022
|
+
id,
|
|
1023
|
+
...(normalizeOptionalString(item.kind) ? { kind: normalizeOptionalString(item.kind) } : {}),
|
|
1024
|
+
...(normalizeOptionalString(item.label) ? { label: normalizeOptionalString(item.label) } : {}),
|
|
1025
|
+
status,
|
|
1026
|
+
...(coverageAreaIds.length > 0 ? { coverage_area_ids: coverageAreaIds } : {}),
|
|
1027
|
+
evidence: normalizeStringArray(item.evidence),
|
|
1028
|
+
blindspots: normalizeStringArray(item.blindspots),
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
|
|
937
1032
|
function normalizeCoverageArea(area, index, ids) {
|
|
938
1033
|
if (!area || typeof area !== 'object' || Array.isArray(area)) {
|
|
939
1034
|
throw new Error(`Coverage plan area at index ${index} must be an object.`);
|
|
@@ -971,6 +1066,22 @@ function normalizeCoverageArea(area, index, ids) {
|
|
|
971
1066
|
}
|
|
972
1067
|
}
|
|
973
1068
|
|
|
1069
|
+
const anchorAction = normalizeOptionalString(area.anchor_action);
|
|
1070
|
+
if (anchorAction && !ANCHOR_ACTIONS.has(anchorAction)) {
|
|
1071
|
+
throw new Error(`Coverage plan area "${id}" has invalid anchor_action: ${anchorAction}`);
|
|
1072
|
+
}
|
|
1073
|
+
const anchorPaths = normalizeStringArray(area.anchor_paths).map(normalizeBlockPath);
|
|
1074
|
+
for (const anchorPath of anchorPaths) {
|
|
1075
|
+
if (
|
|
1076
|
+
!anchorPath.startsWith('architecture/') ||
|
|
1077
|
+
!anchorPath.endsWith('.md') ||
|
|
1078
|
+
path.posix.isAbsolute(anchorPath) ||
|
|
1079
|
+
anchorPath.split('/').includes('..')
|
|
1080
|
+
) {
|
|
1081
|
+
throw new Error(`Coverage plan area "${id}" has invalid anchor_paths entry: ${anchorPath}`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
974
1085
|
return {
|
|
975
1086
|
id,
|
|
976
1087
|
domain: normalizeOptionalString(area.domain),
|
|
@@ -982,6 +1093,9 @@ function normalizeCoverageArea(area, index, ids) {
|
|
|
982
1093
|
evidence: normalizeStringArray(area.evidence),
|
|
983
1094
|
blindspots: normalizeStringArray(area.blindspots),
|
|
984
1095
|
...(normalizeOptionalString(area.reason) ? { reason: normalizeOptionalString(area.reason) } : {}),
|
|
1096
|
+
...(anchorAction ? { anchor_action: anchorAction } : {}),
|
|
1097
|
+
...(normalizeOptionalString(area.anchor_reason) ? { anchor_reason: normalizeOptionalString(area.anchor_reason) } : {}),
|
|
1098
|
+
...(anchorPaths.length > 0 ? { anchor_paths: anchorPaths } : {}),
|
|
985
1099
|
};
|
|
986
1100
|
}
|
|
987
1101
|
|
|
@@ -1059,6 +1173,8 @@ function createArchitectureDocQualityWarnings(blocks) {
|
|
|
1059
1173
|
}
|
|
1060
1174
|
if (!/^\s*-\s*Evidence:\s+\S/im.test(body)) {
|
|
1061
1175
|
warnings.push(`missing_evidence_metadata: ${block.path} is missing review metadata "Evidence".`);
|
|
1176
|
+
} else if (/^\s*-\s*Evidence:\s*repo:path references inspected before making implementation claims\s*$/im.test(body)) {
|
|
1177
|
+
warnings.push(`generic_evidence_metadata: ${block.path} uses generic evidence text; list concrete repo:path references instead.`);
|
|
1062
1178
|
}
|
|
1063
1179
|
if (!/^\s*-\s*Blindspots:\s+\S/im.test(body)) {
|
|
1064
1180
|
warnings.push(`missing_blindspots_metadata: ${block.path} is missing review metadata "Blindspots".`);
|
|
@@ -1081,6 +1197,51 @@ function createArchitectureDocQualityWarnings(blocks) {
|
|
|
1081
1197
|
return warnings;
|
|
1082
1198
|
}
|
|
1083
1199
|
|
|
1200
|
+
function createCoveragePlanQualityWarnings(coverageProjection, anchors) {
|
|
1201
|
+
if (!coverageProjection) {
|
|
1202
|
+
return [];
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
const warnings = [];
|
|
1206
|
+
const hasModernCoverageMetadata = Boolean(coverageProjection.topology || coverageProjection.taxonomy);
|
|
1207
|
+
const hasCompletenessInventory = Array.isArray(coverageProjection.completeness_inventory);
|
|
1208
|
+
if (hasModernCoverageMetadata && !hasCompletenessInventory) {
|
|
1209
|
+
warnings.push('missing_completeness_inventory: coverage plan should enumerate discovered subsystems so deferred or missing areas cannot be silently omitted.');
|
|
1210
|
+
} else if (hasCompletenessInventory) {
|
|
1211
|
+
for (const item of coverageProjection.completeness_inventory) {
|
|
1212
|
+
if (item.status === 'accepted' && (!Array.isArray(item.coverage_area_ids) || item.coverage_area_ids.length === 0)) {
|
|
1213
|
+
warnings.push(`accepted_inventory_without_area: completeness_inventory item "${item.id}" is accepted but does not reference coverage_area_ids.`);
|
|
1214
|
+
}
|
|
1215
|
+
if (item.status !== 'accepted' && (!Array.isArray(item.blindspots) || item.blindspots.length === 0)) {
|
|
1216
|
+
warnings.push(`incomplete_inventory_blindspot: completeness_inventory item "${item.id}" is ${item.status} but does not explain the gap in blindspots.`);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
const hasPriorAnchors =
|
|
1222
|
+
(anchors?.architectureDocs?.length ?? 0) > 0 ||
|
|
1223
|
+
(anchors?.projectMapFeatures?.length ?? 0) > 0 ||
|
|
1224
|
+
Boolean(anchors?.coverage);
|
|
1225
|
+
if (!hasPriorAnchors) {
|
|
1226
|
+
return warnings;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
for (const area of coverageProjection.areas) {
|
|
1230
|
+
if (!area.anchor_action) {
|
|
1231
|
+
warnings.push(`missing_anchor_action: coverage area "${area.id}" should classify prior docs or project-map identities when re-review anchors exist.`);
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
if (['split', 'merge', 'replace'].includes(area.anchor_action) && !area.anchor_reason) {
|
|
1235
|
+
warnings.push(`missing_anchor_reason: coverage area "${area.id}" uses anchor_action "${area.anchor_action}" without an evidence-backed anchor_reason.`);
|
|
1236
|
+
}
|
|
1237
|
+
if (area.anchor_action !== 'new' && (!Array.isArray(area.anchor_paths) || area.anchor_paths.length === 0)) {
|
|
1238
|
+
warnings.push(`missing_anchor_paths: coverage area "${area.id}" uses anchor_action "${area.anchor_action}" without anchor_paths.`);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
return warnings;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1084
1245
|
function extractLevelTwoSections(markdown) {
|
|
1085
1246
|
return new Set(
|
|
1086
1247
|
[...String(markdown ?? '').matchAll(/^##\s+(.+)$/gm)]
|
|
@@ -1687,6 +1848,191 @@ function renderApplyOutputMessage(options) {
|
|
|
1687
1848
|
return `Applied ${parts.length > 0 ? parts.join(', ') : 'accepted docs-review output'} and completed the docs-review marker.`;
|
|
1688
1849
|
}
|
|
1689
1850
|
|
|
1851
|
+
export async function buildDocsReviewAnchorContext(rootDir) {
|
|
1852
|
+
const context = {
|
|
1853
|
+
architectureDocs: [],
|
|
1854
|
+
projectMapFeatures: [],
|
|
1855
|
+
projectMapRelationshipCount: 0,
|
|
1856
|
+
coverage: null,
|
|
1857
|
+
warnings: [],
|
|
1858
|
+
};
|
|
1859
|
+
|
|
1860
|
+
try {
|
|
1861
|
+
const scanned = await scanProjectMemory(rootDir);
|
|
1862
|
+
context.architectureDocs = scanned.documents
|
|
1863
|
+
.filter((document) => document.kind === 'architecture' && document.path !== PROJECT_MAP_PATH && document.extracted)
|
|
1864
|
+
.map((document) => ({
|
|
1865
|
+
path: document.path,
|
|
1866
|
+
domain: document.extracted.domain,
|
|
1867
|
+
feature: document.extracted.feature,
|
|
1868
|
+
component: document.extracted.component,
|
|
1869
|
+
status: document.extracted.status,
|
|
1870
|
+
repos: document.extracted.repo_ids ?? [],
|
|
1871
|
+
}))
|
|
1872
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
1873
|
+
|
|
1874
|
+
if (scanned.errors.length > 0) {
|
|
1875
|
+
context.warnings.push(`${scanned.errors.length} project-memory parse error(s) found while building anchors; inspect status before trusting prior docs.`);
|
|
1876
|
+
}
|
|
1877
|
+
} catch (error) {
|
|
1878
|
+
context.warnings.push(`Could not scan existing project memory for anchors: ${error instanceof Error ? error.message : String(error)}`);
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
try {
|
|
1882
|
+
const projectMap = await readProjectMapAnchor(rootDir);
|
|
1883
|
+
context.projectMapFeatures = projectMap.features;
|
|
1884
|
+
context.projectMapRelationshipCount = projectMap.relationshipCount;
|
|
1885
|
+
} catch (error) {
|
|
1886
|
+
context.warnings.push(`Could not read existing project-map anchors: ${error instanceof Error ? error.message : String(error)}`);
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
try {
|
|
1890
|
+
context.coverage = await readCoverageAnchor(rootDir);
|
|
1891
|
+
} catch (error) {
|
|
1892
|
+
context.warnings.push(`Could not read docs-review coverage anchors: ${error instanceof Error ? error.message : String(error)}`);
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
return context;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
async function readProjectMapAnchor(rootDir) {
|
|
1899
|
+
const projectShapePath = path.join(rootDir, PROJECT_MAP_PATH);
|
|
1900
|
+
let content;
|
|
1901
|
+
try {
|
|
1902
|
+
content = await readFile(projectShapePath, 'utf8');
|
|
1903
|
+
} catch (error) {
|
|
1904
|
+
if (error?.code === 'ENOENT') {
|
|
1905
|
+
return { features: [], relationshipCount: 0 };
|
|
1906
|
+
}
|
|
1907
|
+
throw error;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
const match = content.match(/^```vibecompass-project-map\s+version=1\s*\n([\s\S]*?)^```[ \t]*$/m);
|
|
1911
|
+
if (!match) {
|
|
1912
|
+
return { features: [], relationshipCount: 0 };
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
const parsed = JSON.parse(match[1]);
|
|
1916
|
+
const features = Array.isArray(parsed.features)
|
|
1917
|
+
? parsed.features
|
|
1918
|
+
.map((feature) => ({
|
|
1919
|
+
domain: normalizeOptionalString(feature?.domain),
|
|
1920
|
+
feature: normalizeOptionalString(feature?.feature),
|
|
1921
|
+
isEntryPoint: Boolean(feature?.is_entry_point),
|
|
1922
|
+
}))
|
|
1923
|
+
.filter((feature) => feature.domain && feature.feature)
|
|
1924
|
+
: [];
|
|
1925
|
+
|
|
1926
|
+
return {
|
|
1927
|
+
features,
|
|
1928
|
+
relationshipCount: Array.isArray(parsed.relationships) ? parsed.relationships.length : 0,
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
async function readCoverageAnchor(rootDir) {
|
|
1933
|
+
const coveragePath = path.join(rootDir, 'state', 'docs-review-coverage.json');
|
|
1934
|
+
let content;
|
|
1935
|
+
try {
|
|
1936
|
+
content = await readFile(coveragePath, 'utf8');
|
|
1937
|
+
} catch (error) {
|
|
1938
|
+
if (error?.code === 'ENOENT') {
|
|
1939
|
+
return null;
|
|
1940
|
+
}
|
|
1941
|
+
throw error;
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
const parsed = JSON.parse(content);
|
|
1945
|
+
return {
|
|
1946
|
+
summary: normalizeOptionalString(parsed.summary),
|
|
1947
|
+
areaCount: Number.isInteger(parsed.area_count) ? parsed.area_count : null,
|
|
1948
|
+
coverageScore: typeof parsed.coverage_score === 'number' ? parsed.coverage_score : null,
|
|
1949
|
+
statuses: parsed.statuses && typeof parsed.statuses === 'object' && !Array.isArray(parsed.statuses)
|
|
1950
|
+
? parsed.statuses
|
|
1951
|
+
: null,
|
|
1952
|
+
areas: Array.isArray(parsed.areas)
|
|
1953
|
+
? parsed.areas
|
|
1954
|
+
.map((area) => ({
|
|
1955
|
+
id: normalizeOptionalString(area?.id),
|
|
1956
|
+
path: normalizeOptionalString(area?.proposed_path),
|
|
1957
|
+
status: normalizeOptionalString(area?.status),
|
|
1958
|
+
coverage: normalizeOptionalString(area?.coverage),
|
|
1959
|
+
}))
|
|
1960
|
+
.filter((area) => area.id)
|
|
1961
|
+
: [],
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
function renderAnchorContext(anchors) {
|
|
1966
|
+
const lines = [
|
|
1967
|
+
'## Re-review Anchors',
|
|
1968
|
+
'Use these anchors to prevent taxonomy churn. Reuse prior slugs and doc identities when current evidence still supports them.',
|
|
1969
|
+
'If you propose a taxonomy change, explain the evidence and classify the prior doc/slug action before writing prose.',
|
|
1970
|
+
'',
|
|
1971
|
+
'Required prior-doc classifications for Stage 2:',
|
|
1972
|
+
'- `reuse`: keep path/frontmatter identity and update only if needed',
|
|
1973
|
+
'- `update`: keep path/frontmatter identity but refresh content or coverage',
|
|
1974
|
+
'- `split`: replace one broad prior doc with multiple focused docs',
|
|
1975
|
+
'- `merge`: combine overlapping prior docs',
|
|
1976
|
+
'- `defer`: leave prior area documented but out of the accepted generation scope',
|
|
1977
|
+
'- `replace`: rename or move only when evidence strongly justifies it',
|
|
1978
|
+
'- `new`: add a new area not represented by the prior tree',
|
|
1979
|
+
'',
|
|
1980
|
+
];
|
|
1981
|
+
|
|
1982
|
+
if (anchors?.warnings?.length > 0) {
|
|
1983
|
+
lines.push('Anchor warnings:');
|
|
1984
|
+
for (const warning of anchors.warnings) {
|
|
1985
|
+
lines.push(`- ${warning}`);
|
|
1986
|
+
}
|
|
1987
|
+
lines.push('');
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
if (anchors?.architectureDocs?.length > 0) {
|
|
1991
|
+
lines.push(`Existing architecture docs (${anchors.architectureDocs.length}):`);
|
|
1992
|
+
for (const doc of anchors.architectureDocs.slice(0, 40)) {
|
|
1993
|
+
const identity = [doc.domain, doc.feature, doc.component].filter(Boolean).join(' / ');
|
|
1994
|
+
const repos = doc.repos.length > 0 ? ` repos=${doc.repos.join(',')}` : '';
|
|
1995
|
+
lines.push(`- ${doc.path} -> ${identity || 'unmapped'}; status=${doc.status ?? 'unknown'}${repos}`);
|
|
1996
|
+
}
|
|
1997
|
+
if (anchors.architectureDocs.length > 40) {
|
|
1998
|
+
lines.push(`- ... ${anchors.architectureDocs.length - 40} additional architecture docs omitted from prompt anchor summary; still classify broad prior taxonomy before changing it.`);
|
|
1999
|
+
}
|
|
2000
|
+
} else {
|
|
2001
|
+
lines.push('Existing architecture docs: none beyond the starter overview or none readable.');
|
|
2002
|
+
}
|
|
2003
|
+
lines.push('');
|
|
2004
|
+
|
|
2005
|
+
if (anchors?.projectMapFeatures?.length > 0) {
|
|
2006
|
+
lines.push(`Existing project-map feature identities (${anchors.projectMapFeatures.length}; relationships=${anchors.projectMapRelationshipCount}):`);
|
|
2007
|
+
for (const feature of anchors.projectMapFeatures.slice(0, 40)) {
|
|
2008
|
+
lines.push(`- ${feature.domain} / ${feature.feature}${feature.isEntryPoint ? ' (entry point)' : ''}`);
|
|
2009
|
+
}
|
|
2010
|
+
if (anchors.projectMapFeatures.length > 40) {
|
|
2011
|
+
lines.push(`- ... ${anchors.projectMapFeatures.length - 40} additional project-map features omitted from prompt anchor summary.`);
|
|
2012
|
+
}
|
|
2013
|
+
} else {
|
|
2014
|
+
lines.push('Existing project-map feature identities: none found.');
|
|
2015
|
+
}
|
|
2016
|
+
lines.push('');
|
|
2017
|
+
|
|
2018
|
+
if (anchors?.coverage) {
|
|
2019
|
+
const score = typeof anchors.coverage.coverageScore === 'number'
|
|
2020
|
+
? `${Math.round(anchors.coverage.coverageScore * 100)}% accepted`
|
|
2021
|
+
: 'unscored';
|
|
2022
|
+
lines.push(`Existing coverage projection: ${anchors.coverage.areaCount ?? anchors.coverage.areas.length} areas, ${score}.`);
|
|
2023
|
+
if (anchors.coverage.summary) {
|
|
2024
|
+
lines.push(`Coverage summary: ${anchors.coverage.summary}`);
|
|
2025
|
+
}
|
|
2026
|
+
for (const area of anchors.coverage.areas.slice(0, 20)) {
|
|
2027
|
+
lines.push(`- ${area.id}: ${area.status || 'unknown'} / ${area.coverage || 'unknown'}${area.path ? ` -> ${area.path}` : ''}`);
|
|
2028
|
+
}
|
|
2029
|
+
} else {
|
|
2030
|
+
lines.push('Existing coverage projection: none found.');
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
return lines.join('\n');
|
|
2034
|
+
}
|
|
2035
|
+
|
|
1690
2036
|
function renderReviewPrompt(options) {
|
|
1691
2037
|
const outputPath = path.join(options.rootDir, 'state', 'docs-review-output.md');
|
|
1692
2038
|
const applyCommand = `vibecompass docs-review --root ${shellQuote(options.rootDir)} --apply-output --output ${shellQuote(outputPath)}`;
|
|
@@ -1715,6 +2061,18 @@ function renderReviewPrompt(options) {
|
|
|
1715
2061
|
const stage4WriteLine = isSingleTurn
|
|
1716
2062
|
? `- The package will save the model response to \`${outputPath}\`; local users or hosted proposal reviewers decide what becomes canonical later.`
|
|
1717
2063
|
: `- After the user accepts Stage 2, write the complete accepted fenced output verbatim to \`${outputPath}\`.`;
|
|
2064
|
+
const applyCommandLabel = isSingleTurn ? 'Apply command after acceptance' : 'Apply command after user acceptance';
|
|
2065
|
+
const stage4ApplyLines = isSingleTurn
|
|
2066
|
+
? [
|
|
2067
|
+
'- Do not claim docs-review is applied in this response; generated output is proposal material until local apply or hosted proposal acceptance succeeds.',
|
|
2068
|
+
`- Local apply command after acceptance: \`${applyCommand}\`.`,
|
|
2069
|
+
`- If \`vibecompass\` is not installed on PATH, the local user can run \`${npxApplyCommand}\` instead.`,
|
|
2070
|
+
]
|
|
2071
|
+
: [
|
|
2072
|
+
`- Then run \`${applyCommand}\` to validate and apply the accepted blocks.`,
|
|
2073
|
+
`- If \`vibecompass\` is not installed on PATH, run \`${npxApplyCommand}\` instead.`,
|
|
2074
|
+
'- Only report docs-review as applied after the apply command succeeds. Surface any parser warnings, especially oversized docs, so the user can decide whether to compact or accept the output.',
|
|
2075
|
+
];
|
|
1718
2076
|
const finalOutputLine = isSingleTurn
|
|
1719
2077
|
? `Only include accepted proposal-material blocks for coverage, architecture, and decision recommendations. The package or hosted worker saves the response to \`${outputPath}\`; report that generated output still requires local apply or hosted proposal acceptance before it is canonical.`
|
|
1720
2078
|
: `Only include accepted coverage, architecture, and decision-recommendation blocks. After user acceptance, save the final accepted output to \`${outputPath}\`, run \`${applyCommand}\` or \`${npxApplyCommand}\`, and report applied paths plus parser warnings.`;
|
|
@@ -1735,7 +2093,7 @@ function renderReviewPrompt(options) {
|
|
|
1735
2093
|
`- Review model/version to record: ${options.model}`,
|
|
1736
2094
|
`- Execution mode: ${isSingleTurn ? 'single-turn (emit accepted proposal output now)' : 'interactive (stop at coverage-plan approval gate)'}`,
|
|
1737
2095
|
`- Accepted output file: ${outputPath}`,
|
|
1738
|
-
`-
|
|
2096
|
+
`- ${applyCommandLabel}: ${applyCommand}`,
|
|
1739
2097
|
`- Apply command if the CLI is not installed on PATH: ${npxApplyCommand}`,
|
|
1740
2098
|
'',
|
|
1741
2099
|
'## Required Inputs',
|
|
@@ -1744,38 +2102,50 @@ function renderReviewPrompt(options) {
|
|
|
1744
2102
|
'3. Read all canonical files under `architecture/`, `decisions/`, and finalized `sessions/`.',
|
|
1745
2103
|
'4. Inspect declared repositories before making implementation claims.',
|
|
1746
2104
|
'',
|
|
2105
|
+
renderAnchorContext(options.anchors),
|
|
2106
|
+
'',
|
|
1747
2107
|
'## Staged Review Protocol',
|
|
1748
2108
|
'Stage 1 — Evidence inventory:',
|
|
1749
2109
|
'- Build a compact repo inventory from file paths, manifests/config files, route/job/test directories, and existing architecture frontmatter before reading large file bodies.',
|
|
2110
|
+
'- 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.',
|
|
2111
|
+
'- 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.',
|
|
1750
2112
|
'- Mine finalized session notes for decision candidates and product constraints, but cap proposed decisions to the five highest-value architecture choices.',
|
|
1751
2113
|
'- Identify candidate domains, features, components, ownership boundaries, user journey entry points, feature-to-feature relationships, and system-layer connections with concrete `repo:path` evidence.',
|
|
2114
|
+
'- 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.',
|
|
1752
2115
|
'- Do not fetch or summarize broad source bodies until the coverage plan says a file is needed.',
|
|
1753
2116
|
'',
|
|
1754
2117
|
'Stage 2 — Coverage plan:',
|
|
1755
2118
|
'- Propose the smallest useful architecture-doc set that will let future agents understand the project and retrieve targeted context.',
|
|
1756
2119
|
'- 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.',
|
|
1757
2120
|
'- Prioritize entry points, user-flow/gating relationships, data ownership, external integrations, jobs/async boundaries, auth/security boundaries, and test surfaces.',
|
|
2121
|
+
'- 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.',
|
|
2122
|
+
'- 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
|
+
'- 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
|
+
'- Every `split`, `merge`, or `replace` action requires an evidence-backed anchor reason before prose generation. No unexplained slug rename is allowed.',
|
|
2125
|
+
'- Treat the accepted Stage-2 plan as the taxonomy and feature-list freeze for this docs-review run.',
|
|
1758
2126
|
'- Mark each proposed doc as `comprehensive`, `partial`, or `initial`, with the reason and blindspots.',
|
|
1759
2127
|
...coveragePlanStageLines,
|
|
1760
2128
|
'',
|
|
1761
|
-
'Stage 3 —
|
|
2129
|
+
'Stage 3 — Backbone output and bounded doc generation:',
|
|
1762
2130
|
'- Generate only accepted docs, one complete fenced block per architecture path.',
|
|
1763
|
-
'- Ensure `architecture/overview/project-shape.md` includes
|
|
2131
|
+
'- Ensure `architecture/overview/project-shape.md` includes compact backbone sections for topology, core feature inventory, user journey map, project systems map, and coverage/quality summary.',
|
|
2132
|
+
'- Ensure `architecture/overview/project-shape.md` includes exactly one `vibecompass-project-map version=1` JSON fence that lists journey-facing features, supported feature relationships, and minimal optional derived `systems[]` metadata.',
|
|
1764
2133
|
'- Keep docs concise and retrieval-oriented: explain contracts, flows, invariants, and ownership; do not rewrite source code or produce file-by-file walkthroughs.',
|
|
1765
2134
|
`- 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.`,
|
|
1766
2135
|
'- Include only the most important involved files; prefer representative entry points and owned contracts over exhaustive file lists.',
|
|
1767
2136
|
'',
|
|
1768
2137
|
'Stage 4 — Apply and verify:',
|
|
1769
2138
|
stage4WriteLine,
|
|
1770
|
-
|
|
1771
|
-
`- If \`vibecompass\` is not installed on PATH, run \`${npxApplyCommand}\` instead.`,
|
|
1772
|
-
'- Only report docs-review as applied after the apply command succeeds. Surface any parser warnings, especially oversized docs, so the user can decide whether to compact or accept the output.',
|
|
2139
|
+
...stage4ApplyLines,
|
|
1773
2140
|
'',
|
|
1774
2141
|
'## Architecture Doc Contract',
|
|
1775
2142
|
'When creating or updating architecture docs, use the existing VibeCompass structure:',
|
|
1776
2143
|
'- path: `architecture/<domain-slug>/<feature-slug>/<component-slug>.md` for component docs',
|
|
1777
2144
|
'- frontmatter fields: `domain`, `feature`, `component`, `status`, plus `repo` or `repos` when implementation-scoped',
|
|
1778
2145
|
'- body sections: `## Description`, `## Review metadata`, `## Details`, `## Retrieval guidance`, `## Next steps`, `## Involved files`',
|
|
2146
|
+
'- 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`',
|
|
2147
|
+
'- 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.',
|
|
2148
|
+
'- 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.',
|
|
1779
2149
|
'',
|
|
1780
2150
|
'Use this exact Review metadata sub-header in every generated architecture doc:',
|
|
1781
2151
|
'',
|
|
@@ -1785,14 +2155,14 @@ function renderReviewPrompt(options) {
|
|
|
1785
2155
|
`- Project mode: ${options.project.mode}`,
|
|
1786
2156
|
'- Confidence: high | medium | low',
|
|
1787
2157
|
'- Coverage: comprehensive | partial | initial',
|
|
1788
|
-
'- Evidence: repo:path references inspected before making implementation claims',
|
|
2158
|
+
'- Evidence: concrete repo:path references inspected before making implementation claims',
|
|
1789
2159
|
'- Retrieval scope: when future agents should load this doc',
|
|
1790
2160
|
`- Token posture: compact unless the doc intentionally exceeds ${ARCHITECTURE_DOC_SOFT_SIZE_LIMIT_BYTES} bytes`,
|
|
1791
2161
|
'- Blindspots: explicit list, or "None identified" only when evidence is comprehensive',
|
|
1792
2162
|
'',
|
|
1793
2163
|
'## Review Rules',
|
|
1794
2164
|
'1. Do not delete `architecture/overview/project-shape.md`.',
|
|
1795
|
-
'2. Add or update `architecture/overview/project-shape.md` so it explains the project
|
|
2165
|
+
'2. Add or update `architecture/overview/project-shape.md` so it explains the topology, core feature inventory, user journey map, project systems map, coverage/quality summary, and accepted `vibecompass-project-map version=1` block.',
|
|
1796
2166
|
'3. Add evidence-backed component docs alongside the overview for core journey-facing features and important supporting systems.',
|
|
1797
2167
|
'4. Update `architecture/overview/project-shape.md` coverage and blindspot sections to summarize what has now been mapped.',
|
|
1798
2168
|
'5. Keep all implementation claims source-backed with `repo:path` evidence.',
|
|
@@ -1801,6 +2171,7 @@ function renderReviewPrompt(options) {
|
|
|
1801
2171
|
'8. Do not append real `D-NNN` decisions without explicit user acceptance. Propose candidate decisions separately.',
|
|
1802
2172
|
'9. Do not edit `decisions/INDEX.md` directly unless a canonical decision file was explicitly accepted and appended.',
|
|
1803
2173
|
'10. Optimize for future retrieval: project-level docs stay compact; component docs carry focused context for a specific area.',
|
|
2174
|
+
'11. Evidence metadata must name concrete files such as `app:src/server.ts`; do not use generic placeholders like "repo:path references inspected before making implementation claims".',
|
|
1804
2175
|
'',
|
|
1805
2176
|
'## Project Map Block',
|
|
1806
2177
|
'Include exactly one project-map block inside `architecture/overview/project-shape.md` when the user accepts journey/system mapping output:',
|
|
@@ -1822,19 +2193,36 @@ function renderReviewPrompt(options) {
|
|
|
1822
2193
|
' "kind": "gates | navigates_to | depends_on | writes_to",',
|
|
1823
2194
|
' "label": "Short evidence-backed relationship label"',
|
|
1824
2195
|
' }',
|
|
2196
|
+
' ],',
|
|
2197
|
+
' "systems": [',
|
|
2198
|
+
' {',
|
|
2199
|
+
' "id": "stable-system-id",',
|
|
2200
|
+
' "name": "System or runtime surface name",',
|
|
2201
|
+
' "kind": "frontend | backend | mobile | worker | cli | package | database | integration | mixed",',
|
|
2202
|
+
' "repos": ["repo-id"],',
|
|
2203
|
+
' "summary": "Short evidence-backed system summary",',
|
|
2204
|
+
' "owns_features": [{ "domain": "Domain", "feature": "Feature" }]',
|
|
2205
|
+
' }',
|
|
1825
2206
|
' ]',
|
|
1826
2207
|
'}',
|
|
1827
2208
|
'```',
|
|
1828
2209
|
'',
|
|
1829
2210
|
'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.',
|
|
2211
|
+
'`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.',
|
|
2212
|
+
'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.',
|
|
1830
2213
|
'',
|
|
1831
2214
|
'## Output Contract',
|
|
1832
2215
|
'First provide a concise findings summary with:',
|
|
1833
2216
|
'- mapped areas',
|
|
2217
|
+
'- backbone outputs: topology, core feature inventory, user journey map, project systems map, and coverage/quality summary',
|
|
1834
2218
|
'- user journey and project/system map coverage',
|
|
1835
2219
|
'- gaps/blindspots',
|
|
1836
2220
|
'- evidence inventory summary',
|
|
2221
|
+
'- completeness inventory summary with accepted/deferred/missing accounting',
|
|
2222
|
+
'- topology and chosen taxonomy axis',
|
|
1837
2223
|
'- coverage plan and proposed architecture docs',
|
|
2224
|
+
'- prior-doc anchor classifications and reasons',
|
|
2225
|
+
'- coverage/quality report, including missing/generic evidence, unmapped core features, oversized-doc candidates, and unresolved blindspots',
|
|
1838
2226
|
'- token-budget risks or oversized-doc candidates',
|
|
1839
2227
|
'- proposed decisions, if any',
|
|
1840
2228
|
'',
|
|
@@ -1843,6 +2231,22 @@ function renderReviewPrompt(options) {
|
|
|
1843
2231
|
'```vibecompass-coverage-plan version=1',
|
|
1844
2232
|
'{',
|
|
1845
2233
|
' "summary": "Short coverage plan summary",',
|
|
2234
|
+
' "topology": "single-repo | monolith | multi-repo | multi-surface | package-cli | mixed",',
|
|
2235
|
+
' "taxonomy": {',
|
|
2236
|
+
' "primary_axis": "domain-first | platform-first | repo-first | other",',
|
|
2237
|
+
' "rationale": "Why this axis matches the evidence and how it avoids mixed tiers"',
|
|
2238
|
+
' },',
|
|
2239
|
+
' "completeness_inventory": [',
|
|
2240
|
+
' {',
|
|
2241
|
+
' "id": "stable-subsystem-id",',
|
|
2242
|
+
' "kind": "feature | surface | route | screen | api | data | storage | job | integration | auth | deployment | other",',
|
|
2243
|
+
' "label": "Short discovered subsystem label",',
|
|
2244
|
+
' "status": "accepted | deferred | missing",',
|
|
2245
|
+
' "coverage_area_ids": ["stable-area-id"],',
|
|
2246
|
+
' "evidence": ["repo:path"],',
|
|
2247
|
+
' "blindspots": ["explicit blindspot for deferred or missing items"]',
|
|
2248
|
+
' }',
|
|
2249
|
+
' ],',
|
|
1846
2250
|
' "areas": [',
|
|
1847
2251
|
' {',
|
|
1848
2252
|
' "id": "stable-area-id",',
|
|
@@ -1852,6 +2256,9 @@ function renderReviewPrompt(options) {
|
|
|
1852
2256
|
' "status": "accepted | deferred | missing",',
|
|
1853
2257
|
' "coverage": "comprehensive | partial | initial | missing",',
|
|
1854
2258
|
' "proposed_path": "architecture/domain/feature/component.md",',
|
|
2259
|
+
' "anchor_action": "reuse | update | split | merge | defer | replace | new",',
|
|
2260
|
+
' "anchor_paths": ["architecture/existing/domain/component.md"],',
|
|
2261
|
+
' "anchor_reason": "Evidence-backed reason for changing or reusing the prior doc identity",',
|
|
1855
2262
|
' "evidence": ["repo:path"],',
|
|
1856
2263
|
' "blindspots": ["explicit blindspot"]',
|
|
1857
2264
|
' }',
|
|
@@ -110,11 +110,14 @@ Turn starter project memory into evidence-backed architecture docs under \`archi
|
|
|
110
110
|
1. Read \`${projectYamlPath}\`, \`${contextPath}\`, existing \`${architecturePath}\`, \`${decisionsPath}\`, and finalized \`${sessionsPath}\` notes.
|
|
111
111
|
2. Inspect the declared source repos in stages:
|
|
112
112
|
- inventory paths, manifests, configs, route/job/test directories, and existing architecture frontmatter first
|
|
113
|
+
- identify project topology before choosing taxonomy: single-repo/monolith, multi-repo, multi-surface, package/CLI, workers/services, mobile app, or mixed
|
|
114
|
+
- build a subsystem-level completeness inventory of apps/repos, routes/screens, APIs, tables/storage, jobs/cron/tasks, auth/session flows, external integrations, runtime surfaces, and product features
|
|
115
|
+
- distinguish product features from participating surfaces such as web, mobile, backend/API, data, jobs, integrations, and deployment/runtime
|
|
113
116
|
- produce a coverage plan before reading broad source bodies
|
|
114
117
|
- read targeted source files only when the coverage plan needs evidence
|
|
115
118
|
3. Run \`vibecompass docs-review --guided --llm <tool-name> --model <model-name>\` to record the review marker and print the canonical docs-review contract. Use the current AI tool name and model when known.
|
|
116
|
-
4. Present the evidence inventory, coverage plan, user journey, project
|
|
117
|
-
5. Draft only output the user accepts. After approval, the accepted plan must be fenced as \`vibecompass-coverage-plan version=1\`. Accepted architecture docs must be fenced as \`vibecompass-architecture-doc path=architecture/domain/feature/component.md\` blocks and must include required frontmatter: \`domain\`, \`feature\`, \`component\`, and \`status\`. The accepted \`architecture/overview/project-shape.md\` doc should include exactly one \`vibecompass-project-map version=1\` JSON block with journey-facing features, entry points,
|
|
119
|
+
4. Present the evidence inventory, completeness inventory with accepted/deferred/missing accounting, topology, chosen taxonomy axis, prior-doc reuse/update/split/merge/defer/replace classifications, coverage plan, backbone outputs (core feature inventory, user journey map, project systems map, coverage/quality summary), proposed architecture docs, blindspots, token-budget risks, quality report, and any decision candidates before generating accepted output. If you stop here for user approval, state clearly that no architecture docs have been applied yet and do not label proposed areas as accepted.
|
|
120
|
+
5. Draft only output the user accepts. After approval, the accepted plan must be fenced as \`vibecompass-coverage-plan version=1\`. Accepted architecture docs must be fenced as \`vibecompass-architecture-doc path=architecture/domain/feature/component.md\` blocks and must include required frontmatter: \`domain\`, \`feature\`, \`component\`, and \`status\`. Feature docs may include compact prose-only surface matrices; surface/platform ownership docs stay under the existing \`architecture/platform/<surface>/<component>.md\` shape rather than a new top-level systems tree. The accepted \`architecture/overview/project-shape.md\` doc should include compact backbone sections and exactly one \`vibecompass-project-map version=1\` JSON block with journey-facing features, entry points, evidence-backed relationships, and minimal optional derived \`systems[]\` metadata. Accepted local decisions may be fenced as \`vibecompass-decision-recommendation target=decisions/domain.md\` blocks without D-numbers.
|
|
118
121
|
6. Keep each generated architecture doc focused and retrieval-oriented. Soft budget: under 12000 bytes per doc unless the extra detail is necessary and called out in review metadata or retrieval guidance.
|
|
119
122
|
7. Write the accepted fenced blocks verbatim to \`${outputPath}\`.
|
|
120
123
|
8. Run \`${applyCommand}\` to validate and apply the accepted blocks. If \`vibecompass\` is not installed on PATH, run \`${npxApplyCommand}\` instead. Only report docs-review as applied after this command succeeds. If accepted docs were written manually outside the output file, run \`vibecompass docs-review --root ${rootRelativePath} --complete\` after confirming the marker should be completed.
|
|
@@ -122,7 +125,7 @@ Turn starter project memory into evidence-backed architecture docs under \`archi
|
|
|
122
125
|
|
|
123
126
|
## Rebuild option
|
|
124
127
|
|
|
125
|
-
If the user explicitly wants to rerun docs-review
|
|
128
|
+
If the user explicitly wants to rerun docs-review for a focused architecture-doc scope, preview the scope first with \`vibecompass docs-review --rebuild --path architecture/<scope>\`. Only apply cleanup after the user accepts the preview; use \`vibecompass docs-review --rebuild --apply --stale-policy archive --path architecture/<scope>\` when the user explicitly wants selected stale docs archived before the next review. Rebuild archives selected \`architecture/**/*.md\` docs under \`state/docs-review-archive/\`; it does not mutate decisions, sessions, project.yaml, workflow files, agent instruction files, \`architecture/README.md\`, or \`architecture/overview/project-shape.md\`. Rebuild is not permission to cold-regenerate taxonomy: the next docs-review coverage plan must anchor to the prior tree and explain reuse/update/split/merge/defer/replace actions before prose generation.
|
|
126
129
|
|
|
127
130
|
## Rules
|
|
128
131
|
|