@vibecompass/vibecompass 0.3.3 → 0.3.4
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 +19 -2
- package/package.json +1 -1
- package/src/cli.js +25 -0
- package/src/generators/agent-files/index.js +57 -1
- package/src/generators/agent-files/markers.js +10 -1
- package/src/generators/agent-files/template.js +4 -0
- package/src/setup.js +85 -0
package/README.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://vibecompass.dev">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/jack-whimvy/vibecompass/main/brand/logo-a4.png" alt="VibeCompass" height="96" />
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
1
7
|
# @vibecompass/vibecompass
|
|
2
8
|
|
|
3
9
|
Local-first project-memory core for [VibeCompass](https://vibecompass.dev).
|
|
@@ -243,7 +249,16 @@ npx -y @vibecompass/vibecompass sync-agents --root .compass
|
|
|
243
249
|
```
|
|
244
250
|
|
|
245
251
|
Content outside those markers is preserved. Existing files without markers are
|
|
246
|
-
reported as warnings and left untouched
|
|
252
|
+
reported as warnings and left untouched unless you explicitly adopt them:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
npx -y @vibecompass/vibecompass sync-agents --root .compass --adopt-existing
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Adoption appends a managed VibeCompass block to the end of existing unmarked
|
|
259
|
+
agent files, preserves everything outside the markers, and reports possible
|
|
260
|
+
workflow-overlap lines before you rely on the result. Guided init may offer the
|
|
261
|
+
same adoption step for existing files, but the prompt defaults to no.
|
|
247
262
|
|
|
248
263
|
Connect hosted VibeCompass later when you want browser views, team sync, or hosted docs-review:
|
|
249
264
|
|
|
@@ -314,6 +329,7 @@ Options:
|
|
|
314
329
|
- `--with-workflow`: generate `context.md` plus workflow guide files
|
|
315
330
|
- `--with-claude`: create a starter `CLAUDE.md` if it does not already exist
|
|
316
331
|
- `--with-agents`: create a starter `AGENTS.md` if it does not already exist
|
|
332
|
+
- `--adopt-existing-agent-files`: append managed blocks to existing unmarked agent files
|
|
317
333
|
- `--start-session`: open the first builder session after init
|
|
318
334
|
- `--session-working-on <text>`: required with `--start-session` outside guided mode
|
|
319
335
|
- `--session-id <lane-id>`: required with `--start-session`; names the first builder lane
|
|
@@ -440,12 +456,13 @@ Options:
|
|
|
440
456
|
- `--root <path>`: project-memory root; defaults to `.compass`
|
|
441
457
|
- `--tooling-root <path>`: directory where agent files are written; defaults to cwd
|
|
442
458
|
- `--format <name>`: optional format filter: `claude_md`, `agents_md`, `cursor_rules`, or `copilot_instructions`
|
|
459
|
+
- `--adopt-existing`: append managed blocks to existing unmarked files
|
|
443
460
|
- `--dry-run`: show planned writes without changing files
|
|
444
461
|
|
|
445
462
|
This command generates enabled agent-instruction files from canonical project
|
|
446
463
|
memory. It creates missing files with managed markers, replaces existing
|
|
447
464
|
managed regions, preserves content outside markers, and warns instead of
|
|
448
|
-
overwriting existing unmarked files.
|
|
465
|
+
overwriting existing unmarked files unless `--adopt-existing` is supplied.
|
|
449
466
|
|
|
450
467
|
### `vibecompass docs-review`
|
|
451
468
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -78,6 +78,14 @@ export async function runCli(argv, io = createDefaultIo(), runtime = {}) {
|
|
|
78
78
|
io.stdout.write(`Left existing ${filePath} untouched\n`);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
if (initPlan.agentFileSyncPlan) {
|
|
82
|
+
const agentFileSync = await syncAgentInstructionFiles({
|
|
83
|
+
...initPlan.agentFileSyncPlan,
|
|
84
|
+
...(runtime.cwd ? { cwd: runtime.cwd } : {}),
|
|
85
|
+
});
|
|
86
|
+
writeAgentFileSyncResult(io, agentFileSync);
|
|
87
|
+
}
|
|
88
|
+
|
|
81
89
|
if (initPlan.sessionPlan) {
|
|
82
90
|
const sessionResult = await startProjectSession({
|
|
83
91
|
rootDir: initPlan.initOptions.rootDir,
|
|
@@ -402,6 +410,11 @@ export function parseCliArgs(argv) {
|
|
|
402
410
|
continue;
|
|
403
411
|
}
|
|
404
412
|
|
|
413
|
+
if (token === '--adopt-existing-agent-files') {
|
|
414
|
+
parsed.adoptExistingAgentFiles = true;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
|
|
405
418
|
if (token === '--start-session') {
|
|
406
419
|
parsed.startSession = true;
|
|
407
420
|
continue;
|
|
@@ -508,6 +521,7 @@ export function parseCliArgs(argv) {
|
|
|
508
521
|
sessionId: parsed.sessionId,
|
|
509
522
|
closeSessionGitPublish: parsed.closeSessionGitPublish,
|
|
510
523
|
closeSessionGitRemote: parsed.closeSessionGitRemote,
|
|
524
|
+
adoptExistingAgentFiles: parsed.adoptExistingAgentFiles,
|
|
511
525
|
...(parsed.withWorkflow || parsed.withClaude || parsed.withAgents
|
|
512
526
|
? {
|
|
513
527
|
bootstrap: {
|
|
@@ -804,6 +818,11 @@ function parseSyncAgentsArgs(argv) {
|
|
|
804
818
|
continue;
|
|
805
819
|
}
|
|
806
820
|
|
|
821
|
+
if (token === '--adopt-existing') {
|
|
822
|
+
parsed.adoptExisting = true;
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
|
|
807
826
|
if (!token.startsWith('--')) {
|
|
808
827
|
throw new Error(`Unexpected argument "${token}".`);
|
|
809
828
|
}
|
|
@@ -1087,6 +1106,10 @@ function writeAgentFileSyncResult(io, result) {
|
|
|
1087
1106
|
const suffix = item.warning ? ` — ${item.warning}` : '';
|
|
1088
1107
|
const stream = item.warning ? io.stderr : io.stdout;
|
|
1089
1108
|
stream.write(`- ${item.relativePath}: ${item.status}${suffix}\n`);
|
|
1109
|
+
if (item.conflicts?.length > 0) {
|
|
1110
|
+
const lineNumbers = item.conflicts.map((conflict) => conflict.line).join(', ');
|
|
1111
|
+
io.stderr.write(` possible workflow overlap on lines ${lineNumbers}; review existing instructions if behavior conflicts.\n`);
|
|
1112
|
+
}
|
|
1090
1113
|
}
|
|
1091
1114
|
}
|
|
1092
1115
|
|
|
@@ -1138,6 +1161,7 @@ function usageText() {
|
|
|
1138
1161
|
' --with-workflow Scaffold context.md and workflow guide files',
|
|
1139
1162
|
' --with-claude Create a starter CLAUDE.md if missing',
|
|
1140
1163
|
' --with-agents Create a starter AGENTS.md if missing',
|
|
1164
|
+
' --adopt-existing-agent-files Adopt existing unmarked agent files after init',
|
|
1141
1165
|
' --start-session Open the first builder session after init',
|
|
1142
1166
|
' --session-working-on <text> Required with --start-session outside guided mode',
|
|
1143
1167
|
' --session-id <lane-id> Required with --start-session; names the first builder lane',
|
|
@@ -1187,6 +1211,7 @@ function usageText() {
|
|
|
1187
1211
|
' --root <path> Project-memory root. Defaults to .compass',
|
|
1188
1212
|
' --tooling-root <path> Directory where agent files are written. Defaults to cwd',
|
|
1189
1213
|
' --format <name> Optional format: claude_md, agents_md, cursor_rules, copilot_instructions',
|
|
1214
|
+
' --adopt-existing Append managed blocks to existing unmarked files',
|
|
1190
1215
|
' --dry-run Show planned writes without changing files',
|
|
1191
1216
|
'',
|
|
1192
1217
|
'Sync transport options:',
|
|
@@ -17,6 +17,17 @@ const FORMATS = [
|
|
|
17
17
|
];
|
|
18
18
|
|
|
19
19
|
const FORMAT_MAP = new Map(FORMATS.map((format) => [format.name, format]));
|
|
20
|
+
const CONFLICT_SCAN_PATTERNS = [
|
|
21
|
+
'session',
|
|
22
|
+
'handoff',
|
|
23
|
+
'decisions',
|
|
24
|
+
'wip.md',
|
|
25
|
+
'review handoff',
|
|
26
|
+
'close-session',
|
|
27
|
+
'address review',
|
|
28
|
+
'builder',
|
|
29
|
+
'reviewer',
|
|
30
|
+
];
|
|
20
31
|
|
|
21
32
|
export async function syncAgentInstructionFiles(options = {}) {
|
|
22
33
|
const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
|
|
@@ -26,6 +37,8 @@ export async function syncAgentInstructionFiles(options = {}) {
|
|
|
26
37
|
: cwd;
|
|
27
38
|
const requestedFormat = normalizeOptionalString(options.format);
|
|
28
39
|
const dryRun = Boolean(options.dryRun);
|
|
40
|
+
const adoptExisting = Boolean(options.adoptExisting);
|
|
41
|
+
const existingOnly = Boolean(options.existingOnly);
|
|
29
42
|
|
|
30
43
|
const projectConfig = await readProjectConfig(rootDir);
|
|
31
44
|
const selection = resolveEnabledFormats(projectConfig, requestedFormat);
|
|
@@ -56,8 +69,27 @@ export async function syncAgentInstructionFiles(options = {}) {
|
|
|
56
69
|
for (const format of selection.formats) {
|
|
57
70
|
const outputPath = path.join(toolingRootDir, format.path);
|
|
58
71
|
const existingContent = await readExistingFile(outputPath);
|
|
72
|
+
|
|
73
|
+
if (existingOnly && existingContent === null) {
|
|
74
|
+
results.push({
|
|
75
|
+
format: format.name,
|
|
76
|
+
path: outputPath,
|
|
77
|
+
relativePath: toPosix(path.relative(toolingRootDir, outputPath)),
|
|
78
|
+
status: 'skipped-missing',
|
|
79
|
+
warning: null,
|
|
80
|
+
changed: false,
|
|
81
|
+
conflicts: [],
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
59
86
|
const generatedContent = format.render(context);
|
|
60
|
-
const applied = applyManagedBlock(existingContent, generatedContent
|
|
87
|
+
const applied = applyManagedBlock(existingContent, generatedContent, {
|
|
88
|
+
adoptExisting,
|
|
89
|
+
});
|
|
90
|
+
const conflicts = applied.status === 'adopt'
|
|
91
|
+
? scanPotentialWorkflowConflicts(existingContent)
|
|
92
|
+
: [];
|
|
61
93
|
|
|
62
94
|
if (!dryRun && applied.warning === null && applied.content !== existingContent) {
|
|
63
95
|
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
@@ -71,6 +103,7 @@ export async function syncAgentInstructionFiles(options = {}) {
|
|
|
71
103
|
status: dryRun && applied.warning === null ? `dry-run-${applied.status}` : applied.status,
|
|
72
104
|
warning: applied.warning,
|
|
73
105
|
changed: applied.warning === null && applied.content !== existingContent,
|
|
106
|
+
conflicts,
|
|
74
107
|
});
|
|
75
108
|
}
|
|
76
109
|
|
|
@@ -86,6 +119,10 @@ export function getSupportedAgentFormats() {
|
|
|
86
119
|
return FORMATS.map((format) => format.name);
|
|
87
120
|
}
|
|
88
121
|
|
|
122
|
+
export function getSupportedAgentFilePaths() {
|
|
123
|
+
return FORMATS.map((format) => format.path);
|
|
124
|
+
}
|
|
125
|
+
|
|
89
126
|
async function readProjectConfig(rootDir) {
|
|
90
127
|
const source = await readFile(path.join(rootDir, 'project.yaml'), 'utf8');
|
|
91
128
|
return parseSimpleYaml(source, { sourceName: 'project.yaml' });
|
|
@@ -145,3 +182,22 @@ function normalizeOptionalString(value) {
|
|
|
145
182
|
function toPosix(value) {
|
|
146
183
|
return value.split(path.sep).join('/');
|
|
147
184
|
}
|
|
185
|
+
|
|
186
|
+
function scanPotentialWorkflowConflicts(content) {
|
|
187
|
+
if (typeof content !== 'string' || content.trim() === '') {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return content
|
|
192
|
+
.split(/\r?\n/)
|
|
193
|
+
.map((line, index) => ({
|
|
194
|
+
line: index + 1,
|
|
195
|
+
text: line,
|
|
196
|
+
normalized: line.toLowerCase(),
|
|
197
|
+
}))
|
|
198
|
+
.filter((entry) => CONFLICT_SCAN_PATTERNS.some((pattern) => entry.normalized.includes(pattern)))
|
|
199
|
+
.map(({ line, text }) => ({
|
|
200
|
+
line,
|
|
201
|
+
text: text.trim(),
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
@@ -6,7 +6,7 @@ export function renderManagedBlock(content) {
|
|
|
6
6
|
return `${START_MARKER}\n${trimmed}\n${END_MARKER}\n`;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export function applyManagedBlock(existingContent, generatedContent) {
|
|
9
|
+
export function applyManagedBlock(existingContent, generatedContent, options = {}) {
|
|
10
10
|
const block = renderManagedBlock(generatedContent);
|
|
11
11
|
|
|
12
12
|
if (existingContent === null) {
|
|
@@ -21,6 +21,15 @@ export function applyManagedBlock(existingContent, generatedContent) {
|
|
|
21
21
|
const endIndex = existingContent.indexOf(END_MARKER);
|
|
22
22
|
|
|
23
23
|
if (startIndex === -1 && endIndex === -1) {
|
|
24
|
+
if (options.adoptExisting) {
|
|
25
|
+
const separator = existingContent === '' || existingContent.endsWith('\n') ? '' : '\n';
|
|
26
|
+
return {
|
|
27
|
+
status: 'adopt',
|
|
28
|
+
content: `${existingContent}${separator}${block}`,
|
|
29
|
+
warning: null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
return {
|
|
25
34
|
status: 'warning',
|
|
26
35
|
content: existingContent,
|
|
@@ -22,6 +22,10 @@ export function renderSharedInstructionBody(context, options = {}) {
|
|
|
22
22
|
'',
|
|
23
23
|
intro,
|
|
24
24
|
'',
|
|
25
|
+
`Read \`${context.rootDir}/context.md\` for the full VibeCompass workflow protocol when it exists.`,
|
|
26
|
+
'',
|
|
27
|
+
'Existing project-specific coding, style, framework, and safety instructions outside this managed block remain authoritative. This block owns only VibeCompass project-memory workflow: sessions, handoffs, decisions, docs-review, and close-out.',
|
|
28
|
+
'',
|
|
25
29
|
'## Read First',
|
|
26
30
|
`- Project memory root: \`${context.rootDir}\``,
|
|
27
31
|
'- Read `project.yaml` for project identity, repos, mode, and workflow defaults.',
|
package/src/setup.js
CHANGED
|
@@ -3,6 +3,8 @@ import { access, readFile, readdir, realpath } from 'node:fs/promises';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { createInterface } from 'node:readline/promises';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
|
+
import { getSupportedAgentFilePaths } from './generators/agent-files/index.js';
|
|
7
|
+
import { END_MARKER, START_MARKER } from './generators/agent-files/markers.js';
|
|
6
8
|
|
|
7
9
|
const execFileAsync = promisify(execFile);
|
|
8
10
|
|
|
@@ -68,6 +70,14 @@ export async function resolveInitCliOptions(options, environment = {}) {
|
|
|
68
70
|
},
|
|
69
71
|
placementPattern: resolved.placementPattern ?? null,
|
|
70
72
|
guidedSummary,
|
|
73
|
+
agentFileSyncPlan: resolved.adoptExistingAgentFiles
|
|
74
|
+
? {
|
|
75
|
+
rootDir: resolved.rootDir,
|
|
76
|
+
toolingRootDir: resolved.toolingRootDir,
|
|
77
|
+
adoptExisting: true,
|
|
78
|
+
existingOnly: true,
|
|
79
|
+
}
|
|
80
|
+
: null,
|
|
71
81
|
sessionPlan: resolved.startSession
|
|
72
82
|
? {
|
|
73
83
|
sessionId: resolved.sessionId,
|
|
@@ -336,6 +346,22 @@ async function completeGuidedInitOptions(options, environment) {
|
|
|
336
346
|
}
|
|
337
347
|
}
|
|
338
348
|
|
|
349
|
+
if (resolved.adoptExistingAgentFiles === undefined) {
|
|
350
|
+
const adoptionCandidates = await findExistingUnmarkedAgentFiles({
|
|
351
|
+
cwd,
|
|
352
|
+
toolingRootDir: resolved.toolingRootDir,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
if (adoptionCandidates.length > 0) {
|
|
356
|
+
writeAgentAdoptionPreview(prompter, adoptionCandidates);
|
|
357
|
+
resolved.adoptExistingAgentFiles = await askConfirm(
|
|
358
|
+
prompter,
|
|
359
|
+
'Append VibeCompass managed sections to existing agent instruction files?',
|
|
360
|
+
false,
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
339
365
|
if (!resolved.startSession) {
|
|
340
366
|
resolved.startSession = await askConfirm(
|
|
341
367
|
prompter,
|
|
@@ -558,6 +584,65 @@ function reposMatchDetected(repos, detectedRepos) {
|
|
|
558
584
|
return repos.every((repo) => detectedKeys.has(`${repo.id}\n${repo.remote}`));
|
|
559
585
|
}
|
|
560
586
|
|
|
587
|
+
async function findExistingUnmarkedAgentFiles(context) {
|
|
588
|
+
const toolingRootDir = path.resolve(context.cwd, context.toolingRootDir ?? '.');
|
|
589
|
+
const candidates = [];
|
|
590
|
+
|
|
591
|
+
for (const relativePath of getSupportedAgentFilePaths()) {
|
|
592
|
+
const filePath = path.join(toolingRootDir, relativePath);
|
|
593
|
+
let content = null;
|
|
594
|
+
try {
|
|
595
|
+
content = await readFile(filePath, 'utf8');
|
|
596
|
+
} catch {
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (content.includes(START_MARKER) || content.includes(END_MARKER)) {
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
candidates.push({
|
|
605
|
+
relativePath,
|
|
606
|
+
conflicts: scanPotentialAgentWorkflowConflicts(content),
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return candidates;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function writeAgentAdoptionPreview(prompter, candidates) {
|
|
614
|
+
prompter.write('Existing agent instruction files without VibeCompass markers:\n');
|
|
615
|
+
for (const candidate of candidates) {
|
|
616
|
+
prompter.write(`- ${candidate.relativePath}: VibeCompass can append a managed block at the end of this file.\n`);
|
|
617
|
+
if (candidate.conflicts.length > 0) {
|
|
618
|
+
prompter.write(` possible workflow overlap on lines ${candidate.conflicts.map((conflict) => conflict.line).join(', ')}\n`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
prompter.write('Content outside the managed block stays user-owned and will not be rewritten.\n');
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function scanPotentialAgentWorkflowConflicts(content) {
|
|
625
|
+
const patterns = [
|
|
626
|
+
'session',
|
|
627
|
+
'handoff',
|
|
628
|
+
'decisions',
|
|
629
|
+
'wip.md',
|
|
630
|
+
'review handoff',
|
|
631
|
+
'close-session',
|
|
632
|
+
'address review',
|
|
633
|
+
'builder',
|
|
634
|
+
'reviewer',
|
|
635
|
+
];
|
|
636
|
+
|
|
637
|
+
return content
|
|
638
|
+
.split(/\r?\n/)
|
|
639
|
+
.map((line, index) => ({
|
|
640
|
+
line: index + 1,
|
|
641
|
+
normalized: line.toLowerCase(),
|
|
642
|
+
}))
|
|
643
|
+
.filter((entry) => patterns.some((pattern) => entry.normalized.includes(pattern)));
|
|
644
|
+
}
|
|
645
|
+
|
|
561
646
|
async function promptForRepos(prompter, cwd) {
|
|
562
647
|
const repoCount = await askInteger(prompter, 'How many repos belong to this logical project?', {
|
|
563
648
|
defaultValue: 1,
|