@vibecompass/vibecompass 0.9.3 → 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,10 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.9.3 - 2026-06-14
3
+ ## 0.10.0 - 2026-06-14
4
4
 
5
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
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
- - Update generated docs-review workflow guidance and prompt regression coverage for the interactive vs single-turn split.
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.
8
12
 
9
13
  ## 0.9.2 - 2026-06-14
10
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecompass/vibecompass",
3
- "version": "0.9.3",
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);
@@ -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, then apply accepted output.'
505
- : '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.',
506
514
  };
507
515
  }
508
516
 
@@ -926,6 +934,8 @@ function buildCoverageProjection(blocks, options) {
926
934
  },
927
935
  projected_at: new Date().toISOString(),
928
936
  summary: normalizeOptionalString(plan.summary),
937
+ ...(normalizeOptionalString(plan.topology) ? { topology: normalizeOptionalString(plan.topology) } : {}),
938
+ ...(normalizeCoverageTaxonomy(plan.taxonomy) ? { taxonomy: normalizeCoverageTaxonomy(plan.taxonomy) } : {}),
929
939
  area_count: areas.length,
930
940
  coverage_score: denominator === 0 ? null : Number((acceptedCount / denominator).toFixed(4)),
931
941
  statuses,
@@ -934,6 +944,23 @@ function buildCoverageProjection(blocks, options) {
934
944
  };
935
945
  }
936
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
+
937
964
  function normalizeCoverageArea(area, index, ids) {
938
965
  if (!area || typeof area !== 'object' || Array.isArray(area)) {
939
966
  throw new Error(`Coverage plan area at index ${index} must be an object.`);
@@ -971,6 +998,22 @@ function normalizeCoverageArea(area, index, ids) {
971
998
  }
972
999
  }
973
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
+
974
1017
  return {
975
1018
  id,
976
1019
  domain: normalizeOptionalString(area.domain),
@@ -982,6 +1025,9 @@ function normalizeCoverageArea(area, index, ids) {
982
1025
  evidence: normalizeStringArray(area.evidence),
983
1026
  blindspots: normalizeStringArray(area.blindspots),
984
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 } : {}),
985
1031
  };
986
1032
  }
987
1033
 
@@ -1059,6 +1105,8 @@ function createArchitectureDocQualityWarnings(blocks) {
1059
1105
  }
1060
1106
  if (!/^\s*-\s*Evidence:\s+\S/im.test(body)) {
1061
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.`);
1062
1110
  }
1063
1111
  if (!/^\s*-\s*Blindspots:\s+\S/im.test(body)) {
1064
1112
  warnings.push(`missing_blindspots_metadata: ${block.path} is missing review metadata "Blindspots".`);
@@ -1081,6 +1129,36 @@ function createArchitectureDocQualityWarnings(blocks) {
1081
1129
  return warnings;
1082
1130
  }
1083
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
+
1084
1162
  function extractLevelTwoSections(markdown) {
1085
1163
  return new Set(
1086
1164
  [...String(markdown ?? '').matchAll(/^##\s+(.+)$/gm)]
@@ -1687,6 +1765,191 @@ function renderApplyOutputMessage(options) {
1687
1765
  return `Applied ${parts.length > 0 ? parts.join(', ') : 'accepted docs-review output'} and completed the docs-review marker.`;
1688
1766
  }
1689
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
+
1690
1953
  function renderReviewPrompt(options) {
1691
1954
  const outputPath = path.join(options.rootDir, 'state', 'docs-review-output.md');
1692
1955
  const applyCommand = `vibecompass docs-review --root ${shellQuote(options.rootDir)} --apply-output --output ${shellQuote(outputPath)}`;
@@ -1715,6 +1978,18 @@ function renderReviewPrompt(options) {
1715
1978
  const stage4WriteLine = isSingleTurn
1716
1979
  ? `- The package will save the model response to \`${outputPath}\`; local users or hosted proposal reviewers decide what becomes canonical later.`
1717
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
+ ];
1718
1993
  const finalOutputLine = isSingleTurn
1719
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.`
1720
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.`;
@@ -1735,7 +2010,7 @@ function renderReviewPrompt(options) {
1735
2010
  `- Review model/version to record: ${options.model}`,
1736
2011
  `- Execution mode: ${isSingleTurn ? 'single-turn (emit accepted proposal output now)' : 'interactive (stop at coverage-plan approval gate)'}`,
1737
2012
  `- Accepted output file: ${outputPath}`,
1738
- `- Apply command after user acceptance: ${applyCommand}`,
2013
+ `- ${applyCommandLabel}: ${applyCommand}`,
1739
2014
  `- Apply command if the CLI is not installed on PATH: ${npxApplyCommand}`,
1740
2015
  '',
1741
2016
  '## Required Inputs',
@@ -1744,9 +2019,12 @@ function renderReviewPrompt(options) {
1744
2019
  '3. Read all canonical files under `architecture/`, `decisions/`, and finalized `sessions/`.',
1745
2020
  '4. Inspect declared repositories before making implementation claims.',
1746
2021
  '',
2022
+ renderAnchorContext(options.anchors),
2023
+ '',
1747
2024
  '## Staged Review Protocol',
1748
2025
  'Stage 1 — Evidence inventory:',
1749
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.',
1750
2028
  '- Mine finalized session notes for decision candidates and product constraints, but cap proposed decisions to the five highest-value architecture choices.',
1751
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.',
1752
2030
  '- Do not fetch or summarize broad source bodies until the coverage plan says a file is needed.',
@@ -1755,27 +2033,31 @@ function renderReviewPrompt(options) {
1755
2033
  '- Propose the smallest useful architecture-doc set that will let future agents understand the project and retrieve targeted context.',
1756
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.',
1757
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.',
1758
2040
  '- Mark each proposed doc as `comprehensive`, `partial`, or `initial`, with the reason and blindspots.',
1759
2041
  ...coveragePlanStageLines,
1760
2042
  '',
1761
- 'Stage 3 — Bounded doc generation:',
2043
+ 'Stage 3 — Backbone output and bounded doc generation:',
1762
2044
  '- Generate only accepted docs, one complete fenced block per architecture path.',
1763
- '- 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.',
1764
2047
  '- Keep docs concise and retrieval-oriented: explain contracts, flows, invariants, and ownership; do not rewrite source code or produce file-by-file walkthroughs.',
1765
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.`,
1766
2049
  '- Include only the most important involved files; prefer representative entry points and owned contracts over exhaustive file lists.',
1767
2050
  '',
1768
2051
  'Stage 4 — Apply and verify:',
1769
2052
  stage4WriteLine,
1770
- `- Then run \`${applyCommand}\` to validate and apply the accepted blocks.`,
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.',
2053
+ ...stage4ApplyLines,
1773
2054
  '',
1774
2055
  '## Architecture Doc Contract',
1775
2056
  'When creating or updating architecture docs, use the existing VibeCompass structure:',
1776
2057
  '- path: `architecture/<domain-slug>/<feature-slug>/<component-slug>.md` for component docs',
1777
2058
  '- frontmatter fields: `domain`, `feature`, `component`, `status`, plus `repo` or `repos` when implementation-scoped',
1778
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`',
1779
2061
  '',
1780
2062
  'Use this exact Review metadata sub-header in every generated architecture doc:',
1781
2063
  '',
@@ -1785,14 +2067,14 @@ function renderReviewPrompt(options) {
1785
2067
  `- Project mode: ${options.project.mode}`,
1786
2068
  '- Confidence: high | medium | low',
1787
2069
  '- Coverage: comprehensive | partial | initial',
1788
- '- Evidence: repo:path references inspected before making implementation claims',
2070
+ '- Evidence: concrete repo:path references inspected before making implementation claims',
1789
2071
  '- Retrieval scope: when future agents should load this doc',
1790
2072
  `- Token posture: compact unless the doc intentionally exceeds ${ARCHITECTURE_DOC_SOFT_SIZE_LIMIT_BYTES} bytes`,
1791
2073
  '- Blindspots: explicit list, or "None identified" only when evidence is comprehensive',
1792
2074
  '',
1793
2075
  '## Review Rules',
1794
2076
  '1. Do not delete `architecture/overview/project-shape.md`.',
1795
- '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.',
1796
2078
  '3. Add evidence-backed component docs alongside the overview for core journey-facing features and important supporting systems.',
1797
2079
  '4. Update `architecture/overview/project-shape.md` coverage and blindspot sections to summarize what has now been mapped.',
1798
2080
  '5. Keep all implementation claims source-backed with `repo:path` evidence.',
@@ -1801,6 +2083,7 @@ function renderReviewPrompt(options) {
1801
2083
  '8. Do not append real `D-NNN` decisions without explicit user acceptance. Propose candidate decisions separately.',
1802
2084
  '9. Do not edit `decisions/INDEX.md` directly unless a canonical decision file was explicitly accepted and appended.',
1803
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".',
1804
2087
  '',
1805
2088
  '## Project Map Block',
1806
2089
  'Include exactly one project-map block inside `architecture/overview/project-shape.md` when the user accepts journey/system mapping output:',
@@ -1822,19 +2105,34 @@ function renderReviewPrompt(options) {
1822
2105
  ' "kind": "gates | navigates_to | depends_on | writes_to",',
1823
2106
  ' "label": "Short evidence-backed relationship label"',
1824
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
+ ' }',
1825
2118
  ' ]',
1826
2119
  '}',
1827
2120
  '```',
1828
2121
  '',
1829
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.',
1830
2124
  '',
1831
2125
  '## Output Contract',
1832
2126
  'First provide a concise findings summary with:',
1833
2127
  '- mapped areas',
2128
+ '- backbone outputs: topology, core feature inventory, user journey map, project systems map, and coverage/quality summary',
1834
2129
  '- user journey and project/system map coverage',
1835
2130
  '- gaps/blindspots',
1836
2131
  '- evidence inventory summary',
2132
+ '- topology and chosen taxonomy axis',
1837
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',
1838
2136
  '- token-budget risks or oversized-doc candidates',
1839
2137
  '- proposed decisions, if any',
1840
2138
  '',
@@ -1843,6 +2141,11 @@ function renderReviewPrompt(options) {
1843
2141
  '```vibecompass-coverage-plan version=1',
1844
2142
  '{',
1845
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
+ ' },',
1846
2149
  ' "areas": [',
1847
2150
  ' {',
1848
2151
  ' "id": "stable-area-id",',
@@ -1852,6 +2155,9 @@ function renderReviewPrompt(options) {
1852
2155
  ' "status": "accepted | deferred | missing",',
1853
2156
  ' "coverage": "comprehensive | partial | initial | missing",',
1854
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",',
1855
2161
  ' "evidence": ["repo:path"],',
1856
2162
  ' "blindspots": ["explicit blindspot"]',
1857
2163
  ' }',
@@ -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 and do not label proposed areas as accepted.
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, 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