@vibecompass/vibecompass 0.9.2 → 0.10.0

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0 - 2026-06-14
4
+
5
+ - 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
+ - Clarify that pre-approval coverage plans are proposed scope, not accepted output, so agents should not label proposed areas `accepted` before user approval.
7
+ - 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.
8
+ - Preserve optional topology, taxonomy, and anchor classification fields in accepted coverage-plan projections.
9
+ - Surface quality warnings for architecture docs that keep generic evidence metadata instead of concrete repo:path references.
10
+ - 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.
11
+ - 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.
12
+
3
13
  ## 0.9.2 - 2026-06-14
4
14
 
5
15
  - Clarify docs-review acceptance gates so agents state when no architecture docs have been applied yet and only report completion after apply succeeds.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecompass/vibecompass",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "description": "Local-first project-memory core for VibeCompass",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 v3';
12
+ const DOCS_REVIEW_PROMPT_VERSION = 'VibeCompass Docs Review Prompt v5';
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);
@@ -79,6 +82,9 @@ export async function preflightDocsReview(options = {}, environment = {}) {
79
82
  : normalizeReviewConfig(options);
80
83
  const env = environment.env ?? process.env;
81
84
  const localProvider = resolveLocalProvider(options);
85
+ const promptExecutionMode = options.submitHosted || shouldRunLocal(options)
86
+ ? 'single-turn'
87
+ : 'interactive';
82
88
  const runtime = resolveDocsReviewRuntime(project, {
83
89
  env,
84
90
  guided: Boolean(options.guided),
@@ -92,6 +98,8 @@ export async function preflightDocsReview(options = {}, environment = {}) {
92
98
  rootDir,
93
99
  llm: reviewConfig.llm,
94
100
  model: reviewConfig.model,
101
+ executionMode: promptExecutionMode,
102
+ anchors: await buildDocsReviewAnchorContext(rootDir),
95
103
  });
96
104
 
97
105
  const status = options.submitHosted
@@ -238,8 +246,10 @@ async function applyDocsReviewOutput(options) {
238
246
  outputPath,
239
247
  outputHash: sha256Text(source),
240
248
  });
249
+ const anchorContext = coverageProjection ? await buildDocsReviewAnchorContext(options.rootDir) : null;
241
250
  const decisionRecommendations = validateDecisionRecommendationBlocks(decisionRecommendationBlocks);
242
251
  const warnings = [
252
+ ...createCoveragePlanQualityWarnings(coverageProjection, anchorContext),
243
253
  ...createArchitectureDocSizeWarnings(blocks),
244
254
  ...createArchitectureDocQualityWarnings(blocks),
245
255
  ];
@@ -444,12 +454,13 @@ async function rebuildDocsReview(options) {
444
454
  rebuild: {
445
455
  scope_path: scopePath,
446
456
  stale_policy: stalePolicy,
457
+ anchor_reconcile_required: true,
447
458
  architecture_docs: rebuildEntries,
448
459
  architecture_doc_count: rebuildEntries.length,
449
460
  archive_root: archiveRoot,
450
461
  },
451
462
  warnings: [],
452
- 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.',
453
464
  };
454
465
  }
455
466
 
@@ -475,6 +486,7 @@ async function rebuildDocsReview(options) {
475
486
  rebuild: {
476
487
  scope_path: scopePath,
477
488
  stale_policy: stalePolicy,
489
+ anchor_reconcile_required: true,
478
490
  architecture_docs: rebuildEntries,
479
491
  architecture_doc_count: rebuildEntries.length,
480
492
  ...(archiveRoot ? { archive_root: path.join(options.rootDir, archiveRoot) } : {}),
@@ -497,8 +509,8 @@ async function rebuildDocsReview(options) {
497
509
  rebuild: marker.rebuild,
498
510
  warnings: [],
499
511
  message: stalePolicy === 'archive'
500
- ? 'Archived selected architecture docs and recorded a rebuild marker. Run docs-review again, then apply accepted output.'
501
- : 'Recorded a rebuild marker without moving architecture docs. Run docs-review again, then apply accepted output.',
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.',
502
514
  };
503
515
  }
504
516
 
@@ -922,6 +934,8 @@ function buildCoverageProjection(blocks, options) {
922
934
  },
923
935
  projected_at: new Date().toISOString(),
924
936
  summary: normalizeOptionalString(plan.summary),
937
+ ...(normalizeOptionalString(plan.topology) ? { topology: normalizeOptionalString(plan.topology) } : {}),
938
+ ...(normalizeCoverageTaxonomy(plan.taxonomy) ? { taxonomy: normalizeCoverageTaxonomy(plan.taxonomy) } : {}),
925
939
  area_count: areas.length,
926
940
  coverage_score: denominator === 0 ? null : Number((acceptedCount / denominator).toFixed(4)),
927
941
  statuses,
@@ -930,6 +944,23 @@ function buildCoverageProjection(blocks, options) {
930
944
  };
931
945
  }
932
946
 
947
+ function normalizeCoverageTaxonomy(value) {
948
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
949
+ return null;
950
+ }
951
+
952
+ const primaryAxis = normalizeOptionalString(value.primary_axis);
953
+ const rationale = normalizeOptionalString(value.rationale);
954
+ if (!primaryAxis && !rationale) {
955
+ return null;
956
+ }
957
+
958
+ return {
959
+ ...(primaryAxis ? { primary_axis: primaryAxis } : {}),
960
+ ...(rationale ? { rationale } : {}),
961
+ };
962
+ }
963
+
933
964
  function normalizeCoverageArea(area, index, ids) {
934
965
  if (!area || typeof area !== 'object' || Array.isArray(area)) {
935
966
  throw new Error(`Coverage plan area at index ${index} must be an object.`);
@@ -967,6 +998,22 @@ function normalizeCoverageArea(area, index, ids) {
967
998
  }
968
999
  }
969
1000
 
1001
+ const anchorAction = normalizeOptionalString(area.anchor_action);
1002
+ if (anchorAction && !ANCHOR_ACTIONS.has(anchorAction)) {
1003
+ throw new Error(`Coverage plan area "${id}" has invalid anchor_action: ${anchorAction}`);
1004
+ }
1005
+ const anchorPaths = normalizeStringArray(area.anchor_paths).map(normalizeBlockPath);
1006
+ for (const anchorPath of anchorPaths) {
1007
+ if (
1008
+ !anchorPath.startsWith('architecture/') ||
1009
+ !anchorPath.endsWith('.md') ||
1010
+ path.posix.isAbsolute(anchorPath) ||
1011
+ anchorPath.split('/').includes('..')
1012
+ ) {
1013
+ throw new Error(`Coverage plan area "${id}" has invalid anchor_paths entry: ${anchorPath}`);
1014
+ }
1015
+ }
1016
+
970
1017
  return {
971
1018
  id,
972
1019
  domain: normalizeOptionalString(area.domain),
@@ -978,6 +1025,9 @@ function normalizeCoverageArea(area, index, ids) {
978
1025
  evidence: normalizeStringArray(area.evidence),
979
1026
  blindspots: normalizeStringArray(area.blindspots),
980
1027
  ...(normalizeOptionalString(area.reason) ? { reason: normalizeOptionalString(area.reason) } : {}),
1028
+ ...(anchorAction ? { anchor_action: anchorAction } : {}),
1029
+ ...(normalizeOptionalString(area.anchor_reason) ? { anchor_reason: normalizeOptionalString(area.anchor_reason) } : {}),
1030
+ ...(anchorPaths.length > 0 ? { anchor_paths: anchorPaths } : {}),
981
1031
  };
982
1032
  }
983
1033
 
@@ -1055,6 +1105,8 @@ function createArchitectureDocQualityWarnings(blocks) {
1055
1105
  }
1056
1106
  if (!/^\s*-\s*Evidence:\s+\S/im.test(body)) {
1057
1107
  warnings.push(`missing_evidence_metadata: ${block.path} is missing review metadata "Evidence".`);
1108
+ } else if (/^\s*-\s*Evidence:\s*repo:path references inspected before making implementation claims\s*$/im.test(body)) {
1109
+ warnings.push(`generic_evidence_metadata: ${block.path} uses generic evidence text; list concrete repo:path references instead.`);
1058
1110
  }
1059
1111
  if (!/^\s*-\s*Blindspots:\s+\S/im.test(body)) {
1060
1112
  warnings.push(`missing_blindspots_metadata: ${block.path} is missing review metadata "Blindspots".`);
@@ -1077,6 +1129,36 @@ function createArchitectureDocQualityWarnings(blocks) {
1077
1129
  return warnings;
1078
1130
  }
1079
1131
 
1132
+ function createCoveragePlanQualityWarnings(coverageProjection, anchors) {
1133
+ if (!coverageProjection) {
1134
+ return [];
1135
+ }
1136
+
1137
+ const hasPriorAnchors =
1138
+ (anchors?.architectureDocs?.length ?? 0) > 0 ||
1139
+ (anchors?.projectMapFeatures?.length ?? 0) > 0 ||
1140
+ Boolean(anchors?.coverage);
1141
+ if (!hasPriorAnchors) {
1142
+ return [];
1143
+ }
1144
+
1145
+ const warnings = [];
1146
+ for (const area of coverageProjection.areas) {
1147
+ if (!area.anchor_action) {
1148
+ warnings.push(`missing_anchor_action: coverage area "${area.id}" should classify prior docs or project-map identities when re-review anchors exist.`);
1149
+ continue;
1150
+ }
1151
+ if (['split', 'merge', 'replace'].includes(area.anchor_action) && !area.anchor_reason) {
1152
+ warnings.push(`missing_anchor_reason: coverage area "${area.id}" uses anchor_action "${area.anchor_action}" without an evidence-backed anchor_reason.`);
1153
+ }
1154
+ if (area.anchor_action !== 'new' && (!Array.isArray(area.anchor_paths) || area.anchor_paths.length === 0)) {
1155
+ warnings.push(`missing_anchor_paths: coverage area "${area.id}" uses anchor_action "${area.anchor_action}" without anchor_paths.`);
1156
+ }
1157
+ }
1158
+
1159
+ return warnings;
1160
+ }
1161
+
1080
1162
  function extractLevelTwoSections(markdown) {
1081
1163
  return new Set(
1082
1164
  [...String(markdown ?? '').matchAll(/^##\s+(.+)$/gm)]
@@ -1683,10 +1765,237 @@ function renderApplyOutputMessage(options) {
1683
1765
  return `Applied ${parts.length > 0 ? parts.join(', ') : 'accepted docs-review output'} and completed the docs-review marker.`;
1684
1766
  }
1685
1767
 
1768
+ export async function buildDocsReviewAnchorContext(rootDir) {
1769
+ const context = {
1770
+ architectureDocs: [],
1771
+ projectMapFeatures: [],
1772
+ projectMapRelationshipCount: 0,
1773
+ coverage: null,
1774
+ warnings: [],
1775
+ };
1776
+
1777
+ try {
1778
+ const scanned = await scanProjectMemory(rootDir);
1779
+ context.architectureDocs = scanned.documents
1780
+ .filter((document) => document.kind === 'architecture' && document.path !== PROJECT_MAP_PATH && document.extracted)
1781
+ .map((document) => ({
1782
+ path: document.path,
1783
+ domain: document.extracted.domain,
1784
+ feature: document.extracted.feature,
1785
+ component: document.extracted.component,
1786
+ status: document.extracted.status,
1787
+ repos: document.extracted.repo_ids ?? [],
1788
+ }))
1789
+ .sort((left, right) => left.path.localeCompare(right.path));
1790
+
1791
+ if (scanned.errors.length > 0) {
1792
+ context.warnings.push(`${scanned.errors.length} project-memory parse error(s) found while building anchors; inspect status before trusting prior docs.`);
1793
+ }
1794
+ } catch (error) {
1795
+ context.warnings.push(`Could not scan existing project memory for anchors: ${error instanceof Error ? error.message : String(error)}`);
1796
+ }
1797
+
1798
+ try {
1799
+ const projectMap = await readProjectMapAnchor(rootDir);
1800
+ context.projectMapFeatures = projectMap.features;
1801
+ context.projectMapRelationshipCount = projectMap.relationshipCount;
1802
+ } catch (error) {
1803
+ context.warnings.push(`Could not read existing project-map anchors: ${error instanceof Error ? error.message : String(error)}`);
1804
+ }
1805
+
1806
+ try {
1807
+ context.coverage = await readCoverageAnchor(rootDir);
1808
+ } catch (error) {
1809
+ context.warnings.push(`Could not read docs-review coverage anchors: ${error instanceof Error ? error.message : String(error)}`);
1810
+ }
1811
+
1812
+ return context;
1813
+ }
1814
+
1815
+ async function readProjectMapAnchor(rootDir) {
1816
+ const projectShapePath = path.join(rootDir, PROJECT_MAP_PATH);
1817
+ let content;
1818
+ try {
1819
+ content = await readFile(projectShapePath, 'utf8');
1820
+ } catch (error) {
1821
+ if (error?.code === 'ENOENT') {
1822
+ return { features: [], relationshipCount: 0 };
1823
+ }
1824
+ throw error;
1825
+ }
1826
+
1827
+ const match = content.match(/^```vibecompass-project-map\s+version=1\s*\n([\s\S]*?)^```[ \t]*$/m);
1828
+ if (!match) {
1829
+ return { features: [], relationshipCount: 0 };
1830
+ }
1831
+
1832
+ const parsed = JSON.parse(match[1]);
1833
+ const features = Array.isArray(parsed.features)
1834
+ ? parsed.features
1835
+ .map((feature) => ({
1836
+ domain: normalizeOptionalString(feature?.domain),
1837
+ feature: normalizeOptionalString(feature?.feature),
1838
+ isEntryPoint: Boolean(feature?.is_entry_point),
1839
+ }))
1840
+ .filter((feature) => feature.domain && feature.feature)
1841
+ : [];
1842
+
1843
+ return {
1844
+ features,
1845
+ relationshipCount: Array.isArray(parsed.relationships) ? parsed.relationships.length : 0,
1846
+ };
1847
+ }
1848
+
1849
+ async function readCoverageAnchor(rootDir) {
1850
+ const coveragePath = path.join(rootDir, 'state', 'docs-review-coverage.json');
1851
+ let content;
1852
+ try {
1853
+ content = await readFile(coveragePath, 'utf8');
1854
+ } catch (error) {
1855
+ if (error?.code === 'ENOENT') {
1856
+ return null;
1857
+ }
1858
+ throw error;
1859
+ }
1860
+
1861
+ const parsed = JSON.parse(content);
1862
+ return {
1863
+ summary: normalizeOptionalString(parsed.summary),
1864
+ areaCount: Number.isInteger(parsed.area_count) ? parsed.area_count : null,
1865
+ coverageScore: typeof parsed.coverage_score === 'number' ? parsed.coverage_score : null,
1866
+ statuses: parsed.statuses && typeof parsed.statuses === 'object' && !Array.isArray(parsed.statuses)
1867
+ ? parsed.statuses
1868
+ : null,
1869
+ areas: Array.isArray(parsed.areas)
1870
+ ? parsed.areas
1871
+ .map((area) => ({
1872
+ id: normalizeOptionalString(area?.id),
1873
+ path: normalizeOptionalString(area?.proposed_path),
1874
+ status: normalizeOptionalString(area?.status),
1875
+ coverage: normalizeOptionalString(area?.coverage),
1876
+ }))
1877
+ .filter((area) => area.id)
1878
+ : [],
1879
+ };
1880
+ }
1881
+
1882
+ function renderAnchorContext(anchors) {
1883
+ const lines = [
1884
+ '## Re-review Anchors',
1885
+ 'Use these anchors to prevent taxonomy churn. Reuse prior slugs and doc identities when current evidence still supports them.',
1886
+ 'If you propose a taxonomy change, explain the evidence and classify the prior doc/slug action before writing prose.',
1887
+ '',
1888
+ 'Required prior-doc classifications for Stage 2:',
1889
+ '- `reuse`: keep path/frontmatter identity and update only if needed',
1890
+ '- `update`: keep path/frontmatter identity but refresh content or coverage',
1891
+ '- `split`: replace one broad prior doc with multiple focused docs',
1892
+ '- `merge`: combine overlapping prior docs',
1893
+ '- `defer`: leave prior area documented but out of the accepted generation scope',
1894
+ '- `replace`: rename or move only when evidence strongly justifies it',
1895
+ '- `new`: add a new area not represented by the prior tree',
1896
+ '',
1897
+ ];
1898
+
1899
+ if (anchors?.warnings?.length > 0) {
1900
+ lines.push('Anchor warnings:');
1901
+ for (const warning of anchors.warnings) {
1902
+ lines.push(`- ${warning}`);
1903
+ }
1904
+ lines.push('');
1905
+ }
1906
+
1907
+ if (anchors?.architectureDocs?.length > 0) {
1908
+ lines.push(`Existing architecture docs (${anchors.architectureDocs.length}):`);
1909
+ for (const doc of anchors.architectureDocs.slice(0, 40)) {
1910
+ const identity = [doc.domain, doc.feature, doc.component].filter(Boolean).join(' / ');
1911
+ const repos = doc.repos.length > 0 ? ` repos=${doc.repos.join(',')}` : '';
1912
+ lines.push(`- ${doc.path} -> ${identity || 'unmapped'}; status=${doc.status ?? 'unknown'}${repos}`);
1913
+ }
1914
+ if (anchors.architectureDocs.length > 40) {
1915
+ lines.push(`- ... ${anchors.architectureDocs.length - 40} additional architecture docs omitted from prompt anchor summary; still classify broad prior taxonomy before changing it.`);
1916
+ }
1917
+ } else {
1918
+ lines.push('Existing architecture docs: none beyond the starter overview or none readable.');
1919
+ }
1920
+ lines.push('');
1921
+
1922
+ if (anchors?.projectMapFeatures?.length > 0) {
1923
+ lines.push(`Existing project-map feature identities (${anchors.projectMapFeatures.length}; relationships=${anchors.projectMapRelationshipCount}):`);
1924
+ for (const feature of anchors.projectMapFeatures.slice(0, 40)) {
1925
+ lines.push(`- ${feature.domain} / ${feature.feature}${feature.isEntryPoint ? ' (entry point)' : ''}`);
1926
+ }
1927
+ if (anchors.projectMapFeatures.length > 40) {
1928
+ lines.push(`- ... ${anchors.projectMapFeatures.length - 40} additional project-map features omitted from prompt anchor summary.`);
1929
+ }
1930
+ } else {
1931
+ lines.push('Existing project-map feature identities: none found.');
1932
+ }
1933
+ lines.push('');
1934
+
1935
+ if (anchors?.coverage) {
1936
+ const score = typeof anchors.coverage.coverageScore === 'number'
1937
+ ? `${Math.round(anchors.coverage.coverageScore * 100)}% accepted`
1938
+ : 'unscored';
1939
+ lines.push(`Existing coverage projection: ${anchors.coverage.areaCount ?? anchors.coverage.areas.length} areas, ${score}.`);
1940
+ if (anchors.coverage.summary) {
1941
+ lines.push(`Coverage summary: ${anchors.coverage.summary}`);
1942
+ }
1943
+ for (const area of anchors.coverage.areas.slice(0, 20)) {
1944
+ lines.push(`- ${area.id}: ${area.status || 'unknown'} / ${area.coverage || 'unknown'}${area.path ? ` -> ${area.path}` : ''}`);
1945
+ }
1946
+ } else {
1947
+ lines.push('Existing coverage projection: none found.');
1948
+ }
1949
+
1950
+ return lines.join('\n');
1951
+ }
1952
+
1686
1953
  function renderReviewPrompt(options) {
1687
1954
  const outputPath = path.join(options.rootDir, 'state', 'docs-review-output.md');
1688
1955
  const applyCommand = `vibecompass docs-review --root ${shellQuote(options.rootDir)} --apply-output --output ${shellQuote(outputPath)}`;
1689
1956
  const npxApplyCommand = `npx -y @vibecompass/vibecompass@${PACKAGE_VERSION} docs-review --root ${shellQuote(options.rootDir)} --apply-output --output ${shellQuote(outputPath)}`;
1957
+ const isSingleTurn = options.executionMode === 'single-turn';
1958
+ const coveragePlanStageLines = isSingleTurn
1959
+ ? [
1960
+ '- This is a single-turn execution mode: there is no mid-review user approval turn.',
1961
+ '- Treat the coverage plan you emit in this response as accepted proposal material, then emit the planned `vibecompass-coverage-plan`, architecture-doc, and decision-recommendation fences in the same response.',
1962
+ '- Human review happens later at the local apply-output step or hosted proposal surface; generated output is not canonical until accepted and applied.',
1963
+ ]
1964
+ : [
1965
+ '- Before user approval, present the coverage plan as human-readable proposed scope. Do not label proposed areas `accepted` and do not emit the `vibecompass-coverage-plan` fence yet.',
1966
+ '- Ask for user acceptance before emitting fenced coverage-plan or architecture-doc blocks. When you stop for acceptance, state plainly that no architecture docs have been applied yet.',
1967
+ '- After approval, emit the accepted plan as one `vibecompass-coverage-plan version=1` JSON fence before architecture-doc blocks.',
1968
+ ];
1969
+ const outputContractIntroLines = isSingleTurn
1970
+ ? [
1971
+ 'Because this is a single-turn execution mode, output the machine-readable coverage-plan block in this response before architecture-doc blocks.',
1972
+ ]
1973
+ : [
1974
+ 'If you are stopping for user approval, do not output the machine-readable fence below yet; show the plan as proposed scope and ask the user to approve or revise it.',
1975
+ '',
1976
+ 'After the user accepts the plan, output one machine-readable coverage-plan block for the accepted plan:',
1977
+ ];
1978
+ const stage4WriteLine = isSingleTurn
1979
+ ? `- The package will save the model response to \`${outputPath}\`; local users or hosted proposal reviewers decide what becomes canonical later.`
1980
+ : `- After the user accepts Stage 2, write the complete accepted fenced output verbatim to \`${outputPath}\`.`;
1981
+ const applyCommandLabel = isSingleTurn ? 'Apply command after acceptance' : 'Apply command after user acceptance';
1982
+ const stage4ApplyLines = isSingleTurn
1983
+ ? [
1984
+ '- Do not claim docs-review is applied in this response; generated output is proposal material until local apply or hosted proposal acceptance succeeds.',
1985
+ `- Local apply command after acceptance: \`${applyCommand}\`.`,
1986
+ `- If \`vibecompass\` is not installed on PATH, the local user can run \`${npxApplyCommand}\` instead.`,
1987
+ ]
1988
+ : [
1989
+ `- Then run \`${applyCommand}\` to validate and apply the accepted blocks.`,
1990
+ `- If \`vibecompass\` is not installed on PATH, run \`${npxApplyCommand}\` instead.`,
1991
+ '- 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.',
1992
+ ];
1993
+ const finalOutputLine = isSingleTurn
1994
+ ? `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.`
1995
+ : `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.`;
1996
+ const architectureDocBlockIntro = isSingleTurn
1997
+ ? 'Then, for each planned architecture doc in this single-turn response, output one fenced block exactly like this:'
1998
+ : 'Then, for each architecture doc the user accepts, output one fenced block exactly like this:';
1690
1999
 
1691
2000
  return [
1692
2001
  `# ${DOCS_REVIEW_PROMPT_VERSION}`,
@@ -1699,8 +2008,9 @@ function renderReviewPrompt(options) {
1699
2008
  `- Project mode: ${options.project.mode}`,
1700
2009
  `- Review provider to record: ${options.llm}`,
1701
2010
  `- Review model/version to record: ${options.model}`,
2011
+ `- Execution mode: ${isSingleTurn ? 'single-turn (emit accepted proposal output now)' : 'interactive (stop at coverage-plan approval gate)'}`,
1702
2012
  `- Accepted output file: ${outputPath}`,
1703
- `- Apply command after user acceptance: ${applyCommand}`,
2013
+ `- ${applyCommandLabel}: ${applyCommand}`,
1704
2014
  `- Apply command if the CLI is not installed on PATH: ${npxApplyCommand}`,
1705
2015
  '',
1706
2016
  '## Required Inputs',
@@ -1709,9 +2019,12 @@ function renderReviewPrompt(options) {
1709
2019
  '3. Read all canonical files under `architecture/`, `decisions/`, and finalized `sessions/`.',
1710
2020
  '4. Inspect declared repositories before making implementation claims.',
1711
2021
  '',
2022
+ renderAnchorContext(options.anchors),
2023
+ '',
1712
2024
  '## Staged Review Protocol',
1713
2025
  'Stage 1 — Evidence inventory:',
1714
2026
  '- 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
+ '- 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.',
1715
2028
  '- Mine finalized session notes for decision candidates and product constraints, but cap proposed decisions to the five highest-value architecture choices.',
1716
2029
  '- Identify candidate domains, features, components, ownership boundaries, user journey entry points, feature-to-feature relationships, and system-layer connections with concrete `repo:path` evidence.',
1717
2030
  '- Do not fetch or summarize broad source bodies until the coverage plan says a file is needed.',
@@ -1720,28 +2033,31 @@ function renderReviewPrompt(options) {
1720
2033
  '- Propose the smallest useful architecture-doc set that will let future agents understand the project and retrieve targeted context.',
1721
2034
  '- 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.',
1722
2035
  '- Prioritize entry points, user-flow/gating relationships, data ownership, external integrations, jobs/async boundaries, auth/security boundaries, and test surfaces.',
2036
+ '- 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
+ '- 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
+ '- Every `split`, `merge`, or `replace` action requires an evidence-backed anchor reason before prose generation. No unexplained slug rename is allowed.',
2039
+ '- Treat the accepted Stage-2 plan as the taxonomy and feature-list freeze for this docs-review run.',
1723
2040
  '- Mark each proposed doc as `comprehensive`, `partial`, or `initial`, with the reason and blindspots.',
1724
- '- Emit the accepted plan as one `vibecompass-coverage-plan version=1` JSON fence before architecture-doc blocks.',
1725
- '- Ask for user acceptance before emitting fenced architecture-doc blocks. When you stop for acceptance, state plainly that no architecture docs have been applied yet.',
2041
+ ...coveragePlanStageLines,
1726
2042
  '',
1727
- 'Stage 3 — Bounded doc generation:',
2043
+ 'Stage 3 — Backbone output and bounded doc generation:',
1728
2044
  '- Generate only accepted docs, one complete fenced block per architecture path.',
1729
- '- Ensure `architecture/overview/project-shape.md` includes a concise system map plus one `vibecompass-project-map version=1` JSON fence that lists journey-facing features and supported relationships.',
2045
+ '- 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.',
2046
+ '- 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.',
1730
2047
  '- Keep docs concise and retrieval-oriented: explain contracts, flows, invariants, and ownership; do not rewrite source code or produce file-by-file walkthroughs.',
1731
2048
  `- 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.`,
1732
2049
  '- Include only the most important involved files; prefer representative entry points and owned contracts over exhaustive file lists.',
1733
2050
  '',
1734
2051
  'Stage 4 — Apply and verify:',
1735
- `- After the user accepts Stage 2, write the complete accepted fenced output verbatim to \`${outputPath}\`.`,
1736
- `- Then run \`${applyCommand}\` to validate and apply the accepted blocks.`,
1737
- `- If \`vibecompass\` is not installed on PATH, run \`${npxApplyCommand}\` instead.`,
1738
- '- 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.',
2052
+ stage4WriteLine,
2053
+ ...stage4ApplyLines,
1739
2054
  '',
1740
2055
  '## Architecture Doc Contract',
1741
2056
  'When creating or updating architecture docs, use the existing VibeCompass structure:',
1742
2057
  '- path: `architecture/<domain-slug>/<feature-slug>/<component-slug>.md` for component docs',
1743
2058
  '- frontmatter fields: `domain`, `feature`, `component`, `status`, plus `repo` or `repos` when implementation-scoped',
1744
2059
  '- body sections: `## Description`, `## Review metadata`, `## Details`, `## Retrieval guidance`, `## Next steps`, `## Involved files`',
2060
+ '- 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`',
1745
2061
  '',
1746
2062
  'Use this exact Review metadata sub-header in every generated architecture doc:',
1747
2063
  '',
@@ -1751,14 +2067,14 @@ function renderReviewPrompt(options) {
1751
2067
  `- Project mode: ${options.project.mode}`,
1752
2068
  '- Confidence: high | medium | low',
1753
2069
  '- Coverage: comprehensive | partial | initial',
1754
- '- Evidence: repo:path references inspected before making implementation claims',
2070
+ '- Evidence: concrete repo:path references inspected before making implementation claims',
1755
2071
  '- Retrieval scope: when future agents should load this doc',
1756
2072
  `- Token posture: compact unless the doc intentionally exceeds ${ARCHITECTURE_DOC_SOFT_SIZE_LIMIT_BYTES} bytes`,
1757
2073
  '- Blindspots: explicit list, or "None identified" only when evidence is comprehensive',
1758
2074
  '',
1759
2075
  '## Review Rules',
1760
2076
  '1. Do not delete `architecture/overview/project-shape.md`.',
1761
- '2. Add or update `architecture/overview/project-shape.md` so it explains the project/system map and includes the accepted `vibecompass-project-map version=1` block.',
2077
+ '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.',
1762
2078
  '3. Add evidence-backed component docs alongside the overview for core journey-facing features and important supporting systems.',
1763
2079
  '4. Update `architecture/overview/project-shape.md` coverage and blindspot sections to summarize what has now been mapped.',
1764
2080
  '5. Keep all implementation claims source-backed with `repo:path` evidence.',
@@ -1767,6 +2083,7 @@ function renderReviewPrompt(options) {
1767
2083
  '8. Do not append real `D-NNN` decisions without explicit user acceptance. Propose candidate decisions separately.',
1768
2084
  '9. Do not edit `decisions/INDEX.md` directly unless a canonical decision file was explicitly accepted and appended.',
1769
2085
  '10. Optimize for future retrieval: project-level docs stay compact; component docs carry focused context for a specific area.',
2086
+ '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".',
1770
2087
  '',
1771
2088
  '## Project Map Block',
1772
2089
  'Include exactly one project-map block inside `architecture/overview/project-shape.md` when the user accepts journey/system mapping output:',
@@ -1788,27 +2105,47 @@ function renderReviewPrompt(options) {
1788
2105
  ' "kind": "gates | navigates_to | depends_on | writes_to",',
1789
2106
  ' "label": "Short evidence-backed relationship label"',
1790
2107
  ' }',
2108
+ ' ],',
2109
+ ' "systems": [',
2110
+ ' {',
2111
+ ' "id": "stable-system-id",',
2112
+ ' "name": "System or runtime surface name",',
2113
+ ' "kind": "frontend | backend | mobile | worker | cli | package | database | integration | mixed",',
2114
+ ' "repos": ["repo-id"],',
2115
+ ' "summary": "Short evidence-backed system summary",',
2116
+ ' "owns_features": [{ "domain": "Domain", "feature": "Feature" }]',
2117
+ ' }',
1791
2118
  ' ]',
1792
2119
  '}',
1793
2120
  '```',
1794
2121
  '',
1795
2122
  '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
+ '`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.',
1796
2124
  '',
1797
2125
  '## Output Contract',
1798
2126
  'First provide a concise findings summary with:',
1799
2127
  '- mapped areas',
2128
+ '- backbone outputs: topology, core feature inventory, user journey map, project systems map, and coverage/quality summary',
1800
2129
  '- user journey and project/system map coverage',
1801
2130
  '- gaps/blindspots',
1802
2131
  '- evidence inventory summary',
2132
+ '- topology and chosen taxonomy axis',
1803
2133
  '- coverage plan and proposed architecture docs',
2134
+ '- prior-doc anchor classifications and reasons',
2135
+ '- coverage/quality report, including missing/generic evidence, unmapped core features, oversized-doc candidates, and unresolved blindspots',
1804
2136
  '- token-budget risks or oversized-doc candidates',
1805
2137
  '- proposed decisions, if any',
1806
2138
  '',
1807
- 'Then output one machine-readable coverage-plan block for the accepted plan:',
2139
+ ...outputContractIntroLines,
1808
2140
  '',
1809
2141
  '```vibecompass-coverage-plan version=1',
1810
2142
  '{',
1811
2143
  ' "summary": "Short coverage plan summary",',
2144
+ ' "topology": "single-repo | monolith | multi-repo | multi-surface | package-cli | mixed",',
2145
+ ' "taxonomy": {',
2146
+ ' "primary_axis": "domain-first | platform-first | repo-first | other",',
2147
+ ' "rationale": "Why this axis matches the evidence and how it avoids mixed tiers"',
2148
+ ' },',
1812
2149
  ' "areas": [',
1813
2150
  ' {',
1814
2151
  ' "id": "stable-area-id",',
@@ -1818,6 +2155,9 @@ function renderReviewPrompt(options) {
1818
2155
  ' "status": "accepted | deferred | missing",',
1819
2156
  ' "coverage": "comprehensive | partial | initial | missing",',
1820
2157
  ' "proposed_path": "architecture/domain/feature/component.md",',
2158
+ ' "anchor_action": "reuse | update | split | merge | defer | replace | new",',
2159
+ ' "anchor_paths": ["architecture/existing/domain/component.md"],',
2160
+ ' "anchor_reason": "Evidence-backed reason for changing or reusing the prior doc identity",',
1821
2161
  ' "evidence": ["repo:path"],',
1822
2162
  ' "blindspots": ["explicit blindspot"]',
1823
2163
  ' }',
@@ -1825,7 +2165,7 @@ function renderReviewPrompt(options) {
1825
2165
  '}',
1826
2166
  '```',
1827
2167
  '',
1828
- 'Then, for each architecture doc the user accepts, output one fenced block exactly like this:',
2168
+ architectureDocBlockIntro,
1829
2169
  '',
1830
2170
  '```vibecompass-architecture-doc path=architecture/domain/feature/component.md',
1831
2171
  '<complete markdown file content>',
@@ -1842,7 +2182,7 @@ function renderReviewPrompt(options) {
1842
2182
  '**Context:** Optional supporting context.',
1843
2183
  '```',
1844
2184
  '',
1845
- `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.`,
2185
+ finalOutputLine,
1846
2186
  ].join('\n');
1847
2187
  }
1848
2188
 
@@ -110,11 +110,12 @@ 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
113
114
  - produce a coverage plan before reading broad source bodies
114
115
  - read targeted source files only when the coverage plan needs evidence
115
116
  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/system map, proposed architecture docs, blindspots, token-budget risks, 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.
117
- 5. Draft only output the user accepts. 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, and evidence-backed relationships. Accepted local decisions may be fenced as \`vibecompass-decision-recommendation target=decisions/domain.md\` blocks without D-numbers.
117
+ 4. Present the evidence inventory, 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.
118
+ 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 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
119
  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
120
  7. Write the accepted fenced blocks verbatim to \`${outputPath}\`.
120
121
  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 +123,7 @@ Turn starter project memory into evidence-backed architecture docs under \`archi
122
123
 
123
124
  ## Rebuild option
124
125
 
125
- If the user explicitly wants to rerun docs-review from a clean or narrower architecture-doc slate, 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\`.
126
+ 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
127
 
127
128
  ## Rules
128
129