@vibecompass/vibecompass 0.10.1 → 0.10.3

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/session.js CHANGED
@@ -4,6 +4,7 @@ import { scanProjectMemory } from './project-memory.js';
4
4
  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
+ import { START_MARKER } from './generators/agent-files/markers.js';
7
8
 
8
9
  const CURRENT_SESSION_REQUIRED_FIELDS = ['Date:', 'Working on:', 'Last thing completed:', 'Blockers:', 'Next session should:'];
9
10
  const WIP_HEADER_PATTERN = /^# WIP — (\d{4}-\d{2}-\d{2}) \(session (\d+)\)$/m;
@@ -51,13 +52,13 @@ export async function startProjectSession(options) {
51
52
  const highestDecisionId = await readHighestDecisionId(normalized.rootDir);
52
53
 
53
54
  const nextSessionNumber = await getNextSessionNumber(normalized.sessionsDir, sessionDate);
54
- const currentSession = parseCurrentSessionBlock(claude.content);
55
+ const currentSession = parseCurrentSessionBlock(claude.content, { optional: true });
55
56
  const updatedClaude = replaceCurrentSessionBlock(claude.content, {
56
57
  date: `${sessionDate} (session ${nextSessionNumber}, lane ${sessionId})`,
57
58
  workingOn: `${workingOn} [${sessionId}]`,
58
59
  lastThingCompleted:
59
60
  normalizeOptionalString(options?.lastThingCompleted) ??
60
- currentSession.lastThingCompleted ??
61
+ currentSession?.lastThingCompleted ??
61
62
  'Project memory bootstrap completed.',
62
63
  blockers: normalizeOptionalString(options?.blockers) ?? 'No blocker yet.',
63
64
  nextSessionShould:
@@ -170,11 +171,11 @@ export async function closeProjectSession(options) {
170
171
 
171
172
  const wipContent = await readFile(lanePaths.wipFilePath, 'utf8');
172
173
  const activeSession = parseActiveSession(wipContent, lanePaths.wipFilePath);
173
- const currentSession = parseCurrentSessionBlock(claude.content);
174
+ const currentSession = parseCurrentSessionBlock(claude.content, { optional: true });
174
175
  const workedOn =
175
176
  normalizeOptionalString(options?.workedOn) ??
176
177
  extractSectionBody(wipContent, 'Working on') ??
177
- currentSession.workingOn;
178
+ currentSession?.workingOn;
178
179
 
179
180
  if (!workedOn) {
180
181
  throw new Error('session close could not determine what the session worked on. Pass workedOn explicitly.');
@@ -414,7 +415,7 @@ async function readWorkflowSettings(projectFilePath) {
414
415
  async function readClaudeFile(claudePath) {
415
416
  if (!(await fileExists(claudePath))) {
416
417
  throw new Error(
417
- `No CLAUDE.md was found at ${claudePath}. Re-run "vibecompass init --with-claude" or create a compatible Current session block first.`,
418
+ `No CLAUDE.md was found at ${claudePath}. Re-run "vibecompass init --with-claude" or create the file before using session commands.`,
418
419
  );
419
420
  }
420
421
 
@@ -424,9 +425,13 @@ async function readClaudeFile(claudePath) {
424
425
  };
425
426
  }
426
427
 
427
- function parseCurrentSessionBlock(content) {
428
+ function parseCurrentSessionBlock(content, options = {}) {
428
429
  const sessionFence = findCurrentSessionFence(content);
429
430
  if (!sessionFence) {
431
+ if (options.optional) {
432
+ return null;
433
+ }
434
+
430
435
  throw new Error('CLAUDE.md is missing a recognizable "## Current session" fenced block.');
431
436
  }
432
437
 
@@ -453,19 +458,46 @@ function parseCurrentSessionBlock(content) {
453
458
 
454
459
  function replaceCurrentSessionBlock(content, fields) {
455
460
  const sessionFence = findCurrentSessionFence(content);
461
+ const blockBody = renderCurrentSessionBlockBody(fields);
462
+
456
463
  if (!sessionFence) {
457
- throw new Error('CLAUDE.md is missing a recognizable "## Current session" fenced block.');
464
+ return insertCurrentSessionBlock(content, blockBody);
458
465
  }
459
466
 
460
- const blockBody = [
467
+ return `${content.slice(0, sessionFence.bodyStart)}${blockBody}${content.slice(sessionFence.bodyEnd)}`;
468
+ }
469
+
470
+ function renderCurrentSessionBlockBody(fields) {
471
+ return [
461
472
  `Date: ${fields.date}`,
462
473
  `Working on: ${fields.workingOn}`,
463
474
  `Last thing completed: ${fields.lastThingCompleted}`,
464
475
  `Blockers: ${fields.blockers}`,
465
476
  `Next session should: ${fields.nextSessionShould}`,
466
477
  ].join('\n');
478
+ }
467
479
 
468
- return `${content.slice(0, sessionFence.bodyStart)}${blockBody}${content.slice(sessionFence.bodyEnd)}`;
480
+ function insertCurrentSessionBlock(content, blockBody) {
481
+ const currentSessionBlock = [
482
+ '## Current session',
483
+ '',
484
+ '**Update this block at the start and end of every session.**',
485
+ '',
486
+ '```',
487
+ blockBody,
488
+ '```',
489
+ '',
490
+ ].join('\n');
491
+ const markerIndex = content.indexOf(START_MARKER);
492
+ const normalizedContent = content.endsWith('\n') ? content : `${content}\n`;
493
+
494
+ if (markerIndex >= 0) {
495
+ const beforeMarker = content.slice(0, markerIndex).trimEnd();
496
+ const fromMarker = content.slice(markerIndex);
497
+ return `${beforeMarker}\n\n${currentSessionBlock}${fromMarker}`;
498
+ }
499
+
500
+ return `${normalizedContent}\n${currentSessionBlock}`;
469
501
  }
470
502
 
471
503
  function buildCurrentSessionFieldsForLane(lane, overrides = {}) {