@mutmutco/cli 2.26.0 → 2.27.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/dist/main.cjs CHANGED
@@ -4235,6 +4235,7 @@ function parseHookInput(stdin) {
4235
4235
  }
4236
4236
 
4237
4237
  // src/saga-health.ts
4238
+ var MEMORY_STALE_DAYS = 14;
4238
4239
  function buildHealth(i) {
4239
4240
  const problems = [];
4240
4241
  if (!i.sagaApiUrl) problems.push("Hub API URL not configured");
@@ -4245,6 +4246,10 @@ function buildHealth(i) {
4245
4246
  }
4246
4247
  if (i.reachable && i.authorized === false) problems.push("saga backend rejected authenticated state access");
4247
4248
  if (!i.key.sessionId || i.key.sessionId === "-") problems.push("unsafe session id");
4249
+ const warnings = [];
4250
+ if (i.memoryAgeDays !== void 0 && i.memoryAgeDays > MEMORY_STALE_DAYS) {
4251
+ warnings.push(`PROJECT MEMORY is ${Math.round(i.memoryAgeDays)}d stale \u2014 the saga-keeper may have stalled`);
4252
+ }
4248
4253
  const safeToWrite = problems.length === 0;
4249
4254
  return {
4250
4255
  ok: safeToWrite,
@@ -4258,14 +4263,19 @@ function buildHealth(i) {
4258
4263
  pendingNotes: i.pendingNotes ?? 0,
4259
4264
  key: i.key,
4260
4265
  source: i.source,
4261
- problems
4266
+ problems,
4267
+ warnings,
4268
+ memoryAgeDays: i.memoryAgeDays
4262
4269
  };
4263
4270
  }
4264
4271
  function healthBanner(report) {
4265
- if (report.ok) return null;
4266
- const summary = report.problems.slice(0, 2).join("; ") || "unknown saga health gap";
4267
- const suffix = report.problems.length > 2 ? ` (+${report.problems.length - 2} more)` : "";
4268
- return `saga health: CHECK - ${summary}${suffix}`;
4272
+ if (report.problems.length) {
4273
+ const summary = report.problems.slice(0, 2).join("; ") || "unknown saga health gap";
4274
+ const suffix = report.problems.length > 2 ? ` (+${report.problems.length - 2} more)` : "";
4275
+ return `saga health: CHECK - ${summary}${suffix}`;
4276
+ }
4277
+ if (report.warnings.length) return `saga health: NOTE - ${report.warnings.join("; ")}`;
4278
+ return null;
4269
4279
  }
4270
4280
  function resumeCue() {
4271
4281
  return '> STATUS/RESUME CUE \u2014 For any status, resume, or "where do I stand" report: read THIS saga HEAD first (`mmi-cli saga show`), then reconcile its NEXT / LAST 5 / DECISIONS against the live board + git/gh before reporting. Do not rebuild the picture from board/issues/memory while skipping the HEAD. PRECEDENCE: the HEAD is prior-session belief and MAY BE SUPERSEDED \u2014 the current live user/master instruction WINS over any conflicting HEAD anchor, NEXT, or checklist; follow the live instruction and treat the stale HEAD item as superseded.';
@@ -4384,6 +4394,19 @@ async function probeSagaAccess(url, key) {
4384
4394
  return false;
4385
4395
  }
4386
4396
  }
4397
+ async function fetchMemoryAge(url, project2) {
4398
+ try {
4399
+ const qs = new URLSearchParams({ project: project2 }).toString();
4400
+ const res = await fetch(`${url}/saga/memory-age?${qs}`, { headers: await hubHeaders(), signal: AbortSignal.timeout(8e3) });
4401
+ if (!res.ok) return void 0;
4402
+ const body = await res.json();
4403
+ if (!body.updatedAt) return void 0;
4404
+ const ms = Date.now() - Date.parse(body.updatedAt);
4405
+ return Number.isFinite(ms) && ms >= 0 ? ms / 864e5 : void 0;
4406
+ } catch {
4407
+ return void 0;
4408
+ }
4409
+ }
4387
4410
  async function runSagaHealth(o, io = consoleIo) {
4388
4411
  const cfg = await loadConfig();
4389
4412
  const session = resolveSessionId();
@@ -4394,6 +4417,7 @@ async function runSagaHealth(o, io = consoleIo) {
4394
4417
  cfg.sagaApiUrl ? probeBackend(cfg.sagaApiUrl) : Promise.resolve({ reachable: false })
4395
4418
  ]);
4396
4419
  const authorized = cfg.sagaApiUrl && liveness.reachable ? await probeSagaAccess(cfg.sagaApiUrl, key) : void 0;
4420
+ const memoryAgeDays = cfg.sagaApiUrl && liveness.reachable ? await fetchMemoryAge(cfg.sagaApiUrl, key.project) : void 0;
4397
4421
  const report = buildHealth({
4398
4422
  key,
4399
4423
  source,
@@ -4403,7 +4427,8 @@ async function runSagaHealth(o, io = consoleIo) {
4403
4427
  livenessMessage: liveness.message,
4404
4428
  authorized,
4405
4429
  sagaApiUrl: cfg.sagaApiUrl,
4406
- pendingNotes: readPending().length
4430
+ pendingNotes: readPending().length,
4431
+ memoryAgeDays
4407
4432
  });
4408
4433
  if (o.json) return io.log(JSON.stringify(report));
4409
4434
  if (o.banner) {
@@ -4414,6 +4439,7 @@ async function runSagaHealth(o, io = consoleIo) {
4414
4439
  if (o.quiet) return;
4415
4440
  io.log(`saga health: ${report.ok ? "OK" : "NOT OK"}`);
4416
4441
  if (report.problems.length) io.log(report.problems.map((p) => ` - ${p}`).join("\n"));
4442
+ if (report.warnings.length) io.log(report.warnings.map((w) => ` - ${w}`).join("\n"));
4417
4443
  if (report.pendingNotes > 0) io.log(` - ${report.pendingNotes} note(s) queued locally \u2014 \`mmi-cli saga flush\` to roll forward`);
4418
4444
  }
4419
4445
  function registerSagaCommands(program3) {
package/dist/saga.cjs CHANGED
@@ -3565,6 +3565,7 @@ async function runHeadEngine(prompt, timeoutMs = HEAD_ENGINE_TIMEOUT_MS) {
3565
3565
  }
3566
3566
 
3567
3567
  // src/saga-health.ts
3568
+ var MEMORY_STALE_DAYS = 14;
3568
3569
  function buildHealth(i) {
3569
3570
  const problems = [];
3570
3571
  if (!i.sagaApiUrl) problems.push("Hub API URL not configured");
@@ -3575,6 +3576,10 @@ function buildHealth(i) {
3575
3576
  }
3576
3577
  if (i.reachable && i.authorized === false) problems.push("saga backend rejected authenticated state access");
3577
3578
  if (!i.key.sessionId || i.key.sessionId === "-") problems.push("unsafe session id");
3579
+ const warnings = [];
3580
+ if (i.memoryAgeDays !== void 0 && i.memoryAgeDays > MEMORY_STALE_DAYS) {
3581
+ warnings.push(`PROJECT MEMORY is ${Math.round(i.memoryAgeDays)}d stale \u2014 the saga-keeper may have stalled`);
3582
+ }
3578
3583
  const safeToWrite = problems.length === 0;
3579
3584
  return {
3580
3585
  ok: safeToWrite,
@@ -3588,14 +3593,19 @@ function buildHealth(i) {
3588
3593
  pendingNotes: i.pendingNotes ?? 0,
3589
3594
  key: i.key,
3590
3595
  source: i.source,
3591
- problems
3596
+ problems,
3597
+ warnings,
3598
+ memoryAgeDays: i.memoryAgeDays
3592
3599
  };
3593
3600
  }
3594
3601
  function healthBanner(report) {
3595
- if (report.ok) return null;
3596
- const summary = report.problems.slice(0, 2).join("; ") || "unknown saga health gap";
3597
- const suffix = report.problems.length > 2 ? ` (+${report.problems.length - 2} more)` : "";
3598
- return `saga health: CHECK - ${summary}${suffix}`;
3602
+ if (report.problems.length) {
3603
+ const summary = report.problems.slice(0, 2).join("; ") || "unknown saga health gap";
3604
+ const suffix = report.problems.length > 2 ? ` (+${report.problems.length - 2} more)` : "";
3605
+ return `saga health: CHECK - ${summary}${suffix}`;
3606
+ }
3607
+ if (report.warnings.length) return `saga health: NOTE - ${report.warnings.join("; ")}`;
3608
+ return null;
3599
3609
  }
3600
3610
  function resumeCue() {
3601
3611
  return '> STATUS/RESUME CUE \u2014 For any status, resume, or "where do I stand" report: read THIS saga HEAD first (`mmi-cli saga show`), then reconcile its NEXT / LAST 5 / DECISIONS against the live board + git/gh before reporting. Do not rebuild the picture from board/issues/memory while skipping the HEAD. PRECEDENCE: the HEAD is prior-session belief and MAY BE SUPERSEDED \u2014 the current live user/master instruction WINS over any conflicting HEAD anchor, NEXT, or checklist; follow the live instruction and treat the stale HEAD item as superseded.';
@@ -4205,6 +4215,19 @@ async function probeSagaAccess(url, key) {
4205
4215
  return false;
4206
4216
  }
4207
4217
  }
4218
+ async function fetchMemoryAge(url, project) {
4219
+ try {
4220
+ const qs = new URLSearchParams({ project }).toString();
4221
+ const res = await fetch(`${url}/saga/memory-age?${qs}`, { headers: await hubHeaders(), signal: AbortSignal.timeout(8e3) });
4222
+ if (!res.ok) return void 0;
4223
+ const body = await res.json();
4224
+ if (!body.updatedAt) return void 0;
4225
+ const ms = Date.now() - Date.parse(body.updatedAt);
4226
+ return Number.isFinite(ms) && ms >= 0 ? ms / 864e5 : void 0;
4227
+ } catch {
4228
+ return void 0;
4229
+ }
4230
+ }
4208
4231
  async function runSagaHealth(o, io = consoleIo) {
4209
4232
  const cfg = await loadConfig();
4210
4233
  const session = resolveSessionId();
@@ -4215,6 +4238,7 @@ async function runSagaHealth(o, io = consoleIo) {
4215
4238
  cfg.sagaApiUrl ? probeBackend(cfg.sagaApiUrl) : Promise.resolve({ reachable: false })
4216
4239
  ]);
4217
4240
  const authorized = cfg.sagaApiUrl && liveness.reachable ? await probeSagaAccess(cfg.sagaApiUrl, key) : void 0;
4241
+ const memoryAgeDays = cfg.sagaApiUrl && liveness.reachable ? await fetchMemoryAge(cfg.sagaApiUrl, key.project) : void 0;
4218
4242
  const report = buildHealth({
4219
4243
  key,
4220
4244
  source,
@@ -4224,7 +4248,8 @@ async function runSagaHealth(o, io = consoleIo) {
4224
4248
  livenessMessage: liveness.message,
4225
4249
  authorized,
4226
4250
  sagaApiUrl: cfg.sagaApiUrl,
4227
- pendingNotes: readPending().length
4251
+ pendingNotes: readPending().length,
4252
+ memoryAgeDays
4228
4253
  });
4229
4254
  if (o.json) return io.log(JSON.stringify(report));
4230
4255
  if (o.banner) {
@@ -4235,6 +4260,7 @@ async function runSagaHealth(o, io = consoleIo) {
4235
4260
  if (o.quiet) return;
4236
4261
  io.log(`saga health: ${report.ok ? "OK" : "NOT OK"}`);
4237
4262
  if (report.problems.length) io.log(report.problems.map((p) => ` - ${p}`).join("\n"));
4263
+ if (report.warnings.length) io.log(report.warnings.map((w) => ` - ${w}`).join("\n"));
4238
4264
  if (report.pendingNotes > 0) io.log(` - ${report.pendingNotes} note(s) queued locally \u2014 \`mmi-cli saga flush\` to roll forward`);
4239
4265
  }
4240
4266
  function registerSagaCommands(program2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "2.26.0",
3
+ "version": "2.27.0",
4
4
  "description": "MMI Future CLI — delivers the org rules (whole-file), plus saga and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",