@mutmutco/cli 2.25.1 → 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 +106 -30
- package/dist/saga.cjs +32 -6
- package/package.json +1 -1
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.
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
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) {
|
|
@@ -10587,6 +10613,32 @@ async function planPush(deps, slug, opts = {}) {
|
|
|
10587
10613
|
deps.log(`queued ${slug} for background push \u2014 expected, not a failure (\`mmi-cli northstar status\` to check; \`mmi-cli northstar sync\` or --wait for durable confirmation)`);
|
|
10588
10614
|
return true;
|
|
10589
10615
|
}
|
|
10616
|
+
function buildPutBody(project2, slug, content, opts = {}) {
|
|
10617
|
+
const body = { project: project2, slug, content };
|
|
10618
|
+
const frontmatterMeta = extractPlanMeta(content);
|
|
10619
|
+
if (Object.keys(frontmatterMeta).length) body.meta = frontmatterMeta;
|
|
10620
|
+
if (opts.force) body.force = true;
|
|
10621
|
+
else if (opts.baseEtag) body.baseEtag = opts.baseEtag;
|
|
10622
|
+
return body;
|
|
10623
|
+
}
|
|
10624
|
+
async function reconcilePushConflict(deps, project2, slug, content, baseHash) {
|
|
10625
|
+
const qs = new URLSearchParams({ project: project2, slug }).toString();
|
|
10626
|
+
const pull = await deps.fetch(`${deps.apiUrl}/plan/get?${qs}`, { method: "GET", headers: await deps.headers(), signal: AbortSignal.timeout(TIMEOUT_MS) }).catch(() => null);
|
|
10627
|
+
const doc = pull?.ok ? await pull.json() : null;
|
|
10628
|
+
if (!doc) return null;
|
|
10629
|
+
const remote = normalizeEol(doc.content ?? "");
|
|
10630
|
+
if (remote === content) return { etag: doc.etag };
|
|
10631
|
+
if (baseHash && hashContent(remote) === baseHash) {
|
|
10632
|
+
const res = await deps.fetch(`${deps.apiUrl}/plan/put`, {
|
|
10633
|
+
method: "POST",
|
|
10634
|
+
headers: await deps.headers({ "content-type": "application/json" }),
|
|
10635
|
+
body: JSON.stringify(buildPutBody(project2, slug, content, { baseEtag: doc.etag })),
|
|
10636
|
+
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
10637
|
+
}).catch(() => null);
|
|
10638
|
+
if (res?.ok) return await res.json();
|
|
10639
|
+
}
|
|
10640
|
+
return null;
|
|
10641
|
+
}
|
|
10590
10642
|
async function planPushNow(deps, slug, opts = {}) {
|
|
10591
10643
|
const raw = deps.readLocal(slug);
|
|
10592
10644
|
if (raw == null) {
|
|
@@ -10597,25 +10649,21 @@ async function planPushNow(deps, slug, opts = {}) {
|
|
|
10597
10649
|
const project2 = opts.project ?? await deps.project();
|
|
10598
10650
|
const meta = parseMeta(deps.readMetaRaw());
|
|
10599
10651
|
const entry = meta[metaKey(project2, slug)];
|
|
10600
|
-
const body = { project: project2, slug, content };
|
|
10601
|
-
const frontmatterMeta = extractPlanMeta(content);
|
|
10602
|
-
if (Object.keys(frontmatterMeta).length) body.meta = frontmatterMeta;
|
|
10603
|
-
if (opts.force) body.force = true;
|
|
10604
|
-
else if (entry?.etag) body.baseEtag = entry.etag;
|
|
10605
10652
|
const res = await deps.fetch(`${deps.apiUrl}/plan/put`, {
|
|
10606
10653
|
method: "POST",
|
|
10607
10654
|
headers: await deps.headers({ "content-type": "application/json" }),
|
|
10608
|
-
body: JSON.stringify(
|
|
10655
|
+
body: JSON.stringify(buildPutBody(project2, slug, content, { force: opts.force, baseEtag: entry?.etag })),
|
|
10609
10656
|
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
10610
10657
|
});
|
|
10611
|
-
if (res.ok) {
|
|
10612
|
-
const
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10658
|
+
if (res.ok || res.status === 409) {
|
|
10659
|
+
const adopted = res.ok ? await res.json() : await reconcilePushConflict(deps, project2, slug, content, entry?.hash);
|
|
10660
|
+
if (adopted) {
|
|
10661
|
+
meta[metaKey(project2, slug)] = { etag: adopted.etag, hash: hashContent(content), syncedAt: deps.now() };
|
|
10662
|
+
deps.writeMetaRaw(serializeMeta(meta));
|
|
10663
|
+
dropQueued(deps, project2, slug);
|
|
10664
|
+
deps.log(`pushed ${slug}`);
|
|
10665
|
+
return true;
|
|
10666
|
+
}
|
|
10619
10667
|
deps.err(staleHint(slug));
|
|
10620
10668
|
return false;
|
|
10621
10669
|
} else {
|
|
@@ -10766,11 +10814,7 @@ async function planSync(deps, opts = {}) {
|
|
|
10766
10814
|
const content = normalizeEol(raw);
|
|
10767
10815
|
const meta = parseMeta(deps.readMetaRaw());
|
|
10768
10816
|
const metaEntry = meta[metaKey(entry.project, entry.slug)];
|
|
10769
|
-
const body =
|
|
10770
|
-
const frontmatterMeta = extractPlanMeta(content);
|
|
10771
|
-
if (Object.keys(frontmatterMeta).length) body.meta = frontmatterMeta;
|
|
10772
|
-
if (entry.force) body.force = true;
|
|
10773
|
-
else if (metaEntry?.etag) body.baseEtag = metaEntry.etag;
|
|
10817
|
+
const body = buildPutBody(entry.project, entry.slug, content, { force: entry.force, baseEtag: metaEntry?.etag });
|
|
10774
10818
|
let res;
|
|
10775
10819
|
try {
|
|
10776
10820
|
res = await deps.fetch(`${deps.apiUrl}/plan/put`, {
|
|
@@ -10789,12 +10833,11 @@ async function planSync(deps, opts = {}) {
|
|
|
10789
10833
|
deps.writeMetaRaw(serializeMeta(meta));
|
|
10790
10834
|
if (!opts.quiet) deps.log(`pushed ${entry.slug}`);
|
|
10791
10835
|
} else if (res.status === 409) {
|
|
10792
|
-
const
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
if (doc && normalizeEol(doc.content ?? "") === content) {
|
|
10796
|
-
meta[metaKey(entry.project, entry.slug)] = { etag: doc.etag, hash: hashContent(content), syncedAt: deps.now() };
|
|
10836
|
+
const adopted = await reconcilePushConflict(deps, entry.project, entry.slug, content, metaEntry?.hash);
|
|
10837
|
+
if (adopted) {
|
|
10838
|
+
meta[metaKey(entry.project, entry.slug)] = { etag: adopted.etag, hash: hashContent(content), syncedAt: deps.now() };
|
|
10797
10839
|
deps.writeMetaRaw(serializeMeta(meta));
|
|
10840
|
+
if (!opts.quiet) deps.log(`pushed ${entry.slug}`);
|
|
10798
10841
|
} else {
|
|
10799
10842
|
kept.push({ ...entry, attempts: entry.attempts + 1, conflict: staleHint(entry.slug) });
|
|
10800
10843
|
}
|
|
@@ -10822,6 +10865,38 @@ async function planStatus(deps) {
|
|
|
10822
10865
|
deps.log(idx ? `plan index: ${idx.plans.length} plans, fetched ${idx.fetchedAt}` : "plan index: cold (first read will fetch)");
|
|
10823
10866
|
if (queue.some((e) => e.conflict || e.deadLettered)) process.exitCode = 1;
|
|
10824
10867
|
}
|
|
10868
|
+
async function planReconcile(deps) {
|
|
10869
|
+
const meta = parseMeta(deps.readMetaRaw());
|
|
10870
|
+
let refreshed = 0;
|
|
10871
|
+
let diverged = 0;
|
|
10872
|
+
let unreachable = 0;
|
|
10873
|
+
for (const [key, entry] of Object.entries(meta)) {
|
|
10874
|
+
const i = key.indexOf("/");
|
|
10875
|
+
if (i < 0) continue;
|
|
10876
|
+
const project2 = key.slice(0, i);
|
|
10877
|
+
const slug = key.slice(i + 1);
|
|
10878
|
+
const qs = new URLSearchParams({ project: project2, slug }).toString();
|
|
10879
|
+
const res = await deps.fetch(`${deps.apiUrl}/plan/get?${qs}`, { method: "GET", headers: await deps.headers(), signal: AbortSignal.timeout(TIMEOUT_MS) }).catch(() => null);
|
|
10880
|
+
if (!res || !res.ok) {
|
|
10881
|
+
unreachable++;
|
|
10882
|
+
continue;
|
|
10883
|
+
}
|
|
10884
|
+
const doc = await res.json();
|
|
10885
|
+
const remote = normalizeEol(doc.content ?? "");
|
|
10886
|
+
if (!entry.hash || hashContent(remote) !== entry.hash) {
|
|
10887
|
+
diverged++;
|
|
10888
|
+
continue;
|
|
10889
|
+
}
|
|
10890
|
+
if (doc.etag && doc.etag !== entry.etag) {
|
|
10891
|
+
meta[key] = { ...entry, etag: doc.etag, syncedAt: deps.now() };
|
|
10892
|
+
refreshed++;
|
|
10893
|
+
}
|
|
10894
|
+
}
|
|
10895
|
+
if (refreshed) deps.writeMetaRaw(serializeMeta(meta));
|
|
10896
|
+
deps.log(
|
|
10897
|
+
`reconcile: ${refreshed} etag(s) refreshed` + (diverged ? `, ${diverged} diverged (pull or push them)` : "") + (unreachable ? `, ${unreachable} unreachable` : "")
|
|
10898
|
+
);
|
|
10899
|
+
}
|
|
10825
10900
|
async function planGraduate(deps, slug, opts = {}) {
|
|
10826
10901
|
if (!opts.orgVisible) {
|
|
10827
10902
|
deps.err("refusing to mark an org-visible graduation without --org-visible");
|
|
@@ -11913,6 +11988,7 @@ function registerNorthStarCommands(cmd) {
|
|
|
11913
11988
|
}));
|
|
11914
11989
|
cmd.command("sync").description("drain queued background pushes and refresh the local plan index").option("--quiet", "silent (background worker)").action((o) => withPlan(o.quiet ?? false, (d) => planSync(d, o)));
|
|
11915
11990
|
cmd.command("status").description("show pending/conflicted background pushes and the plan-cache age").action(() => withPlan(false, (d) => planStatus(d)));
|
|
11991
|
+
cmd.command("reconcile").description("refresh stale local etags from the server without --force (recovers from an object-store re-stamp)").action(() => withPlan(false, (d) => planReconcile(d)));
|
|
11916
11992
|
cmd.command("open <slug>").description("pull if needed, then open plans/<slug>.md in $EDITOR").option("--project <name>", "override the project key").action(
|
|
11917
11993
|
(slug, o) => withPlan(false, async (d) => {
|
|
11918
11994
|
const ok = await planPull(d, slug, { project: o.project });
|
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.
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
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.
|
|
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",
|