@vibecompass/vibecompass 0.5.0 → 0.7.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 +18 -0
- package/README.md +80 -680
- package/package.json +4 -2
- package/src/cli.js +50 -1
- package/src/docs-review.js +669 -13
- package/src/status.js +41 -1
- package/src/workflows/registry.js +142 -0
package/src/status.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { inspectProjectCompatibility } from './compatibility.js';
|
|
4
|
+
import { sha256Text } from './hash.js';
|
|
4
5
|
import { parseSimpleYaml } from './simple-yaml.js';
|
|
5
6
|
import { listProjectSessions } from './session.js';
|
|
6
7
|
import { syncAgentInstructionFiles } from './generators/agent-files/index.js';
|
|
@@ -94,6 +95,9 @@ export function renderStatusText(status) {
|
|
|
94
95
|
'',
|
|
95
96
|
'Docs review:',
|
|
96
97
|
`- ${formatDocsReviewStatus(status.docsReview)}`,
|
|
98
|
+
...(status.docsReview.outputDrift?.hasDrift
|
|
99
|
+
? [`- Output drift: ${status.docsReview.outputDrift.reasons.join('; ')}`]
|
|
100
|
+
: []),
|
|
97
101
|
'',
|
|
98
102
|
'Agent files:',
|
|
99
103
|
...formatAgentFileStatus(status.agentFiles),
|
|
@@ -192,12 +196,15 @@ async function readDocsReviewStatus(rootDir) {
|
|
|
192
196
|
const markerPath = path.join(rootDir, 'state', 'docs-review.json');
|
|
193
197
|
try {
|
|
194
198
|
const marker = JSON.parse(await readFile(markerPath, 'utf8'));
|
|
199
|
+
const outputDrift = await readDocsReviewOutputDrift(rootDir, marker);
|
|
195
200
|
return {
|
|
196
201
|
status: typeof marker.status === 'string' ? marker.status : 'unknown',
|
|
197
202
|
markerPath,
|
|
198
203
|
completedAt: typeof marker.completed_at === 'string' ? marker.completed_at : null,
|
|
199
204
|
provider: typeof marker.provider === 'string' ? marker.provider : null,
|
|
200
205
|
model: typeof marker.model === 'string' ? marker.model : null,
|
|
206
|
+
applied: marker.applied ?? null,
|
|
207
|
+
outputDrift,
|
|
201
208
|
};
|
|
202
209
|
} catch (error) {
|
|
203
210
|
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
|
@@ -215,6 +222,36 @@ async function readDocsReviewStatus(rootDir) {
|
|
|
215
222
|
}
|
|
216
223
|
}
|
|
217
224
|
|
|
225
|
+
async function readDocsReviewOutputDrift(rootDir, marker) {
|
|
226
|
+
const applied = marker?.applied;
|
|
227
|
+
if (!applied || typeof applied !== 'object') {
|
|
228
|
+
return { status: 'not-tracked', hasDrift: false, reasons: [] };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const reasons = [];
|
|
232
|
+
if (typeof applied.output_path === 'string' && typeof applied.output_hash === 'string') {
|
|
233
|
+
try {
|
|
234
|
+
const outputContent = await readFile(applied.output_path, 'utf8');
|
|
235
|
+
const currentOutputHash = sha256Text(outputContent);
|
|
236
|
+
if (currentOutputHash !== applied.output_hash) {
|
|
237
|
+
reasons.push('state/docs-review-output.md changed since apply');
|
|
238
|
+
}
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
|
241
|
+
reasons.push('state/docs-review-output.md is missing since apply');
|
|
242
|
+
} else {
|
|
243
|
+
reasons.push('state/docs-review-output.md could not be read for drift check');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
status: reasons.length > 0 ? 'drift' : 'current',
|
|
250
|
+
hasDrift: reasons.length > 0,
|
|
251
|
+
reasons,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
218
255
|
async function readAgentFileStatus(rootDir, toolingRootDir) {
|
|
219
256
|
try {
|
|
220
257
|
const result = await syncAgentInstructionFiles({
|
|
@@ -257,8 +294,10 @@ function buildRecommendations({ project, compatibility, sessions, docsReview, ag
|
|
|
257
294
|
recommendations.push('vibecompass refresh-workflow --dry-run');
|
|
258
295
|
}
|
|
259
296
|
|
|
260
|
-
if (docsReview.status === 'missing') {
|
|
297
|
+
if (docsReview.status === 'missing' || docsReview.status === 'rebuild-ready') {
|
|
261
298
|
recommendations.push('vibecompass docs-review --guided');
|
|
299
|
+
} else if (docsReview.outputDrift?.hasDrift) {
|
|
300
|
+
recommendations.push('vibecompass docs-review --apply-output');
|
|
262
301
|
}
|
|
263
302
|
|
|
264
303
|
if (agentFiles.status === 'ok' && agentFiles.results.some((item) => item.changed || item.warning)) {
|
|
@@ -278,6 +317,7 @@ function hasStatusDrift(status) {
|
|
|
278
317
|
return (
|
|
279
318
|
status.compatibility.warnings.length > 0 ||
|
|
280
319
|
status.docsReview.status !== 'completed' ||
|
|
320
|
+
Boolean(status.docsReview.outputDrift?.hasDrift) ||
|
|
281
321
|
status.agentFiles.status !== 'ok' ||
|
|
282
322
|
status.agentFiles.results.some((item) => item.changed || item.warning)
|
|
283
323
|
);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
export const WORKFLOW_REGISTRY = [
|
|
2
|
+
{
|
|
3
|
+
id: 'session-lifecycle',
|
|
4
|
+
title: 'Session lifecycle',
|
|
5
|
+
detailPath: 'context.md',
|
|
6
|
+
summary: 'Builder/reviewer lane lifecycle, handoffs, planning mode, and close-out.',
|
|
7
|
+
promptCommands: [
|
|
8
|
+
{ trigger: 'start session', summary: 'builder role trigger' },
|
|
9
|
+
{ trigger: 'join as reviewer', summary: 'reviewer role trigger' },
|
|
10
|
+
{ trigger: 'planning mode', summary: 'optional prompt-level mode for scoping work before implementation' },
|
|
11
|
+
{ trigger: 'review handoff', summary: 'reviewer reads the selected lane handoff and appends findings' },
|
|
12
|
+
{ trigger: 'address review', summary: 'builder processes reviewer feedback from the selected lane scratch files' },
|
|
13
|
+
{ trigger: 'close session', summary: 'builder runs the close-out checklist and ends with `vibecompass close-session --session <lane-id>`; `end session` and `vibecompass end-session` are accepted aliases' },
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'docs-review',
|
|
18
|
+
title: 'Docs review',
|
|
19
|
+
detailPath: 'workflows/docs-review.md',
|
|
20
|
+
summary: 'Generate evidence-backed architecture docs from the user\'s preferred AI session.',
|
|
21
|
+
promptCommands: [
|
|
22
|
+
{ trigger: 'docs review', summary: 'agent-led architecture documentation review using the preferred LLM prompt box' },
|
|
23
|
+
],
|
|
24
|
+
generatedFileKind: 'workflow-docs-review',
|
|
25
|
+
renderFile: renderDocsReviewWorkflow,
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export function getPromptCommands() {
|
|
30
|
+
return WORKFLOW_REGISTRY.flatMap((workflow) =>
|
|
31
|
+
workflow.promptCommands.map((command) => ({
|
|
32
|
+
...command,
|
|
33
|
+
workflowId: workflow.id,
|
|
34
|
+
workflowTitle: workflow.title,
|
|
35
|
+
detailPath: workflow.detailPath,
|
|
36
|
+
})),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getWorkflowConflictScanPatterns() {
|
|
41
|
+
const patterns = new Set([
|
|
42
|
+
'session',
|
|
43
|
+
'handoff',
|
|
44
|
+
'decisions',
|
|
45
|
+
'wip.md',
|
|
46
|
+
'builder',
|
|
47
|
+
'reviewer',
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
for (const command of getPromptCommands()) {
|
|
51
|
+
const normalizedTrigger = command.trigger.toLowerCase();
|
|
52
|
+
patterns.add(normalizedTrigger);
|
|
53
|
+
patterns.add(slugifyPromptTrigger(normalizedTrigger));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return Array.from(patterns);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getGeneratedWorkflowFiles(options = {}) {
|
|
60
|
+
const rootRelativePath = options.rootRelativePath ?? '.';
|
|
61
|
+
return WORKFLOW_REGISTRY
|
|
62
|
+
.filter((workflow) => typeof workflow.renderFile === 'function')
|
|
63
|
+
.map((workflow) => ({
|
|
64
|
+
kind: workflow.generatedFileKind,
|
|
65
|
+
relativePath: workflow.detailPath,
|
|
66
|
+
content: workflow.renderFile({
|
|
67
|
+
rootRelativePath,
|
|
68
|
+
workflow,
|
|
69
|
+
}),
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function renderPromptCommandLines(options = {}) {
|
|
74
|
+
const rootRelativePath = options.rootRelativePath ?? '.';
|
|
75
|
+
return getPromptCommands().map((command) => {
|
|
76
|
+
const detailPath = command.detailPath
|
|
77
|
+
? ` Read \`${joinRootRelative(rootRelativePath, command.detailPath)}\` for the full workflow.`
|
|
78
|
+
: '';
|
|
79
|
+
return `- \`${command.trigger}\` — ${command.summary}.${detailPath}`;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function renderDocsReviewWorkflow(options = {}) {
|
|
84
|
+
const rootRelativePath = options.rootRelativePath ?? '.';
|
|
85
|
+
const projectYamlPath = joinRootRelative(rootRelativePath, 'project.yaml');
|
|
86
|
+
const contextPath = joinRootRelative(rootRelativePath, 'context.md');
|
|
87
|
+
const architecturePath = joinRootRelative(rootRelativePath, 'architecture/');
|
|
88
|
+
const decisionsPath = joinRootRelative(rootRelativePath, 'decisions/');
|
|
89
|
+
const sessionsPath = joinRootRelative(rootRelativePath, 'sessions/');
|
|
90
|
+
const outputPath = joinRootRelative(rootRelativePath, 'state/docs-review-output.md');
|
|
91
|
+
|
|
92
|
+
return `# VibeCompass Workflow — docs review
|
|
93
|
+
|
|
94
|
+
Generated by VibeCompass. This file is a package-owned workflow guide and may be refreshed by \`vibecompass refresh-workflow --apply\`.
|
|
95
|
+
|
|
96
|
+
## Prompt trigger
|
|
97
|
+
|
|
98
|
+
When the user says \`docs review\`, run this workflow inside the current Claude Code, Codex, Cursor, or Copilot session. The npm package owns state markers and apply mechanics; the AI session owns repository inspection and writing accepted docs-review output.
|
|
99
|
+
|
|
100
|
+
## Goal
|
|
101
|
+
|
|
102
|
+
Turn starter project memory into evidence-backed architecture docs under \`architecture/<domain>/<feature>/<component>.md\`, a projected coverage plan, and optional accepted decision recommendations.
|
|
103
|
+
|
|
104
|
+
## Steps
|
|
105
|
+
|
|
106
|
+
1. Read \`${projectYamlPath}\`, \`${contextPath}\`, existing \`${architecturePath}\`, \`${decisionsPath}\`, and finalized \`${sessionsPath}\` notes.
|
|
107
|
+
2. Inspect the declared source repos in stages:
|
|
108
|
+
- inventory paths, manifests, configs, route/job/test directories, and existing architecture frontmatter first
|
|
109
|
+
- produce a coverage plan before reading broad source bodies
|
|
110
|
+
- read targeted source files only when the coverage plan needs evidence
|
|
111
|
+
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.
|
|
112
|
+
4. Present the evidence inventory, coverage plan, proposed architecture docs, blindspots, token-budget risks, and any decision candidates before generating accepted output.
|
|
113
|
+
5. Draft only output the user accepts. 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\`. Accepted local decisions may be fenced as \`vibecompass-decision-recommendation target=decisions/domain.md\` blocks without D-numbers.
|
|
114
|
+
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.
|
|
115
|
+
7. Write the accepted fenced blocks to \`${outputPath}\`.
|
|
116
|
+
8. Run \`vibecompass docs-review --apply-output\` to validate and apply the accepted blocks. If accepted docs were written manually outside the output file, run \`vibecompass docs-review --complete\` after confirming the marker should be completed.
|
|
117
|
+
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.
|
|
118
|
+
|
|
119
|
+
## Rebuild option
|
|
120
|
+
|
|
121
|
+
If the user explicitly wants to rerun docs-review from a clean or narrower architecture-doc slate, preview the scope first with \`vibecompass docs-review --rebuild --path architecture/<scope>\`. Only apply cleanup after the user accepts the preview; use \`vibecompass docs-review --rebuild --apply --stale-policy archive --path architecture/<scope>\` when the user explicitly wants selected stale docs archived before the next review. Rebuild archives selected \`architecture/**/*.md\` docs under \`state/docs-review-archive/\`; it does not mutate decisions, sessions, project.yaml, workflow files, agent instruction files, \`architecture/README.md\`, or \`architecture/overview/project-shape.md\`.
|
|
122
|
+
|
|
123
|
+
## Rules
|
|
124
|
+
|
|
125
|
+
- Do not duplicate the full canonical review prompt in this file. The package command prints that contract from its current implementation.
|
|
126
|
+
- Follow the canonical review prompt for evidence, blindspot, and decision-recommendation constraints.
|
|
127
|
+
- Treat the package-owned prompt as the source of truth. Tool-specific skills may wrap this workflow, but should not define a divergent docs-review protocol.
|
|
128
|
+
- Do not edit generated agent files directly. Update VibeCompass project memory or rerun package commands instead.
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function slugifyPromptTrigger(trigger) {
|
|
133
|
+
return trigger.trim().replace(/\s+/g, '-');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function joinRootRelative(rootRelativePath, relativePath) {
|
|
137
|
+
if (!rootRelativePath || rootRelativePath === '.') {
|
|
138
|
+
return relativePath;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return `${rootRelativePath.replace(/\/$/, '')}/${relativePath}`;
|
|
142
|
+
}
|