@vibecompass/vibecompass 0.3.4 → 0.5.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.
@@ -364,7 +364,9 @@ function parseArchitectureDocument(options) {
364
364
  }
365
365
 
366
366
  const data = frontmatter.data;
367
- const requiredFields = ['domain', 'feature', 'component', 'status'];
367
+ const requiredFields = options.relativePath === 'architecture/overview/project-shape.md'
368
+ ? ['status']
369
+ : ['domain', 'feature', 'component', 'status'];
368
370
 
369
371
  for (const field of requiredFields) {
370
372
  if (typeof data[field] !== 'string' || data[field].trim() === '') {
package/src/read-model.js CHANGED
@@ -14,7 +14,7 @@ export async function loadProjectReadModel(rootDir) {
14
14
  throw new Error(`Cannot build read model with canonical parse errors.\n${details}`);
15
15
  }
16
16
 
17
- const existingManifest = await readManifestIfPresent(rootDir);
17
+ const existingManifest = normalizeReadableManifest(await readManifestIfPresent(rootDir));
18
18
  const currentManifest = generateStateManifest(scanResult, {
19
19
  generatedAt: existingManifest?.generated_at ?? new Date(),
20
20
  sync: existingManifest?.sync,
@@ -555,3 +555,19 @@ async function readManifestIfPresent(rootDir) {
555
555
  return null;
556
556
  }
557
557
  }
558
+
559
+ function normalizeReadableManifest(manifest) {
560
+ if (!manifest || typeof manifest !== 'object') {
561
+ return null;
562
+ }
563
+
564
+ if (!manifest.canonical || typeof manifest.canonical !== 'object') {
565
+ return null;
566
+ }
567
+
568
+ if (typeof manifest.canonical.manifest_hash !== 'string') {
569
+ return null;
570
+ }
571
+
572
+ return manifest;
573
+ }
@@ -0,0 +1,236 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { inspectProjectCompatibility } from './compatibility.js';
4
+ import { prepareStateManifest, writeStateManifest } from './manifest.js';
5
+ import { serializeProjectConfig } from './project-yaml.js';
6
+ import { buildWorkflowScaffoldFiles } from './scaffold.js';
7
+ import { parseSimpleYaml } from './simple-yaml.js';
8
+ import { syncAgentInstructionFiles } from './generators/agent-files/index.js';
9
+ import { PACKAGE_VERSION } from './version.js';
10
+
11
+ export async function refreshWorkflow(options = {}) {
12
+ const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
13
+ const rootDir = path.resolve(cwd, options.rootDir ?? '.compass');
14
+ const toolingRootDir = options.toolingRootDir
15
+ ? path.resolve(cwd, options.toolingRootDir)
16
+ : cwd;
17
+ const apply = Boolean(options.apply);
18
+ const dryRun = !apply;
19
+
20
+ const compatibility = await inspectProjectCompatibility({ rootDir, cwd });
21
+ if (apply && compatibility.package.status === 'ahead' && !options.allowDowngradeTemplates) {
22
+ throw new Error(
23
+ 'This root was refreshed by a newer VibeCompass package. Pass --allow-downgrade-templates to apply older templates with this CLI.',
24
+ );
25
+ }
26
+
27
+ const projectFilePath = path.join(rootDir, 'project.yaml');
28
+ const projectConfig = await readProjectConfig(projectFilePath);
29
+ const rootRelativePath = toPosix(path.relative(toolingRootDir, rootDir) || '.');
30
+ const contextRelativeToToolingRoot = toPosix(
31
+ path.relative(toolingRootDir, path.join(rootDir, 'context.md')) || 'context.md',
32
+ );
33
+
34
+ const workflowFiles = await planWorkflowFiles({
35
+ rootDir,
36
+ toolingRootDir,
37
+ projectConfig,
38
+ rootRelativePath,
39
+ contextRelativeToToolingRoot,
40
+ });
41
+
42
+ if (apply) {
43
+ await applyWorkflowFiles(workflowFiles);
44
+ }
45
+
46
+ const projectStamp = planProjectStamp(projectConfig, compatibility, {
47
+ updatePackageStamp: Boolean(options.updatePackageStamp),
48
+ });
49
+ if (apply && projectStamp.changed) {
50
+ await mkdir(path.dirname(projectFilePath), { recursive: true });
51
+ await writeFile(projectFilePath, serializeProjectConfig(projectStamp.projectConfig), 'utf8');
52
+ }
53
+
54
+ const manifest = apply
55
+ ? await writeStateManifest(rootDir)
56
+ : await prepareStateManifest(rootDir);
57
+
58
+ const agentFileSync = await syncAgentInstructionFiles({
59
+ rootDir,
60
+ toolingRootDir,
61
+ dryRun,
62
+ });
63
+
64
+ return {
65
+ rootDir,
66
+ toolingRootDir,
67
+ dryRun,
68
+ applied: apply,
69
+ compatibility,
70
+ projectFile: {
71
+ path: projectFilePath,
72
+ status: projectStamp.status,
73
+ changed: projectStamp.changed,
74
+ packageVersion: projectStamp.packageVersion,
75
+ warning: projectStamp.warning,
76
+ },
77
+ workflowFiles: workflowFiles.map((file) => ({
78
+ kind: file.kind,
79
+ path: file.path,
80
+ relativePath: toPosix(path.relative(rootDir, file.path)),
81
+ status: file.status,
82
+ changed: file.changed,
83
+ warning: file.warning,
84
+ })),
85
+ manifest: {
86
+ path: manifest.manifestPath,
87
+ status: apply ? 'updated' : 'planned-update',
88
+ documentCount: manifest.manifest.canonical.document_count,
89
+ warningCount: manifest.manifest.canonical.warning_count,
90
+ },
91
+ agentFileSync,
92
+ };
93
+ }
94
+
95
+ async function readProjectConfig(projectFilePath) {
96
+ try {
97
+ return parseSimpleYaml(await readFile(projectFilePath, 'utf8'), {
98
+ sourceName: projectFilePath,
99
+ });
100
+ } catch (error) {
101
+ if (error && typeof error === 'object' && error.code === 'ENOENT') {
102
+ throw new Error(`No project.yaml found in ${path.dirname(projectFilePath)}. Run "vibecompass init" first.`);
103
+ }
104
+
105
+ throw error;
106
+ }
107
+ }
108
+
109
+ async function planWorkflowFiles(options) {
110
+ const scaffold = buildWorkflowScaffoldFiles(options);
111
+ const files = [];
112
+
113
+ for (const file of scaffold.files) {
114
+ const existingContent = await readExistingFile(file.path);
115
+ const safety = resolveWorkflowFileSafety(file, existingContent);
116
+ const changed = safety.status !== 'skipped' && existingContent !== file.content;
117
+ files.push({
118
+ ...file,
119
+ existingContent,
120
+ status: changed ? (existingContent === null ? 'create' : 'update') : safety.status,
121
+ changed,
122
+ warning: safety.warning,
123
+ });
124
+ }
125
+
126
+ return files;
127
+ }
128
+
129
+ function resolveWorkflowFileSafety(file, existingContent) {
130
+ if (existingContent === null) {
131
+ return { status: 'create', warning: null };
132
+ }
133
+
134
+ if (existingContent === file.content) {
135
+ return { status: 'unchanged', warning: null };
136
+ }
137
+
138
+ if (file.kind === 'context') {
139
+ return { status: 'update', warning: null };
140
+ }
141
+
142
+ if (isGeneratedWorkflowGuide(file.kind, existingContent)) {
143
+ return { status: 'update', warning: null };
144
+ }
145
+
146
+ return {
147
+ status: 'skipped',
148
+ warning: 'Existing workflow guide does not match a VibeCompass-generated guide shape; left untouched.',
149
+ };
150
+ }
151
+
152
+ function isGeneratedWorkflowGuide(kind, content) {
153
+ // These sentinel strings are the safety boundary for refreshing guide files.
154
+ // Keep them in lockstep with the scaffold guide templates.
155
+ if (kind === 'architecture-guide') {
156
+ return content.includes('Each component doc is canonical. This README is only a convenience guide.');
157
+ }
158
+ if (kind === 'decisions-guide') {
159
+ return content.includes('This README is guidance only; canonical decision content lives in the domain files.');
160
+ }
161
+ if (kind === 'sessions-guide') {
162
+ return content.includes('Those scratch files are session-scoped working artifacts, not finalized history.');
163
+ }
164
+ if (kind === 'workflow-docs-review') {
165
+ return content.includes('Generated by VibeCompass. This file is a package-owned workflow guide');
166
+ }
167
+
168
+ return false;
169
+ }
170
+
171
+ // Refresh writes are intentionally non-atomic, matching init/scaffold behavior.
172
+ async function applyWorkflowFiles(files) {
173
+ for (const file of files) {
174
+ if (!file.changed || file.status === 'skipped') {
175
+ continue;
176
+ }
177
+
178
+ await mkdir(path.dirname(file.path), { recursive: true });
179
+ await writeFile(file.path, file.content, 'utf8');
180
+ }
181
+ }
182
+
183
+ function planProjectStamp(projectConfig, compatibility, options) {
184
+ const currentMetadata = projectConfig.metadata && typeof projectConfig.metadata === 'object'
185
+ ? projectConfig.metadata
186
+ : {};
187
+ const currentStamp = currentMetadata.package_version;
188
+ const hasStringStamp = typeof currentStamp === 'string' && currentStamp.trim() !== '';
189
+ const missingStamp = currentStamp === undefined || currentStamp === null || currentStamp === '';
190
+ const shouldStamp = missingStamp || Boolean(options.updatePackageStamp);
191
+
192
+ if (!shouldStamp) {
193
+ return {
194
+ projectConfig,
195
+ status: 'unchanged',
196
+ changed: false,
197
+ packageVersion: hasStringStamp ? currentStamp : null,
198
+ warning: ['behind', 'invalid'].includes(compatibility.package.status)
199
+ ? 'Existing package stamp left untouched; pass --update-package-stamp to update it.'
200
+ : null,
201
+ };
202
+ }
203
+
204
+ const nextConfig = {
205
+ ...projectConfig,
206
+ metadata: {
207
+ ...currentMetadata,
208
+ package_version: PACKAGE_VERSION,
209
+ },
210
+ };
211
+ const changed = currentStamp !== PACKAGE_VERSION;
212
+
213
+ return {
214
+ projectConfig: nextConfig,
215
+ status: changed ? (missingStamp ? 'stamp-create' : 'stamp-update') : 'unchanged',
216
+ changed,
217
+ packageVersion: PACKAGE_VERSION,
218
+ warning: null,
219
+ };
220
+ }
221
+
222
+ async function readExistingFile(filePath) {
223
+ try {
224
+ return await readFile(filePath, 'utf8');
225
+ } catch (error) {
226
+ if (error && typeof error === 'object' && error.code === 'ENOENT') {
227
+ return null;
228
+ }
229
+
230
+ throw error;
231
+ }
232
+ }
233
+
234
+ function toPosix(value) {
235
+ return value.split(path.sep).join('/');
236
+ }
package/src/scaffold.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { access, mkdir, readdir, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { resolveWorkflowSettings } from './workflow.js';
4
+ import { getGeneratedWorkflowFiles, renderPromptCommandLines } from './workflows/registry.js';
5
+ import { renderManagedBlock } from './generators/agent-files/markers.js';
6
+ import { renderSharedInstructionBody } from './generators/agent-files/template.js';
4
7
 
5
8
  export async function scaffoldInitFiles(options) {
6
9
  const createdFiles = [];
@@ -53,27 +56,13 @@ export async function scaffoldInitFiles(options) {
53
56
  };
54
57
  }
55
58
 
56
- const contextFilePath = path.join(options.rootDir, 'context.md');
59
+ const workflowScaffold = buildWorkflowScaffoldFiles(options);
60
+ const contextFilePath = workflowScaffold.contextFilePath;
57
61
  // context.md is a derived artifact owned by the package; regenerate it on rerun.
58
- await writeFile(contextFilePath, generateContextMarkdown(options), 'utf8');
62
+ await writeFile(contextFilePath, workflowScaffold.context.content, 'utf8');
59
63
  createdFiles.push(contextFilePath);
60
64
 
61
- const workflowFiles = [
62
- {
63
- path: path.join(options.rootDir, 'architecture', 'README.md'),
64
- content: generateArchitectureGuide(options.projectConfig),
65
- },
66
- {
67
- path: path.join(options.rootDir, 'decisions', 'README.md'),
68
- content: generateDecisionsGuide(options.projectConfig),
69
- },
70
- {
71
- path: path.join(options.rootDir, 'sessions', 'README.md'),
72
- content: generateSessionsGuide(options.projectConfig),
73
- },
74
- ];
75
-
76
- for (const file of workflowFiles) {
65
+ for (const file of [...workflowScaffold.guides, ...workflowScaffold.workflowFiles]) {
77
66
  const outcome = await writeIfMissing(file.path, file.content);
78
67
  if (outcome.created) {
79
68
  createdFiles.push(file.path);
@@ -113,6 +102,48 @@ export async function scaffoldInitFiles(options) {
113
102
  };
114
103
  }
115
104
 
105
+ export function buildWorkflowScaffoldFiles(options) {
106
+ const contextFilePath = path.join(options.rootDir, 'context.md');
107
+ const context = {
108
+ kind: 'context',
109
+ path: contextFilePath,
110
+ content: generateContextMarkdown(options),
111
+ };
112
+
113
+ const guides = [
114
+ {
115
+ kind: 'architecture-guide',
116
+ path: path.join(options.rootDir, 'architecture', 'README.md'),
117
+ content: generateArchitectureGuide(options.projectConfig),
118
+ },
119
+ {
120
+ kind: 'decisions-guide',
121
+ path: path.join(options.rootDir, 'decisions', 'README.md'),
122
+ content: generateDecisionsGuide(options.projectConfig),
123
+ },
124
+ {
125
+ kind: 'sessions-guide',
126
+ path: path.join(options.rootDir, 'sessions', 'README.md'),
127
+ content: generateSessionsGuide(options.projectConfig),
128
+ },
129
+ ];
130
+ const workflowFiles = getGeneratedWorkflowFiles({
131
+ rootRelativePath: options.rootRelativePath,
132
+ }).map((file) => ({
133
+ kind: file.kind,
134
+ path: path.join(options.rootDir, file.relativePath),
135
+ content: file.content,
136
+ }));
137
+
138
+ return {
139
+ contextFilePath,
140
+ context,
141
+ guides,
142
+ workflowFiles,
143
+ files: [context, ...guides, ...workflowFiles],
144
+ };
145
+ }
146
+
116
147
  async function writeIfMissing(filePath, content) {
117
148
  if (await fileExists(filePath)) {
118
149
  return { created: false };
@@ -173,6 +204,7 @@ function generateContextMarkdown(options) {
173
204
  const projectName = options.projectConfig.name;
174
205
  const rootRelativePath = options.rootRelativePath;
175
206
  const workflow = resolveWorkflowSettings(options.projectConfig);
207
+ const promptCommands = renderPromptCommandLines({ rootRelativePath }).join('\n');
176
208
  const repos = options.projectConfig.repos
177
209
  .map((repo) => `- \`${repo.id}\` → ${repo.remote}`)
178
210
  .join('\n');
@@ -213,12 +245,7 @@ ${repos}
213
245
  | "join as reviewer" | Reviewer | No — reviews the builder's work, appends findings, and updates handoff guidance |
214
246
 
215
247
  ## Session prompt commands
216
- - \`start session\` — builder role trigger
217
- - \`join as reviewer\` — reviewer role trigger
218
- - \`planning mode\` — optional prompt-level mode for scoping work before implementation
219
- - \`review handoff\` — reviewer reads the selected lane's \`wip.md\`, \`handoff.md\`, latest finalized note, and relevant diffs before appending findings
220
- - \`address review\` — builder reads reviewer feedback in the selected lane's \`wip.md\` / \`handoff.md\`, responds inline, applies accepted changes, and refreshes the builder handoff
221
- - \`close session\` — builder runs the close-out checklist and ends with \`vibecompass close-session --session <lane-id>\`; \`end session\` is accepted as a synonym
248
+ ${promptCommands}
222
249
 
223
250
  These are prompt commands for agent behavior, not \`vibecompass\` CLI subcommands.
224
251
  Reviewer handback is explicit: the reviewer ends the pass by updating the selected lane's \`wip.md\` + \`handoff.md\` and then stopping. The \`close session\` prompt runs builder close-out and ends with \`vibecompass close-session --session <lane-id>\`, which follows the workflow defaults recorded in \`${rootRelativePath}/project.yaml\`.
@@ -230,7 +257,7 @@ ${renderWorkflowDefaults(workflow)}
230
257
  ## Documentation coverage
231
258
  - The architecture, decision, and session files created by init are an initial scaffold, not a comprehensive codebase review.
232
259
  - Before risky implementation work, inspect the relevant code and update the affected architecture docs.
233
- - Run \`vibecompass docs-review --guided\` when you want a comprehensive documentation review. That command is explicit because it may inspect source files and use AI or hosted scan infrastructure.
260
+ - Use the \`docs review\` prompt command when you want the current AI session to run a comprehensive documentation review. It may run \`vibecompass docs-review --guided\` as package mechanics to record marker state and print the canonical review contract.
234
261
 
235
262
  ## Session startup
236
263
  1. Read \`${rootRelativePath}/project.yaml\`.
@@ -367,7 +394,7 @@ Not yet documented:
367
394
  Before changing any undocumented area, inspect the relevant code and update or add architecture docs.
368
395
 
369
396
  ## Next steps
370
- - Run \`vibecompass docs-review --guided\` for a comprehensive documentation review.
397
+ - Use the \`docs review\` prompt command for a comprehensive documentation review.
371
398
  - Replace this placeholder with domain/feature/component docs as the project map becomes evidence-backed.
372
399
  - Keep architecture docs aligned with implementation changes during each builder session.
373
400
 
@@ -425,7 +452,7 @@ Initialized VibeCompass project memory for ${projectConfig.name}.
425
452
  ${repoLines}
426
453
 
427
454
  ## Next session should start with
428
- 1. Run \`vibecompass docs-review --guided\` if you want a comprehensive documentation review.
455
+ 1. Use the \`docs review\` prompt command if you want a comprehensive documentation review.
429
456
  2. Inspect relevant code before making implementation changes in areas not covered by architecture docs.
430
457
  3. Replace starter placeholders with evidence-backed domain/feature/component docs.
431
458
  `;
@@ -511,33 +538,21 @@ Those scratch files are session-scoped working artifacts, not finalized history.
511
538
  }
512
539
 
513
540
  function generateClaudeTemplate(options) {
514
- const workflow = resolveWorkflowSettings(options.projectConfig);
541
+ return [
542
+ generateClaudeSessionHeader(options),
543
+ renderManagedBlock(renderSharedInstructionBody(buildScaffoldAgentContext(options), {
544
+ heading: `${options.projectConfig.name} Claude Instructions`,
545
+ intro:
546
+ 'Claude should use VibeCompass project memory as the source of truth before planning, editing, or reviewing code.',
547
+ })),
548
+ ].join('\n');
549
+ }
515
550
 
551
+ function generateClaudeSessionHeader(options) {
516
552
  return `# ${options.projectConfig.name} Workspace
517
553
 
518
554
  Read \`${options.contextRelativeToToolingRoot}\` before doing substantive work.
519
555
 
520
- ## Session roles
521
-
522
- | Entry trigger | Role | Owns session lifecycle? |
523
- |---|---|---|
524
- | "start session" | Builder | Yes |
525
- | "join as reviewer" | Reviewer | No |
526
- | "planning mode" | Planner | No — scopes work inside the selected active lane before implementation |
527
-
528
- ## Prompt commands
529
-
530
- - \`start session\` — builder role trigger
531
- - \`join as reviewer\` — reviewer role trigger
532
- - \`planning mode\` — optional prompt-level mode for scoping risky or ambiguous work before implementation
533
- - \`review handoff\` — reviewer reads the selected lane's \`wip.md\`, \`handoff.md\`, latest finalized note, and relevant diffs before appending findings
534
- - \`address review\` — builder reads reviewer feedback in the selected lane's \`wip.md\` / \`handoff.md\`, responds inline, applies accepted changes, and refreshes the builder handoff
535
- - \`close session\` — builder runs the close-out checklist and ends with \`vibecompass close-session --session <lane-id>\`; \`end session\` is accepted as a synonym
536
-
537
- These are prompt commands for agent behavior, not \`vibecompass\` CLI subcommands.
538
- Reviewer handback is explicit: the reviewer ends the pass by updating the selected lane's \`wip.md\` + \`handoff.md\` and then stopping. The \`close session\` prompt runs builder close-out and ends with \`vibecompass close-session --session <lane-id>\`, which follows the workflow defaults recorded in \`${options.rootRelativePath}/project.yaml\`.
539
- \`vibecompass end-session\` is accepted as an alias for \`vibecompass close-session\`; \`close-session\` remains the canonical command name.
540
-
541
556
  ## Current session
542
557
 
543
558
  **Update this block at the start and end of every session.**
@@ -549,64 +564,28 @@ Last thing completed: Project memory bootstrap completed.
549
564
  Blockers: None recorded.
550
565
  Next session should: Read \`${options.contextRelativeToToolingRoot}\`, then open the first builder or reviewer session.
551
566
  \`\`\`
552
-
553
- ## Startup
554
- - Read \`${options.contextRelativeToToolingRoot}\`
555
- - Follow the builder/reviewer protocol defined there
556
- - Use \`vibecompass start-session --id <lane-id>\`, \`vibecompass list-sessions\`, \`vibecompass switch-session <lane-id>\`, and \`vibecompass close-session --session <lane-id>\` to manage active lanes when possible
557
- - Treat \`${options.rootRelativePath}/sessions/active/index.yaml\` as the current lane source of truth; this Current session block is only a continuity summary
558
- - Keep the selected lane's \`${options.rootRelativePath}/sessions/active/<lane-id>/wip.md\` and \`${options.rootRelativePath}/sessions/active/<lane-id>/handoff.md\` current during an active session
559
- - Use planning mode when scope is unclear; record the agreed plan in the selected lane's \`wip.md\` before implementing
560
- - If stale scratch files block \`start-session\`, read them first, then close, recover, move, or delete them intentionally before retrying
561
- - Workflow defaults:
562
- ${renderWorkflowDefaults(workflow)}
563
567
  `;
564
568
  }
565
569
 
566
570
  function generateAgentsTemplate(options) {
567
- const workflow = resolveWorkflowSettings(options.projectConfig);
568
-
569
- return `# ${options.projectConfig.name} Workspace
570
-
571
- This workspace uses VibeCompass project memory rooted at \`${options.rootRelativePath}\`.
572
-
573
- ## Session roles
574
-
575
- | Entry trigger | Role | Owns session lifecycle? |
576
- |---|---|---|
577
- | "start session" | Builder | Yes — opens and closes the session lifecycle |
578
- | "join as reviewer" | Reviewer | No — reviews the builder's work and updates the handoff |
579
- | "planning mode" | Planner | No — scopes work inside the selected active lane before implementation |
580
-
581
- ## Prompt commands
582
-
583
- - \`start session\` — builder role trigger
584
- - \`join as reviewer\` — reviewer role trigger
585
- - \`planning mode\` — optional prompt-level mode for scoping risky or ambiguous work before implementation
586
- - \`review handoff\` — reviewer reads the selected lane's \`wip.md\`, \`handoff.md\`, latest finalized note, and relevant diffs before appending findings
587
- - \`address review\` — builder reads reviewer feedback in the selected lane's \`wip.md\` / \`handoff.md\`, responds inline, applies accepted changes, and refreshes the builder handoff
588
- - \`close session\` — builder runs the close-out checklist and ends with \`vibecompass close-session --session <lane-id>\`; \`end session\` is accepted as a synonym
571
+ return renderManagedBlock(renderSharedInstructionBody(buildScaffoldAgentContext(options), {
572
+ heading: `${options.projectConfig.name} Agent Instructions`,
573
+ intro:
574
+ 'Agents should use VibeCompass project memory as the source of truth before planning, editing, or reviewing code.',
575
+ }));
576
+ }
589
577
 
590
- These are prompt commands for agent behavior, not \`vibecompass\` CLI subcommands.
591
- Reviewer handback is explicit: the reviewer ends the pass by updating the selected lane's \`wip.md\` + \`handoff.md\` and then stopping. The \`close session\` prompt runs builder close-out and ends with \`vibecompass close-session --session <lane-id>\`, which follows the workflow defaults recorded in \`${options.rootRelativePath}/project.yaml\`.
592
- \`vibecompass end-session\` is accepted as an alias for \`vibecompass close-session\`; \`close-session\` remains the canonical command name.
593
-
594
- ## Startup
595
- Before doing substantive work:
596
- 1. Read \`${options.contextRelativeToToolingRoot}\`.
597
- 2. Read the latest finalized session note under \`${options.rootRelativePath}/sessions/\`.
598
- 3. If present, read \`${options.rootRelativePath}/sessions/active/index.yaml\` and choose the selected or current lane.
599
- 4. If present, read \`${options.rootRelativePath}/sessions/active/<lane-id>/wip.md\`.
600
- 5. If present, read \`${options.rootRelativePath}/sessions/active/<lane-id>/handoff.md\`.
601
- 6. Read the relevant docs under \`${options.rootRelativePath}/architecture/\` and \`${options.rootRelativePath}/decisions/\`.
602
-
603
- ## Working rule
604
- Treat \`${options.contextRelativeToToolingRoot}\` as the local workflow source of truth for the builder/reviewer protocol and session scratch-file structure. When the package provides \`start-session\` / \`close-session\`, prefer those commands over hand-editing boilerplate.
605
- Use planning mode when scope is unclear; record the agreed plan in the selected lane's \`wip.md\` before implementing.
606
- If stale scratch files block \`start-session\`, read them first, then close, recover, move, or delete them intentionally before retrying.
607
- - Workflow defaults:
608
- ${renderWorkflowDefaults(workflow)}
609
- `;
578
+ function buildScaffoldAgentContext(options) {
579
+ return {
580
+ projectName: options.projectConfig.name ?? 'Unnamed project',
581
+ description: options.projectConfig.description ?? null,
582
+ mode: options.projectConfig.mode ?? null,
583
+ rootDir: options.rootRelativePath,
584
+ repos: options.projectConfig.repos ?? [],
585
+ domains: [],
586
+ recentDecisions: [],
587
+ recentSessions: [],
588
+ };
610
589
  }
611
590
 
612
591
  function renderWorkflowDefaults(workflow) {