@mutmutco/cli 2.32.4 → 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 +780 -153
- 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
|
|
@@ -7229,7 +7649,7 @@ ${buildReportBody(body, sourceRepo)}`;
|
|
|
7229
7649
|
|
|
7230
7650
|
// src/skill-lesson.ts
|
|
7231
7651
|
var SKILL_LESSON_LABEL = "skill-lesson";
|
|
7232
|
-
var SKILL_NAMES = ["bootstrap", "browser-automation", "grind", "hotfix", "mmi", "rcand", "release", "secrets", "stage"];
|
|
7652
|
+
var SKILL_NAMES = ["bootstrap", "browser-automation", "build", "grind", "hotfix", "mmi", "rcand", "release", "secrets", "stage"];
|
|
7233
7653
|
function assertSkillName(name) {
|
|
7234
7654
|
const match = SKILL_NAMES.find((skill) => skill === name);
|
|
7235
7655
|
if (!match) throw new Error(`unknown skill "${name}" \u2014 expected one of: ${SKILL_NAMES.join(", ")}`);
|
|
@@ -7355,127 +7775,173 @@ function buildPanelPlan(input) {
|
|
|
7355
7775
|
};
|
|
7356
7776
|
}
|
|
7357
7777
|
|
|
7358
|
-
// src/
|
|
7359
|
-
var
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7778
|
+
// src/build-policy.ts
|
|
7779
|
+
var TIER_BUDGETS = {
|
|
7780
|
+
light: {
|
|
7781
|
+
tier: "light",
|
|
7782
|
+
agents: 2,
|
|
7783
|
+
planners: 0,
|
|
7784
|
+
parallelSitesCap: 1,
|
|
7785
|
+
verificationDepth: "panel-once",
|
|
7786
|
+
reasoningEffort: "standard"
|
|
7787
|
+
},
|
|
7788
|
+
standard: {
|
|
7789
|
+
tier: "standard",
|
|
7790
|
+
agents: 3,
|
|
7791
|
+
planners: 0,
|
|
7792
|
+
parallelSitesCap: 2,
|
|
7793
|
+
verificationDepth: "full-panel",
|
|
7794
|
+
reasoningEffort: "standard-high"
|
|
7795
|
+
},
|
|
7796
|
+
deep: {
|
|
7797
|
+
tier: "deep",
|
|
7798
|
+
agents: 4,
|
|
7799
|
+
planners: 2,
|
|
7800
|
+
parallelSitesCap: 3,
|
|
7801
|
+
verificationDepth: "full-double-pass",
|
|
7802
|
+
reasoningEffort: "high"
|
|
7803
|
+
},
|
|
7804
|
+
max: {
|
|
7805
|
+
tier: "max",
|
|
7806
|
+
agents: 5,
|
|
7807
|
+
planners: 3,
|
|
7808
|
+
parallelSitesCap: 4,
|
|
7809
|
+
verificationDepth: "integration-checkpoints",
|
|
7810
|
+
reasoningEffort: "max"
|
|
7811
|
+
}
|
|
7372
7812
|
};
|
|
7373
|
-
function
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7813
|
+
function getTierBudgets(tier) {
|
|
7814
|
+
return TIER_BUDGETS[tier];
|
|
7815
|
+
}
|
|
7816
|
+
function isWideOrArchitectural(scope) {
|
|
7817
|
+
return scope === "wide" || scope === "architectural";
|
|
7818
|
+
}
|
|
7819
|
+
function isCrossModuleOrProduct(blast) {
|
|
7820
|
+
return blast === "cross-module" || blast === "product";
|
|
7821
|
+
}
|
|
7822
|
+
function pickEffortTier(signals) {
|
|
7823
|
+
if (signals.explicitTier) {
|
|
7824
|
+
return {
|
|
7825
|
+
tier: signals.explicitTier,
|
|
7826
|
+
reason: `explicit user override \u2192 ${signals.explicitTier}`,
|
|
7827
|
+
budgets: getTierBudgets(signals.explicitTier)
|
|
7828
|
+
};
|
|
7389
7829
|
}
|
|
7390
|
-
if (
|
|
7391
|
-
|
|
7830
|
+
if (signals.risk === "critical") {
|
|
7831
|
+
return { tier: "max", reason: "critical risk", budgets: getTierBudgets("max") };
|
|
7392
7832
|
}
|
|
7393
|
-
|
|
7394
|
-
webSearch: Boolean(input.toolPolicy?.webSearch),
|
|
7395
|
-
maxQueriesPerLens: input.toolPolicy?.maxQueriesPerLens ?? 3,
|
|
7396
|
-
denyDomains: input.toolPolicy?.denyDomains ?? []
|
|
7397
|
-
};
|
|
7398
|
-
return {
|
|
7399
|
-
provider,
|
|
7400
|
-
routing,
|
|
7401
|
-
lenses,
|
|
7402
|
-
models,
|
|
7403
|
-
toolPolicy,
|
|
7404
|
-
criteria: input.criteria,
|
|
7405
|
-
diff: input.diff,
|
|
7406
|
-
fallback: "host-panel",
|
|
7407
|
-
instructions: "Hosted fusion when provider is configured; else spawn host lenses and pipe JSON to `mmi-cli verify synthesize`. Synthesizer slot must differ from builder."
|
|
7408
|
-
};
|
|
7409
|
-
}
|
|
7410
|
-
function adaptFusionResponse(raw) {
|
|
7411
|
-
if (raw.lenses) {
|
|
7412
|
-
const lenses = parseLensResults(raw.lenses);
|
|
7413
|
-
const base2 = synthesizePanelReport(lenses);
|
|
7833
|
+
if (signals.foundational && (isWideOrArchitectural(signals.scope) || signals.blastRadius === "product")) {
|
|
7414
7834
|
return {
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
partial_coverage: raw.partial_coverage ?? base2.partial_coverage,
|
|
7419
|
-
unique_insights: raw.unique_insights ?? base2.unique_insights,
|
|
7420
|
-
blind_spots: raw.blind_spots ?? base2.blind_spots,
|
|
7421
|
-
nits: raw.nits ?? base2.nits
|
|
7835
|
+
tier: "max",
|
|
7836
|
+
reason: "foundational seam with wide/architectural scope or product blast radius",
|
|
7837
|
+
budgets: getTierBudgets("max")
|
|
7422
7838
|
};
|
|
7423
7839
|
}
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7840
|
+
if (signals.risk === "high") {
|
|
7841
|
+
return { tier: "deep", reason: "high risk", budgets: getTierBudgets("deep") };
|
|
7842
|
+
}
|
|
7843
|
+
if (signals.ambiguity === "high") {
|
|
7844
|
+
return { tier: "deep", reason: "high ambiguity", budgets: getTierBudgets("deep") };
|
|
7845
|
+
}
|
|
7846
|
+
if (signals.scope === "architectural") {
|
|
7847
|
+
return { tier: "deep", reason: "architectural scope", budgets: getTierBudgets("deep") };
|
|
7848
|
+
}
|
|
7849
|
+
if (isCrossModuleOrProduct(signals.blastRadius)) {
|
|
7850
|
+
return {
|
|
7851
|
+
tier: "deep",
|
|
7852
|
+
reason: `${signals.blastRadius} blast radius`,
|
|
7853
|
+
budgets: getTierBudgets("deep")
|
|
7854
|
+
};
|
|
7855
|
+
}
|
|
7856
|
+
if (signals.scope === "trivial" && signals.ambiguity === "low" && signals.risk === "low") {
|
|
7857
|
+
return {
|
|
7858
|
+
tier: "light",
|
|
7859
|
+
reason: "trivial scope with low risk and low ambiguity",
|
|
7860
|
+
budgets: getTierBudgets("light")
|
|
7861
|
+
};
|
|
7862
|
+
}
|
|
7863
|
+
return { tier: "standard", reason: "default \u2014 normal slice", budgets: getTierBudgets("standard") };
|
|
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}`;
|
|
7432
7882
|
return {
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
unique_insights: raw.unique_insights ?? [],
|
|
7437
|
-
blind_spots: raw.blind_spots ?? [],
|
|
7438
|
-
blockers,
|
|
7439
|
-
nits: raw.nits ?? []
|
|
7883
|
+
costUnits,
|
|
7884
|
+
breakdown,
|
|
7885
|
+
exceedsCeiling: costUnits > COST_CEILING
|
|
7440
7886
|
};
|
|
7441
7887
|
}
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
if (
|
|
7445
|
-
|
|
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" };
|
|
7446
7912
|
}
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
const headers = { "content-type": "application/json" };
|
|
7450
|
-
if (apiKey) headers.authorization = `Bearer ${apiKey}`;
|
|
7451
|
-
try {
|
|
7452
|
-
const res = await fetchImpl(url, {
|
|
7453
|
-
method: "POST",
|
|
7454
|
-
headers,
|
|
7455
|
-
body: JSON.stringify({
|
|
7456
|
-
routing: plan2.routing,
|
|
7457
|
-
lenses: plan2.lenses,
|
|
7458
|
-
models: plan2.models,
|
|
7459
|
-
toolPolicy: plan2.toolPolicy,
|
|
7460
|
-
criteria: plan2.criteria,
|
|
7461
|
-
diff: plan2.diff
|
|
7462
|
-
}),
|
|
7463
|
-
signal: AbortSignal.timeout(3e4)
|
|
7464
|
-
});
|
|
7465
|
-
if (!res.ok) {
|
|
7466
|
-
return { ok: false, source: "fallback", error: `provider HTTP ${res.status}` };
|
|
7467
|
-
}
|
|
7468
|
-
const body = await res.json();
|
|
7469
|
-
return { ok: true, source: "hosted-fusion", report: adaptFusionResponse(body) };
|
|
7470
|
-
} catch (e) {
|
|
7471
|
-
return { ok: false, source: "fallback", error: e.message };
|
|
7913
|
+
if (capHit) {
|
|
7914
|
+
return { frontierExhausted: false, iterationCapExceeded: true, iterationCap, recommend: "halt", haltReason: "iteration-cap" };
|
|
7472
7915
|
}
|
|
7916
|
+
return { frontierExhausted: false, iterationCapExceeded: false, iterationCap, recommend: "continue" };
|
|
7473
7917
|
}
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
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
|
+
};
|
|
7479
7945
|
}
|
|
7480
7946
|
|
|
7481
7947
|
// src/gc.ts
|
|
@@ -8661,7 +9127,7 @@ function buildMmiPluginCacheCleanupCheck(input) {
|
|
|
8661
9127
|
};
|
|
8662
9128
|
}
|
|
8663
9129
|
var NESTED_PLUGIN_TREE_LABEL = "self-nested MMI plugin cache tree (#1126)";
|
|
8664
|
-
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)";
|
|
8665
9131
|
function nestedPluginTreeCleanupCommand(paths, isWindows) {
|
|
8666
9132
|
if (paths.length === 0) return "";
|
|
8667
9133
|
if (isWindows) {
|
|
@@ -10704,9 +11170,8 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
|
|
|
10704
11170
|
if (releaseExists) {
|
|
10705
11171
|
releaseNote = `Release ${tag} already exists \u2014 resumed without recreating`;
|
|
10706
11172
|
} else {
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
releaseNote = `Release ${tag} created (target ${tagCommit.slice(0, 7)})`;
|
|
11173
|
+
await deps.run("gh", ["release", "create", tag, "--repo", ctx.repo, "--target", "main", "--generate-notes", "--latest"]);
|
|
11174
|
+
releaseNote = `Release ${tag} created (target main)`;
|
|
10710
11175
|
if (deps.announce) {
|
|
10711
11176
|
announceNote = (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note;
|
|
10712
11177
|
}
|
|
@@ -11561,6 +12026,13 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
11561
12026
|
ok: Boolean(config?.projectOwner && config?.projectNumber && config?.projectId && config?.statusFieldId && config?.statusOptions),
|
|
11562
12027
|
label: "registry project board META exists"
|
|
11563
12028
|
});
|
|
12029
|
+
if (config?.projectId && config.projectNumber == null) {
|
|
12030
|
+
checks.push({
|
|
12031
|
+
ok: false,
|
|
12032
|
+
label: "registry projectNumber present when projectId set",
|
|
12033
|
+
detail: "projectNumber is missing \u2014 bootstrap apply must pass PROJECT_NUMBER or derive it from the live board GraphQL query"
|
|
12034
|
+
});
|
|
12035
|
+
}
|
|
11564
12036
|
if (config?.projectOwner && config.projectNumber != null) {
|
|
11565
12037
|
const fieldsQuery = `query($login: String!, $number: Int!) { organization(login: $login) { projectV2(number: $number) { fields(first: 50) { nodes { ... on ProjectV2FieldCommon { id name } ... on ProjectV2SingleSelectField { id name options { id name } } } } } } }`;
|
|
11566
12038
|
const fields = await (async () => {
|
|
@@ -12187,6 +12659,25 @@ function boardRegistryGaps(meta) {
|
|
|
12187
12659
|
if (meta.projectNumber != null) return [];
|
|
12188
12660
|
return ["projectNumber"];
|
|
12189
12661
|
}
|
|
12662
|
+
function previewRegistryMetaMerge(existing, patch) {
|
|
12663
|
+
const out = { ...existing ?? {} };
|
|
12664
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
12665
|
+
if (value === null) delete out[key];
|
|
12666
|
+
else out[key] = value;
|
|
12667
|
+
}
|
|
12668
|
+
return out;
|
|
12669
|
+
}
|
|
12670
|
+
function boardLinkWriteError(patch, existing) {
|
|
12671
|
+
const patchHasProjectId = typeof patch.projectId === "string" && patch.projectId.length > 0;
|
|
12672
|
+
const patchHasProjectNumber = typeof patch.projectNumber === "number" && Number.isFinite(patch.projectNumber);
|
|
12673
|
+
if (patchHasProjectId && !patchHasProjectNumber && existing?.projectNumber == null) {
|
|
12674
|
+
return "projectId requires projectNumber in registry META \u2014 pass projectNumber with board coords";
|
|
12675
|
+
}
|
|
12676
|
+
if (patch.projectNumber === null && boardRegistryGaps(previewRegistryMetaMerge(existing, patch)).length) {
|
|
12677
|
+
return "projectId requires projectNumber in registry META \u2014 pass projectNumber with board coords";
|
|
12678
|
+
}
|
|
12679
|
+
return null;
|
|
12680
|
+
}
|
|
12190
12681
|
function boardRegistryGapMessage(repo) {
|
|
12191
12682
|
return `Board META incomplete for ${repo}: registry has projectId but no projectNumber \u2014 board claim and auto-add will fail until projectNumber is backfilled (re-run \`node infra/migrate/seed-registry.mjs\` or \`mmi-cli bootstrap apply --execute\` with board vars)`;
|
|
12192
12683
|
}
|
|
@@ -14404,6 +14895,9 @@ project.command("set [owner/repo]").description("MASTER-ONLY: upsert a project M
|
|
|
14404
14895
|
} catch (e) {
|
|
14405
14896
|
return fail(e.message.replace(/^project set: /, "project set: "));
|
|
14406
14897
|
}
|
|
14898
|
+
const existing = await fetchProjectBySlug(slug, registryClientDeps(cfg));
|
|
14899
|
+
const boardError = boardLinkWriteError(patch, existing);
|
|
14900
|
+
if (boardError) return fail(`project set: ${boardError}`);
|
|
14407
14901
|
const res = await upsertProject(slug, patch, registryClientDeps(cfg));
|
|
14408
14902
|
return reportWrite("project set", res);
|
|
14409
14903
|
});
|
|
@@ -14680,39 +15174,112 @@ verify.command("synthesize").description("merge lens JSON array into a PanelRepo
|
|
|
14680
15174
|
return fail(`verify synthesize: ${e.message}`);
|
|
14681
15175
|
}
|
|
14682
15176
|
});
|
|
14683
|
-
var
|
|
14684
|
-
|
|
14685
|
-
|
|
14686
|
-
|
|
14687
|
-
|
|
14688
|
-
|
|
14689
|
-
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
|
|
14693
|
-
|
|
14694
|
-
|
|
14695
|
-
|
|
14696
|
-
|
|
14697
|
-
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
});
|
|
14702
|
-
console.log(
|
|
14703
|
-
} catch (e) {
|
|
14704
|
-
return fail(`verify fusion plan: ${e.message}`);
|
|
15177
|
+
var build = program2.command("build").description("Build skill helpers \u2014 effort-tier selection and milestone partitioning");
|
|
15178
|
+
build.command("tier").description("Recommend an effort tier (light|standard|deep|max) from scope/risk/ambiguity signals").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("--json", "output JSON").action((opts) => {
|
|
15179
|
+
const signals = {
|
|
15180
|
+
scope: opts.scope,
|
|
15181
|
+
risk: opts.risk,
|
|
15182
|
+
ambiguity: opts.ambiguity,
|
|
15183
|
+
blastRadius: opts.blastRadius,
|
|
15184
|
+
foundational: opts.foundational ?? void 0,
|
|
15185
|
+
explicitTier: opts.explicit
|
|
15186
|
+
};
|
|
15187
|
+
const decision = pickEffortTier(signals);
|
|
15188
|
+
if (opts.json) console.log(JSON.stringify(decision, null, 2));
|
|
15189
|
+
else {
|
|
15190
|
+
console.log(`tier: ${decision.tier}`);
|
|
15191
|
+
console.log(`reason: ${decision.reason}`);
|
|
15192
|
+
console.log(`agents: ${decision.budgets.agents}`);
|
|
15193
|
+
console.log(`planners: ${decision.budgets.planners}`);
|
|
15194
|
+
console.log(`parallel: ${decision.budgets.parallelSitesCap} sites`);
|
|
15195
|
+
console.log(`verify: ${decision.budgets.verificationDepth}`);
|
|
15196
|
+
console.log(`reasoning: ${decision.budgets.reasoningEffort}`);
|
|
14705
15197
|
}
|
|
14706
15198
|
});
|
|
14707
|
-
|
|
14708
|
-
|
|
14709
|
-
|
|
14710
|
-
|
|
14711
|
-
|
|
14712
|
-
|
|
14713
|
-
|
|
14714
|
-
|
|
14715
|
-
|
|
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
|
+
});
|
|
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) => {
|
|
15255
|
+
const plan2 = {
|
|
15256
|
+
waves: issues.length > 0 ? [{ wave: 0, mode: "parallel", issues }] : [],
|
|
15257
|
+
note: issues.length === 0 ? "No issues provided." : "v1 partitioning: single parallel wave. Use grind --auto Phase 00 patterns for deeper analysis."
|
|
15258
|
+
};
|
|
15259
|
+
if (opts.json) console.log(JSON.stringify(plan2, null, 2));
|
|
15260
|
+
else {
|
|
15261
|
+
console.log(plan2.note);
|
|
15262
|
+
for (const w of plan2.waves) console.log(`wave ${w.wave} (${w.mode}): ${w.issues.join(" ")}`);
|
|
15263
|
+
}
|
|
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"}`);
|
|
14716
15283
|
}
|
|
14717
15284
|
});
|
|
14718
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) => {
|
|
@@ -15866,6 +16433,46 @@ function quarantinePluginCacheDirs(plan2) {
|
|
|
15866
16433
|
}
|
|
15867
16434
|
return moved;
|
|
15868
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
|
+
}
|
|
15869
16476
|
var gitignorePath = () => (0, import_node_path14.join)(process.cwd(), ".gitignore");
|
|
15870
16477
|
function readTextFile(path2) {
|
|
15871
16478
|
try {
|
|
@@ -16028,7 +16635,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
16028
16635
|
releasedVersion,
|
|
16029
16636
|
surface
|
|
16030
16637
|
});
|
|
16031
|
-
if (!installedVersionCheck.ok && repairFull) {
|
|
16638
|
+
if (!installedVersionCheck.ok && (repairFull || repairLocal)) {
|
|
16032
16639
|
const claudeStale = installedVersionCheck.staleSurfaces?.some((s) => s.surface === "claude") ?? false;
|
|
16033
16640
|
if (claudeStale && await applyClaudePluginHeal(surface, (m) => io.err(m))) {
|
|
16034
16641
|
const healed = buildInstalledPluginVersionCheck({
|
|
@@ -16069,13 +16676,33 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
16069
16676
|
};
|
|
16070
16677
|
}
|
|
16071
16678
|
checks.push(cacheCleanupCheck);
|
|
16072
|
-
|
|
16073
|
-
|
|
16074
|
-
|
|
16075
|
-
|
|
16076
|
-
|
|
16077
|
-
|
|
16078
|
-
|
|
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);
|
|
16079
16706
|
const cursorCacheRoot = cursorPluginCacheRoot();
|
|
16080
16707
|
let cursorPins = cursorPluginCachePinSnapshots() ?? [];
|
|
16081
16708
|
let cursorPluginCheck = buildCursorPluginInstallCheck({
|