@vibecompass/vibecompass 0.10.7 → 0.11.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.
@@ -1,4 +1,15 @@
1
1
  const KEY_VALUE_PATTERN = /^([A-Za-z_][A-Za-z0-9_-]*)\s*:(?:\s+(.*)|\s*)$/;
2
+ const SAFE_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]*$/;
3
+
4
+ /**
5
+ * Whether a value can appear as a block-map key in this subset. Writers that
6
+ * derive map keys from user-supplied identifiers (e.g. repo ids in lane
7
+ * metadata) must check this and fail closed instead of emitting a key the
8
+ * parser will reject — a parse throw degrades the whole document for readers.
9
+ */
10
+ export function isSimpleYamlKeySafe(value) {
11
+ return typeof value === 'string' && SAFE_KEY_PATTERN.test(value);
12
+ }
2
13
 
3
14
  export function parseSimpleYaml(source, options = {}) {
4
15
  // This intentionally supports only the YAML subset used by project.yaml and
@@ -208,10 +219,19 @@ function parseScalar(rawValue) {
208
219
  return Number(rawValue);
209
220
  }
210
221
 
211
- if (
212
- (rawValue.startsWith('"') && rawValue.endsWith('"')) ||
213
- (rawValue.startsWith("'") && rawValue.endsWith("'"))
214
- ) {
222
+ if (rawValue.startsWith('"') && rawValue.endsWith('"')) {
223
+ // Writers quote free-text values with JSON.stringify; parse the token
224
+ // back symmetrically so escaped quotes and backslashes (e.g. Windows
225
+ // paths) round-trip. Hand-authored quoting that is not valid JSON keeps
226
+ // the historical naive slice so legacy files stay readable.
227
+ try {
228
+ return JSON.parse(rawValue);
229
+ } catch {
230
+ return rawValue.slice(1, -1);
231
+ }
232
+ }
233
+
234
+ if (rawValue.startsWith("'") && rawValue.endsWith("'")) {
215
235
  return rawValue.slice(1, -1);
216
236
  }
217
237
 
package/src/status.js CHANGED
@@ -2,17 +2,21 @@ import { readFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { inspectProjectCompatibility } from './compatibility.js';
4
4
  import { sha256Text } from './hash.js';
5
+ import { resolveLaneMarkerContext } from './lane-marker.js';
5
6
  import { parseSimpleYaml } from './simple-yaml.js';
6
- import { listProjectSessions } from './session.js';
7
+ import { inferToolingRootForMemoryRoot, listProjectSessions } from './session.js';
7
8
  import { syncAgentInstructionFiles } from './generators/agent-files/index.js';
8
9
  import { scanProjectMemory } from './project-memory.js';
9
10
 
10
11
  export async function getProjectStatus(options = {}) {
11
12
  const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
12
- const rootDir = path.resolve(cwd, options.rootDir ?? '.compass');
13
+ const rootContext = await resolveStatusRootContext(cwd, options.rootDir);
14
+ const rootDir = rootContext.rootDir;
13
15
  const toolingRootDir = options.toolingRootDir
14
16
  ? path.resolve(cwd, options.toolingRootDir)
15
- : cwd;
17
+ : rootContext.rootSource === 'marker'
18
+ ? await inferToolingRootForMemoryRoot(rootDir)
19
+ : cwd;
16
20
 
17
21
  const compatibility = await inspectProjectCompatibility({
18
22
  rootDir,
@@ -36,7 +40,15 @@ export async function getProjectStatus(options = {}) {
36
40
  cliPackageVersion: compatibility.cliPackageVersion,
37
41
  package: compatibility.package,
38
42
  state: compatibility.state,
39
- warnings: compatibility.warnings,
43
+ warnings: [
44
+ ...(rootContext.markerError
45
+ ? [{
46
+ code: 'lane-marker-unreadable',
47
+ message: `A lane marker near ${cwd} could not be resolved (${rootContext.markerError}); reporting on ${rootDir} instead. Lane-scoped commands will fail closed on it.`,
48
+ }]
49
+ : []),
50
+ ...compatibility.warnings,
51
+ ],
40
52
  },
41
53
  sessions,
42
54
  docsReview,
@@ -53,6 +65,30 @@ export async function getProjectStatus(options = {}) {
53
65
  };
54
66
  }
55
67
 
68
+ /**
69
+ * D-280: status adopts the nearest valid marker's memory root when --root is
70
+ * omitted, matching lane-scoped commands, so `status` from a worktree cwd
71
+ * reports the bound root instead of a nonexistent `cwd/.compass`. Status is
72
+ * read-only and must not fail on a corrupt or foreign marker — resolution
73
+ * failures fall back to the cwd default.
74
+ */
75
+ async function resolveStatusRootContext(cwd, explicitRootDir) {
76
+ if (explicitRootDir) {
77
+ return { rootDir: path.resolve(cwd, explicitRootDir), rootSource: 'flag', markerError: null };
78
+ }
79
+
80
+ try {
81
+ const context = await resolveLaneMarkerContext({ cwd });
82
+ return { rootDir: context.rootDir, rootSource: context.rootSource, markerError: null };
83
+ } catch (error) {
84
+ return {
85
+ rootDir: path.resolve(cwd, '.compass'),
86
+ rootSource: 'default',
87
+ markerError: error instanceof Error ? error.message : String(error),
88
+ };
89
+ }
90
+ }
91
+
56
92
  export function renderStatusText(status) {
57
93
  if (status.project.status === 'unreadable') {
58
94
  return [
@@ -375,7 +411,9 @@ function buildRecommendations({ project, compatibility, sessions, docsReview, ag
375
411
  recommendations.push(
376
412
  sessions.status === 'ok' && sessions.current
377
413
  ? `vibecompass docs-update --session ${sessions.current}`
378
- : 'vibecompass docs-review --guided',
414
+ : sessions.status === 'ok' && sessions.lanes.length >= 2
415
+ ? 'vibecompass list-sessions'
416
+ : 'vibecompass docs-review --guided',
379
417
  );
380
418
  }
381
419
 
package/src/sync.js CHANGED
@@ -2,6 +2,7 @@ import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { sha256Text } from './hash.js';
4
4
  import { writeStateManifest } from './manifest.js';
5
+ import { withMemoryRootLock } from './serialization.js';
5
6
  import { parseSimpleYaml } from './simple-yaml.js';
6
7
  import {
7
8
  buildSyncStateWithCursor,
@@ -143,6 +144,10 @@ export async function pullExportProjectMemory(options = {}, environment = {}) {
143
144
  export async function applyPullExport(options = {}, environment = {}) {
144
145
  const cwd = environment.cwd ? path.resolve(environment.cwd) : process.cwd();
145
146
  const rootDir = path.resolve(cwd, options.rootDir ?? '.compass');
147
+ return withMemoryRootLock(rootDir, 'apply-export', () => applyPullExportLocked(options, rootDir));
148
+ }
149
+
150
+ async function applyPullExportLocked(options, rootDir) {
146
151
  const outputPath = path.resolve(rootDir, options.outputPath ?? DEFAULT_EXPORT_PATH);
147
152
  const bundle = await readJsonRequired(outputPath, 'pull-export bundle');
148
153
  const operations = Array.isArray(bundle.operations) ? bundle.operations : [];
package/src/workflow.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { resolveSyncBinding } from './sync-binding.js';
2
+
1
3
  export const DEFAULT_REVIEWER_HANDBACK = 'handoff-file';
2
4
  export const DEFAULT_GIT_REMOTE = 'origin';
3
5
  export const DEFAULT_COMMIT_TEMPLATE = 'docs(session): YYYY-MM-DD-N — <summary>';
@@ -74,8 +76,10 @@ export function buildWorkflowMetadata(existingMetadata, overrides = {}) {
74
76
  };
75
77
  }
76
78
 
77
- export function buildCloseSessionGuidance(workflowSettings) {
79
+ export function buildCloseSessionGuidance(workflowSettings, options = {}) {
78
80
  const guidance = [];
81
+ const projectConfig = isPlainObject(options.projectConfig) ? options.projectConfig : null;
82
+ const rootFlag = normalizeOptionalString(options.rootFlag) ?? '--root .compass';
79
83
 
80
84
  guidance.push('Reviewer handback stays in the selected sessions/active/<lane-id>/wip.md and handoff.md before close-session runs.');
81
85
  guidance.push('Close-session requires document-maintenance checkpoint statuses for architecture docs, decision log, and session handoff/scratch: updated, not-needed, or deferred.');
@@ -95,9 +99,43 @@ export function buildCloseSessionGuidance(workflowSettings) {
95
99
  guidance.push(`Use commit message format: ${workflowSettings.closeSession.commitTemplate}`);
96
100
  }
97
101
 
102
+ guidance.push(...buildHostedProjectionGuidance(projectConfig, rootFlag));
103
+
98
104
  return guidance;
99
105
  }
100
106
 
107
+ function buildHostedProjectionGuidance(projectConfig, rootFlag) {
108
+ if (!projectConfig) {
109
+ return [
110
+ 'Hosted projection freshness could not be determined because project.yaml was unavailable; if this root is connected to hosted, push or refresh hosted state explicitly after close-session.',
111
+ ];
112
+ }
113
+
114
+ if (projectConfig.mode === 'local-primary') {
115
+ const binding = resolveSyncBinding(projectConfig, null);
116
+ if (!binding) {
117
+ return [
118
+ 'Hosted projection freshness: no hosted sync binding is configured for this local-primary root, so no hosted push command is available.',
119
+ ];
120
+ }
121
+
122
+ const targetFlag = binding.target ? ` --sync-target ${binding.target}` : '';
123
+ return [
124
+ `Hosted projection freshness: after local canonical files are finalized, run \`vibecompass push ${rootFlag}${targetFlag}\` when the hosted dashboard should reflect this session.`,
125
+ ];
126
+ }
127
+
128
+ if (projectConfig.mode === 'hosted-only') {
129
+ return [
130
+ 'Hosted projection freshness: this root is hosted-only, so there is no authoritative local push; confirm hosted dashboard/proposal/Understanding state was updated or record it as deferred.',
131
+ ];
132
+ }
133
+
134
+ return [
135
+ 'Hosted projection freshness: local-only mode has no hosted push expectation unless the project is later connected with connect-hosted.',
136
+ ];
137
+ }
138
+
101
139
  function normalizeReviewerHandback(value) {
102
140
  const normalized = normalizeOptionalString(value);
103
141
  if (!normalized) {
@@ -10,7 +10,7 @@ export const WORKFLOW_REGISTRY = [
10
10
  { trigger: 'start session', summary: 'builder role trigger' },
11
11
  { trigger: 'join as reviewer', summary: 'reviewer role trigger' },
12
12
  { trigger: 'planning mode', summary: 'optional prompt-level mode for scoping work before implementation' },
13
- { trigger: 'review handoff', summary: 'reviewer reads the selected lane handoff and appends findings' },
13
+ { trigger: 'review handoff', summary: 'reviewer reads the selected lane handoff and appends findings; with multiple active lanes name the lane explicitly: `review handoff <lane-id>`' },
14
14
  { trigger: 'address review', summary: 'builder critically resolves reviewer feedback from the selected lane scratch files' },
15
15
  { trigger: 'docs update', summary: 'builder runs a targeted session-delta documentation maintenance plan with `vibecompass docs-update --session <lane-id>`' },
16
16
  { trigger: 'close session', summary: 'builder runs the close-out checklist and ends with `vibecompass close-session --session <lane-id>` plus required document-maintenance checkpoint statuses; `end session` and `vibecompass end-session` are accepted aliases' },
@@ -106,6 +106,8 @@ When the user says \`docs review\`, run this workflow inside the current Claude
106
106
 
107
107
  Turn starter project memory into evidence-backed architecture docs under \`architecture/<domain>/<feature>/<component>.md\`, a projected coverage plan, a journey-aware project map, and optional accepted decision recommendations.
108
108
 
109
+ The first pass is breadth-first: establish an honest project denominator, compact baseline docs, and explicit deferred or missing areas before spending tokens on deep dives. Focused deepening happens after that baseline, either because the user chooses a feature/system or because an implementation session needs better docs for a specific area.
110
+
109
111
  ## Steps
110
112
 
111
113
  1. Read \`${projectYamlPath}\`, \`${contextPath}\`, existing \`${architecturePath}\`, \`${decisionsPath}\`, and finalized \`${sessionsPath}\` notes.
@@ -115,14 +117,15 @@ Turn starter project memory into evidence-backed architecture docs under \`archi
115
117
  - build a subsystem-level completeness inventory of apps/repos, routes/screens, APIs, tables/storage, jobs/cron/tasks, auth/session flows, external integrations, runtime surfaces, and product features
116
118
  - distinguish product features from participating surfaces such as web, mobile, backend/API, data, jobs, integrations, and deployment/runtime
117
119
  - produce a coverage plan before reading broad source bodies
120
+ - classify each planned area as \`baseline\` breadth or \`deepening\` follow-up scope
118
121
  - read targeted source files only when the coverage plan needs evidence
119
122
  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.
120
- 4. Present the evidence inventory, completeness inventory with accepted/deferred/missing accounting, topology, chosen taxonomy axis, prior-doc reuse/update/split/merge/defer/replace classifications, coverage plan, backbone outputs (core feature inventory, user journey map, project systems map, coverage/quality summary), proposed architecture docs, blindspots, token-budget risks, quality report, and any decision candidates before generating accepted output. If you stop here for user approval, state clearly that no architecture docs have been applied yet and do not label proposed areas as accepted.
121
- 5. Draft only output the user accepts. After approval, the accepted plan must be fenced as \`vibecompass-coverage-plan version=1\`. Accepted architecture docs must be fenced as \`vibecompass-architecture-doc path=architecture/domain/feature/component.md\` blocks and must include required frontmatter: \`domain\`, \`feature\`, \`component\`, and \`status\`. Feature docs may include compact prose-only surface matrices; surface/platform ownership docs stay under the existing \`architecture/platform/<surface>/<component>.md\` shape rather than a new top-level systems tree. The accepted \`architecture/overview/project-shape.md\` doc should include compact backbone sections and exactly one \`vibecompass-project-map version=1\` JSON block with journey-facing features, entry points, evidence-backed relationships, and minimal optional derived \`systems[]\` metadata. Accepted local decisions may be fenced as \`vibecompass-decision-recommendation target=decisions/domain.md\` blocks without D-numbers.
123
+ 4. Present the evidence inventory, completeness inventory with accepted/deferred/missing accounting, topology, chosen taxonomy axis, prior-doc reuse/update/split/merge/defer/replace classifications, coverage plan, backbone outputs (core feature inventory, user journey map, project systems map, coverage/quality summary), proposed architecture docs, blindspots, token-budget risks, quality report, and any decision candidates before generating accepted output. At this gate, ask whether to apply the compact baseline now, adjust the plan, or spend a follow-up pass deepening specific high-risk areas. 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.
124
+ 5. Draft only output the user accepts. After approval, the accepted plan must be fenced as \`vibecompass-coverage-plan version=1\` and should preserve each area's \`run_scope\` as \`baseline\` or \`deepening\`. Accepted architecture docs must be fenced as \`vibecompass-architecture-doc path=architecture/domain/feature/component.md\` blocks and must include required frontmatter: \`domain\`, \`feature\`, \`component\`, and \`status\`. Feature docs may include compact prose-only surface matrices; surface/platform ownership docs stay under the existing \`architecture/platform/<surface>/<component>.md\` shape rather than a new top-level systems tree. The accepted \`architecture/overview/project-shape.md\` doc should include compact backbone sections and exactly one \`vibecompass-project-map version=1\` JSON block with journey-facing features, entry points, evidence-backed relationships, and minimal optional derived \`systems[]\` metadata. Accepted local decisions may be fenced as \`vibecompass-decision-recommendation target=decisions/domain.md\` blocks without D-numbers.
122
125
  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.
123
126
  7. Write the accepted fenced blocks verbatim to \`${outputPath}\`.
124
127
  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.
125
- 9. Run \`vibecompass status\` to confirm the docs-review marker is complete and not drifted. Report the applied architecture paths, coverage projection, decision recommendations, parser warnings such as oversized docs or missing review metadata, and any blindspots documented in the accepted docs-review output.
128
+ 9. Run \`vibecompass status\` to confirm the docs-review marker is complete and not drifted. Report the applied architecture paths, coverage projection, baseline/deepening split, decision recommendations, parser warnings such as oversized docs or missing review metadata, and any blindspots or deepening candidates documented in the accepted docs-review output.
126
129
 
127
130
  ## Rebuild option
128
131