@vibecompass/vibecompass 0.11.2 → 0.13.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 +57 -0
- package/README.md +20 -0
- package/package.json +1 -1
- package/src/cli.js +200 -0
- package/src/docs-review.js +131 -22
- package/src/docs-update.js +15 -3
- package/src/index.js +8 -1
- package/src/mode-transition.js +265 -0
- package/src/project-memory.js +422 -0
- package/src/scaffold.js +7 -5
- package/src/serialization.js +26 -0
- package/src/status.js +78 -0
- package/src/sync.js +264 -13
- package/src/workflow.js +3 -0
- package/src/workflows/registry.js +1 -1
package/src/sync.js
CHANGED
|
@@ -155,21 +155,28 @@ async function applyPullExportLocked(options, rootDir) {
|
|
|
155
155
|
throw new Error(`Pull-export bundle at ${outputPath} has no operations.`);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
// Pre-validate EVERY operation before writing anything, and classify
|
|
159
|
+
// operations whose target already matches the after-state as
|
|
160
|
+
// already-applied. This makes a retry after a partial failure converge
|
|
161
|
+
// instead of failing forever on the ops that succeeded last time.
|
|
162
|
+
const plan = [];
|
|
159
163
|
for (const operation of operations) {
|
|
160
164
|
const relativePath = normalizeCanonicalPath(operation.path);
|
|
161
165
|
const targetPath = path.join(rootDir, relativePath);
|
|
162
166
|
const currentHash = await readCurrentHash(targetPath);
|
|
163
167
|
const expectedBefore = operation.before_content_hash ?? null;
|
|
164
|
-
if (currentHash !== expectedBefore) {
|
|
165
|
-
throw new Error(
|
|
166
|
-
`Cannot apply ${relativePath}: local content hash ${currentHash ?? 'missing'} does not match expected ${expectedBefore ?? 'missing'}. Run pull-preview again or resolve locally.`,
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
168
|
|
|
170
169
|
if (operation.op === 'delete') {
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
if (currentHash === null) {
|
|
171
|
+
plan.push({ relativePath, targetPath, action: 'skip', status: 'already-deleted' });
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (currentHash !== expectedBefore) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Cannot apply ${relativePath}: local content hash ${currentHash} does not match expected ${expectedBefore ?? 'missing'}. Run pull-preview again or resolve locally.`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
plan.push({ relativePath, targetPath, action: 'delete' });
|
|
173
180
|
continue;
|
|
174
181
|
}
|
|
175
182
|
|
|
@@ -180,10 +187,32 @@ async function applyPullExportLocked(options, rootDir) {
|
|
|
180
187
|
if (afterHash !== operation.after_content_hash) {
|
|
181
188
|
throw new Error(`Exported content hash does not match raw_text for ${relativePath}.`);
|
|
182
189
|
}
|
|
190
|
+
if (currentHash === afterHash) {
|
|
191
|
+
plan.push({ relativePath, targetPath, action: 'skip', status: 'already-applied' });
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (currentHash !== expectedBefore) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`Cannot apply ${relativePath}: local content hash ${currentHash ?? 'missing'} does not match expected ${expectedBefore ?? 'missing'}. Run pull-preview again or resolve locally.`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
plan.push({ relativePath, targetPath, action: 'write', rawText: operation.raw_text });
|
|
200
|
+
}
|
|
183
201
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
202
|
+
const applied = [];
|
|
203
|
+
for (const step of plan) {
|
|
204
|
+
if (step.action === 'skip') {
|
|
205
|
+
applied.push({ path: step.relativePath, status: step.status });
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (step.action === 'delete') {
|
|
209
|
+
await unlink(step.targetPath);
|
|
210
|
+
applied.push({ path: step.relativePath, status: 'deleted' });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
await mkdir(path.dirname(step.targetPath), { recursive: true });
|
|
214
|
+
await writeFile(step.targetPath, ensureTrailingNewline(step.rawText), 'utf8');
|
|
215
|
+
applied.push({ path: step.relativePath, status: 'written' });
|
|
187
216
|
}
|
|
188
217
|
|
|
189
218
|
bundle.applied_at = new Date().toISOString();
|
|
@@ -200,6 +229,220 @@ async function applyPullExportLocked(options, rootDir) {
|
|
|
200
229
|
};
|
|
201
230
|
}
|
|
202
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Re-baselines this root's sync cursor onto the hosted head (D-287 sync
|
|
234
|
+
* trust): the recovery path for a fresh clone or second device whose
|
|
235
|
+
* gitignored cursor is missing, which previously hit a permanent 409
|
|
236
|
+
* needs_rebase. Preserves D-215's inspect-then-choose workflow: a pull
|
|
237
|
+
* preview always runs first, and pending hosted proposals or conflicts
|
|
238
|
+
* refuse the adopt unless --accept-divergence is passed.
|
|
239
|
+
*/
|
|
240
|
+
export async function adoptRemoteHead(options = {}, environment = {}) {
|
|
241
|
+
const context = await loadSyncContext(options, environment);
|
|
242
|
+
const existingManifest = await readJsonIfPresent(context.manifestPath);
|
|
243
|
+
const manifestResult = await writeStateManifest(context.rootDir, {
|
|
244
|
+
sync: existingManifest?.sync,
|
|
245
|
+
});
|
|
246
|
+
const manifest = manifestResult.manifest;
|
|
247
|
+
const cursor = readSyncCursor(manifest.sync, context.binding);
|
|
248
|
+
|
|
249
|
+
const response = await postJson(context, 'pull-preview', {
|
|
250
|
+
state_version: manifest.state_version,
|
|
251
|
+
base_remote_revision_id: normalizeUuid(cursor.last_successful_remote_revision),
|
|
252
|
+
local_root_revision: manifest.canonical.local_root_revision,
|
|
253
|
+
local_manifest_hash: manifest.canonical.manifest_hash,
|
|
254
|
+
local_documents: buildLocalDocumentSummaries(manifest.documents),
|
|
255
|
+
include_pending_proposals: true,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const targetRemoteRevisionId = normalizeUuid(response.target_remote_revision_id);
|
|
259
|
+
if (!targetRemoteRevisionId) {
|
|
260
|
+
throw new Error('The hosted project has no baseline revision to adopt yet. Run vibecompass push first.');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const proposals = Array.isArray(response.proposals) ? response.proposals : [];
|
|
264
|
+
const conflicts = Array.isArray(response.conflicts) ? response.conflicts : [];
|
|
265
|
+
if ((proposals.length > 0 || conflicts.length > 0) && options.acceptDivergence !== true) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
`Refusing to adopt the hosted head: ${proposals.length} pending proposal${proposals.length === 1 ? '' : 's'} and ${conflicts.length} conflict${conflicts.length === 1 ? '' : 's'} exist for this project (D-215: inspect divergence before choosing a baseline). Review with vibecompass pull-preview, then re-run sync-adopt --accept-divergence to adopt anyway.`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const sync = buildSyncStateWithCursor(manifest.sync, context.binding, {
|
|
272
|
+
...cursor,
|
|
273
|
+
last_successful_remote_revision: targetRemoteRevisionId,
|
|
274
|
+
last_successful_local_root_revision: manifest.canonical.local_root_revision,
|
|
275
|
+
last_successful_manifest_hash: manifest.canonical.manifest_hash,
|
|
276
|
+
last_sync_direction: 'adopt',
|
|
277
|
+
last_sync_at: new Date().toISOString(),
|
|
278
|
+
pending_previews: [],
|
|
279
|
+
});
|
|
280
|
+
const updatedManifest = await writeStateManifest(context.rootDir, { sync });
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
status: 'adopted',
|
|
284
|
+
rootDir: context.rootDir,
|
|
285
|
+
manifestPath: updatedManifest.manifestPath,
|
|
286
|
+
remoteRevisionId: targetRemoteRevisionId,
|
|
287
|
+
proposalCount: proposals.length,
|
|
288
|
+
conflictCount: conflicts.length,
|
|
289
|
+
warnings: [
|
|
290
|
+
'The next push will record this root\'s content as a new revision on top of the adopted head. If local files differ from the hosted head, the hosted content is replaced (visibly, as a new revision).',
|
|
291
|
+
],
|
|
292
|
+
message: `Adopted hosted head ${targetRemoteRevisionId} as this root's sync baseline.`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Materializes a complete local memory root from a hosted bootstrap bundle
|
|
298
|
+
* (D-289): the escape hatch, disaster-recovery, and second-device path. All
|
|
299
|
+
* document hashes are verified before anything is written (fail closed). For
|
|
300
|
+
* local-primary bundles with a resolvable sync binding, the bundle's server
|
|
301
|
+
* head is seeded as the sync cursor so the first push has the correct parent;
|
|
302
|
+
* hosted-only bundles materialize a fully usable offline root (the server
|
|
303
|
+
* rejects hosted-only push until the project is demoted).
|
|
304
|
+
*/
|
|
305
|
+
export async function bootstrapFromBundle(options = {}, environment = {}) {
|
|
306
|
+
const cwd = environment.cwd ? path.resolve(environment.cwd) : process.cwd();
|
|
307
|
+
const rootDir = path.resolve(cwd, options.rootDir ?? '.compass');
|
|
308
|
+
if (!options.bundlePath) {
|
|
309
|
+
throw new Error('bootstrap requires --bundle <file> (download it from the hosted Setup page).');
|
|
310
|
+
}
|
|
311
|
+
const bundlePath = path.resolve(cwd, options.bundlePath);
|
|
312
|
+
return withMemoryRootLock(rootDir, 'bootstrap', () =>
|
|
313
|
+
bootstrapFromBundleLocked(options, rootDir, bundlePath),
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function bootstrapFromBundleLocked(options, rootDir, bundlePath) {
|
|
318
|
+
const bundle = await readJsonRequired(bundlePath, 'bootstrap bundle');
|
|
319
|
+
if (bundle.bundle_kind !== 'bootstrap_export') {
|
|
320
|
+
throw new Error(
|
|
321
|
+
`File at ${bundlePath} is not a bootstrap bundle (bundle_kind: ${bundle.bundle_kind ?? 'missing'}).`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
const documents = Array.isArray(bundle.documents) ? bundle.documents : [];
|
|
325
|
+
if (documents.length === 0) {
|
|
326
|
+
throw new Error(`Bootstrap bundle at ${bundlePath} has no documents.`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const projectFilePath = path.join(rootDir, 'project.yaml');
|
|
330
|
+
let existingProjectFile = false;
|
|
331
|
+
try {
|
|
332
|
+
await readFile(projectFilePath, 'utf8');
|
|
333
|
+
existingProjectFile = true;
|
|
334
|
+
} catch {
|
|
335
|
+
existingProjectFile = false;
|
|
336
|
+
}
|
|
337
|
+
if (existingProjectFile) {
|
|
338
|
+
throw new Error(
|
|
339
|
+
`${rootDir} already contains project.yaml. Bootstrap only materializes fresh roots — pick an empty --root or move the existing one aside.`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Fail closed BEFORE writing anything: verify every document's hash and
|
|
344
|
+
// path, and require the bundle to carry project.yaml.
|
|
345
|
+
let hasProjectFile = false;
|
|
346
|
+
const validated = documents.map((document) => {
|
|
347
|
+
const relativePath = normalizeCanonicalPath(document.path);
|
|
348
|
+
if (document.op !== 'write' || typeof document.raw_text !== 'string') {
|
|
349
|
+
throw new Error(`Unsupported bootstrap operation for ${relativePath}.`);
|
|
350
|
+
}
|
|
351
|
+
const hash = sha256Text(document.raw_text);
|
|
352
|
+
if (hash !== document.after_content_hash) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
`Bootstrap bundle content hash does not match raw_text for ${relativePath}. The bundle is corrupt — download it again.`,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
if (relativePath === 'project.yaml') hasProjectFile = true;
|
|
358
|
+
return { relativePath, rawText: document.raw_text };
|
|
359
|
+
});
|
|
360
|
+
if (!hasProjectFile) {
|
|
361
|
+
throw new Error('Bootstrap bundle does not include project.yaml.');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Content is written verbatim (no newline normalization) so the local root
|
|
365
|
+
// byte-matches the hosted head the bundle's cursor points at.
|
|
366
|
+
const written = [];
|
|
367
|
+
for (const document of validated) {
|
|
368
|
+
const targetPath = path.join(rootDir, document.relativePath);
|
|
369
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
370
|
+
await writeFile(targetPath, document.rawText, 'utf8');
|
|
371
|
+
written.push(document.relativePath);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const warnings = [];
|
|
375
|
+
let manifestPath = null;
|
|
376
|
+
let cursorSeeded = false;
|
|
377
|
+
let projectMode = null;
|
|
378
|
+
try {
|
|
379
|
+
const project = parseSimpleYaml(await readFile(projectFilePath, 'utf8'), {
|
|
380
|
+
sourceName: projectFilePath,
|
|
381
|
+
});
|
|
382
|
+
projectMode = typeof project.mode === 'string' ? project.mode : null;
|
|
383
|
+
|
|
384
|
+
let sync;
|
|
385
|
+
const serverHead = bundle.server_head ?? null;
|
|
386
|
+
if (projectMode === 'local-primary' && serverHead?.remote_revision_id) {
|
|
387
|
+
const binding = resolveSyncBinding(project, options.syncTarget ?? null);
|
|
388
|
+
if (binding) {
|
|
389
|
+
const manifestResult = await writeStateManifest(rootDir);
|
|
390
|
+
// Fail closed on cursor seeding: the cursor claims "this root's
|
|
391
|
+
// content IS the server head" — only true when the materialized
|
|
392
|
+
// manifest hash matches the head hash captured in the bundle. A
|
|
393
|
+
// mismatched bundle still materializes (files are hash-verified
|
|
394
|
+
// individually) but must not claim the head as its baseline.
|
|
395
|
+
const localHash = manifestResult.manifest.canonical.manifest_hash;
|
|
396
|
+
if (serverHead.manifest_hash && localHash !== serverHead.manifest_hash) {
|
|
397
|
+
warnings.push(
|
|
398
|
+
`Sync cursor NOT seeded: the materialized root's manifest hash (${localHash}) does not match the bundle's server head (${serverHead.manifest_hash}). `
|
|
399
|
+
+ 'The files are intact, but run vibecompass sync-adopt (after reviewing divergence) before pushing.',
|
|
400
|
+
);
|
|
401
|
+
} else {
|
|
402
|
+
sync = buildSyncStateWithCursor(manifestResult.manifest.sync, binding, {
|
|
403
|
+
last_successful_remote_revision: serverHead.remote_revision_id,
|
|
404
|
+
last_successful_local_root_revision:
|
|
405
|
+
manifestResult.manifest.canonical.local_root_revision,
|
|
406
|
+
last_successful_manifest_hash: localHash,
|
|
407
|
+
last_sync_direction: 'bootstrap',
|
|
408
|
+
last_sync_at: new Date().toISOString(),
|
|
409
|
+
pending_previews: [],
|
|
410
|
+
});
|
|
411
|
+
cursorSeeded = true;
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
warnings.push(
|
|
415
|
+
'No sync binding found in the bundled project.yaml; run connect-hosted before pushing.',
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const manifestResult = await writeStateManifest(rootDir, sync ? { sync } : undefined);
|
|
420
|
+
manifestPath = manifestResult.manifestPath;
|
|
421
|
+
} catch (error) {
|
|
422
|
+
warnings.push(
|
|
423
|
+
`Documents were written, but the state manifest could not be generated: ${error.message}`,
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (projectMode === 'hosted-only') {
|
|
428
|
+
warnings.push(
|
|
429
|
+
'This is a hosted-only project: the root is fully usable offline, but hosted sync rejects local push until the project is demoted. '
|
|
430
|
+
+ 'To make this root canonical: run vibecompass connect-hosted (with the API URL, project id, and token env var from the hosted Setup page) to add the sync binding, then vibecompass demote-hosted.',
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
status: 'bootstrapped',
|
|
436
|
+
rootDir,
|
|
437
|
+
manifestPath,
|
|
438
|
+
documentCount: written.length,
|
|
439
|
+
mode: projectMode,
|
|
440
|
+
cursorSeeded,
|
|
441
|
+
warnings,
|
|
442
|
+
message: `Materialized ${written.length} document${written.length === 1 ? '' : 's'} into ${rootDir}${cursorSeeded ? ' and seeded the sync cursor from the bundle head' : ''}.`,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
203
446
|
async function loadSyncContext(options, environment) {
|
|
204
447
|
const cwd = environment.cwd ? path.resolve(environment.cwd) : process.cwd();
|
|
205
448
|
const rootDir = path.resolve(cwd, options.rootDir ?? '.compass');
|
|
@@ -210,7 +453,11 @@ async function loadSyncContext(options, environment) {
|
|
|
210
453
|
});
|
|
211
454
|
|
|
212
455
|
if (project.mode !== 'local-primary') {
|
|
213
|
-
throw new Error(
|
|
456
|
+
throw new Error(
|
|
457
|
+
`Hosted sync commands require project.yaml mode: local-primary (this root says "${project.mode}"). `
|
|
458
|
+
+ 'Mode is recorded twice — locally in project.yaml and on the hosted project — and they must agree. '
|
|
459
|
+
+ 'Run vibecompass status to compare both records; use promote-hosted/demote-hosted to change modes on both sides together instead of hand-editing.',
|
|
460
|
+
);
|
|
214
461
|
}
|
|
215
462
|
|
|
216
463
|
const binding = resolveSyncBinding(project, options.syncTarget ?? null);
|
|
@@ -220,7 +467,11 @@ async function loadSyncContext(options, environment) {
|
|
|
220
467
|
|
|
221
468
|
const credential = normalizeOptionalString((environment.env ?? process.env)[binding.credentialEnvVar]);
|
|
222
469
|
if (!credential) {
|
|
223
|
-
throw new Error(
|
|
470
|
+
throw new Error(
|
|
471
|
+
`Hosted sync command requires ${binding.credentialEnvVar}. New terminals do not inherit one-off exports: `
|
|
472
|
+
+ `re-export it (export ${binding.credentialEnvVar}="<sync token>") or persist it in your shell profile (~/.zshenv or ~/.bashrc). `
|
|
473
|
+
+ 'Lost the token? Rotate it on the hosted dashboard under Setup -> Hosted sync, then export the new value.',
|
|
474
|
+
);
|
|
224
475
|
}
|
|
225
476
|
|
|
226
477
|
const fetchImpl = environment.runtime?.fetch ?? globalThis.fetch;
|
package/src/workflow.js
CHANGED
|
@@ -83,6 +83,9 @@ export function buildCloseSessionGuidance(workflowSettings, options = {}) {
|
|
|
83
83
|
|
|
84
84
|
guidance.push('Reviewer handback stays in the selected sessions/active/<lane-id>/wip.md and handoff.md before close-session runs.');
|
|
85
85
|
guidance.push('Close-session requires document-maintenance checkpoint statuses for architecture docs, decision log, and session handoff/scratch: updated, not-needed, or deferred.');
|
|
86
|
+
// D-292/D-293: unconditional — the authorship rule holds even when the
|
|
87
|
+
// refresh-architecture-docs workflow step is disabled.
|
|
88
|
+
guidance.push('Architecture docs keep current behavior plus the durable plan, unresolved next steps, and material rollout state — fold changes in place, never dated session sections; session notes keep work chronology and close-out next steps; decisions keep accepted choices and rationale; transient lane scratch drains into the session note at close (D-292, D-293).');
|
|
86
89
|
|
|
87
90
|
if (workflowSettings.closeSession.refreshArchitectureDocs) {
|
|
88
91
|
guidance.push('Refresh any relevant architecture docs before finalizing the session.');
|
|
@@ -122,7 +122,7 @@ The first pass is breadth-first: establish an honest project denominator, compac
|
|
|
122
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.
|
|
123
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
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.
|
|
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.
|
|
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. Write every doc as a current-state contract (D-292): updates rewrite the affected sections in place or create a new focused doc — never append dated session/audit sections. Keep the durable plan and rollout state in the doc; work/ship chronology belongs in session notes, and decision entries record accepted choices (D-293).
|
|
126
126
|
7. Write the accepted fenced blocks verbatim to \`${outputPath}\`.
|
|
127
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.
|
|
128
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.
|