@vibecompass/vibecompass 0.7.0 → 0.9.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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0 - 2026-06-14
4
+
5
+ - Add explicit local folder sources for non-Git projects: `project.yaml.repos[]` may use `source: local` plus `path`, and `init` accepts `--repo-local <id=path>`.
6
+ - Keep blank Git remotes invalid, but report an actionable `--repo-local` hint when `--repo <id=remote>` resolves to an empty value.
7
+ - Update guided init, starter docs, status, scanning, and read-model output so local-source roots validate and render cleanly.
8
+ - Bump docs-review to `VibeCompass Docs Review Prompt v3`, requiring first-pass docs to include a user journey, project/system map, and core journey-facing feature summaries.
9
+ - Add `vibecompass-project-map version=1` support inside `architecture/overview/project-shape.md`, including nested-fence parsing so generated overview docs can round-trip the machine-readable journey map block.
10
+
11
+ ## 0.8.0 - 2026-06-12
12
+
13
+ - Add named sync targets (D-236): `connect-hosted --target <name>` binds one project root to multiple hosted environments (for example `dev` on localhost and `prod` on vibecompass.dev).
14
+ - Add a `sync-target` command to list named targets and switch the default; flat `sync` fields in project.yaml always mirror the default target so older CLIs keep working.
15
+ - Add `--sync-target <name>` to `push`, `pull-preview`, `pull-export`, and `docs-review` for per-command environment selection; unknown target names error instead of falling back.
16
+ - Partition local sync cursor state per target (D-237): each target keeps its own last-pushed revision and pull previews, identity-checked against its URL and project id, so one environment's history can never become another environment's baseline.
17
+ - Carry the existing sync history over when a flat binding is converted into its first named target with the same URL and project id.
18
+ - Record the submitted sync target in the docs-review marker so `--poll-hosted` stays pinned to the environment the run was submitted to.
19
+
3
20
  ## 0.7.0 - 2026-05-30
4
21
 
5
22
  - Add `docs-review --rebuild` with dry-run preview, scoped architecture paths, and archive-based stale-doc preparation.
package/README.md CHANGED
@@ -38,6 +38,15 @@ Create project memory:
38
38
  npx -y @vibecompass/vibecompass init --guided
39
39
  ```
40
40
 
41
+ For a folder that is not a Git repo, use an explicit local source:
42
+
43
+ ```bash
44
+ npx -y @vibecompass/vibecompass init \
45
+ --name "My Project" \
46
+ --mode local-primary \
47
+ --repo-local app=.
48
+ ```
49
+
41
50
  Check what to do next:
42
51
 
43
52
  ```bash
@@ -125,7 +134,7 @@ After setup, use these phrases inside your AI coding tool:
125
134
  - `planning mode`
126
135
  - `review handoff`
127
136
  - `address review`
128
- - `close session`
137
+ - `close session` / `end session`
129
138
  - `docs review`
130
139
 
131
140
  The npm package owns the file mechanics. Your AI session owns reading the
@@ -146,6 +155,28 @@ vibecompass connect-hosted \
146
155
  vibecompass push --root .compass
147
156
  ```
148
157
 
158
+ ### Multiple environments
159
+
160
+ One project can have named sync targets (for example a local dev server and
161
+ production) so you never have to rebind:
162
+
163
+ ```bash
164
+ # add targets once — the first one becomes the default
165
+ vibecompass connect-hosted --root .compass --target dev \
166
+ --sync-api-url http://localhost:3000 --sync-project-id vc_proj_dev \
167
+ --sync-credential-env-var VIBECOMPASS_SYNC_TOKEN
168
+ vibecompass connect-hosted --root .compass --target prod \
169
+ --sync-api-url https://vibecompass.dev --sync-project-id vc_proj_prod \
170
+ --sync-credential-env-var VIBECOMPASS_SYNC_TOKEN_PROD
171
+
172
+ # then pick per command, or switch the default
173
+ vibecompass push --root .compass --sync-target prod
174
+ vibecompass sync-target prod --root .compass
175
+ ```
176
+
177
+ Tip: if you already have a single (flat) binding, add it as your first named
178
+ target using the same URL and project id — its sync history carries over.
179
+
149
180
  See https://vibecompass.dev/developers for the full local-primary workflow.
150
181
 
151
182
  ## JavaScript API
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecompass/vibecompass",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Local-first project-memory core for VibeCompass",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { realpathSync } from 'node:fs';
4
4
  import { pathToFileURL } from 'node:url';
5
- import { connectHostedProjectMemory, initializeProjectMemory } from './init.js';
5
+ import { connectHostedProjectMemory, initializeProjectMemory, setDefaultSyncTarget } from './init.js';
6
6
  import { preflightDocsReview } from './docs-review.js';
7
7
  import { resolveConnectHostedCliOptions, resolveInitCliOptions } from './setup.js';
8
8
  import { closeProjectSession, listProjectSessions, startProjectSession, switchProjectSession } from './session.js';
@@ -140,6 +140,10 @@ export async function runCli(argv, io = createDefaultIo(), runtime = {}) {
140
140
  });
141
141
  io.stdout.write(`Connected hosted VibeCompass for ${result.mode}\n`);
142
142
  io.stdout.write(`Updated ${result.projectFilePath}\n`);
143
+ if (result.syncTarget) {
144
+ io.stdout.write(`Sync target: ${result.syncTarget.name} (default: ${result.syncTarget.defaultTarget})\n`);
145
+ io.stdout.write(`Select per command with --sync-target ${result.syncTarget.name}, or switch the default with: vibecompass sync-target ${result.syncTarget.name}\n`);
146
+ }
143
147
  io.stdout.write(`Next step: set ${result.syncEnvVar} locally before your first hosted command.\n`);
144
148
  if (result.mode === 'local-primary') {
145
149
  io.stdout.write('Then run: vibecompass push\n');
@@ -150,6 +154,28 @@ export async function runCli(argv, io = createDefaultIo(), runtime = {}) {
150
154
  return 0;
151
155
  }
152
156
 
157
+ if (parsed.command === 'sync-target') {
158
+ const result = await setDefaultSyncTarget({
159
+ ...parsed.options,
160
+ ...(runtime.cwd ? { cwd: runtime.cwd } : {}),
161
+ });
162
+ if (parsed.options.targetName) {
163
+ io.stdout.write(
164
+ result.changed
165
+ ? `Default sync target set to ${result.defaultTarget}\n`
166
+ : `Default sync target already ${result.defaultTarget}\n`,
167
+ );
168
+ io.stdout.write(`Updated ${result.projectFilePath}\n`);
169
+ } else {
170
+ io.stdout.write(`Default sync target: ${result.defaultTarget ?? '(none)'}\n`);
171
+ }
172
+ for (const [name, target] of Object.entries(result.targets)) {
173
+ const marker = name === result.defaultTarget ? '*' : ' ';
174
+ io.stdout.write(`${marker} ${name}: ${target.api_url} (project ${target.project_id}, env ${target.credential_env_var})\n`);
175
+ }
176
+ return 0;
177
+ }
178
+
153
179
  if (parsed.command === 'status') {
154
180
  const result = await getProjectStatus({
155
181
  ...parsed.options,
@@ -401,6 +427,10 @@ export function parseCliArgs(argv) {
401
427
  return parseConnectHostedArgs(rest);
402
428
  }
403
429
 
430
+ if (command === 'sync-target') {
431
+ return parseSyncTargetArgs(rest);
432
+ }
433
+
404
434
  if (command === 'status') {
405
435
  return parseStatusArgs(rest);
406
436
  }
@@ -538,6 +568,11 @@ export function parseCliArgs(argv) {
538
568
  parsed.repos.push({ id, remote });
539
569
  break;
540
570
  }
571
+ case '--repo-local': {
572
+ const [id, repoPath] = splitAssignment(value, '--repo-local');
573
+ parsed.repos.push({ id, source: 'local', path: repoPath });
574
+ break;
575
+ }
541
576
  case '--repo-branch': {
542
577
  const [id, defaultBranch] = splitAssignment(value, '--repo-branch');
543
578
  parsed.repoBranches.set(id, defaultBranch);
@@ -702,6 +737,9 @@ function parseConnectHostedArgs(argv) {
702
737
  case '--root':
703
738
  parsed.rootDir = value;
704
739
  break;
740
+ case '--target':
741
+ parsed.targetName = value;
742
+ break;
705
743
  case '--sync-api-url':
706
744
  parsed.syncApiUrl = value;
707
745
  break;
@@ -726,6 +764,7 @@ function parseConnectHostedArgs(argv) {
726
764
  command: 'connect-hosted',
727
765
  options: {
728
766
  rootDir: parsed.rootDir,
767
+ ...(parsed.targetName ? { targetName: parsed.targetName } : {}),
729
768
  ...(syncValues.length > 0
730
769
  ? {
731
770
  sync: {
@@ -1128,6 +1167,9 @@ function parseDocsReviewArgs(argv) {
1128
1167
  case '--artifact':
1129
1168
  parsed.artifactId = value;
1130
1169
  break;
1170
+ case '--sync-target':
1171
+ parsed.syncTarget = value;
1172
+ break;
1131
1173
  case '--max-tokens':
1132
1174
  parsed.maxTokens = value;
1133
1175
  break;
@@ -1145,7 +1187,72 @@ function parseDocsReviewArgs(argv) {
1145
1187
  function parsePushArgs(argv) {
1146
1188
  return {
1147
1189
  command: 'push',
1148
- options: parseRootOnlyArgs(argv, 'push'),
1190
+ options: parseRootAndSyncTargetArgs(argv, 'push'),
1191
+ };
1192
+ }
1193
+
1194
+ function parseRootAndSyncTargetArgs(argv, commandName) {
1195
+ const parsed = {};
1196
+
1197
+ for (let index = 0; index < argv.length; index += 1) {
1198
+ const token = argv[index];
1199
+ if (!token.startsWith('--')) {
1200
+ throw new Error(`Unexpected argument "${token}".`);
1201
+ }
1202
+
1203
+ const value = argv[index + 1];
1204
+ if (value === undefined) {
1205
+ throw new Error(`Flag "${token}" requires a value.`);
1206
+ }
1207
+ index += 1;
1208
+
1209
+ switch (token) {
1210
+ case '--root':
1211
+ parsed.rootDir = value;
1212
+ break;
1213
+ case '--sync-target':
1214
+ parsed.syncTarget = value;
1215
+ break;
1216
+ default:
1217
+ throw new Error(`Unknown flag "${token}" for ${commandName}.`);
1218
+ }
1219
+ }
1220
+
1221
+ return parsed;
1222
+ }
1223
+
1224
+ function parseSyncTargetArgs(argv) {
1225
+ const parsed = {};
1226
+
1227
+ for (let index = 0; index < argv.length; index += 1) {
1228
+ const token = argv[index];
1229
+
1230
+ if (!token.startsWith('--')) {
1231
+ if (parsed.targetName !== undefined) {
1232
+ throw new Error(`Unexpected argument "${token}".`);
1233
+ }
1234
+ parsed.targetName = token;
1235
+ continue;
1236
+ }
1237
+
1238
+ const value = argv[index + 1];
1239
+ if (value === undefined) {
1240
+ throw new Error(`Flag "${token}" requires a value.`);
1241
+ }
1242
+ index += 1;
1243
+
1244
+ switch (token) {
1245
+ case '--root':
1246
+ parsed.rootDir = value;
1247
+ break;
1248
+ default:
1249
+ throw new Error(`Unknown flag "${token}" for sync-target.`);
1250
+ }
1251
+ }
1252
+
1253
+ return {
1254
+ command: 'sync-target',
1255
+ options: parsed,
1149
1256
  };
1150
1257
  }
1151
1258
 
@@ -1173,6 +1280,9 @@ function parsePullPreviewArgs(argv) {
1173
1280
  case '--root':
1174
1281
  parsed.rootDir = value;
1175
1282
  break;
1283
+ case '--sync-target':
1284
+ parsed.syncTarget = value;
1285
+ break;
1176
1286
  default:
1177
1287
  throw new Error(`Unknown flag "${token}".`);
1178
1288
  }
@@ -1206,6 +1316,9 @@ function parsePullExportArgs(argv) {
1206
1316
  case '--root':
1207
1317
  parsed.rootDir = value;
1208
1318
  break;
1319
+ case '--sync-target':
1320
+ parsed.syncTarget = value;
1321
+ break;
1209
1322
  case '--preview-token':
1210
1323
  parsed.previewToken = value;
1211
1324
  break;
@@ -1353,6 +1466,11 @@ function writeWarnings(io, warnings = []) {
1353
1466
  function splitAssignment(value, flagName) {
1354
1467
  const separatorIndex = value.indexOf('=');
1355
1468
  if (separatorIndex <= 0 || separatorIndex === value.length - 1) {
1469
+ if (flagName === '--repo' && separatorIndex > 0 && separatorIndex === value.length - 1) {
1470
+ throw new Error(
1471
+ '--repo remote resolved to empty. For a non-Git folder, use --repo-local <id=path> instead of --repo <id=remote>.',
1472
+ );
1473
+ }
1356
1474
  throw new Error(`${flagName} expects id=value.`);
1357
1475
  }
1358
1476
 
@@ -1364,6 +1482,7 @@ function usageText() {
1364
1482
  'Usage:',
1365
1483
  ' vibecompass init --name <project-name> --mode <local-only|local-primary|hosted-only> --repo <id=remote> [options]',
1366
1484
  ' vibecompass connect-hosted [options]',
1485
+ ' vibecompass sync-target [<name>] [options]',
1367
1486
  ' vibecompass status [options]',
1368
1487
  ' vibecompass refresh-workflow [--dry-run|--apply] [options]',
1369
1488
  ' vibecompass start-session --id <lane-id> --working-on <text> [options]',
@@ -1386,7 +1505,8 @@ function usageText() {
1386
1505
  ' --placement <workspace-root|dedicated-memory-repo|primary-repo>',
1387
1506
  ' Optional explicit placement pattern',
1388
1507
  ' --guided Ask placement and setup questions interactively',
1389
- ' --repo <id=remote> Repeatable repo descriptor',
1508
+ ' --repo <id=remote> Repeatable Git-backed repo descriptor',
1509
+ ' --repo-local <id=path> Repeatable local folder source for non-Git projects',
1390
1510
  ' --repo-branch <id=branch> Optional per-repo default branch',
1391
1511
  ' --sync-api-url <url> Optional hosted sync api_url',
1392
1512
  ' --sync-project-id <id> Optional hosted sync project_id',
@@ -1405,10 +1525,17 @@ function usageText() {
1405
1525
  '',
1406
1526
  'Connect-hosted options:',
1407
1527
  ' --root <path> Project-memory root. Defaults to .compass',
1528
+ ' --target <name> Add or update a named sync target (e.g. dev, prod)',
1529
+ ' First named target becomes the default; flat sync fields mirror the default target',
1408
1530
  ' --sync-api-url <url> Hosted sync api_url',
1409
1531
  ' --sync-project-id <id> Hosted sync project_id',
1410
1532
  ' --sync-credential-env-var <name> Hosted sync env var reference',
1411
- ' Replaces any existing project.yaml sync binding',
1533
+ ' Without --target, replaces any existing flat project.yaml sync binding',
1534
+ '',
1535
+ 'Sync-target options:',
1536
+ ' vibecompass sync-target List named sync targets and the current default',
1537
+ ' vibecompass sync-target <name> Switch the default sync target (re-mirrors flat sync fields)',
1538
+ ' --root <path> Project-memory root. Defaults to .compass',
1412
1539
  '',
1413
1540
  'Status options:',
1414
1541
  ' --root <path> Project-memory root. Defaults to .compass',
@@ -1463,6 +1590,7 @@ function usageText() {
1463
1590
  '',
1464
1591
  'Sync transport options:',
1465
1592
  ' push --root <path> Push canonical local project memory to hosted',
1593
+ ' --sync-target <name> Use a named sync target for this command (push, pull-preview, pull-export, docs-review)',
1466
1594
  ' pull-preview --root <path> Preview hosted proposals and remote state',
1467
1595
  ' pull-preview --no-pending-proposals Exclude pending proposals from preview',
1468
1596
  ' pull-export --proposal <id> Export a selected proposal; repeatable',
@@ -5,8 +5,9 @@ import { parseFrontmatter } from './frontmatter.js';
5
5
  import { sha256Text } from './hash.js';
6
6
  import { writeStateManifest } from './manifest.js';
7
7
  import { parseSimpleYaml } from './simple-yaml.js';
8
+ import { readSyncCursor, resolveSyncBinding } from './sync-binding.js';
8
9
 
9
- const DOCS_REVIEW_PROMPT_VERSION = 'VibeCompass Docs Review Prompt v2';
10
+ const DOCS_REVIEW_PROMPT_VERSION = 'VibeCompass Docs Review Prompt v3';
10
11
  const ARCHITECTURE_DOC_SOFT_SIZE_LIMIT_BYTES = 12000;
11
12
  const COVERAGE_PLAN_OUTPUT_VERSION = 1;
12
13
  const COVERAGE_PLAN_STATUSES = new Set(['accepted', 'deferred', 'missing']);
@@ -83,6 +84,7 @@ export async function preflightDocsReview(options = {}, environment = {}) {
83
84
  localProvider,
84
85
  runLocal: shouldRunLocal(options),
85
86
  anthropicEnvVar: options.anthropicEnvVar ?? 'ANTHROPIC_API_KEY',
87
+ syncTarget: options.syncTarget ?? null,
86
88
  });
87
89
  const reviewPrompt = renderReviewPrompt({
88
90
  project,
@@ -617,11 +619,10 @@ function resolveDocsReviewRuntime(project, options) {
617
619
  localProvider &&
618
620
  typeof options.env?.[localProvider.credentialEnvVar] === 'string' &&
619
621
  options.env[localProvider.credentialEnvVar].trim() !== '';
620
- const hasHostedBinding =
621
- project?.sync &&
622
- typeof project.sync === 'object' &&
623
- typeof project.sync.api_url === 'string' &&
624
- typeof project.sync.project_id === 'string';
622
+ // Throws on an explicitly requested unknown/incomplete --sync-target so a
623
+ // typo can never fall back to a different environment (D-236).
624
+ const hostedBinding = resolveSyncBinding(project, options.syncTarget ?? null);
625
+ const hasHostedBinding = Boolean(hostedBinding);
625
626
 
626
627
  if (mode === 'local-only') {
627
628
  if (options.runLocal && !localProvider) {
@@ -663,11 +664,10 @@ function resolveDocsReviewRuntime(project, options) {
663
664
  if (hasHostedBinding) {
664
665
  return {
665
666
  provider: 'hosted',
666
- api_url: project.sync.api_url,
667
- project_id: project.sync.project_id,
668
- ...(typeof project.sync.credential_env_var === 'string'
669
- ? { credential_env_var: project.sync.credential_env_var }
670
- : {}),
667
+ api_url: hostedBinding.apiUrl,
668
+ project_id: hostedBinding.projectId,
669
+ credential_env_var: hostedBinding.credentialEnvVar,
670
+ ...(hostedBinding.target ? { sync_target: hostedBinding.target } : {}),
671
671
  };
672
672
  }
673
673
 
@@ -699,11 +699,10 @@ function resolveDocsReviewRuntime(project, options) {
699
699
 
700
700
  return {
701
701
  provider: 'hosted',
702
- api_url: project.sync.api_url,
703
- project_id: project.sync.project_id,
704
- ...(typeof project.sync.credential_env_var === 'string'
705
- ? { credential_env_var: project.sync.credential_env_var }
706
- : {}),
702
+ api_url: hostedBinding.apiUrl,
703
+ project_id: hostedBinding.projectId,
704
+ credential_env_var: hostedBinding.credentialEnvVar,
705
+ ...(hostedBinding.target ? { sync_target: hostedBinding.target } : {}),
707
706
  };
708
707
  }
709
708
 
@@ -786,13 +785,13 @@ function extractAnthropicText(body) {
786
785
 
787
786
  function parseArchitectureDocBlocks(source) {
788
787
  const blocks = [];
789
- const pattern = /^```vibecompass-architecture-doc\s+path=([^\s`]+)\s*\n([\s\S]*?)^```[ \t]*$/gm;
788
+ const pattern = /^(`{3,})vibecompass-architecture-doc\s+path=([^\s`]+)\s*\n([\s\S]*?)^\1[ \t]*$/gm;
790
789
  let match;
791
790
 
792
791
  while ((match = pattern.exec(source)) !== null) {
793
792
  blocks.push({
794
- path: normalizeBlockPath(match[1]),
795
- content: match[2].replace(/\n$/, ''),
793
+ path: normalizeBlockPath(match[2]),
794
+ content: match[3].replace(/\n$/, ''),
796
795
  });
797
796
  }
798
797
 
@@ -830,8 +829,8 @@ function parseDecisionRecommendationBlocks(source) {
830
829
  }
831
830
 
832
831
  function countMalformedArchitectureDocFences(source) {
833
- const fencePattern = /^```vibecompass-architecture-doc\b.*$/gm;
834
- const acceptedOpeningPattern = /^```vibecompass-architecture-doc\s+path=[^\s`]+\s*$/;
832
+ const fencePattern = /^`{3,}vibecompass-architecture-doc\b.*$/gm;
833
+ const acceptedOpeningPattern = /^`{3,}vibecompass-architecture-doc\s+path=[^\s`]+\s*$/;
835
834
  let count = 0;
836
835
  let match;
837
836
 
@@ -1107,8 +1106,15 @@ async function submitHostedDocsReview(options) {
1107
1106
  `api/sync/projects/${encodeURIComponent(options.runtime.project_id)}/docs-review`,
1108
1107
  ensureTrailingSlash(options.runtime.api_url),
1109
1108
  );
1109
+ // D-237: the baseline must come from the submitted target's own cursor so a
1110
+ // dev revision can never be sent as another environment's baseline.
1111
+ const submitCursor = readSyncCursor(options.manifest?.manifest?.sync, {
1112
+ target: options.runtime.sync_target ?? null,
1113
+ apiUrl: options.runtime.api_url,
1114
+ projectId: options.runtime.project_id,
1115
+ });
1110
1116
  const baselineRemoteRevisionId = normalizeUuid(
1111
- options.manifest?.manifest?.sync?.last_successful_remote_revision,
1117
+ submitCursor.last_successful_remote_revision,
1112
1118
  );
1113
1119
  const response = await options.fetch(endpoint.href, {
1114
1120
  method: 'POST',
@@ -1699,18 +1705,20 @@ function renderReviewPrompt(options) {
1699
1705
  'Stage 1 — Evidence inventory:',
1700
1706
  '- Build a compact repo inventory from file paths, manifests/config files, route/job/test directories, and existing architecture frontmatter before reading large file bodies.',
1701
1707
  '- Mine finalized session notes for decision candidates and product constraints, but cap proposed decisions to the five highest-value architecture choices.',
1702
- '- Identify candidate domains, features, components, and ownership boundaries with concrete `repo:path` evidence.',
1708
+ '- Identify candidate domains, features, components, ownership boundaries, user journey entry points, feature-to-feature relationships, and system-layer connections with concrete `repo:path` evidence.',
1703
1709
  '- Do not fetch or summarize broad source bodies until the coverage plan says a file is needed.',
1704
1710
  '',
1705
1711
  'Stage 2 — Coverage plan:',
1706
1712
  '- Propose the smallest useful architecture-doc set that will let future agents understand the project and retrieve targeted context.',
1707
- '- Prioritize entry points, data ownership, external integrations, jobs/async boundaries, auth/security boundaries, and test surfaces.',
1713
+ '- 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.',
1714
+ '- Prioritize entry points, user-flow/gating relationships, data ownership, external integrations, jobs/async boundaries, auth/security boundaries, and test surfaces.',
1708
1715
  '- Mark each proposed doc as `comprehensive`, `partial`, or `initial`, with the reason and blindspots.',
1709
1716
  '- Emit the accepted plan as one `vibecompass-coverage-plan version=1` JSON fence before architecture-doc blocks.',
1710
1717
  '- Ask for user acceptance before emitting fenced architecture-doc blocks.',
1711
1718
  '',
1712
1719
  'Stage 3 — Bounded doc generation:',
1713
1720
  '- Generate only accepted docs, one complete fenced block per architecture path.',
1721
+ '- 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.',
1714
1722
  '- Keep docs concise and retrieval-oriented: explain contracts, flows, invariants, and ownership; do not rewrite source code or produce file-by-file walkthroughs.',
1715
1723
  `- 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.`,
1716
1724
  '- Include only the most important involved files; prefer representative entry points and owned contracts over exhaustive file lists.',
@@ -1740,18 +1748,46 @@ function renderReviewPrompt(options) {
1740
1748
  '',
1741
1749
  '## Review Rules',
1742
1750
  '1. Do not delete `architecture/overview/project-shape.md`.',
1743
- '2. Add evidence-backed component docs alongside the overview.',
1744
- '3. Update `architecture/overview/project-shape.md` coverage and blindspot sections to summarize what has now been mapped.',
1745
- '4. Keep all implementation claims source-backed with `repo:path` evidence.',
1746
- '5. Use only the exact confidence and coverage enum values listed above.',
1747
- '6. Put uncertainty in `Blindspots`; do not fill gaps with guesses.',
1748
- '7. Do not append real `D-NNN` decisions without explicit user acceptance. Propose candidate decisions separately.',
1749
- '8. Do not edit `decisions/INDEX.md` directly unless a canonical decision file was explicitly accepted and appended.',
1750
- '9. Optimize for future retrieval: project-level docs stay compact; component docs carry focused context for a specific area.',
1751
+ '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.',
1752
+ '3. Add evidence-backed component docs alongside the overview for core journey-facing features and important supporting systems.',
1753
+ '4. Update `architecture/overview/project-shape.md` coverage and blindspot sections to summarize what has now been mapped.',
1754
+ '5. Keep all implementation claims source-backed with `repo:path` evidence.',
1755
+ '6. Use only the exact confidence and coverage enum values listed above.',
1756
+ '7. Put uncertainty in `Blindspots`; do not fill gaps with guesses.',
1757
+ '8. Do not append real `D-NNN` decisions without explicit user acceptance. Propose candidate decisions separately.',
1758
+ '9. Do not edit `decisions/INDEX.md` directly unless a canonical decision file was explicitly accepted and appended.',
1759
+ '10. Optimize for future retrieval: project-level docs stay compact; component docs carry focused context for a specific area.',
1760
+ '',
1761
+ '## Project Map Block',
1762
+ 'Include exactly one project-map block inside `architecture/overview/project-shape.md` when the user accepts journey/system mapping output:',
1763
+ '',
1764
+ '```vibecompass-project-map version=1',
1765
+ '{',
1766
+ ' "features": [',
1767
+ ' {',
1768
+ ' "domain": "Domain name matching architecture frontmatter",',
1769
+ ' "feature": "Feature name matching architecture frontmatter",',
1770
+ ' "is_entry_point": true,',
1771
+ ' "summary": "Short user-facing feature summary"',
1772
+ ' }',
1773
+ ' ],',
1774
+ ' "relationships": [',
1775
+ ' {',
1776
+ ' "from": { "domain": "Domain", "feature": "Feature" },',
1777
+ ' "to": { "domain": "Domain", "feature": "Feature" },',
1778
+ ' "kind": "gates | navigates_to | depends_on | writes_to",',
1779
+ ' "label": "Short evidence-backed relationship label"',
1780
+ ' }',
1781
+ ' ]',
1782
+ '}',
1783
+ '```',
1784
+ '',
1785
+ '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.',
1751
1786
  '',
1752
1787
  '## Output Contract',
1753
1788
  'First provide a concise findings summary with:',
1754
1789
  '- mapped areas',
1790
+ '- user journey and project/system map coverage',
1755
1791
  '- gaps/blindspots',
1756
1792
  '- evidence inventory summary',
1757
1793
  '- coverage plan and proposed architecture docs',
@@ -1785,6 +1821,8 @@ function renderReviewPrompt(options) {
1785
1821
  '<complete markdown file content>',
1786
1822
  '```',
1787
1823
  '',
1824
+ 'If the complete markdown file content contains its own fenced block, such as `vibecompass-project-map version=1`, use a longer outer fence for the architecture-doc block (for example four backticks) so the inner fence stays inside the document body.',
1825
+ '',
1788
1826
  'If the review surfaced accepted decision candidates, output each candidate as a separate block without assigning a D-number:',
1789
1827
  '',
1790
1828
  '```vibecompass-decision-recommendation target=decisions/cross-cutting.md',