@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.
- package/README.md +198 -14
- package/package.json +1 -1
- package/src/cli.js +263 -0
- package/src/compatibility.js +308 -0
- package/src/docs-review.js +51 -4
- package/src/generators/agent-files/index.js +2 -11
- package/src/generators/agent-files/template.js +33 -34
- package/src/index.js +5 -1
- package/src/init.js +42 -2
- package/src/manifest.js +16 -4
- package/src/project-memory.js +3 -1
- package/src/read-model.js +17 -1
- package/src/refresh-workflow.js +236 -0
- package/src/scaffold.js +81 -102
- package/src/setup.js +253 -11
- package/src/status.js +350 -0
- package/src/version.js +6 -0
package/src/project-memory.js
CHANGED
|
@@ -364,7 +364,9 @@ function parseArchitectureDocument(options) {
|
|
|
364
364
|
}
|
|
365
365
|
|
|
366
366
|
const data = frontmatter.data;
|
|
367
|
-
const requiredFields =
|
|
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
|
|
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,
|
|
62
|
+
await writeFile(contextFilePath, workflowScaffold.context.content, 'utf8');
|
|
59
63
|
createdFiles.push(contextFilePath);
|
|
60
64
|
|
|
61
|
-
const
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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) {
|