@mutmutco/cli 2.33.0 → 2.34.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 +645 -9
- package/dist/saga.cjs +420 -0
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -4450,6 +4450,309 @@ function formatCaptureFailure(status, message) {
|
|
|
4450
4450
|
return `saga: HTTP ${status}`;
|
|
4451
4451
|
}
|
|
4452
4452
|
|
|
4453
|
+
// src/saga-snapshot.ts
|
|
4454
|
+
var GS_OPEN = "gs-open:";
|
|
4455
|
+
var GS_RESOLVED = "gs-resolved:";
|
|
4456
|
+
var GS_CEILING = "gs-ceiling:";
|
|
4457
|
+
var BS_DONE = "bs-done:";
|
|
4458
|
+
var BS_PROGRESS = "bs-progress:";
|
|
4459
|
+
var BS_BLOCKED = "bs-blocked:";
|
|
4460
|
+
var BS_TIER = "bs-tier:";
|
|
4461
|
+
var BS_CEILING = "bs-ceiling:";
|
|
4462
|
+
var BS_HALT = "bs-halt:";
|
|
4463
|
+
var GRIND_PREFIX = /^grind\s+/i;
|
|
4464
|
+
var META_ACTION_SEP = " | ";
|
|
4465
|
+
var LEGACY_BLOCKER_OPEN = /^blocker:(.+)$/i;
|
|
4466
|
+
var LEGACY_BLOCKER_RESOLVED = /^resolved:(.+)$/i;
|
|
4467
|
+
var LEGACY_CEILING = /^ceiling:(.+)$/i;
|
|
4468
|
+
var LEGACY_BUILD_DONE = /^done:(.+)$/i;
|
|
4469
|
+
var LEGACY_BUILD_PROGRESS = /^progress:(.+)$/i;
|
|
4470
|
+
var LEGACY_BUILD_BLOCKED = /^blocked:([^|]+)\|([^|]+)(?:\|(.+))?$/i;
|
|
4471
|
+
var LEGACY_BUILD_TIER = /^tier:([^|]+)\|([^|]+)(?:\|(.+))?$/i;
|
|
4472
|
+
var LEGACY_BUILD_CEILING = /^ceiling:([^|]+)\|(.+)$/i;
|
|
4473
|
+
var LEGACY_BUILD_HALT = /^halt:(.+)$/i;
|
|
4474
|
+
function isGrindSnapshotItem(text) {
|
|
4475
|
+
return text.startsWith(GS_OPEN) || text.startsWith(GS_RESOLVED) || text.startsWith(GS_CEILING) || LEGACY_BLOCKER_OPEN.test(text) || LEGACY_BLOCKER_RESOLVED.test(text) || LEGACY_CEILING.test(text);
|
|
4476
|
+
}
|
|
4477
|
+
function isBuildSnapshotItem(text) {
|
|
4478
|
+
return text.startsWith(BS_DONE) || text.startsWith(BS_PROGRESS) || text.startsWith(BS_BLOCKED) || text.startsWith(BS_TIER) || text.startsWith(BS_CEILING) || text.startsWith(BS_HALT) || LEGACY_BUILD_DONE.test(text) || LEGACY_BUILD_PROGRESS.test(text) || LEGACY_BUILD_BLOCKED.test(text) || LEGACY_BUILD_TIER.test(text) || LEGACY_BUILD_CEILING.test(text) || LEGACY_BUILD_HALT.test(text);
|
|
4479
|
+
}
|
|
4480
|
+
function snapshotClearIndices(kind, head) {
|
|
4481
|
+
const pred = kind === "grind" ? isGrindSnapshotItem : isBuildSnapshotItem;
|
|
4482
|
+
return (head.queued ?? []).map((item, index) => ({ item, index })).filter(({ item }) => !item.done && pred(item.text)).map(({ index }) => index);
|
|
4483
|
+
}
|
|
4484
|
+
function parseGrindNext(next) {
|
|
4485
|
+
if (!next?.trim()) return {};
|
|
4486
|
+
const raw = next.replace(GRIND_PREFIX, "").trim();
|
|
4487
|
+
const pipe = raw.indexOf(META_ACTION_SEP);
|
|
4488
|
+
const meta = pipe >= 0 ? raw.slice(0, pipe).trim() : raw;
|
|
4489
|
+
const nextAction = pipe >= 0 ? raw.slice(pipe + META_ACTION_SEP.length).trim() : void 0;
|
|
4490
|
+
const out = { nextAction };
|
|
4491
|
+
for (const part of meta.split(/\s+/)) {
|
|
4492
|
+
const eq = part.indexOf("=");
|
|
4493
|
+
if (eq < 0) continue;
|
|
4494
|
+
const k = part.slice(0, eq);
|
|
4495
|
+
const v = part.slice(eq + 1);
|
|
4496
|
+
if (k === "class") out.class = v;
|
|
4497
|
+
else if (k === "routing") out.routing = v;
|
|
4498
|
+
else if (k === "ultra") out.ultra = v;
|
|
4499
|
+
else if (k === "phase") out.phase = v;
|
|
4500
|
+
else if (k === "verify") {
|
|
4501
|
+
const [r, c] = v.split("/");
|
|
4502
|
+
const rn = Number(r);
|
|
4503
|
+
const cn = Number(c);
|
|
4504
|
+
if (Number.isFinite(rn)) out.verifyRound = rn;
|
|
4505
|
+
if (Number.isFinite(cn)) out.verifyCap = cn;
|
|
4506
|
+
} else if (k === "resolved" && v) {
|
|
4507
|
+
out.resolvedBlockerIds = v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
return out;
|
|
4511
|
+
}
|
|
4512
|
+
function formatGrindNext(s) {
|
|
4513
|
+
const parts = ["grind"];
|
|
4514
|
+
if (s.class) parts.push(`class=${s.class}`);
|
|
4515
|
+
if (s.routing) parts.push(`routing=${s.routing}`);
|
|
4516
|
+
if (s.ultra) parts.push(`ultra=${s.ultra}`);
|
|
4517
|
+
if (s.phase) parts.push(`phase=${s.phase}`);
|
|
4518
|
+
if (Number.isFinite(s.verifyRound) && Number.isFinite(s.verifyCap)) parts.push(`verify=${s.verifyRound}/${s.verifyCap}`);
|
|
4519
|
+
if (s.resolvedBlockerIds.length) parts.push(`resolved=${s.resolvedBlockerIds.join(",")}`);
|
|
4520
|
+
const meta = parts.join(" ");
|
|
4521
|
+
return s.nextAction ? `${meta}${META_ACTION_SEP}${s.nextAction}` : meta;
|
|
4522
|
+
}
|
|
4523
|
+
function parseGrindQueueItem(item, open, resolved, ceiling) {
|
|
4524
|
+
if (item.text.startsWith(GS_OPEN) && !item.done) open.push(item.text.slice(GS_OPEN.length).trim());
|
|
4525
|
+
else if (item.text.startsWith(GS_RESOLVED) && !item.done) resolved.push(item.text.slice(GS_RESOLVED.length).trim());
|
|
4526
|
+
else if (item.text.startsWith(GS_CEILING) && !item.done) ceiling.value = item.text.slice(GS_CEILING.length).trim();
|
|
4527
|
+
else {
|
|
4528
|
+
const legacyOpen = item.text.match(LEGACY_BLOCKER_OPEN);
|
|
4529
|
+
if (legacyOpen && !item.done) open.push(legacyOpen[1].trim());
|
|
4530
|
+
const legacyResolved = item.text.match(LEGACY_BLOCKER_RESOLVED);
|
|
4531
|
+
if (legacyResolved && item.done) resolved.push(legacyResolved[1].trim());
|
|
4532
|
+
const legacyCeil = item.text.match(LEGACY_CEILING);
|
|
4533
|
+
if (legacyCeil && !item.done) ceiling.value = legacyCeil[1].trim();
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
function parseGrindSnapshot(head) {
|
|
4537
|
+
const fromNext = parseGrindNext(head.next);
|
|
4538
|
+
const openBlockerIds = [];
|
|
4539
|
+
const resolvedFromQueue = [];
|
|
4540
|
+
const ceiling = { value: void 0 };
|
|
4541
|
+
for (const item of head.queued ?? []) {
|
|
4542
|
+
parseGrindQueueItem(item, openBlockerIds, resolvedFromQueue, ceiling);
|
|
4543
|
+
}
|
|
4544
|
+
const resolvedBlockerIds = [.../* @__PURE__ */ new Set([...resolvedFromQueue, ...fromNext.resolvedBlockerIds ?? []])];
|
|
4545
|
+
const { resolvedBlockerIds: _drop, ...restNext } = fromNext;
|
|
4546
|
+
return {
|
|
4547
|
+
kind: "grind",
|
|
4548
|
+
criteria: head.anchor?.intent,
|
|
4549
|
+
openBlockerIds,
|
|
4550
|
+
resolvedBlockerIds,
|
|
4551
|
+
verificationCeiling: ceiling.value,
|
|
4552
|
+
...restNext
|
|
4553
|
+
};
|
|
4554
|
+
}
|
|
4555
|
+
function parseBuildSnapshot(head) {
|
|
4556
|
+
const doneLastTurn = [];
|
|
4557
|
+
const inProgress = [];
|
|
4558
|
+
const blocked = [];
|
|
4559
|
+
const tierLedger = [];
|
|
4560
|
+
const verificationCeiling = [];
|
|
4561
|
+
let haltReason;
|
|
4562
|
+
for (const item of head.queued ?? []) {
|
|
4563
|
+
if (item.text.startsWith(BS_DONE) && !item.done) {
|
|
4564
|
+
doneLastTurn.push(item.text.slice(BS_DONE.length).trim());
|
|
4565
|
+
continue;
|
|
4566
|
+
}
|
|
4567
|
+
if (item.text.startsWith(BS_PROGRESS) && !item.done) {
|
|
4568
|
+
inProgress.push(item.text.slice(BS_PROGRESS.length).trim());
|
|
4569
|
+
continue;
|
|
4570
|
+
}
|
|
4571
|
+
if (item.text.startsWith(BS_BLOCKED) && !item.done) {
|
|
4572
|
+
const body = item.text.slice(BS_BLOCKED.length);
|
|
4573
|
+
const [site, decision, issue2] = body.split("|");
|
|
4574
|
+
blocked.push({ site: site?.trim() ?? "", decision: decision?.trim() ?? "", issue: issue2?.trim() });
|
|
4575
|
+
continue;
|
|
4576
|
+
}
|
|
4577
|
+
if (item.text.startsWith(BS_TIER) && !item.done) {
|
|
4578
|
+
const body = item.text.slice(BS_TIER.length);
|
|
4579
|
+
const [site, tier2, signals] = body.split("|");
|
|
4580
|
+
tierLedger.push({ site: site?.trim() ?? "", tier: tier2?.trim() ?? "", signals: signals?.trim() });
|
|
4581
|
+
continue;
|
|
4582
|
+
}
|
|
4583
|
+
if (item.text.startsWith(BS_CEILING) && !item.done) {
|
|
4584
|
+
const body = item.text.slice(BS_CEILING.length);
|
|
4585
|
+
const [site, rung] = body.split("|");
|
|
4586
|
+
verificationCeiling.push({ site: site?.trim() ?? "", rung: rung?.trim() ?? "" });
|
|
4587
|
+
continue;
|
|
4588
|
+
}
|
|
4589
|
+
if (item.text.startsWith(BS_HALT) && !item.done) {
|
|
4590
|
+
haltReason = item.text.slice(BS_HALT.length).trim();
|
|
4591
|
+
continue;
|
|
4592
|
+
}
|
|
4593
|
+
if (item.done) {
|
|
4594
|
+
const d = item.text.match(LEGACY_BUILD_DONE);
|
|
4595
|
+
if (d) doneLastTurn.push(d[1].trim());
|
|
4596
|
+
continue;
|
|
4597
|
+
}
|
|
4598
|
+
const prog = item.text.match(LEGACY_BUILD_PROGRESS);
|
|
4599
|
+
if (prog) {
|
|
4600
|
+
inProgress.push(prog[1].trim());
|
|
4601
|
+
continue;
|
|
4602
|
+
}
|
|
4603
|
+
const blk = item.text.match(LEGACY_BUILD_BLOCKED);
|
|
4604
|
+
if (blk) {
|
|
4605
|
+
blocked.push({ site: blk[1].trim(), decision: blk[2].trim(), issue: blk[3]?.trim() });
|
|
4606
|
+
continue;
|
|
4607
|
+
}
|
|
4608
|
+
const tier = item.text.match(LEGACY_BUILD_TIER);
|
|
4609
|
+
if (tier) {
|
|
4610
|
+
tierLedger.push({ site: tier[1].trim(), tier: tier[2].trim(), signals: tier[3]?.trim() });
|
|
4611
|
+
continue;
|
|
4612
|
+
}
|
|
4613
|
+
const ceil = item.text.match(LEGACY_BUILD_CEILING);
|
|
4614
|
+
if (ceil) {
|
|
4615
|
+
verificationCeiling.push({ site: ceil[1].trim(), rung: ceil[2].trim() });
|
|
4616
|
+
continue;
|
|
4617
|
+
}
|
|
4618
|
+
const halt = item.text.match(LEGACY_BUILD_HALT);
|
|
4619
|
+
if (halt) haltReason = halt[1].trim();
|
|
4620
|
+
}
|
|
4621
|
+
const nextFrontier = [];
|
|
4622
|
+
if (head.next?.trim()) {
|
|
4623
|
+
for (const line of head.next.split(";").map((s) => s.trim()).filter(Boolean)) {
|
|
4624
|
+
nextFrontier.push(line);
|
|
4625
|
+
}
|
|
4626
|
+
}
|
|
4627
|
+
return {
|
|
4628
|
+
kind: "build",
|
|
4629
|
+
milestone: head.anchor?.intent,
|
|
4630
|
+
northStarSlug: head.anchor?.slug,
|
|
4631
|
+
doneLastTurn,
|
|
4632
|
+
inProgress,
|
|
4633
|
+
blocked,
|
|
4634
|
+
nextFrontier,
|
|
4635
|
+
tierLedger,
|
|
4636
|
+
verificationCeiling,
|
|
4637
|
+
haltReason
|
|
4638
|
+
};
|
|
4639
|
+
}
|
|
4640
|
+
function parseSnapshot(kind, head) {
|
|
4641
|
+
return kind === "grind" ? parseGrindSnapshot(head) : parseBuildSnapshot(head);
|
|
4642
|
+
}
|
|
4643
|
+
function strOrU(v) {
|
|
4644
|
+
return typeof v === "string" && v.length ? v : void 0;
|
|
4645
|
+
}
|
|
4646
|
+
function numOrU(v) {
|
|
4647
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
4648
|
+
}
|
|
4649
|
+
function asStringArray(v) {
|
|
4650
|
+
return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
4651
|
+
}
|
|
4652
|
+
function normalizeSnapshot(kind, raw) {
|
|
4653
|
+
if (!raw || typeof raw !== "object") throw new Error("snapshot JSON must be an object");
|
|
4654
|
+
const o = raw;
|
|
4655
|
+
if (o.kind !== kind) throw new Error(`snapshot kind ${JSON.stringify(o.kind)} does not match --kind ${kind}`);
|
|
4656
|
+
if (kind === "grind") {
|
|
4657
|
+
return {
|
|
4658
|
+
kind: "grind",
|
|
4659
|
+
class: strOrU(o.class),
|
|
4660
|
+
routing: strOrU(o.routing),
|
|
4661
|
+
ultra: strOrU(o.ultra),
|
|
4662
|
+
criteria: strOrU(o.criteria),
|
|
4663
|
+
phase: strOrU(o.phase),
|
|
4664
|
+
verifyRound: numOrU(o.verifyRound),
|
|
4665
|
+
verifyCap: numOrU(o.verifyCap),
|
|
4666
|
+
openBlockerIds: asStringArray(o.openBlockerIds),
|
|
4667
|
+
resolvedBlockerIds: asStringArray(o.resolvedBlockerIds),
|
|
4668
|
+
verificationCeiling: strOrU(o.verificationCeiling),
|
|
4669
|
+
nextAction: strOrU(o.nextAction)
|
|
4670
|
+
};
|
|
4671
|
+
}
|
|
4672
|
+
const blocked = Array.isArray(o.blocked) ? o.blocked.filter((b) => !!b && typeof b === "object").map((b) => ({ site: strOrU(b.site) ?? "", decision: strOrU(b.decision) ?? "", issue: strOrU(b.issue) })) : [];
|
|
4673
|
+
const tierLedger = Array.isArray(o.tierLedger) ? o.tierLedger.filter((t) => !!t && typeof t === "object").map((t) => ({ site: strOrU(t.site) ?? "", tier: strOrU(t.tier) ?? "", signals: strOrU(t.signals) })) : [];
|
|
4674
|
+
const verificationCeiling = Array.isArray(o.verificationCeiling) ? o.verificationCeiling.filter((v) => !!v && typeof v === "object").map((v) => ({ site: strOrU(v.site) ?? "", rung: strOrU(v.rung) ?? "" })) : [];
|
|
4675
|
+
return {
|
|
4676
|
+
kind: "build",
|
|
4677
|
+
milestone: strOrU(o.milestone),
|
|
4678
|
+
northStarSlug: strOrU(o.northStarSlug),
|
|
4679
|
+
doneLastTurn: asStringArray(o.doneLastTurn),
|
|
4680
|
+
inProgress: asStringArray(o.inProgress),
|
|
4681
|
+
blocked,
|
|
4682
|
+
nextFrontier: asStringArray(o.nextFrontier),
|
|
4683
|
+
tierLedger,
|
|
4684
|
+
verificationCeiling,
|
|
4685
|
+
haltReason: strOrU(o.haltReason)
|
|
4686
|
+
};
|
|
4687
|
+
}
|
|
4688
|
+
function planGrindSnapshotWrite(s, summary = "grind snapshot refresh") {
|
|
4689
|
+
const queueOps = [];
|
|
4690
|
+
for (const id of s.openBlockerIds) queueOps.push({ text: `${GS_OPEN}${id}` });
|
|
4691
|
+
for (const id of s.resolvedBlockerIds) queueOps.push({ text: `${GS_RESOLVED}${id}` });
|
|
4692
|
+
if (s.verificationCeiling) queueOps.push({ text: `${GS_CEILING}${s.verificationCeiling}` });
|
|
4693
|
+
return {
|
|
4694
|
+
summary,
|
|
4695
|
+
next: formatGrindNext(s),
|
|
4696
|
+
anchor: s.criteria,
|
|
4697
|
+
queueOps,
|
|
4698
|
+
clearIndices: []
|
|
4699
|
+
};
|
|
4700
|
+
}
|
|
4701
|
+
function planBuildSnapshotWrite(s, summary = "build snapshot refresh") {
|
|
4702
|
+
const queueOps = [];
|
|
4703
|
+
for (const site of s.doneLastTurn) queueOps.push({ text: `${BS_DONE}${site}` });
|
|
4704
|
+
for (const site of s.inProgress) queueOps.push({ text: `${BS_PROGRESS}${site}` });
|
|
4705
|
+
for (const b of s.blocked) {
|
|
4706
|
+
queueOps.push({ text: `${BS_BLOCKED}${b.site}|${b.decision}${b.issue ? `|${b.issue}` : ""}` });
|
|
4707
|
+
}
|
|
4708
|
+
for (const t of s.tierLedger) {
|
|
4709
|
+
queueOps.push({ text: `${BS_TIER}${t.site}|${t.tier}${t.signals ? `|${t.signals}` : ""}` });
|
|
4710
|
+
}
|
|
4711
|
+
for (const v of s.verificationCeiling) queueOps.push({ text: `${BS_CEILING}${v.site}|${v.rung}` });
|
|
4712
|
+
if (s.haltReason) queueOps.push({ text: `${BS_HALT}${s.haltReason}` });
|
|
4713
|
+
return {
|
|
4714
|
+
summary,
|
|
4715
|
+
next: s.nextFrontier.length ? s.nextFrontier.join("; ") : void 0,
|
|
4716
|
+
anchor: s.milestone,
|
|
4717
|
+
anchorSlug: s.northStarSlug,
|
|
4718
|
+
queueOps,
|
|
4719
|
+
clearIndices: []
|
|
4720
|
+
};
|
|
4721
|
+
}
|
|
4722
|
+
function planSnapshotWrite(snapshot, head, summary) {
|
|
4723
|
+
const plan2 = snapshot.kind === "grind" ? planGrindSnapshotWrite(snapshot, summary) : planBuildSnapshotWrite(snapshot, summary);
|
|
4724
|
+
plan2.clearIndices = snapshotClearIndices(snapshot.kind, head);
|
|
4725
|
+
return plan2;
|
|
4726
|
+
}
|
|
4727
|
+
function formatSnapshotHuman(snapshot) {
|
|
4728
|
+
if (snapshot.kind === "grind") {
|
|
4729
|
+
const lines2 = [
|
|
4730
|
+
`kind: grind`,
|
|
4731
|
+
snapshot.class ? `class: ${snapshot.class}` : "",
|
|
4732
|
+
snapshot.routing ? `routing: ${snapshot.routing}` : "",
|
|
4733
|
+
snapshot.ultra ? `ultra: ${snapshot.ultra}` : "",
|
|
4734
|
+
snapshot.criteria ? `criteria: ${snapshot.criteria}` : "",
|
|
4735
|
+
snapshot.phase ? `phase: ${snapshot.phase}` : "",
|
|
4736
|
+
snapshot.verifyRound != null ? `verify: ${snapshot.verifyRound}/${snapshot.verifyCap ?? "?"}` : "",
|
|
4737
|
+
snapshot.openBlockerIds.length ? `open blockers: ${snapshot.openBlockerIds.join(", ")}` : "open blockers: (none)",
|
|
4738
|
+
snapshot.resolvedBlockerIds.length ? `resolved: ${snapshot.resolvedBlockerIds.join(", ")}` : "",
|
|
4739
|
+
snapshot.verificationCeiling ? `ceiling: ${snapshot.verificationCeiling}` : "",
|
|
4740
|
+
snapshot.nextAction ? `next: ${snapshot.nextAction}` : ""
|
|
4741
|
+
].filter(Boolean);
|
|
4742
|
+
return lines2.join("\n");
|
|
4743
|
+
}
|
|
4744
|
+
const lines = [
|
|
4745
|
+
`kind: build`,
|
|
4746
|
+
snapshot.milestone ? `milestone: ${snapshot.milestone}` : "",
|
|
4747
|
+
snapshot.northStarSlug ? `north star: ${snapshot.northStarSlug}` : "",
|
|
4748
|
+
snapshot.doneLastTurn.length ? `done: ${snapshot.doneLastTurn.join(", ")}` : "",
|
|
4749
|
+
snapshot.inProgress.length ? `in progress: ${snapshot.inProgress.join(", ")}` : "",
|
|
4750
|
+
snapshot.nextFrontier.length ? `next frontier: ${snapshot.nextFrontier.join("; ")}` : "",
|
|
4751
|
+
snapshot.haltReason ? `halt: ${snapshot.haltReason}` : ""
|
|
4752
|
+
].filter(Boolean);
|
|
4753
|
+
return lines.join("\n");
|
|
4754
|
+
}
|
|
4755
|
+
|
|
4453
4756
|
// src/saga-commands.ts
|
|
4454
4757
|
async function runNote(summary, o) {
|
|
4455
4758
|
const [sha, key] = await Promise.all([gitOut(["rev-parse", "--short", "HEAD"]), sagaKey(await loadConfig())]);
|
|
@@ -4582,6 +4885,60 @@ async function runSagaHealth(o, io = consoleIo) {
|
|
|
4582
4885
|
if (report.warnings.length) io.log(report.warnings.map((w) => ` - ${w}`).join("\n"));
|
|
4583
4886
|
if (report.pendingNotes > 0) io.log(` - ${report.pendingNotes} note(s) queued locally \u2014 \`mmi-cli saga flush\` to roll forward`);
|
|
4584
4887
|
}
|
|
4888
|
+
async function fetchSagaHead(io = consoleIo) {
|
|
4889
|
+
const cfg = await loadConfig();
|
|
4890
|
+
if (!cfg.sagaApiUrl) {
|
|
4891
|
+
io.err("saga snapshot: Hub API URL not configured");
|
|
4892
|
+
return null;
|
|
4893
|
+
}
|
|
4894
|
+
try {
|
|
4895
|
+
const key = await sagaKey(cfg);
|
|
4896
|
+
const qs = new URLSearchParams(key).toString();
|
|
4897
|
+
const res = await fetch(`${cfg.sagaApiUrl}/saga/state?${qs}`, { headers: await hubHeaders(), signal: AbortSignal.timeout(8e3) });
|
|
4898
|
+
if (!res.ok) {
|
|
4899
|
+
io.err(`saga snapshot: HTTP ${res.status}`);
|
|
4900
|
+
return null;
|
|
4901
|
+
}
|
|
4902
|
+
const state = await res.json();
|
|
4903
|
+
return state.head ?? {};
|
|
4904
|
+
} catch (e) {
|
|
4905
|
+
io.err(`saga snapshot: ${e.message}`);
|
|
4906
|
+
return null;
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
4909
|
+
async function postSnapshotNotes(plan2, anchorForce) {
|
|
4910
|
+
const [sha, key] = await Promise.all([gitOut(["rev-parse", "--short", "HEAD"]), sagaKey(await loadConfig())]);
|
|
4911
|
+
const evidence = { sha: sha || void 0, branch: key.branch };
|
|
4912
|
+
for (const idx of [...plan2.clearIndices].sort((a, b) => b - a)) {
|
|
4913
|
+
const cap = buildNoteCapture("snapshot retire", { queueDone: String(idx) }, (0, import_node_crypto3.randomUUID)(), evidence);
|
|
4914
|
+
await postCapture(cap);
|
|
4915
|
+
}
|
|
4916
|
+
const primary = buildNoteCapture(plan2.summary, {
|
|
4917
|
+
next: plan2.next,
|
|
4918
|
+
anchor: plan2.anchor,
|
|
4919
|
+
anchorSlug: plan2.anchorSlug,
|
|
4920
|
+
anchorForce
|
|
4921
|
+
}, (0, import_node_crypto3.randomUUID)(), evidence);
|
|
4922
|
+
await postCapture(primary);
|
|
4923
|
+
for (const op of plan2.queueOps) {
|
|
4924
|
+
const cap = buildNoteCapture("snapshot checklist", { queueAdd: op.text }, (0, import_node_crypto3.randomUUID)(), evidence);
|
|
4925
|
+
await postCapture(cap);
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4928
|
+
async function runSagaSnapshotShow(opts, io = consoleIo) {
|
|
4929
|
+
const head = await fetchSagaHead(io);
|
|
4930
|
+
if (!head) return;
|
|
4931
|
+
const snapshot = parseSnapshot(opts.kind, head);
|
|
4932
|
+
if (opts.json) return io.log(JSON.stringify(snapshot, null, 2));
|
|
4933
|
+
io.log(formatSnapshotHuman(snapshot));
|
|
4934
|
+
}
|
|
4935
|
+
async function runSagaSnapshotSet(snapshot, opts = {}, io = consoleIo) {
|
|
4936
|
+
const head = await fetchSagaHead(io) ?? { queued: [] };
|
|
4937
|
+
const plan2 = planSnapshotWrite(snapshot, head);
|
|
4938
|
+
await postSnapshotNotes(plan2, opts.anchorForce);
|
|
4939
|
+
if (opts.json) return io.log(JSON.stringify({ ok: true, snapshot, retired: plan2.clearIndices.length, queued: plan2.queueOps.length + 1 }));
|
|
4940
|
+
io.log(`saga snapshot: wrote ${snapshot.kind} snapshot (retired ${plan2.clearIndices.length}, ${plan2.queueOps.length + 1} capture(s))`);
|
|
4941
|
+
}
|
|
4585
4942
|
function registerSagaCommands(program3) {
|
|
4586
4943
|
const saga = program3.command("saga").description("per-session continuity");
|
|
4587
4944
|
saga.command("note [summary]").description("record a one-line structured note into your saga (the per-turn capture)").option("--next <text>", 'set "where I left off" (NEXT)').option("--decision <text>", "append a verbatim decision").option("--queue-add <text>", "add a worklist item").option("--queue-done <n>", "mark worklist item N done").option("--verified", "mark this claim as checked against source (state: verified, else asserted)").option("--diagnostic", "isolate a probe write (state: diagnostic, source: probe) \u2014 never resume/LAST 5").option("--supersedes <key>", "retire prior decisions matching an evidence key (pr:N | file:path)").option("--anchor <intent>", "set the sprint North-Star (write-protected; needs --anchor-force to change)").option("--anchor-slug <slug>", "bind the anchor to a North Star plan slug (SSOT at plans/.../<slug>.md)").option("--anchor-force", "overwrite an existing anchor").option("--message-file <path|->", "read the summary from a UTF-8 file, or from stdin with - (avoids cmd.exe quoting)").action(async (summary, o) => {
|
|
@@ -4647,6 +5004,69 @@ function registerSagaCommands(program3) {
|
|
|
4647
5004
|
console.log(`project=${key.project} branch=${key.branch} session=${key.sessionId} source=${source}`);
|
|
4648
5005
|
});
|
|
4649
5006
|
saga.command("health").option("--json", "machine-readable output").option("--banner", "one-line SessionStart banner; silent when healthy").option("--quiet", "suppress detail output").description("zero-write health check: auth, backend reachability, resolved key").action((o) => runSagaHealth(o));
|
|
5007
|
+
const snapshot = saga.command("snapshot").description("structured grind/build resume snapshot over saga HEAD");
|
|
5008
|
+
snapshot.command("show").description("read the structured resume snapshot from current saga HEAD").requiredOption("--kind <kind>", "grind | build").option("--json", "output JSON").action(async (o) => {
|
|
5009
|
+
const kind = o.kind;
|
|
5010
|
+
if (kind !== "grind" && kind !== "build") return fail("saga snapshot show: --kind must be grind or build");
|
|
5011
|
+
await runSagaSnapshotShow({ kind, json: o.json });
|
|
5012
|
+
});
|
|
5013
|
+
snapshot.command("set").description("write a structured resume snapshot (maps to saga note HEAD primitives)").requiredOption("--kind <kind>", "grind | build").option("--json-file <path>", "full snapshot JSON (overrides field flags)").option("--anchor-force", "overwrite an existing anchor").option("--json", "output result JSON").option("--class <c>", "grind class").option("--routing <r>", "grind routing tier").option("--ultra <u>", "ultra mode").option("--criteria <text>", "grind criteria / frame").option("--phase <p>", "phase reached").option("--verify-round <n>", "verify round", (v) => parseInt(v, 10)).option("--verify-cap <n>", "verify cap", (v) => parseInt(v, 10)).option("--open-blocker <id>", "open blocker id (repeatable)", (v, prev) => [...prev, v], []).option("--resolved-blocker <id>", "resolved blocker id (repeatable)", (v, prev) => [...prev, v], []).option("--verification-ceiling <text>", "verification ceiling").option("--next-action <text>", "next action").option("--milestone <text>", "build milestone one-liner").option("--north-star-slug <slug>", "North Star slug").option("--done <site>", "done last turn site (repeatable)", (v, prev) => [...prev, v], []).option("--in-progress <site>", "in-progress site (repeatable)", (v, prev) => [...prev, v], []).option("--blocked <spec>", "site|decision|issue (repeatable)", (v, prev) => [...prev, v], []).option("--next-frontier <item>", "frontier item (repeatable)", (v, prev) => [...prev, v], []).option("--tier <spec>", "site|tier|signals (repeatable)", (v, prev) => [...prev, v], []).option("--site-ceiling <spec>", "site|rung (repeatable)", (v, prev) => [...prev, v], []).option("--halt-reason <text>", "halt reason").action(async (o) => {
|
|
5014
|
+
const kind = o.kind;
|
|
5015
|
+
if (kind !== "grind" && kind !== "build") return fail("saga snapshot set: --kind must be grind or build");
|
|
5016
|
+
let snap;
|
|
5017
|
+
if (o.jsonFile) {
|
|
5018
|
+
const raw = await (0, import_promises2.readFile)(o.jsonFile, "utf8");
|
|
5019
|
+
let parsed;
|
|
5020
|
+
try {
|
|
5021
|
+
parsed = JSON.parse(raw);
|
|
5022
|
+
} catch (e) {
|
|
5023
|
+
return fail(`saga snapshot set: invalid JSON in ${o.jsonFile}: ${e.message}`);
|
|
5024
|
+
}
|
|
5025
|
+
try {
|
|
5026
|
+
snap = normalizeSnapshot(kind, parsed);
|
|
5027
|
+
} catch (e) {
|
|
5028
|
+
return fail(`saga snapshot set: ${e.message}`);
|
|
5029
|
+
}
|
|
5030
|
+
} else if (kind === "grind") {
|
|
5031
|
+
snap = {
|
|
5032
|
+
kind: "grind",
|
|
5033
|
+
class: o.class,
|
|
5034
|
+
routing: o.routing,
|
|
5035
|
+
ultra: o.ultra,
|
|
5036
|
+
criteria: o.criteria,
|
|
5037
|
+
phase: o.phase,
|
|
5038
|
+
verifyRound: o.verifyRound,
|
|
5039
|
+
verifyCap: o.verifyCap,
|
|
5040
|
+
openBlockerIds: o.openBlocker ?? [],
|
|
5041
|
+
resolvedBlockerIds: o.resolvedBlocker ?? [],
|
|
5042
|
+
verificationCeiling: o.verificationCeiling,
|
|
5043
|
+
nextAction: o.nextAction
|
|
5044
|
+
};
|
|
5045
|
+
} else {
|
|
5046
|
+
snap = {
|
|
5047
|
+
kind: "build",
|
|
5048
|
+
milestone: o.milestone,
|
|
5049
|
+
northStarSlug: o.northStarSlug,
|
|
5050
|
+
doneLastTurn: o.done ?? [],
|
|
5051
|
+
inProgress: o.inProgress ?? [],
|
|
5052
|
+
blocked: (o.blocked ?? []).map((spec) => {
|
|
5053
|
+
const [site, decision, issue2] = spec.split("|");
|
|
5054
|
+
return { site: site ?? "", decision: decision ?? "", issue: issue2 };
|
|
5055
|
+
}),
|
|
5056
|
+
nextFrontier: o.nextFrontier ?? [],
|
|
5057
|
+
tierLedger: (o.tier ?? []).map((spec) => {
|
|
5058
|
+
const [site, tier, signals] = spec.split("|");
|
|
5059
|
+
return { site: site ?? "", tier: tier ?? "", signals };
|
|
5060
|
+
}),
|
|
5061
|
+
verificationCeiling: (o.siteCeiling ?? []).map((spec) => {
|
|
5062
|
+
const [site, rung] = spec.split("|");
|
|
5063
|
+
return { site: site ?? "", rung: rung ?? "" };
|
|
5064
|
+
}),
|
|
5065
|
+
haltReason: o.haltReason
|
|
5066
|
+
};
|
|
5067
|
+
}
|
|
5068
|
+
await runSagaSnapshotSet(snap, { anchorForce: o.anchorForce, json: o.json });
|
|
5069
|
+
});
|
|
4650
5070
|
}
|
|
4651
5071
|
|
|
4652
5072
|
// src/honcho-commands.ts
|
|
@@ -7442,6 +7862,87 @@ function pickEffortTier(signals) {
|
|
|
7442
7862
|
}
|
|
7443
7863
|
return { tier: "standard", reason: "default \u2014 normal slice", budgets: getTierBudgets("standard") };
|
|
7444
7864
|
}
|
|
7865
|
+
var VERIFICATION_DEPTH_ROUNDS = {
|
|
7866
|
+
"panel-once": 1,
|
|
7867
|
+
"full-panel": 2,
|
|
7868
|
+
"full-double-pass": 4,
|
|
7869
|
+
"integration-checkpoints": 6
|
|
7870
|
+
};
|
|
7871
|
+
var TIER_LADDER = ["light", "standard", "deep", "max"];
|
|
7872
|
+
var CAMPAIGN_ITERATION_CAP = 15;
|
|
7873
|
+
var COST_CEILING = 500;
|
|
7874
|
+
function estimateWorstCaseCost(input) {
|
|
7875
|
+
const tier = input.tier ?? (input.signals ? pickEffortTier(input.signals).tier : "standard");
|
|
7876
|
+
const budgets = getTierBudgets(tier);
|
|
7877
|
+
const verifyRounds = VERIFICATION_DEPTH_ROUNDS[budgets.verificationDepth];
|
|
7878
|
+
const sites = Math.max(1, input.plannedSiteCount ?? 1);
|
|
7879
|
+
const fusionMultiplier = budgets.planners > 0 ? 1.5 : 1;
|
|
7880
|
+
const costUnits = Math.ceil(budgets.agents * verifyRounds * sites * fusionMultiplier);
|
|
7881
|
+
const breakdown = `tier=${tier} agents=${budgets.agents} verifyRounds=${verifyRounds} sites=${sites} fusion\xD7${fusionMultiplier}`;
|
|
7882
|
+
return {
|
|
7883
|
+
costUnits,
|
|
7884
|
+
breakdown,
|
|
7885
|
+
exceedsCeiling: costUnits > COST_CEILING
|
|
7886
|
+
};
|
|
7887
|
+
}
|
|
7888
|
+
function decideOverCeiling(estimate, tier) {
|
|
7889
|
+
if (!estimate.exceedsCeiling) return { action: "ok" };
|
|
7890
|
+
if (tier === "light") return { action: "halt" };
|
|
7891
|
+
const idx = TIER_LADDER.indexOf(tier);
|
|
7892
|
+
const suggestedTier = idx > 0 ? TIER_LADDER[idx - 1] : "light";
|
|
7893
|
+
return { action: "lower-tier", suggestedTier };
|
|
7894
|
+
}
|
|
7895
|
+
function frontierExhausted(state) {
|
|
7896
|
+
const noUnblocked = (state.openUnblockedClaims ?? 0) === 0;
|
|
7897
|
+
const noOpenPrs = (state.openInflightPrs ?? 0) === 0;
|
|
7898
|
+
const frontierEmpty = state.nextFrontierEmpty ?? false;
|
|
7899
|
+
const noUnresolvedParked = !(state.parkedSitesUnresolved ?? false);
|
|
7900
|
+
return frontierEmpty && noUnblocked && noOpenPrs && noUnresolvedParked;
|
|
7901
|
+
}
|
|
7902
|
+
function campaignIterationCapExceeded(state) {
|
|
7903
|
+
const cap = state.iterationCapOverride ?? CAMPAIGN_ITERATION_CAP;
|
|
7904
|
+
return (state.orientCount ?? 0) >= cap;
|
|
7905
|
+
}
|
|
7906
|
+
function evaluateBuildFrontier(state) {
|
|
7907
|
+
const iterationCap = state.iterationCapOverride ?? CAMPAIGN_ITERATION_CAP;
|
|
7908
|
+
const exhausted = frontierExhausted(state);
|
|
7909
|
+
const capHit = campaignIterationCapExceeded(state);
|
|
7910
|
+
if (exhausted) {
|
|
7911
|
+
return { frontierExhausted: true, iterationCapExceeded: capHit, iterationCap, recommend: "halt", haltReason: "frontier-exhausted" };
|
|
7912
|
+
}
|
|
7913
|
+
if (capHit) {
|
|
7914
|
+
return { frontierExhausted: false, iterationCapExceeded: true, iterationCap, recommend: "halt", haltReason: "iteration-cap" };
|
|
7915
|
+
}
|
|
7916
|
+
return { frontierExhausted: false, iterationCapExceeded: false, iterationCap, recommend: "continue" };
|
|
7917
|
+
}
|
|
7918
|
+
|
|
7919
|
+
// src/grind-policy.ts
|
|
7920
|
+
function isExplicitUltraYolo(input) {
|
|
7921
|
+
return Boolean(input.ultra);
|
|
7922
|
+
}
|
|
7923
|
+
function getUltraCaps(input) {
|
|
7924
|
+
if (isExplicitUltraYolo(input)) return { verifyRounds: 8, ciFixRounds: 5 };
|
|
7925
|
+
return { verifyRounds: 5, ciFixRounds: 3 };
|
|
7926
|
+
}
|
|
7927
|
+
var GRIND_LENS_COUNT = 5;
|
|
7928
|
+
var GRIND_COST_CEILING = 200;
|
|
7929
|
+
var MIN_STABLE_CLEAN_ROUNDS = 2;
|
|
7930
|
+
function estimateGrindWorstCaseCost(opts) {
|
|
7931
|
+
const caps = getUltraCaps(opts.input);
|
|
7932
|
+
const lenses = opts.lensCount ?? GRIND_LENS_COUNT;
|
|
7933
|
+
const models = opts.modelCount ?? (isExplicitUltraYolo(opts.input) || opts.input.autoUltra ? 3 : 2);
|
|
7934
|
+
const verifyCost = caps.verifyRounds * lenses * models;
|
|
7935
|
+
const synthesisCost = caps.verifyRounds;
|
|
7936
|
+
const ciFixCost = opts.includeCiFix ? caps.ciFixRounds * 2 : 0;
|
|
7937
|
+
const perPass = verifyCost + synthesisCost + ciFixCost;
|
|
7938
|
+
const costUnits = perPass * MIN_STABLE_CLEAN_ROUNDS;
|
|
7939
|
+
const breakdown = `verifyRounds=${caps.verifyRounds} lenses=${lenses} models=${models} synthesis=${synthesisCost} ciFix=${ciFixCost} stableRounds=${MIN_STABLE_CLEAN_ROUNDS}`;
|
|
7940
|
+
return {
|
|
7941
|
+
costUnits,
|
|
7942
|
+
breakdown,
|
|
7943
|
+
exceedsCeiling: costUnits > GRIND_COST_CEILING
|
|
7944
|
+
};
|
|
7945
|
+
}
|
|
7445
7946
|
|
|
7446
7947
|
// src/gc.ts
|
|
7447
7948
|
var DEFERRED_SWEEP_COMMAND = "mmi-cli gc --apply";
|
|
@@ -8626,7 +9127,7 @@ function buildMmiPluginCacheCleanupCheck(input) {
|
|
|
8626
9127
|
};
|
|
8627
9128
|
}
|
|
8628
9129
|
var NESTED_PLUGIN_TREE_LABEL = "self-nested MMI plugin cache tree (#1126)";
|
|
8629
|
-
var NESTED_PLUGIN_TREE_FIX = "
|
|
9130
|
+
var NESTED_PLUGIN_TREE_FIX = "SessionStart and mmi-cli doctor auto-clear when possible; if this persists, run the MAX_PATH-safe robocopy cleanup for a self-nested MMI plugin cache tree (#1126)";
|
|
8630
9131
|
function nestedPluginTreeCleanupCommand(paths, isWindows) {
|
|
8631
9132
|
if (paths.length === 0) return "";
|
|
8632
9133
|
if (isWindows) {
|
|
@@ -14695,6 +15196,61 @@ build.command("tier").description("Recommend an effort tier (light|standard|deep
|
|
|
14695
15196
|
console.log(`reasoning: ${decision.budgets.reasoningEffort}`);
|
|
14696
15197
|
}
|
|
14697
15198
|
});
|
|
15199
|
+
build.command("estimate").description("Worst-case cost proxy (agent-call units) for a milestone run \u2014 not a dollar promise").option("--scope <s>", "trivial|narrow|normal|wide|architectural").option("--risk <r>", "low|medium|high|critical").option("--ambiguity <a>", "low|medium|high").option("--blast-radius <b>", "isolated|module|cross-module|product").option("--foundational", "touches shared seams").option("--explicit <t>", "force a tier: light|standard|deep|max").option("--sites <n>", "planned L1 site count", (v) => parseInt(v, 10)).option("--json", "output JSON").action((opts) => {
|
|
15200
|
+
const signals = {
|
|
15201
|
+
scope: opts.scope,
|
|
15202
|
+
risk: opts.risk,
|
|
15203
|
+
ambiguity: opts.ambiguity,
|
|
15204
|
+
blastRadius: opts.blastRadius,
|
|
15205
|
+
foundational: opts.foundational ?? void 0,
|
|
15206
|
+
explicitTier: opts.explicit
|
|
15207
|
+
};
|
|
15208
|
+
const tierDecision = pickEffortTier(signals);
|
|
15209
|
+
const estimate = estimateWorstCaseCost({
|
|
15210
|
+
signals,
|
|
15211
|
+
tier: tierDecision.tier,
|
|
15212
|
+
plannedSiteCount: opts.sites
|
|
15213
|
+
});
|
|
15214
|
+
const overCeiling = decideOverCeiling(estimate, tierDecision.tier);
|
|
15215
|
+
const payload = {
|
|
15216
|
+
tier: tierDecision.tier,
|
|
15217
|
+
estimate,
|
|
15218
|
+
ceiling: COST_CEILING,
|
|
15219
|
+
overCeiling,
|
|
15220
|
+
iterationCap: CAMPAIGN_ITERATION_CAP,
|
|
15221
|
+
verificationDepthRounds: VERIFICATION_DEPTH_ROUNDS
|
|
15222
|
+
};
|
|
15223
|
+
if (opts.json) console.log(JSON.stringify(payload, null, 2));
|
|
15224
|
+
else {
|
|
15225
|
+
console.log(`tier: ${tierDecision.tier}`);
|
|
15226
|
+
console.log(`estimate: ${estimate.costUnits} agent-call units (proxy, not dollars)`);
|
|
15227
|
+
console.log(`breakdown: ${estimate.breakdown}`);
|
|
15228
|
+
const ceilingMsg = estimate.exceedsCeiling ? `EXCEEDS \u2192 ${overCeiling.action}${overCeiling.suggestedTier ? ` (try ${overCeiling.suggestedTier})` : ""}` : "within";
|
|
15229
|
+
console.log(`ceiling: ${COST_CEILING} units \u2014 ${ceilingMsg}`);
|
|
15230
|
+
console.log(`L0 cap: ${CAMPAIGN_ITERATION_CAP} orients (Phase 0 may override)`);
|
|
15231
|
+
}
|
|
15232
|
+
});
|
|
15233
|
+
build.command("frontier").description("Evaluate external frontier exhaustion + L0 iteration cap (Ralph Wiggum guard)").option("--next-frontier-empty", "in-hand North Star has no unblocked frontier").option("--open-claims <n>", "open unblocked board claims", (v) => parseInt(v, 10)).option("--open-prs <n>", "in-flight PRs for milestone", (v) => parseInt(v, 10)).option("--parked-unresolved", "parked sites still waiting on hard decisions").option("--orient-count <n>", "L0 orient count this run", (v) => parseInt(v, 10)).option("--iteration-cap <n>", "Phase 0 override for L0 cap", (v) => parseInt(v, 10)).option("--json-file <path>", "full BuildExternalState JSON").option("--json", "output JSON").action(async (opts) => {
|
|
15234
|
+
let state = {
|
|
15235
|
+
nextFrontierEmpty: opts.nextFrontierEmpty ?? void 0,
|
|
15236
|
+
openUnblockedClaims: opts.openClaims,
|
|
15237
|
+
openInflightPrs: opts.openPrs,
|
|
15238
|
+
parkedSitesUnresolved: opts.parkedUnresolved ?? void 0,
|
|
15239
|
+
orientCount: opts.orientCount,
|
|
15240
|
+
iterationCapOverride: opts.iterationCap
|
|
15241
|
+
};
|
|
15242
|
+
if (opts.jsonFile) {
|
|
15243
|
+
const raw = await (0, import_promises5.readFile)(opts.jsonFile, "utf8");
|
|
15244
|
+
state = { ...state, ...JSON.parse(raw) };
|
|
15245
|
+
}
|
|
15246
|
+
const result = evaluateBuildFrontier(state);
|
|
15247
|
+
if (opts.json) console.log(JSON.stringify(result, null, 2));
|
|
15248
|
+
else {
|
|
15249
|
+
console.log(`frontierExhausted: ${result.frontierExhausted}`);
|
|
15250
|
+
console.log(`iterationCapExceeded: ${result.iterationCapExceeded} (cap ${result.iterationCap})`);
|
|
15251
|
+
console.log(`recommend: ${result.recommend}${result.haltReason ? ` (${result.haltReason})` : ""}`);
|
|
15252
|
+
}
|
|
15253
|
+
});
|
|
14698
15254
|
build.command("plan").description("Partition a set of issue refs into sites + waves (parallel/serialize/batch) for milestone construction").argument("[issues...]", "issue refs like owner/repo#N or #N").option("--json", "output JSON").action((issues, opts) => {
|
|
14699
15255
|
const plan2 = {
|
|
14700
15256
|
waves: issues.length > 0 ? [{ wave: 0, mode: "parallel", issues }] : [],
|
|
@@ -14706,6 +15262,26 @@ build.command("plan").description("Partition a set of issue refs into sites + wa
|
|
|
14706
15262
|
for (const w of plan2.waves) console.log(`wave ${w.wave} (${w.mode}): ${w.issues.join(" ")}`);
|
|
14707
15263
|
}
|
|
14708
15264
|
});
|
|
15265
|
+
var grindCmd = program2.command("grind").description("Grind skill helpers \u2014 routing and cost proxies");
|
|
15266
|
+
grindCmd.command("estimate").description("Worst-case cost proxy (agent-call units) for one grind \u2014 not a dollar promise").option("--class <c>", "bug|feature|research|task|security-critical", "feature").option("--ultra", "explicit --ultra YOLO").option("--auto-ultra", "auto-ultra verify uplift (3 models)").option("--auto", "include CI-fix rounds").option("--models <n>", "panel model count", (v) => parseInt(v, 10)).option("--json", "output JSON").action((opts) => {
|
|
15267
|
+
const input = {
|
|
15268
|
+
grindClass: opts.class ?? "feature",
|
|
15269
|
+
ultra: opts.ultra ?? void 0,
|
|
15270
|
+
autoUltra: opts.autoUltra ?? void 0
|
|
15271
|
+
};
|
|
15272
|
+
const estimate = estimateGrindWorstCaseCost({
|
|
15273
|
+
input,
|
|
15274
|
+
modelCount: opts.models,
|
|
15275
|
+
includeCiFix: opts.auto ?? false
|
|
15276
|
+
});
|
|
15277
|
+
const payload = { estimate, ceiling: GRIND_COST_CEILING, input };
|
|
15278
|
+
if (opts.json) console.log(JSON.stringify(payload, null, 2));
|
|
15279
|
+
else {
|
|
15280
|
+
console.log(`estimate: ${estimate.costUnits} agent-call units (proxy, not dollars)`);
|
|
15281
|
+
console.log(`breakdown: ${estimate.breakdown}`);
|
|
15282
|
+
console.log(`ceiling: ${GRIND_COST_CEILING} units \u2014 ${estimate.exceedsCeiling ? "EXCEEDS \u2192 ask human (cap/stuck path)" : "within"}`);
|
|
15283
|
+
}
|
|
15284
|
+
});
|
|
14709
15285
|
program2.command("skill-lesson").description("file a skill-lesson on the Hub board (GitHub auth, dedups open lessons) and print {number,url} JSON").requiredOption("--skill <name>", `which skill misfired (${SKILL_NAMES.join(" | ")})`).option("--title <title>", "one-line summary of what misfired").option("--title-file <path|->", "read the one-line summary from a UTF-8 file, or from stdin with -").option("--body <body>", "lesson body: what misfired, the evidence, and the proposed amendment (markdown)").option("--body-file <path|->", "read the lesson body from a UTF-8 file, or from stdin with -").option("--priority <priority>", "urgent | high | medium | low (board Priority field when configured)", "medium").option("--repo <owner/repo>", `target repo (defaults to the org Hub: ${HUB_REPO})`).option("--force", "file a new issue even when an open lesson looks like a duplicate").option("--json", "machine-readable output (already the default \u2014 skill-lesson always prints JSON)").action(async (o) => {
|
|
14710
15286
|
const targetRepo2 = o.repo ?? HUB_REPO;
|
|
14711
15287
|
const sourceRepo = await resolveRepo(void 0);
|
|
@@ -15857,6 +16433,46 @@ function quarantinePluginCacheDirs(plan2) {
|
|
|
15857
16433
|
}
|
|
15858
16434
|
return moved;
|
|
15859
16435
|
}
|
|
16436
|
+
async function robocopyMirrorEmpty(emptyDir, target) {
|
|
16437
|
+
try {
|
|
16438
|
+
await execFileP2("robocopy", [emptyDir, target, "/MIR", "/NJH", "/NJS", "/NFL", "/NDL"], { timeout: CLAUDE_PLUGIN_TIMEOUT_MS });
|
|
16439
|
+
} catch (e) {
|
|
16440
|
+
const code = e.code;
|
|
16441
|
+
if (typeof code === "number" && code <= 7) return;
|
|
16442
|
+
throw e;
|
|
16443
|
+
}
|
|
16444
|
+
}
|
|
16445
|
+
async function clearNestedPluginTreeDir(targetPath) {
|
|
16446
|
+
try {
|
|
16447
|
+
if (!(0, import_node_fs15.existsSync)(targetPath)) return true;
|
|
16448
|
+
if (isWin) {
|
|
16449
|
+
const emptyDir = (0, import_node_path14.join)((0, import_node_os5.tmpdir)(), `mmi-empty-${Date.now()}`);
|
|
16450
|
+
(0, import_node_fs15.mkdirSync)(emptyDir, { recursive: true });
|
|
16451
|
+
try {
|
|
16452
|
+
await robocopyMirrorEmpty(emptyDir, targetPath);
|
|
16453
|
+
(0, import_node_fs15.rmSync)(targetPath, { recursive: true, force: true });
|
|
16454
|
+
} finally {
|
|
16455
|
+
try {
|
|
16456
|
+
(0, import_node_fs15.rmSync)(emptyDir, { recursive: true, force: true });
|
|
16457
|
+
} catch {
|
|
16458
|
+
}
|
|
16459
|
+
}
|
|
16460
|
+
return !(0, import_node_fs15.existsSync)(targetPath);
|
|
16461
|
+
}
|
|
16462
|
+
(0, import_node_fs15.rmSync)(targetPath, { recursive: true, force: true });
|
|
16463
|
+
return !(0, import_node_fs15.existsSync)(targetPath);
|
|
16464
|
+
} catch {
|
|
16465
|
+
return false;
|
|
16466
|
+
}
|
|
16467
|
+
}
|
|
16468
|
+
async function applyNestedPluginTreeCleanup(paths, log) {
|
|
16469
|
+
if (paths.length === 0) return false;
|
|
16470
|
+
log(` \u21BB clearing ${paths.length} self-nested MMI plugin cache tree(s) (#1126)\u2026`);
|
|
16471
|
+
for (const path2 of paths) {
|
|
16472
|
+
if (!await clearNestedPluginTreeDir(path2)) return false;
|
|
16473
|
+
}
|
|
16474
|
+
return true;
|
|
16475
|
+
}
|
|
15860
16476
|
var gitignorePath = () => (0, import_node_path14.join)(process.cwd(), ".gitignore");
|
|
15861
16477
|
function readTextFile(path2) {
|
|
15862
16478
|
try {
|
|
@@ -16019,7 +16635,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
16019
16635
|
releasedVersion,
|
|
16020
16636
|
surface
|
|
16021
16637
|
});
|
|
16022
|
-
if (!installedVersionCheck.ok && repairFull) {
|
|
16638
|
+
if (!installedVersionCheck.ok && (repairFull || repairLocal)) {
|
|
16023
16639
|
const claudeStale = installedVersionCheck.staleSurfaces?.some((s) => s.surface === "claude") ?? false;
|
|
16024
16640
|
if (claudeStale && await applyClaudePluginHeal(surface, (m) => io.err(m))) {
|
|
16025
16641
|
const healed = buildInstalledPluginVersionCheck({
|
|
@@ -16060,13 +16676,33 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
16060
16676
|
};
|
|
16061
16677
|
}
|
|
16062
16678
|
checks.push(cacheCleanupCheck);
|
|
16063
|
-
|
|
16064
|
-
|
|
16065
|
-
|
|
16066
|
-
|
|
16067
|
-
|
|
16068
|
-
|
|
16069
|
-
|
|
16679
|
+
let nestedPluginTreeCheck = buildNestedPluginTreeCheck({
|
|
16680
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
16681
|
+
isWindows: isWin,
|
|
16682
|
+
entries: nestedPluginTreeSnapshot()
|
|
16683
|
+
});
|
|
16684
|
+
if (!nestedPluginTreeCheck.ok && nestedPluginTreeCheck.nested?.length && repairLocal) {
|
|
16685
|
+
const nestedPaths = nestedPluginTreeCheck.nested.map((n) => n.path);
|
|
16686
|
+
if (await applyNestedPluginTreeCleanup(nestedPaths, (m) => io.err(m))) {
|
|
16687
|
+
nestedPluginTreeCheck = buildNestedPluginTreeCheck({
|
|
16688
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
16689
|
+
isWindows: isWin,
|
|
16690
|
+
entries: nestedPluginTreeSnapshot()
|
|
16691
|
+
});
|
|
16692
|
+
if (nestedPluginTreeCheck.ok) {
|
|
16693
|
+
io.err(` \u21BB cleared self-nested MMI plugin cache tree(s) \u2014 reinstalling plugin\u2026`);
|
|
16694
|
+
}
|
|
16695
|
+
if (await applyClaudePluginHeal(surface, (m) => io.err(m))) {
|
|
16696
|
+
io.err(` \u21BB reinstalled MMI plugin after nested-cache cleanup \u2014 ${reloadAction(surface)} to load MMI commands`);
|
|
16697
|
+
nestedPluginTreeCheck = buildNestedPluginTreeCheck({
|
|
16698
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
16699
|
+
isWindows: isWin,
|
|
16700
|
+
entries: nestedPluginTreeSnapshot()
|
|
16701
|
+
});
|
|
16702
|
+
}
|
|
16703
|
+
}
|
|
16704
|
+
}
|
|
16705
|
+
checks.push(nestedPluginTreeCheck);
|
|
16070
16706
|
const cursorCacheRoot = cursorPluginCacheRoot();
|
|
16071
16707
|
let cursorPins = cursorPluginCachePinSnapshots() ?? [];
|
|
16072
16708
|
let cursorPluginCheck = buildCursorPluginInstallCheck({
|
package/dist/saga.cjs
CHANGED
|
@@ -3900,6 +3900,309 @@ function formatCaptureFailure(status, message) {
|
|
|
3900
3900
|
return `saga: HTTP ${status}`;
|
|
3901
3901
|
}
|
|
3902
3902
|
|
|
3903
|
+
// src/saga-snapshot.ts
|
|
3904
|
+
var GS_OPEN = "gs-open:";
|
|
3905
|
+
var GS_RESOLVED = "gs-resolved:";
|
|
3906
|
+
var GS_CEILING = "gs-ceiling:";
|
|
3907
|
+
var BS_DONE = "bs-done:";
|
|
3908
|
+
var BS_PROGRESS = "bs-progress:";
|
|
3909
|
+
var BS_BLOCKED = "bs-blocked:";
|
|
3910
|
+
var BS_TIER = "bs-tier:";
|
|
3911
|
+
var BS_CEILING = "bs-ceiling:";
|
|
3912
|
+
var BS_HALT = "bs-halt:";
|
|
3913
|
+
var GRIND_PREFIX = /^grind\s+/i;
|
|
3914
|
+
var META_ACTION_SEP = " | ";
|
|
3915
|
+
var LEGACY_BLOCKER_OPEN = /^blocker:(.+)$/i;
|
|
3916
|
+
var LEGACY_BLOCKER_RESOLVED = /^resolved:(.+)$/i;
|
|
3917
|
+
var LEGACY_CEILING = /^ceiling:(.+)$/i;
|
|
3918
|
+
var LEGACY_BUILD_DONE = /^done:(.+)$/i;
|
|
3919
|
+
var LEGACY_BUILD_PROGRESS = /^progress:(.+)$/i;
|
|
3920
|
+
var LEGACY_BUILD_BLOCKED = /^blocked:([^|]+)\|([^|]+)(?:\|(.+))?$/i;
|
|
3921
|
+
var LEGACY_BUILD_TIER = /^tier:([^|]+)\|([^|]+)(?:\|(.+))?$/i;
|
|
3922
|
+
var LEGACY_BUILD_CEILING = /^ceiling:([^|]+)\|(.+)$/i;
|
|
3923
|
+
var LEGACY_BUILD_HALT = /^halt:(.+)$/i;
|
|
3924
|
+
function isGrindSnapshotItem(text) {
|
|
3925
|
+
return text.startsWith(GS_OPEN) || text.startsWith(GS_RESOLVED) || text.startsWith(GS_CEILING) || LEGACY_BLOCKER_OPEN.test(text) || LEGACY_BLOCKER_RESOLVED.test(text) || LEGACY_CEILING.test(text);
|
|
3926
|
+
}
|
|
3927
|
+
function isBuildSnapshotItem(text) {
|
|
3928
|
+
return text.startsWith(BS_DONE) || text.startsWith(BS_PROGRESS) || text.startsWith(BS_BLOCKED) || text.startsWith(BS_TIER) || text.startsWith(BS_CEILING) || text.startsWith(BS_HALT) || LEGACY_BUILD_DONE.test(text) || LEGACY_BUILD_PROGRESS.test(text) || LEGACY_BUILD_BLOCKED.test(text) || LEGACY_BUILD_TIER.test(text) || LEGACY_BUILD_CEILING.test(text) || LEGACY_BUILD_HALT.test(text);
|
|
3929
|
+
}
|
|
3930
|
+
function snapshotClearIndices(kind, head) {
|
|
3931
|
+
const pred = kind === "grind" ? isGrindSnapshotItem : isBuildSnapshotItem;
|
|
3932
|
+
return (head.queued ?? []).map((item, index) => ({ item, index })).filter(({ item }) => !item.done && pred(item.text)).map(({ index }) => index);
|
|
3933
|
+
}
|
|
3934
|
+
function parseGrindNext(next) {
|
|
3935
|
+
if (!next?.trim()) return {};
|
|
3936
|
+
const raw = next.replace(GRIND_PREFIX, "").trim();
|
|
3937
|
+
const pipe = raw.indexOf(META_ACTION_SEP);
|
|
3938
|
+
const meta = pipe >= 0 ? raw.slice(0, pipe).trim() : raw;
|
|
3939
|
+
const nextAction = pipe >= 0 ? raw.slice(pipe + META_ACTION_SEP.length).trim() : void 0;
|
|
3940
|
+
const out = { nextAction };
|
|
3941
|
+
for (const part of meta.split(/\s+/)) {
|
|
3942
|
+
const eq = part.indexOf("=");
|
|
3943
|
+
if (eq < 0) continue;
|
|
3944
|
+
const k = part.slice(0, eq);
|
|
3945
|
+
const v = part.slice(eq + 1);
|
|
3946
|
+
if (k === "class") out.class = v;
|
|
3947
|
+
else if (k === "routing") out.routing = v;
|
|
3948
|
+
else if (k === "ultra") out.ultra = v;
|
|
3949
|
+
else if (k === "phase") out.phase = v;
|
|
3950
|
+
else if (k === "verify") {
|
|
3951
|
+
const [r, c] = v.split("/");
|
|
3952
|
+
const rn = Number(r);
|
|
3953
|
+
const cn = Number(c);
|
|
3954
|
+
if (Number.isFinite(rn)) out.verifyRound = rn;
|
|
3955
|
+
if (Number.isFinite(cn)) out.verifyCap = cn;
|
|
3956
|
+
} else if (k === "resolved" && v) {
|
|
3957
|
+
out.resolvedBlockerIds = v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
return out;
|
|
3961
|
+
}
|
|
3962
|
+
function formatGrindNext(s) {
|
|
3963
|
+
const parts = ["grind"];
|
|
3964
|
+
if (s.class) parts.push(`class=${s.class}`);
|
|
3965
|
+
if (s.routing) parts.push(`routing=${s.routing}`);
|
|
3966
|
+
if (s.ultra) parts.push(`ultra=${s.ultra}`);
|
|
3967
|
+
if (s.phase) parts.push(`phase=${s.phase}`);
|
|
3968
|
+
if (Number.isFinite(s.verifyRound) && Number.isFinite(s.verifyCap)) parts.push(`verify=${s.verifyRound}/${s.verifyCap}`);
|
|
3969
|
+
if (s.resolvedBlockerIds.length) parts.push(`resolved=${s.resolvedBlockerIds.join(",")}`);
|
|
3970
|
+
const meta = parts.join(" ");
|
|
3971
|
+
return s.nextAction ? `${meta}${META_ACTION_SEP}${s.nextAction}` : meta;
|
|
3972
|
+
}
|
|
3973
|
+
function parseGrindQueueItem(item, open, resolved, ceiling) {
|
|
3974
|
+
if (item.text.startsWith(GS_OPEN) && !item.done) open.push(item.text.slice(GS_OPEN.length).trim());
|
|
3975
|
+
else if (item.text.startsWith(GS_RESOLVED) && !item.done) resolved.push(item.text.slice(GS_RESOLVED.length).trim());
|
|
3976
|
+
else if (item.text.startsWith(GS_CEILING) && !item.done) ceiling.value = item.text.slice(GS_CEILING.length).trim();
|
|
3977
|
+
else {
|
|
3978
|
+
const legacyOpen = item.text.match(LEGACY_BLOCKER_OPEN);
|
|
3979
|
+
if (legacyOpen && !item.done) open.push(legacyOpen[1].trim());
|
|
3980
|
+
const legacyResolved = item.text.match(LEGACY_BLOCKER_RESOLVED);
|
|
3981
|
+
if (legacyResolved && item.done) resolved.push(legacyResolved[1].trim());
|
|
3982
|
+
const legacyCeil = item.text.match(LEGACY_CEILING);
|
|
3983
|
+
if (legacyCeil && !item.done) ceiling.value = legacyCeil[1].trim();
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
function parseGrindSnapshot(head) {
|
|
3987
|
+
const fromNext = parseGrindNext(head.next);
|
|
3988
|
+
const openBlockerIds = [];
|
|
3989
|
+
const resolvedFromQueue = [];
|
|
3990
|
+
const ceiling = { value: void 0 };
|
|
3991
|
+
for (const item of head.queued ?? []) {
|
|
3992
|
+
parseGrindQueueItem(item, openBlockerIds, resolvedFromQueue, ceiling);
|
|
3993
|
+
}
|
|
3994
|
+
const resolvedBlockerIds = [.../* @__PURE__ */ new Set([...resolvedFromQueue, ...fromNext.resolvedBlockerIds ?? []])];
|
|
3995
|
+
const { resolvedBlockerIds: _drop, ...restNext } = fromNext;
|
|
3996
|
+
return {
|
|
3997
|
+
kind: "grind",
|
|
3998
|
+
criteria: head.anchor?.intent,
|
|
3999
|
+
openBlockerIds,
|
|
4000
|
+
resolvedBlockerIds,
|
|
4001
|
+
verificationCeiling: ceiling.value,
|
|
4002
|
+
...restNext
|
|
4003
|
+
};
|
|
4004
|
+
}
|
|
4005
|
+
function parseBuildSnapshot(head) {
|
|
4006
|
+
const doneLastTurn = [];
|
|
4007
|
+
const inProgress = [];
|
|
4008
|
+
const blocked = [];
|
|
4009
|
+
const tierLedger = [];
|
|
4010
|
+
const verificationCeiling = [];
|
|
4011
|
+
let haltReason;
|
|
4012
|
+
for (const item of head.queued ?? []) {
|
|
4013
|
+
if (item.text.startsWith(BS_DONE) && !item.done) {
|
|
4014
|
+
doneLastTurn.push(item.text.slice(BS_DONE.length).trim());
|
|
4015
|
+
continue;
|
|
4016
|
+
}
|
|
4017
|
+
if (item.text.startsWith(BS_PROGRESS) && !item.done) {
|
|
4018
|
+
inProgress.push(item.text.slice(BS_PROGRESS.length).trim());
|
|
4019
|
+
continue;
|
|
4020
|
+
}
|
|
4021
|
+
if (item.text.startsWith(BS_BLOCKED) && !item.done) {
|
|
4022
|
+
const body = item.text.slice(BS_BLOCKED.length);
|
|
4023
|
+
const [site, decision, issue] = body.split("|");
|
|
4024
|
+
blocked.push({ site: site?.trim() ?? "", decision: decision?.trim() ?? "", issue: issue?.trim() });
|
|
4025
|
+
continue;
|
|
4026
|
+
}
|
|
4027
|
+
if (item.text.startsWith(BS_TIER) && !item.done) {
|
|
4028
|
+
const body = item.text.slice(BS_TIER.length);
|
|
4029
|
+
const [site, tier2, signals] = body.split("|");
|
|
4030
|
+
tierLedger.push({ site: site?.trim() ?? "", tier: tier2?.trim() ?? "", signals: signals?.trim() });
|
|
4031
|
+
continue;
|
|
4032
|
+
}
|
|
4033
|
+
if (item.text.startsWith(BS_CEILING) && !item.done) {
|
|
4034
|
+
const body = item.text.slice(BS_CEILING.length);
|
|
4035
|
+
const [site, rung] = body.split("|");
|
|
4036
|
+
verificationCeiling.push({ site: site?.trim() ?? "", rung: rung?.trim() ?? "" });
|
|
4037
|
+
continue;
|
|
4038
|
+
}
|
|
4039
|
+
if (item.text.startsWith(BS_HALT) && !item.done) {
|
|
4040
|
+
haltReason = item.text.slice(BS_HALT.length).trim();
|
|
4041
|
+
continue;
|
|
4042
|
+
}
|
|
4043
|
+
if (item.done) {
|
|
4044
|
+
const d = item.text.match(LEGACY_BUILD_DONE);
|
|
4045
|
+
if (d) doneLastTurn.push(d[1].trim());
|
|
4046
|
+
continue;
|
|
4047
|
+
}
|
|
4048
|
+
const prog = item.text.match(LEGACY_BUILD_PROGRESS);
|
|
4049
|
+
if (prog) {
|
|
4050
|
+
inProgress.push(prog[1].trim());
|
|
4051
|
+
continue;
|
|
4052
|
+
}
|
|
4053
|
+
const blk = item.text.match(LEGACY_BUILD_BLOCKED);
|
|
4054
|
+
if (blk) {
|
|
4055
|
+
blocked.push({ site: blk[1].trim(), decision: blk[2].trim(), issue: blk[3]?.trim() });
|
|
4056
|
+
continue;
|
|
4057
|
+
}
|
|
4058
|
+
const tier = item.text.match(LEGACY_BUILD_TIER);
|
|
4059
|
+
if (tier) {
|
|
4060
|
+
tierLedger.push({ site: tier[1].trim(), tier: tier[2].trim(), signals: tier[3]?.trim() });
|
|
4061
|
+
continue;
|
|
4062
|
+
}
|
|
4063
|
+
const ceil = item.text.match(LEGACY_BUILD_CEILING);
|
|
4064
|
+
if (ceil) {
|
|
4065
|
+
verificationCeiling.push({ site: ceil[1].trim(), rung: ceil[2].trim() });
|
|
4066
|
+
continue;
|
|
4067
|
+
}
|
|
4068
|
+
const halt = item.text.match(LEGACY_BUILD_HALT);
|
|
4069
|
+
if (halt) haltReason = halt[1].trim();
|
|
4070
|
+
}
|
|
4071
|
+
const nextFrontier = [];
|
|
4072
|
+
if (head.next?.trim()) {
|
|
4073
|
+
for (const line of head.next.split(";").map((s) => s.trim()).filter(Boolean)) {
|
|
4074
|
+
nextFrontier.push(line);
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
return {
|
|
4078
|
+
kind: "build",
|
|
4079
|
+
milestone: head.anchor?.intent,
|
|
4080
|
+
northStarSlug: head.anchor?.slug,
|
|
4081
|
+
doneLastTurn,
|
|
4082
|
+
inProgress,
|
|
4083
|
+
blocked,
|
|
4084
|
+
nextFrontier,
|
|
4085
|
+
tierLedger,
|
|
4086
|
+
verificationCeiling,
|
|
4087
|
+
haltReason
|
|
4088
|
+
};
|
|
4089
|
+
}
|
|
4090
|
+
function parseSnapshot(kind, head) {
|
|
4091
|
+
return kind === "grind" ? parseGrindSnapshot(head) : parseBuildSnapshot(head);
|
|
4092
|
+
}
|
|
4093
|
+
function strOrU(v) {
|
|
4094
|
+
return typeof v === "string" && v.length ? v : void 0;
|
|
4095
|
+
}
|
|
4096
|
+
function numOrU(v) {
|
|
4097
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
4098
|
+
}
|
|
4099
|
+
function asStringArray(v) {
|
|
4100
|
+
return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
4101
|
+
}
|
|
4102
|
+
function normalizeSnapshot(kind, raw) {
|
|
4103
|
+
if (!raw || typeof raw !== "object") throw new Error("snapshot JSON must be an object");
|
|
4104
|
+
const o = raw;
|
|
4105
|
+
if (o.kind !== kind) throw new Error(`snapshot kind ${JSON.stringify(o.kind)} does not match --kind ${kind}`);
|
|
4106
|
+
if (kind === "grind") {
|
|
4107
|
+
return {
|
|
4108
|
+
kind: "grind",
|
|
4109
|
+
class: strOrU(o.class),
|
|
4110
|
+
routing: strOrU(o.routing),
|
|
4111
|
+
ultra: strOrU(o.ultra),
|
|
4112
|
+
criteria: strOrU(o.criteria),
|
|
4113
|
+
phase: strOrU(o.phase),
|
|
4114
|
+
verifyRound: numOrU(o.verifyRound),
|
|
4115
|
+
verifyCap: numOrU(o.verifyCap),
|
|
4116
|
+
openBlockerIds: asStringArray(o.openBlockerIds),
|
|
4117
|
+
resolvedBlockerIds: asStringArray(o.resolvedBlockerIds),
|
|
4118
|
+
verificationCeiling: strOrU(o.verificationCeiling),
|
|
4119
|
+
nextAction: strOrU(o.nextAction)
|
|
4120
|
+
};
|
|
4121
|
+
}
|
|
4122
|
+
const blocked = Array.isArray(o.blocked) ? o.blocked.filter((b) => !!b && typeof b === "object").map((b) => ({ site: strOrU(b.site) ?? "", decision: strOrU(b.decision) ?? "", issue: strOrU(b.issue) })) : [];
|
|
4123
|
+
const tierLedger = Array.isArray(o.tierLedger) ? o.tierLedger.filter((t) => !!t && typeof t === "object").map((t) => ({ site: strOrU(t.site) ?? "", tier: strOrU(t.tier) ?? "", signals: strOrU(t.signals) })) : [];
|
|
4124
|
+
const verificationCeiling = Array.isArray(o.verificationCeiling) ? o.verificationCeiling.filter((v) => !!v && typeof v === "object").map((v) => ({ site: strOrU(v.site) ?? "", rung: strOrU(v.rung) ?? "" })) : [];
|
|
4125
|
+
return {
|
|
4126
|
+
kind: "build",
|
|
4127
|
+
milestone: strOrU(o.milestone),
|
|
4128
|
+
northStarSlug: strOrU(o.northStarSlug),
|
|
4129
|
+
doneLastTurn: asStringArray(o.doneLastTurn),
|
|
4130
|
+
inProgress: asStringArray(o.inProgress),
|
|
4131
|
+
blocked,
|
|
4132
|
+
nextFrontier: asStringArray(o.nextFrontier),
|
|
4133
|
+
tierLedger,
|
|
4134
|
+
verificationCeiling,
|
|
4135
|
+
haltReason: strOrU(o.haltReason)
|
|
4136
|
+
};
|
|
4137
|
+
}
|
|
4138
|
+
function planGrindSnapshotWrite(s, summary = "grind snapshot refresh") {
|
|
4139
|
+
const queueOps = [];
|
|
4140
|
+
for (const id of s.openBlockerIds) queueOps.push({ text: `${GS_OPEN}${id}` });
|
|
4141
|
+
for (const id of s.resolvedBlockerIds) queueOps.push({ text: `${GS_RESOLVED}${id}` });
|
|
4142
|
+
if (s.verificationCeiling) queueOps.push({ text: `${GS_CEILING}${s.verificationCeiling}` });
|
|
4143
|
+
return {
|
|
4144
|
+
summary,
|
|
4145
|
+
next: formatGrindNext(s),
|
|
4146
|
+
anchor: s.criteria,
|
|
4147
|
+
queueOps,
|
|
4148
|
+
clearIndices: []
|
|
4149
|
+
};
|
|
4150
|
+
}
|
|
4151
|
+
function planBuildSnapshotWrite(s, summary = "build snapshot refresh") {
|
|
4152
|
+
const queueOps = [];
|
|
4153
|
+
for (const site of s.doneLastTurn) queueOps.push({ text: `${BS_DONE}${site}` });
|
|
4154
|
+
for (const site of s.inProgress) queueOps.push({ text: `${BS_PROGRESS}${site}` });
|
|
4155
|
+
for (const b of s.blocked) {
|
|
4156
|
+
queueOps.push({ text: `${BS_BLOCKED}${b.site}|${b.decision}${b.issue ? `|${b.issue}` : ""}` });
|
|
4157
|
+
}
|
|
4158
|
+
for (const t of s.tierLedger) {
|
|
4159
|
+
queueOps.push({ text: `${BS_TIER}${t.site}|${t.tier}${t.signals ? `|${t.signals}` : ""}` });
|
|
4160
|
+
}
|
|
4161
|
+
for (const v of s.verificationCeiling) queueOps.push({ text: `${BS_CEILING}${v.site}|${v.rung}` });
|
|
4162
|
+
if (s.haltReason) queueOps.push({ text: `${BS_HALT}${s.haltReason}` });
|
|
4163
|
+
return {
|
|
4164
|
+
summary,
|
|
4165
|
+
next: s.nextFrontier.length ? s.nextFrontier.join("; ") : void 0,
|
|
4166
|
+
anchor: s.milestone,
|
|
4167
|
+
anchorSlug: s.northStarSlug,
|
|
4168
|
+
queueOps,
|
|
4169
|
+
clearIndices: []
|
|
4170
|
+
};
|
|
4171
|
+
}
|
|
4172
|
+
function planSnapshotWrite(snapshot, head, summary) {
|
|
4173
|
+
const plan = snapshot.kind === "grind" ? planGrindSnapshotWrite(snapshot, summary) : planBuildSnapshotWrite(snapshot, summary);
|
|
4174
|
+
plan.clearIndices = snapshotClearIndices(snapshot.kind, head);
|
|
4175
|
+
return plan;
|
|
4176
|
+
}
|
|
4177
|
+
function formatSnapshotHuman(snapshot) {
|
|
4178
|
+
if (snapshot.kind === "grind") {
|
|
4179
|
+
const lines2 = [
|
|
4180
|
+
`kind: grind`,
|
|
4181
|
+
snapshot.class ? `class: ${snapshot.class}` : "",
|
|
4182
|
+
snapshot.routing ? `routing: ${snapshot.routing}` : "",
|
|
4183
|
+
snapshot.ultra ? `ultra: ${snapshot.ultra}` : "",
|
|
4184
|
+
snapshot.criteria ? `criteria: ${snapshot.criteria}` : "",
|
|
4185
|
+
snapshot.phase ? `phase: ${snapshot.phase}` : "",
|
|
4186
|
+
snapshot.verifyRound != null ? `verify: ${snapshot.verifyRound}/${snapshot.verifyCap ?? "?"}` : "",
|
|
4187
|
+
snapshot.openBlockerIds.length ? `open blockers: ${snapshot.openBlockerIds.join(", ")}` : "open blockers: (none)",
|
|
4188
|
+
snapshot.resolvedBlockerIds.length ? `resolved: ${snapshot.resolvedBlockerIds.join(", ")}` : "",
|
|
4189
|
+
snapshot.verificationCeiling ? `ceiling: ${snapshot.verificationCeiling}` : "",
|
|
4190
|
+
snapshot.nextAction ? `next: ${snapshot.nextAction}` : ""
|
|
4191
|
+
].filter(Boolean);
|
|
4192
|
+
return lines2.join("\n");
|
|
4193
|
+
}
|
|
4194
|
+
const lines = [
|
|
4195
|
+
`kind: build`,
|
|
4196
|
+
snapshot.milestone ? `milestone: ${snapshot.milestone}` : "",
|
|
4197
|
+
snapshot.northStarSlug ? `north star: ${snapshot.northStarSlug}` : "",
|
|
4198
|
+
snapshot.doneLastTurn.length ? `done: ${snapshot.doneLastTurn.join(", ")}` : "",
|
|
4199
|
+
snapshot.inProgress.length ? `in progress: ${snapshot.inProgress.join(", ")}` : "",
|
|
4200
|
+
snapshot.nextFrontier.length ? `next frontier: ${snapshot.nextFrontier.join("; ")}` : "",
|
|
4201
|
+
snapshot.haltReason ? `halt: ${snapshot.haltReason}` : ""
|
|
4202
|
+
].filter(Boolean);
|
|
4203
|
+
return lines.join("\n");
|
|
4204
|
+
}
|
|
4205
|
+
|
|
3903
4206
|
// src/hub-auth.ts
|
|
3904
4207
|
var import_node_crypto = require("node:crypto");
|
|
3905
4208
|
var import_node_fs5 = require("node:fs");
|
|
@@ -4321,6 +4624,60 @@ async function runSagaHealth(o, io = consoleIo) {
|
|
|
4321
4624
|
if (report.warnings.length) io.log(report.warnings.map((w) => ` - ${w}`).join("\n"));
|
|
4322
4625
|
if (report.pendingNotes > 0) io.log(` - ${report.pendingNotes} note(s) queued locally \u2014 \`mmi-cli saga flush\` to roll forward`);
|
|
4323
4626
|
}
|
|
4627
|
+
async function fetchSagaHead(io = consoleIo) {
|
|
4628
|
+
const cfg = await loadConfig();
|
|
4629
|
+
if (!cfg.sagaApiUrl) {
|
|
4630
|
+
io.err("saga snapshot: Hub API URL not configured");
|
|
4631
|
+
return null;
|
|
4632
|
+
}
|
|
4633
|
+
try {
|
|
4634
|
+
const key = await sagaKey(cfg);
|
|
4635
|
+
const qs = new URLSearchParams(key).toString();
|
|
4636
|
+
const res = await fetch(`${cfg.sagaApiUrl}/saga/state?${qs}`, { headers: await hubHeaders(), signal: AbortSignal.timeout(8e3) });
|
|
4637
|
+
if (!res.ok) {
|
|
4638
|
+
io.err(`saga snapshot: HTTP ${res.status}`);
|
|
4639
|
+
return null;
|
|
4640
|
+
}
|
|
4641
|
+
const state = await res.json();
|
|
4642
|
+
return state.head ?? {};
|
|
4643
|
+
} catch (e) {
|
|
4644
|
+
io.err(`saga snapshot: ${e.message}`);
|
|
4645
|
+
return null;
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
async function postSnapshotNotes(plan, anchorForce) {
|
|
4649
|
+
const [sha, key] = await Promise.all([gitOut(["rev-parse", "--short", "HEAD"]), sagaKey(await loadConfig())]);
|
|
4650
|
+
const evidence = { sha: sha || void 0, branch: key.branch };
|
|
4651
|
+
for (const idx of [...plan.clearIndices].sort((a, b) => b - a)) {
|
|
4652
|
+
const cap = buildNoteCapture("snapshot retire", { queueDone: String(idx) }, (0, import_node_crypto3.randomUUID)(), evidence);
|
|
4653
|
+
await postCapture(cap);
|
|
4654
|
+
}
|
|
4655
|
+
const primary = buildNoteCapture(plan.summary, {
|
|
4656
|
+
next: plan.next,
|
|
4657
|
+
anchor: plan.anchor,
|
|
4658
|
+
anchorSlug: plan.anchorSlug,
|
|
4659
|
+
anchorForce
|
|
4660
|
+
}, (0, import_node_crypto3.randomUUID)(), evidence);
|
|
4661
|
+
await postCapture(primary);
|
|
4662
|
+
for (const op of plan.queueOps) {
|
|
4663
|
+
const cap = buildNoteCapture("snapshot checklist", { queueAdd: op.text }, (0, import_node_crypto3.randomUUID)(), evidence);
|
|
4664
|
+
await postCapture(cap);
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
async function runSagaSnapshotShow(opts, io = consoleIo) {
|
|
4668
|
+
const head = await fetchSagaHead(io);
|
|
4669
|
+
if (!head) return;
|
|
4670
|
+
const snapshot = parseSnapshot(opts.kind, head);
|
|
4671
|
+
if (opts.json) return io.log(JSON.stringify(snapshot, null, 2));
|
|
4672
|
+
io.log(formatSnapshotHuman(snapshot));
|
|
4673
|
+
}
|
|
4674
|
+
async function runSagaSnapshotSet(snapshot, opts = {}, io = consoleIo) {
|
|
4675
|
+
const head = await fetchSagaHead(io) ?? { queued: [] };
|
|
4676
|
+
const plan = planSnapshotWrite(snapshot, head);
|
|
4677
|
+
await postSnapshotNotes(plan, opts.anchorForce);
|
|
4678
|
+
if (opts.json) return io.log(JSON.stringify({ ok: true, snapshot, retired: plan.clearIndices.length, queued: plan.queueOps.length + 1 }));
|
|
4679
|
+
io.log(`saga snapshot: wrote ${snapshot.kind} snapshot (retired ${plan.clearIndices.length}, ${plan.queueOps.length + 1} capture(s))`);
|
|
4680
|
+
}
|
|
4324
4681
|
function registerSagaCommands(program2) {
|
|
4325
4682
|
const saga = program2.command("saga").description("per-session continuity");
|
|
4326
4683
|
saga.command("note [summary]").description("record a one-line structured note into your saga (the per-turn capture)").option("--next <text>", 'set "where I left off" (NEXT)').option("--decision <text>", "append a verbatim decision").option("--queue-add <text>", "add a worklist item").option("--queue-done <n>", "mark worklist item N done").option("--verified", "mark this claim as checked against source (state: verified, else asserted)").option("--diagnostic", "isolate a probe write (state: diagnostic, source: probe) \u2014 never resume/LAST 5").option("--supersedes <key>", "retire prior decisions matching an evidence key (pr:N | file:path)").option("--anchor <intent>", "set the sprint North-Star (write-protected; needs --anchor-force to change)").option("--anchor-slug <slug>", "bind the anchor to a North Star plan slug (SSOT at plans/.../<slug>.md)").option("--anchor-force", "overwrite an existing anchor").option("--message-file <path|->", "read the summary from a UTF-8 file, or from stdin with - (avoids cmd.exe quoting)").action(async (summary, o) => {
|
|
@@ -4386,6 +4743,69 @@ function registerSagaCommands(program2) {
|
|
|
4386
4743
|
console.log(`project=${key.project} branch=${key.branch} session=${key.sessionId} source=${source}`);
|
|
4387
4744
|
});
|
|
4388
4745
|
saga.command("health").option("--json", "machine-readable output").option("--banner", "one-line SessionStart banner; silent when healthy").option("--quiet", "suppress detail output").description("zero-write health check: auth, backend reachability, resolved key").action((o) => runSagaHealth(o));
|
|
4746
|
+
const snapshot = saga.command("snapshot").description("structured grind/build resume snapshot over saga HEAD");
|
|
4747
|
+
snapshot.command("show").description("read the structured resume snapshot from current saga HEAD").requiredOption("--kind <kind>", "grind | build").option("--json", "output JSON").action(async (o) => {
|
|
4748
|
+
const kind = o.kind;
|
|
4749
|
+
if (kind !== "grind" && kind !== "build") return fail("saga snapshot show: --kind must be grind or build");
|
|
4750
|
+
await runSagaSnapshotShow({ kind, json: o.json });
|
|
4751
|
+
});
|
|
4752
|
+
snapshot.command("set").description("write a structured resume snapshot (maps to saga note HEAD primitives)").requiredOption("--kind <kind>", "grind | build").option("--json-file <path>", "full snapshot JSON (overrides field flags)").option("--anchor-force", "overwrite an existing anchor").option("--json", "output result JSON").option("--class <c>", "grind class").option("--routing <r>", "grind routing tier").option("--ultra <u>", "ultra mode").option("--criteria <text>", "grind criteria / frame").option("--phase <p>", "phase reached").option("--verify-round <n>", "verify round", (v) => parseInt(v, 10)).option("--verify-cap <n>", "verify cap", (v) => parseInt(v, 10)).option("--open-blocker <id>", "open blocker id (repeatable)", (v, prev) => [...prev, v], []).option("--resolved-blocker <id>", "resolved blocker id (repeatable)", (v, prev) => [...prev, v], []).option("--verification-ceiling <text>", "verification ceiling").option("--next-action <text>", "next action").option("--milestone <text>", "build milestone one-liner").option("--north-star-slug <slug>", "North Star slug").option("--done <site>", "done last turn site (repeatable)", (v, prev) => [...prev, v], []).option("--in-progress <site>", "in-progress site (repeatable)", (v, prev) => [...prev, v], []).option("--blocked <spec>", "site|decision|issue (repeatable)", (v, prev) => [...prev, v], []).option("--next-frontier <item>", "frontier item (repeatable)", (v, prev) => [...prev, v], []).option("--tier <spec>", "site|tier|signals (repeatable)", (v, prev) => [...prev, v], []).option("--site-ceiling <spec>", "site|rung (repeatable)", (v, prev) => [...prev, v], []).option("--halt-reason <text>", "halt reason").action(async (o) => {
|
|
4753
|
+
const kind = o.kind;
|
|
4754
|
+
if (kind !== "grind" && kind !== "build") return fail("saga snapshot set: --kind must be grind or build");
|
|
4755
|
+
let snap;
|
|
4756
|
+
if (o.jsonFile) {
|
|
4757
|
+
const raw = await (0, import_promises2.readFile)(o.jsonFile, "utf8");
|
|
4758
|
+
let parsed;
|
|
4759
|
+
try {
|
|
4760
|
+
parsed = JSON.parse(raw);
|
|
4761
|
+
} catch (e) {
|
|
4762
|
+
return fail(`saga snapshot set: invalid JSON in ${o.jsonFile}: ${e.message}`);
|
|
4763
|
+
}
|
|
4764
|
+
try {
|
|
4765
|
+
snap = normalizeSnapshot(kind, parsed);
|
|
4766
|
+
} catch (e) {
|
|
4767
|
+
return fail(`saga snapshot set: ${e.message}`);
|
|
4768
|
+
}
|
|
4769
|
+
} else if (kind === "grind") {
|
|
4770
|
+
snap = {
|
|
4771
|
+
kind: "grind",
|
|
4772
|
+
class: o.class,
|
|
4773
|
+
routing: o.routing,
|
|
4774
|
+
ultra: o.ultra,
|
|
4775
|
+
criteria: o.criteria,
|
|
4776
|
+
phase: o.phase,
|
|
4777
|
+
verifyRound: o.verifyRound,
|
|
4778
|
+
verifyCap: o.verifyCap,
|
|
4779
|
+
openBlockerIds: o.openBlocker ?? [],
|
|
4780
|
+
resolvedBlockerIds: o.resolvedBlocker ?? [],
|
|
4781
|
+
verificationCeiling: o.verificationCeiling,
|
|
4782
|
+
nextAction: o.nextAction
|
|
4783
|
+
};
|
|
4784
|
+
} else {
|
|
4785
|
+
snap = {
|
|
4786
|
+
kind: "build",
|
|
4787
|
+
milestone: o.milestone,
|
|
4788
|
+
northStarSlug: o.northStarSlug,
|
|
4789
|
+
doneLastTurn: o.done ?? [],
|
|
4790
|
+
inProgress: o.inProgress ?? [],
|
|
4791
|
+
blocked: (o.blocked ?? []).map((spec) => {
|
|
4792
|
+
const [site, decision, issue] = spec.split("|");
|
|
4793
|
+
return { site: site ?? "", decision: decision ?? "", issue };
|
|
4794
|
+
}),
|
|
4795
|
+
nextFrontier: o.nextFrontier ?? [],
|
|
4796
|
+
tierLedger: (o.tier ?? []).map((spec) => {
|
|
4797
|
+
const [site, tier, signals] = spec.split("|");
|
|
4798
|
+
return { site: site ?? "", tier: tier ?? "", signals };
|
|
4799
|
+
}),
|
|
4800
|
+
verificationCeiling: (o.siteCeiling ?? []).map((spec) => {
|
|
4801
|
+
const [site, rung] = spec.split("|");
|
|
4802
|
+
return { site: site ?? "", rung: rung ?? "" };
|
|
4803
|
+
}),
|
|
4804
|
+
haltReason: o.haltReason
|
|
4805
|
+
};
|
|
4806
|
+
}
|
|
4807
|
+
await runSagaSnapshotSet(snap, { anchorForce: o.anchorForce, json: o.json });
|
|
4808
|
+
});
|
|
4389
4809
|
}
|
|
4390
4810
|
|
|
4391
4811
|
// src/saga-boot.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.34.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",
|