@vibecompass/vibecompass 0.10.4 → 0.10.6

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/src/scaffold.js CHANGED
@@ -216,7 +216,7 @@ This workspace uses VibeCompass project memory rooted at \`${rootRelativePath}\`
216
216
  ## Canonical files
217
217
  - \`${rootRelativePath}/project.yaml\` — machine-oriented project metadata
218
218
  - \`${rootRelativePath}/architecture/\` — canonical architecture docs
219
- - \`${rootRelativePath}/decisions/\` — canonical decision log
219
+ - \`${rootRelativePath}/decisions/\` — append-only decision log for this project-memory root
220
220
  - \`${rootRelativePath}/sessions/\` — finalized session notes
221
221
 
222
222
  ## Derived and scratch files
@@ -233,7 +233,7 @@ This workspace uses VibeCompass project memory rooted at \`${rootRelativePath}\`
233
233
  - The active lane scratch files live under \`${rootRelativePath}/sessions/active/<lane-id>/\`.
234
234
  - \`${rootRelativePath}/sessions/active/index.yaml\` is the authoritative current lane pointer; the tool-specific Current session block is a human-readable continuity summary, not the lane-selection source of truth.
235
235
  - Finalized sessions are append-only notes named \`${rootRelativePath}/sessions/YYYY-MM-DD-N-title.md\`; multiple sessions on the same day increment \`N\`.
236
- - Decisions remain append-only and independent from session notes. A session note may reference decisions, but the decision entry in \`${rootRelativePath}/decisions/\` is the durable source of truth.
236
+ - Decisions remain append-only and independent from session notes. A session note may reference decisions, but the decision entry in \`${rootRelativePath}/decisions/\` is the durable decision record for this root.
237
237
 
238
238
  ## Repos in scope
239
239
  ${repos}
@@ -248,7 +248,7 @@ ${repos}
248
248
  ${promptCommands}
249
249
 
250
250
  These are prompt commands for agent behavior, not \`vibecompass\` CLI subcommands.
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\`.
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>\` plus document-maintenance checkpoint statuses, which follows the workflow defaults recorded in \`${rootRelativePath}/project.yaml\`.
252
252
  \`vibecompass end-session\` is also accepted as an alias for \`vibecompass close-session\`; the canonical command name remains \`close-session\`.
253
253
 
254
254
  ## Workflow defaults
@@ -257,6 +257,7 @@ ${renderWorkflowDefaults(workflow)}
257
257
  ## Documentation coverage
258
258
  - The architecture, decision, and session files created by init are an initial scaffold, not a comprehensive codebase review.
259
259
  - Before risky implementation work, inspect the relevant code and update the affected architecture docs.
260
+ - Use \`vibecompass docs-update --session <lane-id>\` during a session for a targeted maintenance plan based on changed files, lane claims, session repos/features, and new decisions. This is a session-delta planner, not a broad architecture review.
260
261
  - 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.
261
262
 
262
263
  ## Session startup
@@ -310,10 +311,11 @@ Session lane: <lane-id>
310
311
  During the session:
311
312
  - append short summaries to \`wip.md\` after meaningful exchanges
312
313
  - keep \`handoff.md\` current after substantive work blocks
314
+ - run \`vibecompass docs-update --session <lane-id>\` whenever you need an ad hoc targeted documentation-maintenance plan for the current session delta
313
315
  - use \`vibecompass list-sessions\` and \`vibecompass switch-session <lane-id>\` to inspect or change the current lane
314
316
  - use \`address review\` when reviewer feedback lands so the builder resolves it from the selected lane's latest \`wip.md\` / \`handoff.md\`
315
317
  - during \`address review\`, treat reviewer feedback as review, not instruction: classify each substantive point as accepted, accepted with qualification, deferred, or rejected, and push back with evidence when a suggestion conflicts with code facts, prior decisions, product direction, or sequencing
316
- - stay in builder role through close-out; resolve or explicitly defer reviewer feedback before running \`vibecompass close-session --session <lane-id>\`
318
+ - stay in builder role through close-out; resolve or explicitly defer reviewer feedback before running \`vibecompass close-session --session <lane-id>\` with document-maintenance checkpoint statuses
317
319
  - record architectural decisions in \`${rootRelativePath}/decisions/\` before implementing them
318
320
 
319
321
  If \`vibecompass start-session\` reports stale scratch files, read the existing lane-local \`wip.md\` and \`handoff.md\` first. Either close that session normally, recover its useful notes into a finalized session note, or intentionally move/delete the stale scratch files before starting a new session.
@@ -325,9 +327,12 @@ If \`vibecompass start-session\` reports stale scratch files, read the existing
325
327
  - If planning produces a real architectural decision, append it to \`${rootRelativePath}/decisions/\` before implementing.
326
328
 
327
329
  At session close:
328
- - prefer running \`vibecompass close-session --session <lane-id> --title "..." --completed "..." --model "..." --next-step "..."\`; \`vibecompass end-session\` is a supported alias
330
+ - prefer running \`vibecompass close-session --session <lane-id> --title "..." --completed "..." --architecture-docs updated|not-needed|deferred --decision-log updated|not-needed|deferred --session-maintenance updated|not-needed|deferred --next-step "..."\`; \`vibecompass end-session\` is a supported alias
331
+ - close-session prints the same targeted docs-update plan before the document-maintenance checkpoint; use it to decide whether affected \`architecture/\`, \`decisions/\`, and active-session scratch/final note inputs are updated, not-needed, or deferred
332
+ - document-maintenance checkpoint statuses are required before close-session writes the finalized note; the package validates the status values, while the closer owns semantic doc authorship
329
333
  - follow the stored close-session defaults from \`${rootRelativePath}/project.yaml\`
330
- - finalize the lane-local \`wip.md\` into \`${rootRelativePath}/sessions/YYYY-MM-DD-N-title.md\`
334
+ - finalize the lane-local \`wip.md\` into \`${rootRelativePath}/sessions/YYYY-MM-DD-N-title.md\`; the permanent note distills decisions, completions, blockers, and next steps rather than preserving the full \`## Review log\`
335
+ - if a granular reviewer trail must remain durable, summarize it explicitly in the session note inputs or create a separate finalized session note before close-session deletes lane scratch files
331
336
  - delete the closed lane directory under \`${rootRelativePath}/sessions/active/<lane-id>/\`
332
337
  - refresh any affected architecture/decision docs
333
338
 
@@ -371,6 +376,10 @@ Initial project-memory scaffold for ${projectConfig.name}.
371
376
 
372
377
  ${projectConfig.description ? `${projectConfig.description}\n` : ''}This document is a starting map, not a comprehensive architecture review.
373
378
 
379
+ ## Review metadata
380
+ - Evidence: \`project.yaml\`, declared repo descriptors, and the files created by \`vibecompass init\`
381
+ - Blindspots: Runtime architecture, feature flows, persistence, integrations, deployment, observability, and test strategy are not reviewed by the init scaffold.
382
+
374
383
  ## Details
375
384
  - Mode: \`${projectConfig.mode}\`
376
385
  - Documentation coverage: initial scaffold
@@ -394,6 +403,11 @@ Not yet documented:
394
403
 
395
404
  Before changing any undocumented area, inspect the relevant code and update or add architecture docs.
396
405
 
406
+ ## Retrieval guidance
407
+ - Use this doc to understand the project-memory root shape, declared repositories, and the fact that the initial coverage is scaffold-only.
408
+ - Do not use this doc as evidence for runtime behavior, feature ownership, data flow, external integrations, or deployment behavior.
409
+ - Prefer more specific domain/feature/component docs once they exist, and run \`vibecompass docs-update --session <lane-id>\` during active work to find targeted maintenance needs.
410
+
397
411
  ## Next steps
398
412
  - Use the \`docs review\` prompt command for a comprehensive documentation review.
399
413
  - Replace this placeholder with domain/feature/component docs as the project map becomes evidence-backed.
@@ -487,10 +501,18 @@ architecture/<domain-slug>/<feature-slug>/<component>.md
487
501
 
488
502
  ## Recommended sections
489
503
  - \`## Description\`
504
+ - \`## Review metadata\`
490
505
  - \`## Details\`
506
+ - \`## Retrieval guidance\`
491
507
  - \`## Next steps\`
492
508
  - \`## Involved files\`
493
509
 
510
+ ## Shared quality bar
511
+ - Review metadata should record concrete evidence sources and blindspots.
512
+ - Retrieval guidance should say when to consult the doc, what it does not cover, and which related docs or decisions matter.
513
+ - Details should distinguish confirmed behavior from known gaps or open follow-up.
514
+ - Involved files should use concrete \`repo:path\` references that match the affected implementation surface.
515
+
494
516
  Each component doc is canonical. This README is only a convenience guide.
495
517
  `;
496
518
  }
@@ -600,6 +622,7 @@ function buildScaffoldAgentContext(options) {
600
622
  function renderWorkflowDefaults(workflow) {
601
623
  const lines = [
602
624
  `- reviewer handback: ${describeReviewerHandback(workflow)}`,
625
+ '- close-session: require document-maintenance checkpoint statuses for architecture docs, decision log, and session handoff/scratch',
603
626
  '- close-session: refresh any relevant architecture docs before finalizing the session',
604
627
  '- close-session: refresh any relevant decision files before finalizing the session',
605
628
  ];
package/src/session.js CHANGED
@@ -5,11 +5,34 @@ import { parseSimpleYaml } from './simple-yaml.js';
5
5
  import { buildCloseSessionGuidance, resolveWorkflowSettings } from './workflow.js';
6
6
  import { syncAgentInstructionFiles } from './generators/agent-files/index.js';
7
7
  import { START_MARKER } from './generators/agent-files/markers.js';
8
+ import { planDocsUpdate } from './docs-update.js';
9
+ // This lazy-only cycle is intentional: manifest.js reads lane state from this
10
+ // module, and session command handlers rewrite the derived manifest after
11
+ // lane mutations. Keep both sides free of top-level calls into the other.
12
+ import { writeStateManifest } from './manifest.js';
8
13
 
9
14
  const CURRENT_SESSION_REQUIRED_FIELDS = ['Date:', 'Working on:', 'Last thing completed:', 'Blockers:', 'Next session should:'];
10
15
  const WIP_HEADER_PATTERN = /^# WIP — (\d{4}-\d{2}-\d{2}) \(session (\d+)\)$/m;
11
16
  const SESSION_FILENAME_PATTERN = /^(\d{4}-\d{2}-\d{2})-(\d+)-([a-z0-9-]+)\.md$/i;
12
17
  const LANE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/;
18
+ const DOCUMENT_MAINTENANCE_STATUSES = new Set(['updated', 'not-needed', 'deferred']);
19
+ const DOCUMENT_MAINTENANCE_FIELDS = [
20
+ {
21
+ key: 'architectureDocs',
22
+ label: 'Architecture docs',
23
+ flag: '--architecture-docs',
24
+ },
25
+ {
26
+ key: 'decisionLog',
27
+ label: 'Decision log',
28
+ flag: '--decision-log',
29
+ },
30
+ {
31
+ key: 'sessionMaintenance',
32
+ label: 'Session handoff/scratch',
33
+ flag: '--session-maintenance',
34
+ },
35
+ ];
13
36
  const RESERVED_LANE_IDS = new Set([
14
37
  'active',
15
38
  'current',
@@ -114,6 +137,7 @@ export async function startProjectSession(options) {
114
137
  status: 'active',
115
138
  workingOn,
116
139
  }, { current: sessionId });
140
+ const manifestRefresh = await refreshStateManifestSafely(normalized.rootDir);
117
141
  const auditWarnings = await buildDocsReviewWarnings(normalized);
118
142
  const metadataWarnings = collectLaneMetadataWarnings(existingLanes);
119
143
  const agentFileSync = await syncAgentInstructionFilesSafely({
@@ -134,8 +158,10 @@ export async function startProjectSession(options) {
134
158
  warnings: [
135
159
  ...metadataWarnings,
136
160
  ...overlapWarnings,
161
+ ...manifestRefresh.warnings,
137
162
  ...auditWarnings,
138
163
  ],
164
+ manifest: manifestRefresh.manifest,
139
165
  agentFileSync,
140
166
  };
141
167
  }
@@ -162,6 +188,14 @@ export async function closeProjectSession(options) {
162
188
  const workflowSettings = await readWorkflowSettings(normalized.projectFilePath);
163
189
  const sessionId = await resolveExistingSessionId(normalized, options);
164
190
  const lanePaths = sessionId ? getLanePaths(normalized, sessionId) : normalized;
191
+ const documentMaintenance = normalizeDocumentMaintenanceCheckpoint(options?.documentMaintenance);
192
+ const docsUpdate = await planDocsUpdateSafely({
193
+ rootDir: normalized.rootDir,
194
+ cwd: normalized.cwd,
195
+ sessionId,
196
+ // Test injection seam; production callers should let close-session use the default planner.
197
+ planner: options?.docsUpdatePlanner,
198
+ });
165
199
 
166
200
  if (!(await fileExists(lanePaths.wipFilePath))) {
167
201
  throw new Error(
@@ -205,6 +239,7 @@ export async function closeProjectSession(options) {
205
239
  completed,
206
240
  decisions,
207
241
  models: models.length > 0 ? models : ['Not recorded.'],
242
+ documentMaintenance,
208
243
  blockers,
209
244
  nextSteps,
210
245
  }),
@@ -241,6 +276,7 @@ export async function closeProjectSession(options) {
241
276
  );
242
277
 
243
278
  await writeFile(normalized.claudePath, updatedClaude, 'utf8');
279
+ const manifestRefresh = await refreshStateManifestSafely(normalized.rootDir);
244
280
  const agentFileSync = await syncAgentInstructionFilesSafely({
245
281
  rootDir: normalized.rootDir,
246
282
  toolingRootDir: normalized.toolingRootDir,
@@ -256,7 +292,14 @@ export async function closeProjectSession(options) {
256
292
  sessionRelativePath,
257
293
  sessionDate: activeSession.sessionDate,
258
294
  sessionNumber: activeSession.sessionNumber,
295
+ documentMaintenance,
296
+ docsUpdatePlan: docsUpdate.plan,
259
297
  workflowGuidance: buildCloseSessionGuidance(workflowSettings),
298
+ warnings: [
299
+ ...docsUpdate.warnings,
300
+ ...manifestRefresh.warnings,
301
+ ],
302
+ manifest: manifestRefresh.manifest,
260
303
  agentFileSync,
261
304
  removedScratchFiles: [
262
305
  lanePaths.wipFilePath,
@@ -265,6 +308,46 @@ export async function closeProjectSession(options) {
265
308
  };
266
309
  }
267
310
 
311
+ function normalizeDocumentMaintenanceCheckpoint(value) {
312
+ const source = value && typeof value === 'object' && !Array.isArray(value)
313
+ ? value
314
+ : {};
315
+ const missing = [];
316
+ const invalid = [];
317
+ const checkpoint = {};
318
+
319
+ for (const field of DOCUMENT_MAINTENANCE_FIELDS) {
320
+ const normalized = normalizeOptionalString(source[field.key]);
321
+ if (!normalized) {
322
+ missing.push(`${field.flag} <updated|not-needed|deferred>`);
323
+ continue;
324
+ }
325
+
326
+ if (!DOCUMENT_MAINTENANCE_STATUSES.has(normalized)) {
327
+ invalid.push(`${field.flag}=${normalized}`);
328
+ continue;
329
+ }
330
+
331
+ checkpoint[field.key] = normalized;
332
+ }
333
+
334
+ if (missing.length > 0 || invalid.length > 0) {
335
+ const details = [
336
+ missing.length > 0
337
+ ? `Missing document-maintenance checkpoint status: ${missing.join(', ')}.`
338
+ : null,
339
+ invalid.length > 0
340
+ ? `Invalid document-maintenance checkpoint status: ${invalid.join(', ')}. Allowed values: updated, not-needed, deferred.`
341
+ : null,
342
+ 'The package validates the close-session checkpoint, but the closer remains responsible for semantic doc updates.',
343
+ ].filter(Boolean);
344
+
345
+ throw new Error(details.join(' '));
346
+ }
347
+
348
+ return checkpoint;
349
+ }
350
+
268
351
  export async function listProjectSessions(options = {}) {
269
352
  const normalized = normalizeSessionPaths(options);
270
353
  await ensureInitializedProjectMemory(normalized.rootDir, { allowMissingProjectFile: true });
@@ -300,6 +383,7 @@ export async function switchProjectSession(options = {}) {
300
383
  ),
301
384
  'utf8',
302
385
  );
386
+ const manifestRefresh = await refreshStateManifestSafely(normalized.rootDir);
303
387
  const agentFileSync = await syncAgentInstructionFilesSafely({
304
388
  rootDir: normalized.rootDir,
305
389
  toolingRootDir: normalized.toolingRootDir,
@@ -310,10 +394,50 @@ export async function switchProjectSession(options = {}) {
310
394
  claudePath: normalized.claudePath,
311
395
  current: sessionId,
312
396
  lanes,
397
+ warnings: manifestRefresh.warnings,
398
+ manifest: manifestRefresh.manifest,
313
399
  agentFileSync,
314
400
  };
315
401
  }
316
402
 
403
+ async function refreshStateManifestSafely(rootDir) {
404
+ try {
405
+ const manifest = await writeStateManifest(rootDir);
406
+ return {
407
+ manifest,
408
+ warnings: [],
409
+ };
410
+ } catch (error) {
411
+ return {
412
+ manifest: null,
413
+ warnings: [
414
+ `State manifest refresh skipped: ${error instanceof Error ? error.message : String(error)}`,
415
+ ],
416
+ };
417
+ }
418
+ }
419
+
420
+ async function planDocsUpdateSafely(options) {
421
+ try {
422
+ const planner = typeof options.planner === 'function' ? options.planner : planDocsUpdate;
423
+ return {
424
+ plan: await planner({
425
+ rootDir: options.rootDir,
426
+ cwd: options.cwd,
427
+ sessionId: options.sessionId,
428
+ }),
429
+ warnings: [],
430
+ };
431
+ } catch (error) {
432
+ return {
433
+ plan: null,
434
+ warnings: [
435
+ `Docs-update plan skipped: ${error instanceof Error ? error.message : String(error)}`,
436
+ ],
437
+ };
438
+ }
439
+ }
440
+
317
441
  async function syncAgentInstructionFilesSafely(options) {
318
442
  try {
319
443
  return await syncAgentInstructionFiles(options);
@@ -364,6 +488,7 @@ function normalizeSessionPaths(options) {
364
488
  : cwd;
365
489
 
366
490
  return {
491
+ cwd,
367
492
  rootDir,
368
493
  toolingRootDir,
369
494
  projectFilePath: path.resolve(rootDir, 'project.yaml'),
@@ -918,6 +1043,9 @@ ${renderBulletList(options.decisions, 'No new decisions were logged in this sess
918
1043
  ## Models used
919
1044
  ${renderBulletList(options.models)}
920
1045
 
1046
+ ## Document maintenance checkpoint
1047
+ ${renderDocumentMaintenanceCheckpoint(options.documentMaintenance)}
1048
+
921
1049
  ## Blockers / open questions
922
1050
  ${renderBulletList(options.blockers, 'No blocker remains.')}
923
1051
 
@@ -926,6 +1054,12 @@ ${renderOrderedList(options.nextSteps)}
926
1054
  `;
927
1055
  }
928
1056
 
1057
+ function renderDocumentMaintenanceCheckpoint(documentMaintenance) {
1058
+ return DOCUMENT_MAINTENANCE_FIELDS
1059
+ .map((field) => `- ${field.label}: ${documentMaintenance[field.key]}`)
1060
+ .join('\n');
1061
+ }
1062
+
929
1063
  function renderBulletList(items, fallback) {
930
1064
  if (items.length === 0) {
931
1065
  return `- ${fallback}`;
package/src/status.js CHANGED
@@ -5,6 +5,7 @@ import { sha256Text } from './hash.js';
5
5
  import { parseSimpleYaml } from './simple-yaml.js';
6
6
  import { listProjectSessions } from './session.js';
7
7
  import { syncAgentInstructionFiles } from './generators/agent-files/index.js';
8
+ import { scanProjectMemory } from './project-memory.js';
8
9
 
9
10
  export async function getProjectStatus(options = {}) {
10
11
  const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
@@ -18,11 +19,12 @@ export async function getProjectStatus(options = {}) {
18
19
  cwd,
19
20
  ...(options.packageVersion ? { packageVersion: options.packageVersion } : {}),
20
21
  });
21
- const [project, sessions, docsReview, agentFiles] = await Promise.all([
22
+ const [project, sessions, docsReview, agentFiles, projectMemory] = await Promise.all([
22
23
  readProjectSummary(compatibility.projectFilePath),
23
24
  readSessionsStatus(rootDir),
24
25
  readDocsReviewStatus(rootDir),
25
26
  readAgentFileStatus(rootDir, toolingRootDir),
27
+ readProjectMemoryStatus(rootDir),
26
28
  ]);
27
29
 
28
30
  return {
@@ -39,12 +41,14 @@ export async function getProjectStatus(options = {}) {
39
41
  sessions,
40
42
  docsReview,
41
43
  agentFiles,
44
+ projectMemory,
42
45
  recommendations: buildRecommendations({
43
46
  project,
44
47
  compatibility,
45
48
  sessions,
46
49
  docsReview,
47
50
  agentFiles,
51
+ projectMemory,
48
52
  }),
49
53
  };
50
54
  }
@@ -99,6 +103,9 @@ export function renderStatusText(status) {
99
103
  ? [`- Output drift: ${status.docsReview.outputDrift.reasons.join('; ')}`]
100
104
  : []),
101
105
  '',
106
+ 'Project memory:',
107
+ ...formatProjectMemoryStatus(status.projectMemory),
108
+ '',
102
109
  'Agent files:',
103
110
  ...formatAgentFileStatus(status.agentFiles),
104
111
  );
@@ -125,6 +132,7 @@ export function toStatusJson(status) {
125
132
  sessions: status.sessions,
126
133
  docsReview: status.docsReview,
127
134
  agentFiles: status.agentFiles,
135
+ projectMemory: status.projectMemory,
128
136
  recommendations: status.recommendations,
129
137
  };
130
138
  }
@@ -297,7 +305,48 @@ async function readAgentFileStatus(rootDir, toolingRootDir) {
297
305
  }
298
306
  }
299
307
 
300
- function buildRecommendations({ project, compatibility, sessions, docsReview, agentFiles }) {
308
+ async function readProjectMemoryStatus(rootDir) {
309
+ try {
310
+ const scan = await scanProjectMemory(rootDir);
311
+ const architectureDocs = scan.documents.filter((document) => document.kind === 'architecture');
312
+ const architectureWarnings = scan.warnings
313
+ .filter((warning) => warning.path?.startsWith('architecture/'))
314
+ .map((warning) => ({
315
+ path: warning.path,
316
+ code: warning.code,
317
+ message: warning.message,
318
+ }));
319
+
320
+ return {
321
+ status: scan.errors.length > 0 ? 'errors' : 'ok',
322
+ architectureDocCount: architectureDocs.length,
323
+ architectureWarningCount: architectureWarnings.length,
324
+ warningSample: architectureWarnings.slice(0, 10),
325
+ truncatedWarningCount: Math.max(0, architectureWarnings.length - 10),
326
+ errorCount: scan.errors.length,
327
+ errorSample: scan.errors.slice(0, 10).map((error) => ({
328
+ path: error.path,
329
+ code: error.code,
330
+ message: error.message,
331
+ })),
332
+ truncatedErrorCount: Math.max(0, scan.errors.length - 10),
333
+ };
334
+ } catch (error) {
335
+ return {
336
+ status: 'unavailable',
337
+ architectureDocCount: 0,
338
+ architectureWarningCount: 0,
339
+ warningSample: [],
340
+ truncatedWarningCount: 0,
341
+ errorCount: 0,
342
+ errorSample: [],
343
+ truncatedErrorCount: 0,
344
+ errorMessage: error instanceof Error ? error.message : 'Unable to scan project memory.',
345
+ };
346
+ }
347
+ }
348
+
349
+ function buildRecommendations({ project, compatibility, sessions, docsReview, agentFiles, projectMemory }) {
301
350
  const recommendations = [];
302
351
  if (project.status === 'unreadable') {
303
352
  recommendations.push('vibecompass init --guided');
@@ -320,6 +369,16 @@ function buildRecommendations({ project, compatibility, sessions, docsReview, ag
320
369
  recommendations.push('vibecompass sync-agents --dry-run');
321
370
  }
322
371
 
372
+ if (projectMemory.status === 'errors') {
373
+ recommendations.push('vibecompass status --json');
374
+ } else if (projectMemory.architectureWarningCount > 0) {
375
+ recommendations.push(
376
+ sessions.status === 'ok' && sessions.current
377
+ ? `vibecompass docs-update --session ${sessions.current}`
378
+ : 'vibecompass docs-review --guided',
379
+ );
380
+ }
381
+
323
382
  if (sessions.status === 'ok' && sessions.lanes.length === 0) {
324
383
  recommendations.push('vibecompass start-session --id LANE_ID --working-on "TASK"');
325
384
  } else if (sessions.status === 'ok' && sessions.lanes.length >= 2) {
@@ -334,6 +393,8 @@ function hasStatusDrift(status) {
334
393
  status.compatibility.warnings.length > 0 ||
335
394
  status.docsReview.status !== 'completed' ||
336
395
  Boolean(status.docsReview.outputDrift?.hasDrift) ||
396
+ status.projectMemory.status !== 'ok' ||
397
+ status.projectMemory.architectureWarningCount > 0 ||
337
398
  status.agentFiles.status !== 'ok' ||
338
399
  status.agentFiles.results.some((item) => item.changed || item.warning)
339
400
  );
@@ -390,6 +451,34 @@ function formatDocsReviewStatus(docsReview) {
390
451
  return `${docsReview.status}${docsReview.completedAt ? ` at ${docsReview.completedAt}` : ''}`;
391
452
  }
392
453
 
454
+ function formatProjectMemoryStatus(projectMemory) {
455
+ if (projectMemory.status === 'unavailable') {
456
+ return [`- unavailable (${projectMemory.errorMessage})`];
457
+ }
458
+
459
+ const lines = [
460
+ `- Architecture docs: ${projectMemory.architectureDocCount}`,
461
+ `- Architecture warnings: ${projectMemory.architectureWarningCount}`,
462
+ ];
463
+
464
+ for (const warning of projectMemory.warningSample) {
465
+ lines.push(`- ${warning.path}: ${warning.code} — ${warning.message}`);
466
+ }
467
+
468
+ if (projectMemory.truncatedWarningCount > 0) {
469
+ lines.push(`- ${projectMemory.truncatedWarningCount} more architecture warning(s) omitted; use --json for the full status sample.`);
470
+ }
471
+
472
+ if (projectMemory.errorCount > 0) {
473
+ lines.push(`- Scan errors: ${projectMemory.errorCount}`);
474
+ for (const error of projectMemory.errorSample) {
475
+ lines.push(`- ${error.path}: ${error.code} — ${error.message}`);
476
+ }
477
+ }
478
+
479
+ return lines;
480
+ }
481
+
393
482
  function formatAgentFileStatus(agentFiles) {
394
483
  if (agentFiles.status !== 'ok') {
395
484
  return [`- unavailable (${agentFiles.errorMessage})`];
package/src/workflow.js CHANGED
@@ -78,6 +78,7 @@ export function buildCloseSessionGuidance(workflowSettings) {
78
78
  const guidance = [];
79
79
 
80
80
  guidance.push('Reviewer handback stays in the selected sessions/active/<lane-id>/wip.md and handoff.md before close-session runs.');
81
+ guidance.push('Close-session requires document-maintenance checkpoint statuses for architecture docs, decision log, and session handoff/scratch: updated, not-needed, or deferred.');
81
82
 
82
83
  if (workflowSettings.closeSession.refreshArchitectureDocs) {
83
84
  guidance.push('Refresh any relevant architecture docs before finalizing the session.');
@@ -12,7 +12,8 @@ export const WORKFLOW_REGISTRY = [
12
12
  { trigger: 'planning mode', summary: 'optional prompt-level mode for scoping work before implementation' },
13
13
  { trigger: 'review handoff', summary: 'reviewer reads the selected lane handoff and appends findings' },
14
14
  { trigger: 'address review', summary: 'builder critically resolves reviewer feedback from the selected lane scratch files' },
15
- { 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' },
15
+ { trigger: 'docs update', summary: 'builder runs a targeted session-delta documentation maintenance plan with `vibecompass docs-update --session <lane-id>`' },
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' },
16
17
  ],
17
18
  },
18
19
  {