@eventmodelers/cli 2.0.53 → 2.0.55

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/cli.js CHANGED
@@ -44,7 +44,7 @@ import { defaultAgentName, withInstanceSuffix } from './shared/build-kit/lib/age
44
44
  import { acquireAgentId } from './shared/build-kit/lib/agent-lock.js';
45
45
  import { readEvents, appendEvent, locationOf } from './shared/build-kit/lib/slice-events.js';
46
46
  import { findPromptEvent, replayRequest } from './shared/build-kit/lib/prompt-replay.js';
47
- import { prunableEvents, pruneLog, rememberPruneAnswer, shouldOfferPrune } from './shared/build-kit/lib/event-prune.js';
47
+ import { clearLog, prunableEvents, pruneLog, rememberPruneAnswer, shouldOfferPrune } from './shared/build-kit/lib/event-prune.js';
48
48
  import { fold } from './shared/build-kit/lib/slice-state.js';
49
49
  import { filterEvents, baselineIds, formatEvent, formatPayload, sliceTitles, summarize } from './shared/build-kit/lib/events-view.js';
50
50
  import { stableGlobalAgentId } from './lib/global-agent-id.js';
@@ -4525,7 +4525,7 @@ credentialFlags(program
4525
4525
  // path with no stream-json parsing to gate. RALPH_LOCAL is read by ralph.js's startRalph to force
4526
4526
  // the local-only branch even when .eventmodelers/config.json has valid credentials.
4527
4527
  // RALPH_AGENT_ID/RALPH_AGENT_NAME (--id/--name) are read there too.
4528
- execSync(cmd, { cwd: kitDir, stdio: 'inherit', env: { ...process.env, RALPH_VERBOSE: opts.verbose ? '1' : '', RALPH_SYNC: opts.sync ? '1' : '', RALPH_LOCAL: opts.local ? '1' : '', RALPH_HANDOVER: opts.handover ? '1' : '', RALPH_STATUS_TRIGGER: opts.statusTrigger ? String(opts.statusTrigger).trim() : '', RALPH_FEATURE: opts.feature ? String(opts.feature).trim() : '', RALPH_AGENT_ID: identity.agentId ?? '', RALPH_AGENT_NAME: identity.agentName ?? '', ...(typeof localAiTarget === 'string' ? { LOCAL_AI_TARGET: localAiTarget } : {}), ...(typeof opts.exec === 'string' ? { RALPH_EXEC_CMD: opts.exec } : {}), ...(acpAgent ? { RALPH_ACP_AGENT: opts.acpAgent ?? '', RALPH_ACP_CMD: opts.acpCmd ?? '' } : {}) } });
4528
+ execSync(cmd, { cwd: kitDir, stdio: 'inherit', env: { ...process.env, RALPH_VERBOSE: opts.verbose ? '1' : '', RALPH_SYNC: opts.sync ? '1' : '', RALPH_LOCAL: opts.local ? '1' : '', RALPH_HANDOVER: opts.handover ? '1' : '', RALPH_STATUS_TRIGGER: opts.statusTrigger ? String(opts.statusTrigger).trim() : '', RALPH_FEATURE: opts.feature ? String(opts.feature).trim() : '', EVENTMODELERS_KIT_DIR: kitDir, RALPH_AGENT_ID: identity.agentId ?? '', RALPH_AGENT_NAME: identity.agentName ?? '', ...(typeof localAiTarget === 'string' ? { LOCAL_AI_TARGET: localAiTarget } : {}), ...(typeof opts.exec === 'string' ? { RALPH_EXEC_CMD: opts.exec } : {}), ...(acpAgent ? { RALPH_ACP_AGENT: opts.acpAgent ?? '', RALPH_ACP_CMD: opts.acpCmd ?? '' } : {}) } });
4529
4529
  } catch (err) {
4530
4530
  process.exit(err.status || 1);
4531
4531
  }
@@ -5296,10 +5296,27 @@ program
5296
5296
  console.log(`✅ Pushed status change to the board (node ${slice.id}).`);
5297
5297
  });
5298
5298
 
5299
+ // The kit directory the run loop pinned for its agent (EVENTMODELERS_KIT_DIR, set by `eventmodelers run`), or null.
5300
+ // An agent's shell cwd drifts; the loop's does not. A stale value (directory gone) is ignored rather than trusted.
5301
+ function pinnedKitDir() {
5302
+ const dir = process.env.EVENTMODELERS_KIT_DIR;
5303
+ return dir && existsSync(dir) ? resolve(dir) : null;
5304
+ }
5305
+
5306
+ // Where an agent-side command resolves the project from: the pinned kit's project, else the shell's cwd.
5307
+ function agentCwd() {
5308
+ const pinned = pinnedKitDir();
5309
+ return pinned ? dirname(pinned) : process.cwd();
5310
+ }
5311
+
5299
5312
  // The kit a command that records events works against: a build kit if one is installed, else the modeling
5300
5313
  // kit. The build kit wins when both are installed — the modeling agent never runs these commands itself
5301
5314
  // (its Bash is an allowlist); it states its learnings in its reply and `runModeling` records them.
5302
5315
  function requireBuildKitDir(cwd) {
5316
+ // The run loop exports the kit it runs in. Trust it over the shell's cwd: the build agent may `cd` elsewhere in the
5317
+ // project (an app with a root kit and a web/ kit) and its commands must still reach the kit that holds the claim.
5318
+ const pinned = pinnedKitDir();
5319
+ if (pinned) return pinned;
5303
5320
  const installed = findAllInstalledKitDirs(cwd);
5304
5321
  const kitDir = installed.find((d) => !d.endsWith(MODELING_KIT.kitDirName)) ?? installed.find((d) => d.endsWith(MODELING_KIT.kitDirName));
5305
5322
  if (!kitDir) {
@@ -5325,7 +5342,7 @@ program
5325
5342
  .description("For a build agent: record the outcome of the slice the run loop is building (Done, Waiting, Blocked, ...). The loop owns the slice's status on the board and pushes it when the build ends — never edit index.json or call update-slice-status for a loop build.")
5326
5343
  .option('--slice <idOrTitle>', 'The slice, when the loop has claimed more than one (default: the one it is building)')
5327
5344
  .action((status, opts, command) => {
5328
- const cwd = process.cwd();
5345
+ const cwd = agentCwd();
5329
5346
  // `Handover` is the old name of `Waiting`; older skills and prompts still say it
5330
5347
  const wanted = status.toLowerCase() === 'handover' ? 'waiting' : status.toLowerCase();
5331
5348
  const canonical = SLICE_STATUSES.find((s) => s.value.toLowerCase() === wanted)?.value;
@@ -5359,7 +5376,7 @@ program
5359
5376
  console.error('❌ Action must be `check` or `record`.');
5360
5377
  process.exit(1);
5361
5378
  }
5362
- const cwd = process.cwd();
5379
+ const cwd = agentCwd();
5363
5380
  const kitDir = requireBuildKitDir(cwd);
5364
5381
  let wanted = opts.slice;
5365
5382
  if (!wanted) {
@@ -5367,7 +5384,7 @@ program
5367
5384
  if (claimed.length !== 1) {
5368
5385
  if (!claimed.length) {
5369
5386
  // Where it looked: an agent running from another kit's directory finds no claim in that kit's log.
5370
- try { appendEvent(kitDir, { type: 'claim-not-found', command: 'build-baseline', claimed: 0, ...locationOf(kitDir, cwd) }, agentEventMeta(cwd, kitDir, command)); } catch { /* best effort */ }
5387
+ try { appendEvent(kitDir, { type: 'claim-not-found', command: 'build-baseline', claimed: 0, ...locationOf(kitDir, process.cwd()) }, agentEventMeta(cwd, kitDir, command)); } catch { /* best effort */ }
5371
5388
  }
5372
5389
  console.error(`❌ ${claimed.length ? `${claimed.length} slices are claimed` : 'No slice is claimed by the run loop'} — name the slice with --slice.`);
5373
5390
  process.exit(1);
@@ -5432,7 +5449,7 @@ program
5432
5449
  .option('--merge <ids>', 'Replace these learnings (comma-separated ids) by one with the given text — the curator\'s tool')
5433
5450
  .option('--reason <reason>', 'Why — recorded with a retraction or a merge')
5434
5451
  .action((text, opts, command) => {
5435
- const cwd = process.cwd();
5452
+ const cwd = agentCwd();
5436
5453
  const kitDir = requireBuildKitDir(cwd);
5437
5454
  const meta = agentEventMeta(cwd, kitDir, command);
5438
5455
  try {
@@ -5465,7 +5482,7 @@ program
5465
5482
  .option('--slice <idOrTitle>', 'The slice it is about (default: the one the run loop has claimed)')
5466
5483
  .option('--tag <tag>', 'Label the info (repeatable)', (tag, tags) => [...tags, tag], [])
5467
5484
  .action(async (text, opts, command) => {
5468
- const cwd = process.cwd();
5485
+ const cwd = agentCwd();
5469
5486
  const kitDir = requireBuildKitDir(cwd);
5470
5487
  const meta = agentEventMeta(cwd, kitDir, command);
5471
5488
  try {
@@ -5508,6 +5525,9 @@ program
5508
5525
  .option('--verbose', 'Print each event\'s payload under its line — for a tool call, the input it was sent (recorded by runs started with --verbose). Also shows the baseline events: the kit\'s first look at each slice (a fresh kit records every slice already on the board), which are hidden by default. `--type board-observed` and `--json` show them too.')
5509
5526
  .option('--state', 'Print the state the log folds to (slices, queue, open prompts, learnings) instead of the events')
5510
5527
  .option('--prune', 'Delete the events nothing reads any more — the journal of finished prompts — and stop. Slice state, learnings and open prompts are kept; the same prune a run offers at every 1000 events.')
5528
+ .option('--clear', "Delete the whole log, state included, so the kit starts over (it looks at the board again; attempt counts, blocks and failure history are gone). Lists what it would delete and stops unless --yes is given. Refuses while a run holds a claim, unless --force.")
5529
+ .option('--yes', 'With --clear: really delete')
5530
+ .option('--force', 'With --clear: delete even while a run holds a claim')
5511
5531
  .option('--follow', 'Keep running and print events as they are appended')
5512
5532
  .option('--watch', 'Same as --follow')
5513
5533
  .action(async (opts) => {
@@ -5521,6 +5541,19 @@ program
5521
5541
  const prototypeLog = join(cwd, '.prototype');
5522
5542
  const inPrototype = !opts.global && !opts.kit && !findInstalledKitDir(cwd) && existsSync(join(prototypeLog, 'events.log'));
5523
5543
  const kitDir = opts.global ? globalLog : opts.kit ? resolve(cwd, opts.kit) : inPrototype ? prototypeLog : requireBuildKitDir(cwd);
5544
+ if (opts.clear) {
5545
+ try {
5546
+ const where = relative(cwd, kitDir) || kitDir;
5547
+ const { removed } = clearLog(kitDir, { force: opts.force, dryRun: !opts.yes });
5548
+ console.log(opts.yes
5549
+ ? `Cleared ${removed} events from ${where}.`
5550
+ : `Would delete all ${removed} events from ${where} — state included. Nothing was deleted; run again with --yes to do it.`);
5551
+ } catch (err) {
5552
+ console.error(`❌ ${err.message}`);
5553
+ process.exit(1);
5554
+ }
5555
+ return;
5556
+ }
5524
5557
  if (opts.prune) {
5525
5558
  try {
5526
5559
  const { removed, kept } = pruneLog(kitDir);
@@ -5631,7 +5664,7 @@ program
5631
5664
  .description('Print the recorded learnings — what .build-kit/AGENTS.md says. With --ids, one line each with the id to retract it by.')
5632
5665
  .option('--ids', 'One line per learning: id, topic, text')
5633
5666
  .action((opts, command) => {
5634
- const cwd = process.cwd();
5667
+ const cwd = agentCwd();
5635
5668
  const kitDir = requireBuildKitDir(cwd);
5636
5669
  syncLearnings({ kitDir, meta: agentEventMeta(cwd, kitDir, command) });
5637
5670
  const all = listLearnings(kitDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "2.0.53",
3
+ "version": "2.0.55",
4
4
  "description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, OpenCQRS, UmaDB, Kurrent, or modeling-only)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -7,8 +7,9 @@
7
7
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'fs';
8
8
  import { isDeepStrictEqual } from 'util';
9
9
  import { join } from 'path';
10
- import { EVENTS_DIR, INDEX_FILE, readEvents } from './slice-events.js';
10
+ import { EVENTS_DIR, INDEX_FILE, SEQ_FILE, readEvents } from './slice-events.js';
11
11
  import { fold } from './slice-state.js';
12
+ import { isProcessAlive } from './slice-run.js';
12
13
 
13
14
  export const PRUNE_EVERY = 1000;
14
15
  const ASKED_FILE = '.prune-asked'; // inside events/, which readEvents never lists and the kit's git rules already cover
@@ -40,6 +41,24 @@ export function pruneLog(kitDir) {
40
41
  return { removed: doomed.size, kept: rest.length };
41
42
  }
42
43
 
44
+ // Deletes the whole log — every event, state included — so the kit starts over as if freshly installed: it looks at
45
+ // the board again and every slice's attempt count, block and failure history is gone. Refuses while a live process
46
+ // holds a claim (its build would end without a trace) unless `force`. Returns { removed, liveClaims }; with
47
+ // `dryRun` nothing is deleted.
48
+ export function clearLog(kitDir, { force = false, dryRun = false } = {}) {
49
+ const events = readEvents(kitDir);
50
+ const liveClaims = Object.entries(fold(events).slices)
51
+ .filter(([, s]) => s.claim && isProcessAlive(s.claim.pid))
52
+ .map(([id, s]) => s.claim.title ?? id);
53
+ if (liveClaims.length && !force) throw new Error(`a run is building ${liveClaims.join(', ')} — stop it first, or pass --force`);
54
+ if (dryRun) return { removed: events.length, liveClaims };
55
+ const dir = join(kitDir, EVENTS_DIR);
56
+ rmSync(dir, { recursive: true, force: true });
57
+ rmSync(join(kitDir, INDEX_FILE), { force: true });
58
+ rmSync(join(kitDir, SEQ_FILE), { force: true });
59
+ return { removed: events.length, liveClaims };
60
+ }
61
+
43
62
  // Asked when the log has reached a multiple of PRUNE_EVERY it has not been asked at yet — 1000, then 2000, … — so a
44
63
  // "no" is not repeated on every start. `remember` records the size the answer was given at.
45
64
  const askedPath = (kitDir) => join(kitDir, EVENTS_DIR, ASKED_FILE);