@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.
- package/CHANGELOG.md +37 -1
- package/README.md +42 -1
- package/package.json +1 -1
- package/src/cli.js +602 -8
- package/src/compatibility.js +34 -1
- package/src/decision-index.js +386 -0
- package/src/decisions.js +195 -0
- package/src/docs-review.js +27 -27
- package/src/docs-update.js +459 -24
- package/src/generators/agent-files/index.js +8 -0
- package/src/generators/agent-files/template.js +6 -3
- package/src/git-binding.js +676 -0
- package/src/init.js +9 -0
- package/src/lane-marker.js +339 -0
- package/src/lane-runtime.js +254 -0
- package/src/manifest.js +19 -0
- package/src/project-memory.js +27 -1
- package/src/refresh-workflow.js +8 -0
- package/src/scaffold.js +24 -12
- package/src/serialization.js +205 -0
- package/src/session.js +955 -80
- package/src/simple-yaml.js +24 -4
- package/src/status.js +43 -5
- package/src/sync.js +5 -0
- package/src/workflow.js +39 -1
- package/src/workflows/registry.js +7 -4
package/src/simple-yaml.js
CHANGED
|
@@ -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
|
-
|
|
213
|
-
(
|
|
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
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
: '
|
|
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
|
|