@mutmutco/cli 3.106.0 → 3.107.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 +777 -35
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -5020,6 +5020,20 @@ var import_node_path2 = require("node:path");
|
|
|
5020
5020
|
|
|
5021
5021
|
// ../infra/compat.mjs
|
|
5022
5022
|
var CLIENT_VERSION_HEADER = "x-client-version";
|
|
5023
|
+
function parseSemver(s) {
|
|
5024
|
+
if (typeof s !== "string") return null;
|
|
5025
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:[-+]|$)/.exec(s.trim());
|
|
5026
|
+
if (!m) return null;
|
|
5027
|
+
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) };
|
|
5028
|
+
}
|
|
5029
|
+
function versionAtLeast(v, min) {
|
|
5030
|
+
const a = parseSemver(v);
|
|
5031
|
+
const b = parseSemver(min);
|
|
5032
|
+
if (!a || !b) return false;
|
|
5033
|
+
if (a.major !== b.major) return a.major > b.major;
|
|
5034
|
+
if (a.minor !== b.minor) return a.minor > b.minor;
|
|
5035
|
+
return a.patch >= b.patch;
|
|
5036
|
+
}
|
|
5023
5037
|
|
|
5024
5038
|
// src/client-version.ts
|
|
5025
5039
|
function resolveClientVersionManifestCandidates(distDir = __dirname) {
|
|
@@ -9814,7 +9828,7 @@ var LINK_RE = /\]\(([^)#\s]+?\.md)(?:#[^)]*)?\)/g;
|
|
|
9814
9828
|
var CMD_RE = /`mmi-cli ((?:[a-z][a-z-]*)(?: [a-z][a-z-]*){0,3})/g;
|
|
9815
9829
|
var RETIRED_RE = /\b(retired|historical|removed|deleted|gone|no longer|superseded|legacy)\b/i;
|
|
9816
9830
|
var ROOT_DOCS = ["README.md", "architecture.md"];
|
|
9817
|
-
var SKIP_WALK = ["docs/Archive/", "docs/incidents/", "docs/research/"];
|
|
9831
|
+
var SKIP_WALK = ["docs/Archive/", "docs/incidents/", "docs/research/", "docs/decisions/"];
|
|
9818
9832
|
function refFirstSegment(ref) {
|
|
9819
9833
|
return ref.replace(/^(\.\/)+/, "").split("/")[0];
|
|
9820
9834
|
}
|
|
@@ -10117,7 +10131,7 @@ function isRoutableDocsPath(relPath) {
|
|
|
10117
10131
|
const normalized = relPath.replace(/\\/g, "/");
|
|
10118
10132
|
return normalized !== "index.md" && normalized.split("/")[0]?.toLowerCase() !== "archive";
|
|
10119
10133
|
}
|
|
10120
|
-
var GENERATED_HEADER = "<!-- Generated by `mmi-cli docs index --write`. Do not edit by hand. -->";
|
|
10134
|
+
var GENERATED_HEADER = "<!-- Generated by `mmi-cli oracle docs index --write`. Do not edit by hand. -->";
|
|
10121
10135
|
function plainInline(text) {
|
|
10122
10136
|
return text.replace(/\r?\n/g, " ").replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").replace(/`/g, "").replace(/\*+/g, "").replace(/~~/g, "").replace(/__([^_]+)__/g, "$1").replace(/(^|[\s(])_([^_]+)_($|[\s).,!?:;])/g, "$1$2$3").replace(/^>\s*/, "").replace(/\\([\\`*_\[\]{}()#+.!|>-])/g, "$1").replace(/\s+/g, " ").trim();
|
|
10123
10137
|
}
|
|
@@ -10162,7 +10176,7 @@ function renderDocsIndex(entries) {
|
|
|
10162
10176
|
"",
|
|
10163
10177
|
"# Documentation index",
|
|
10164
10178
|
"",
|
|
10165
|
-
"Generated from the `docs/` tree by `mmi-cli docs index --write`. `--check` fails on drift, so this index",
|
|
10179
|
+
"Generated from the `docs/` tree by `mmi-cli oracle docs index --write`. `--check` fails on drift, so this index",
|
|
10166
10180
|
"can never diverge from the records it lists.",
|
|
10167
10181
|
""
|
|
10168
10182
|
];
|
|
@@ -19706,9 +19720,15 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
19706
19720
|
const contest = await checkLaneContest(client, item);
|
|
19707
19721
|
if (contest.contested) throw new Error(laneContestMessage(item.ref, contest, "claim"));
|
|
19708
19722
|
}
|
|
19723
|
+
if (options.check) {
|
|
19724
|
+
return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, alreadyClaimed: true, checked: true };
|
|
19725
|
+
}
|
|
19709
19726
|
await postClaimMarkerComment(client, item);
|
|
19710
19727
|
return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, alreadyClaimed: true };
|
|
19711
19728
|
}
|
|
19729
|
+
if (options.check) {
|
|
19730
|
+
return { item, viewer: report.viewer, repo: report.repo, status: item.status, partial: false, checked: true };
|
|
19731
|
+
}
|
|
19712
19732
|
try {
|
|
19713
19733
|
await client.rest("POST", `repos/${item.repository}/issues/${item.number}/assignees`, { body: { assignees: [assignedLogin] } });
|
|
19714
19734
|
} catch (e) {
|
|
@@ -19764,7 +19784,7 @@ async function claimBoardIssues(options, deps = {}) {
|
|
|
19764
19784
|
const ref = `${selector.repo}#${selector.number}`;
|
|
19765
19785
|
try {
|
|
19766
19786
|
const result = await claimOneBoardItem(ctx, selector, options);
|
|
19767
|
-
results[index] = { ref: result.item.ref, claimed: true, item: result.item, status: result.status, partial: result.partial, warning: result.warning, alreadyClaimed: result.alreadyClaimed };
|
|
19787
|
+
results[index] = { ref: result.item.ref, claimed: true, item: result.item, status: result.status, partial: result.partial, warning: result.warning, alreadyClaimed: result.alreadyClaimed, checked: result.checked };
|
|
19768
19788
|
} catch (e) {
|
|
19769
19789
|
results[index] = { ref, claimed: false, reason: e.message };
|
|
19770
19790
|
}
|
|
@@ -20748,7 +20768,7 @@ function isHouseRoot(token) {
|
|
|
20748
20768
|
return HOUSE_ROOTS.includes(token);
|
|
20749
20769
|
}
|
|
20750
20770
|
var COMPAT_SHIM_DEATH_WAVE = "Wave 3";
|
|
20751
|
-
var
|
|
20771
|
+
var FLAT_ALIAS_CUT_NOTE = `flat Wave 0 alias \u2014 removed in ${COMPAT_SHIM_DEATH_WAVE} (#4316, #4438); only the canonical house-prefixed path parses`;
|
|
20752
20772
|
var HOUSE_QUESTIONS = {
|
|
20753
20773
|
oracle: "Is it live?",
|
|
20754
20774
|
harbour: "Is it bounded?",
|
|
@@ -20808,6 +20828,8 @@ var HOUSE_MAP = {
|
|
|
20808
20828
|
report: "learning",
|
|
20809
20829
|
// friction reports
|
|
20810
20830
|
"skill-lesson": "learning",
|
|
20831
|
+
"closure-rate": "learning",
|
|
20832
|
+
// closure rate per loop kind, computed at read (#4440)
|
|
20811
20833
|
// --- core — the front door itself ---------------------------------------------------------------
|
|
20812
20834
|
commands: "core",
|
|
20813
20835
|
whoami: "core",
|
|
@@ -20842,6 +20864,11 @@ function canonicalPathFor(path2) {
|
|
|
20842
20864
|
if (!house) return void 0;
|
|
20843
20865
|
return house === "core" ? path2 : `${house} ${path2}`;
|
|
20844
20866
|
}
|
|
20867
|
+
function canonicalArgvFor(args) {
|
|
20868
|
+
const lookup = args.filter((tok) => !tok.startsWith("-")).slice(0, 2).join(" ");
|
|
20869
|
+
const house = houseForPath(lookup);
|
|
20870
|
+
return house && house !== "core" ? [house, ...args] : args;
|
|
20871
|
+
}
|
|
20845
20872
|
|
|
20846
20873
|
// src/command-taxonomy.ts
|
|
20847
20874
|
var COMMAND_METADATA = /* @__PURE__ */ Symbol.for("mmi.commandTaxonomy.metadata");
|
|
@@ -20853,10 +20880,10 @@ var PRIMARY_GROUPS = [
|
|
|
20853
20880
|
// step invokes (`docs refs`, `tests policy`), not org-plane operations (#3605). `spawn policy`
|
|
20854
20881
|
// joins them on the same footing (#3979).
|
|
20855
20882
|
["Setup and support", ["bootstrap", "secrets", "docs", "repo-index", "tests", "spawn"]],
|
|
20856
|
-
["Coordinate and improve", ["wave", "report", "skill-lesson"]]
|
|
20883
|
+
["Coordinate and improve", ["wave", "report", "skill-lesson", "closure-rate"]]
|
|
20857
20884
|
];
|
|
20858
20885
|
var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin"]);
|
|
20859
|
-
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "find", "tests", "spawn", "wave", "report", "skill-lesson"]);
|
|
20886
|
+
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "find", "tests", "spawn", "wave", "report", "skill-lesson", "closure-rate"]);
|
|
20860
20887
|
var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
|
|
20861
20888
|
var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
|
|
20862
20889
|
var topLevelPosition = 0;
|
|
@@ -20906,6 +20933,7 @@ var COMMAND_OWNERSHIP = {
|
|
|
20906
20933
|
wave: { module_owner: "cli/src/wave-land.ts", consumer: "campaign-orchestrator" },
|
|
20907
20934
|
report: { module_owner: "cli/src/report.ts", consumer: "campaign-orchestrator" },
|
|
20908
20935
|
"skill-lesson": { module_owner: "cli/src/skill-lesson.ts", consumer: "campaign-orchestrator" },
|
|
20936
|
+
"closure-rate": { module_owner: "cli/src/learning-closure-rate.ts", consumer: "campaign-orchestrator" },
|
|
20909
20937
|
org: { module_owner: "cli/src/command-consolidation.ts", consumer: "org-operator" },
|
|
20910
20938
|
runtime: { module_owner: "cli/src/command-consolidation.ts", consumer: "runtime-operator" },
|
|
20911
20939
|
plugin: { module_owner: "cli/src/plugin-guard-io.ts", consumer: "host-runtime" }
|
|
@@ -21121,7 +21149,7 @@ function buildCommand(cmd, path2) {
|
|
|
21121
21149
|
...metadata
|
|
21122
21150
|
};
|
|
21123
21151
|
if (cmd._allowUnknownOption) out.parses_own_argv = true;
|
|
21124
|
-
if (path2 && house !== "core") out.
|
|
21152
|
+
if (path2 && house !== "core") out.flat_removed = true;
|
|
21125
21153
|
const description = cmd.description();
|
|
21126
21154
|
if (description) out.description = description;
|
|
21127
21155
|
const examples = readExamples(cmd);
|
|
@@ -21170,19 +21198,23 @@ function buildCommandManifest(program3) {
|
|
|
21170
21198
|
const primaryTree = primaryCopy(tree, true);
|
|
21171
21199
|
const primaryIndex = [];
|
|
21172
21200
|
collectLeaves(primaryTree, primaryIndex);
|
|
21201
|
+
const houses = buildHouses(tree);
|
|
21173
21202
|
const manifest = {
|
|
21174
21203
|
schema_version: 2,
|
|
21175
21204
|
scope: "all",
|
|
21176
21205
|
name: tree.name,
|
|
21177
21206
|
tree,
|
|
21178
21207
|
index,
|
|
21179
|
-
houses
|
|
21208
|
+
houses,
|
|
21180
21209
|
doors: buildDoorsCatalog(tree),
|
|
21181
|
-
|
|
21210
|
+
flat_alias_cut: {
|
|
21182
21211
|
canonical: "mmi-cli <house> <command> \u2026",
|
|
21183
|
-
|
|
21184
|
-
|
|
21185
|
-
note:
|
|
21212
|
+
removed: "mmi-cli <command> \u2026",
|
|
21213
|
+
wave: COMPAT_SHIM_DEATH_WAVE,
|
|
21214
|
+
note: FLAT_ALIAS_CUT_NOTE,
|
|
21215
|
+
// The cut record names every removed alias by path — the same entry points the houses block
|
|
21216
|
+
// presents, so the record can never drift from what actually refuses.
|
|
21217
|
+
removed_aliases: [...new Set(houses.flatMap((house) => house.commands.map((entry) => entry.path)))].sort()
|
|
21186
21218
|
},
|
|
21187
21219
|
primary_tree: primaryTree,
|
|
21188
21220
|
primary_index: primaryIndex,
|
|
@@ -21280,8 +21312,8 @@ function formatManifestHuman(manifest, options = {}) {
|
|
|
21280
21312
|
}
|
|
21281
21313
|
lines.push(
|
|
21282
21314
|
"",
|
|
21283
|
-
`Canonical form: \`mmi-cli <house> <command> \u2026\`. The
|
|
21284
|
-
`
|
|
21315
|
+
`Canonical form: \`mmi-cli <house> <command> \u2026\`. The paths above are house-relative \u2014 their flat`,
|
|
21316
|
+
`Wave 0 aliases were removed in ${COMPAT_SHIM_DEATH_WAVE} (#4316, #4438); core stays unprefixed.`,
|
|
21285
21317
|
"",
|
|
21286
21318
|
options.all ? "Use `mmi-cli explain <group|command>` for a focused map." : "Use `mmi-cli explain <group|command>` for detail or `mmi-cli commands --all` for operational depth."
|
|
21287
21319
|
);
|
|
@@ -28658,7 +28690,7 @@ function registerBootstrapCommands(program3) {
|
|
|
28658
28690
|
target: DOCS_INDEX_PATH,
|
|
28659
28691
|
action: indexAction,
|
|
28660
28692
|
ownership: "org",
|
|
28661
|
-
reason: indexCurrent === null ? "generated routing index (#3545)" : "routing index present \u2014 regenerate with `mmi-cli docs index --write`, which sees the whole tree"
|
|
28693
|
+
reason: indexCurrent === null ? "generated routing index (#3545)" : "routing index present \u2014 regenerate with `mmi-cli oracle docs index --write`, which sees the whole tree"
|
|
28662
28694
|
});
|
|
28663
28695
|
if (o.execute && indexAction === "create") {
|
|
28664
28696
|
await putSeed(DOCS_INDEX_PATH, indexContent, seedPlan.ref, void 0);
|
|
@@ -29668,10 +29700,13 @@ function registerBoardCommands(program3) {
|
|
|
29668
29700
|
return failGraceful(`board read failed: ${withDiscoverMissDetail(e.message)}`);
|
|
29669
29701
|
}
|
|
29670
29702
|
}
|
|
29703
|
+
function checkVerdict(ref, alreadyClaimed) {
|
|
29704
|
+
return alreadyClaimed ? `Check ${ref}: claimed and In Progress, no live contest - claim would renew the lease (nothing written)` : `Check ${ref}: free - claim would proceed (nothing written)`;
|
|
29705
|
+
}
|
|
29671
29706
|
const board = program3.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
|
|
29672
29707
|
board.command("read", { isDefault: true }).description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--allow-partial", "return partial board results when later page/detail reads fail").option("--live", "bypass the projection and read the authoritative GitHub Project v2 board").addHelpText("after", "\nBy default, read uses the explicitly-stale Hub projection and prints its watermark. --live is authoritative.\n--allow-partial applies to the live paginated path and detail reads; the projection row itself is atomic.\n").action((o) => runBoardRead(o));
|
|
29673
29708
|
withExamples(mutating(
|
|
29674
|
-
board.command("claim <issues...>").description("claim issues: assign them and move their Project v2 Status to In Progress \u2014 idempotent, so an item already yours and In Progress succeeds unchanged (one or more refs)").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--for <login>", "assign to this login instead of @me \u2014 agent claims on behalf of the master").option("--force", "take an item already claimed by another lane that shows live evidence of active work (#3727)").option("--allow-partial", "return success JSON if assignment succeeds but the status move fails"),
|
|
29709
|
+
board.command("claim <issues...>").description("claim issues: assign them and move their Project v2 Status to In Progress \u2014 idempotent, so an item already yours and In Progress succeeds unchanged (one or more refs)").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--for <login>", "assign to this login instead of @me \u2014 agent claims on behalf of the master").option("--force", "take an item already claimed by another lane that shows live evidence of active work (#3727)").option("--check", "read-only: run every claim gate and report the verdict, writing nothing \u2014 exits 1 with the same refusal a real claim would raise (#4511)").option("--allow-partial", "return success JSON if assignment succeeds but the status move fails"),
|
|
29675
29710
|
(_opts, args) => ({ command: "board claim", issues: args[0] ?? [] })
|
|
29676
29711
|
).action(async (issueRefs, o) => {
|
|
29677
29712
|
if (issueRefs.length === 1) {
|
|
@@ -29683,11 +29718,12 @@ function registerBoardCommands(program3) {
|
|
|
29683
29718
|
repo: o.repo,
|
|
29684
29719
|
assignee: o.for,
|
|
29685
29720
|
force: o.force,
|
|
29721
|
+
check: o.check,
|
|
29686
29722
|
allowPartial: o.allowPartial
|
|
29687
29723
|
});
|
|
29688
|
-
invalidateStatuslineBoardCache();
|
|
29724
|
+
if (!result.checked) invalidateStatuslineBoardCache();
|
|
29689
29725
|
if (o.json) return console.log(JSON.stringify(result));
|
|
29690
|
-
console.log(result.partial ? `Partially claimed ${result.item.ref}: ${result.warning}` : result.alreadyClaimed ? `Already claimed ${result.item.ref} - In Progress (no change)` : `Claimed ${result.item.ref} - In Progress`);
|
|
29726
|
+
console.log(result.checked ? checkVerdict(result.item.ref, result.alreadyClaimed) : result.partial ? `Partially claimed ${result.item.ref}: ${result.warning}` : result.alreadyClaimed ? `Already claimed ${result.item.ref} - In Progress (no change)` : `Claimed ${result.item.ref} - In Progress`);
|
|
29691
29727
|
} catch (e) {
|
|
29692
29728
|
return failGraceful(`board claim failed: ${e.message}`);
|
|
29693
29729
|
}
|
|
@@ -29700,14 +29736,15 @@ function registerBoardCommands(program3) {
|
|
|
29700
29736
|
repo: o.repo,
|
|
29701
29737
|
assignee: o.for,
|
|
29702
29738
|
force: o.force,
|
|
29739
|
+
check: o.check,
|
|
29703
29740
|
allowPartial: o.allowPartial
|
|
29704
29741
|
});
|
|
29705
|
-
if (bulk.results.some((r) => r.claimed)) invalidateStatuslineBoardCache();
|
|
29742
|
+
if (bulk.results.some((r) => r.claimed && !r.checked)) invalidateStatuslineBoardCache();
|
|
29706
29743
|
if (o.json) {
|
|
29707
29744
|
console.log(JSON.stringify(bulk.results));
|
|
29708
29745
|
} else {
|
|
29709
29746
|
for (const result of bulk.results) {
|
|
29710
|
-
console.log(result.claimed ? result.partial ? `Partially claimed ${result.ref}: ${result.warning}` : result.alreadyClaimed ? `Already claimed ${result.ref} - In Progress (no change)` : `Claimed ${result.ref} - In Progress` : `Skipped ${result.ref}: ${result.reason}`);
|
|
29747
|
+
console.log(result.claimed ? result.checked ? checkVerdict(result.ref, result.alreadyClaimed) : result.partial ? `Partially claimed ${result.ref}: ${result.warning}` : result.alreadyClaimed ? `Already claimed ${result.ref} - In Progress (no change)` : `Claimed ${result.ref} - In Progress` : `Skipped ${result.ref}: ${result.reason}`);
|
|
29711
29748
|
}
|
|
29712
29749
|
}
|
|
29713
29750
|
if (bulk.failed > 0) process.exitCode = 1;
|
|
@@ -29716,11 +29753,13 @@ function registerBoardCommands(program3) {
|
|
|
29716
29753
|
}
|
|
29717
29754
|
}), [
|
|
29718
29755
|
"mmi-cli board claim 2680",
|
|
29719
|
-
"mmi-cli board claim 2680 2681 --for teammate-login"
|
|
29756
|
+
"mmi-cli board claim 2680 2681 --for teammate-login",
|
|
29757
|
+
"mmi-cli board claim 2680 --check"
|
|
29720
29758
|
], [
|
|
29721
29759
|
"Pass raw issue numbers/refs, not URLs.",
|
|
29722
29760
|
"Claim already assigns and moves Status to In Progress, so do not also board move it.",
|
|
29723
|
-
"Multiple refs are handled as a batch and return per-item results."
|
|
29761
|
+
"Multiple refs are handled as a batch and return per-item results.",
|
|
29762
|
+
"--check is the live gate read (it calls GitHub); --dry-run only echoes the parsed argv plan."
|
|
29724
29763
|
]);
|
|
29725
29764
|
board.command("show <issue>").description("print one board item (status, assignees, type, url) with its body and comments").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--allow-partial", "return the item even if its body/comments fetch fails").action(async (issueRef, o) => {
|
|
29726
29765
|
try {
|
|
@@ -32635,6 +32674,392 @@ function registerTrainCommands(program3) {
|
|
|
32635
32674
|
});
|
|
32636
32675
|
}
|
|
32637
32676
|
|
|
32677
|
+
// src/fleet-track-inventory.ts
|
|
32678
|
+
var NO_DEPLOY_SURFACE_MODELS = /* @__PURE__ */ new Set(["none", "content"]);
|
|
32679
|
+
function ghErrorText(e) {
|
|
32680
|
+
const err = e;
|
|
32681
|
+
return String(err?.stderr || err?.message || e).trim().replace(/\s+/g, " ");
|
|
32682
|
+
}
|
|
32683
|
+
function isGhNotFound2(e) {
|
|
32684
|
+
const err = e;
|
|
32685
|
+
return /\bNOT[_ ]FOUND\b|HTTP 404/i.test(`${err?.stderr ?? ""} ${err?.message ?? ""} ${err?.stdout ?? ""}`);
|
|
32686
|
+
}
|
|
32687
|
+
async function readFleetPluginProbe(deps, repo, branch) {
|
|
32688
|
+
const [owner, name] = repo.split("/");
|
|
32689
|
+
const ref = branch ? `?ref=${encodeURIComponent(branch)}` : "";
|
|
32690
|
+
try {
|
|
32691
|
+
const raw = await deps.ghJson([
|
|
32692
|
+
"api",
|
|
32693
|
+
`repos/${encodeURIComponent(owner ?? "")}/${encodeURIComponent(name ?? "")}/contents/.claude-plugin/plugin.json${ref}`
|
|
32694
|
+
]);
|
|
32695
|
+
if (raw.encoding !== "base64" || typeof raw.content !== "string") {
|
|
32696
|
+
return { repo, state: "unknown", unknown: "plugin.json contents read returned no base64 content" };
|
|
32697
|
+
}
|
|
32698
|
+
const decoded = Buffer.from(raw.content.replace(/\s/g, ""), "base64").toString("utf8");
|
|
32699
|
+
const manifest = JSON.parse(decoded);
|
|
32700
|
+
return {
|
|
32701
|
+
repo,
|
|
32702
|
+
state: "present",
|
|
32703
|
+
version: typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0
|
|
32704
|
+
};
|
|
32705
|
+
} catch (e) {
|
|
32706
|
+
return isGhNotFound2(e) ? { repo, state: "absent" } : { repo, state: "unknown", unknown: `plugin surface read failed: ${ghErrorText(e)}` };
|
|
32707
|
+
}
|
|
32708
|
+
}
|
|
32709
|
+
async function mapBounded2(values, limit, read) {
|
|
32710
|
+
const results = new Array(values.length);
|
|
32711
|
+
let next = 0;
|
|
32712
|
+
const worker = async () => {
|
|
32713
|
+
while (next < values.length) {
|
|
32714
|
+
const index = next++;
|
|
32715
|
+
results[index] = await read(values[index]);
|
|
32716
|
+
}
|
|
32717
|
+
};
|
|
32718
|
+
await Promise.all(Array.from({ length: Math.min(limit, values.length) }, () => worker()));
|
|
32719
|
+
return results;
|
|
32720
|
+
}
|
|
32721
|
+
function validRepo2(repo) {
|
|
32722
|
+
return typeof repo === "string" && /^[^/\s]+\/[^/\s]+$/.test(repo.trim());
|
|
32723
|
+
}
|
|
32724
|
+
function classifySurfaces(project2, plugin) {
|
|
32725
|
+
const board = project2?.projectId && project2.projectNumber !== void 0 && project2.statusFieldId ? "present" : "absent";
|
|
32726
|
+
const vault = typeof project2?.vaultPath === "string" && project2.vaultPath.trim() ? "present" : "absent";
|
|
32727
|
+
const deployModel = typeof project2?.deployModel === "string" ? project2.deployModel : void 0;
|
|
32728
|
+
const deploy = deployModel && !NO_DEPLOY_SURFACE_MODELS.has(deployModel) ? "present" : "absent";
|
|
32729
|
+
return { board, vault, deploy, plugin: plugin?.state ?? "unknown" };
|
|
32730
|
+
}
|
|
32731
|
+
function classificationsOf(surfaces) {
|
|
32732
|
+
const classes = [];
|
|
32733
|
+
if (surfaces.board === "absent") classes.push("no-board");
|
|
32734
|
+
if (surfaces.vault === "absent") classes.push("no-vault");
|
|
32735
|
+
if (surfaces.plugin === "absent") classes.push("no-plugin");
|
|
32736
|
+
if (surfaces.deploy === "absent") classes.push("no-deploy-surface");
|
|
32737
|
+
return classes;
|
|
32738
|
+
}
|
|
32739
|
+
function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generatedAt) {
|
|
32740
|
+
const releaseByRepo = new Map(releaseProbes.map((p) => [p.repo.toLowerCase(), p]));
|
|
32741
|
+
const pluginByRepo = new Map(pluginProbes.map((p) => [p.repo.toLowerCase(), p]));
|
|
32742
|
+
const projectByRepo = /* @__PURE__ */ new Map();
|
|
32743
|
+
const anomalies = [];
|
|
32744
|
+
for (const project2 of projects) {
|
|
32745
|
+
const repos2 = (project2.repos ?? []).filter(validRepo2);
|
|
32746
|
+
if (!repos2.length) {
|
|
32747
|
+
anomalies.push({
|
|
32748
|
+
kind: "project-without-repo",
|
|
32749
|
+
slug: String(project2.slug ?? "?"),
|
|
32750
|
+
name: String(project2.name ?? project2.slug ?? "?")
|
|
32751
|
+
});
|
|
32752
|
+
}
|
|
32753
|
+
for (const repo of repos2) {
|
|
32754
|
+
const key = repo.trim().toLowerCase();
|
|
32755
|
+
projectByRepo.set(key, projectByRepo.get(key) ?? project2);
|
|
32756
|
+
}
|
|
32757
|
+
}
|
|
32758
|
+
const repos = orgVersionRepos(projects).map((repo) => {
|
|
32759
|
+
const project2 = projectByRepo.get(repo.toLowerCase());
|
|
32760
|
+
const release = releaseByRepo.get(repo.toLowerCase());
|
|
32761
|
+
const plugin = pluginByRepo.get(repo.toLowerCase());
|
|
32762
|
+
const surfaces = classifySurfaces(project2, plugin);
|
|
32763
|
+
const unknowns = [...release?.unknowns ?? []];
|
|
32764
|
+
if (!release) unknowns.push("GitHub release evidence was not collected");
|
|
32765
|
+
if (plugin?.unknown) unknowns.push(plugin.unknown);
|
|
32766
|
+
if (!plugin) unknowns.push("plugin surface was not probed");
|
|
32767
|
+
return {
|
|
32768
|
+
repo,
|
|
32769
|
+
slug: String(project2?.slug ?? repo.split("/")[1] ?? repo).toLowerCase(),
|
|
32770
|
+
track: resolveReleaseTrack(project2, void 0, repo),
|
|
32771
|
+
declaredTrack: typeof project2?.releaseTrack === "string" ? project2.releaseTrack : null,
|
|
32772
|
+
branch: typeof project2?.branch === "string" ? project2.branch : null,
|
|
32773
|
+
repoClass: typeof project2?.class === "string" ? project2.class : null,
|
|
32774
|
+
deployModel: typeof project2?.deployModel === "string" ? project2.deployModel : null,
|
|
32775
|
+
surfaces,
|
|
32776
|
+
classifications: classificationsOf(surfaces),
|
|
32777
|
+
pluginVersion: plugin?.version ?? null,
|
|
32778
|
+
release: {
|
|
32779
|
+
tag: release?.releaseTag ?? null,
|
|
32780
|
+
version: release?.releasedVersion ?? null,
|
|
32781
|
+
at: release?.releasedAt ?? null,
|
|
32782
|
+
coordinatedVersion: release?.coordinatedVersion ?? null,
|
|
32783
|
+
coordinatedSource: release?.coordinatedSource ?? null
|
|
32784
|
+
},
|
|
32785
|
+
ci: {
|
|
32786
|
+
declared: typeof project2?.ci === "string" ? project2.ci : null,
|
|
32787
|
+
requiredChecks: Array.isArray(project2?.requiredChecks) ? project2.requiredChecks : null,
|
|
32788
|
+
exemptReason: typeof project2?.ciExemptReason === "string" ? project2.ciExemptReason : null
|
|
32789
|
+
},
|
|
32790
|
+
source: { registry: "hub-registry-live", release: "github-releases-live", plugin: "github-contents-live" },
|
|
32791
|
+
freshness: { readAt: generatedAt, releasedAt: release?.releasedAt ?? null },
|
|
32792
|
+
unknowns
|
|
32793
|
+
};
|
|
32794
|
+
});
|
|
32795
|
+
const tracks = { full: 0, direct: 0, trunk: 0 };
|
|
32796
|
+
for (const row of repos) tracks[row.track] += 1;
|
|
32797
|
+
return {
|
|
32798
|
+
generatedAt,
|
|
32799
|
+
source: {
|
|
32800
|
+
roster: "hub-registry-live",
|
|
32801
|
+
releases: "github-releases-live",
|
|
32802
|
+
plugin: "github-contents-live",
|
|
32803
|
+
committedInventory: "never"
|
|
32804
|
+
},
|
|
32805
|
+
counts: {
|
|
32806
|
+
projects: projects.length,
|
|
32807
|
+
registeredRepos: repos.length,
|
|
32808
|
+
tracks,
|
|
32809
|
+
noBoard: repos.filter((r) => r.classifications.includes("no-board")).length,
|
|
32810
|
+
noVault: repos.filter((r) => r.classifications.includes("no-vault")).length,
|
|
32811
|
+
noPlugin: repos.filter((r) => r.classifications.includes("no-plugin")).length,
|
|
32812
|
+
noDeploySurface: repos.filter((r) => r.classifications.includes("no-deploy-surface")).length,
|
|
32813
|
+
unknownRows: repos.filter((r) => r.unknowns.length > 0).length,
|
|
32814
|
+
anomalies: anomalies.length
|
|
32815
|
+
},
|
|
32816
|
+
repos,
|
|
32817
|
+
anomalies
|
|
32818
|
+
};
|
|
32819
|
+
}
|
|
32820
|
+
function defaultFleetTrackInventoryDeps() {
|
|
32821
|
+
return {
|
|
32822
|
+
projects: async () => {
|
|
32823
|
+
const cfg = await loadConfig();
|
|
32824
|
+
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
32825
|
+
if (!projects) throw new Error("live Hub registry roster is unavailable \u2014 a committed inventory is never current truth, so there is no fallback; retry when the Hub API is reachable");
|
|
32826
|
+
return projects;
|
|
32827
|
+
},
|
|
32828
|
+
query: queryDeps(),
|
|
32829
|
+
now: () => /* @__PURE__ */ new Date()
|
|
32830
|
+
};
|
|
32831
|
+
}
|
|
32832
|
+
async function readFleetTrackInventory(deps = defaultFleetTrackInventoryDeps()) {
|
|
32833
|
+
const projects = await deps.projects();
|
|
32834
|
+
const repos = orgVersionRepos(projects);
|
|
32835
|
+
const branchByRepo = /* @__PURE__ */ new Map();
|
|
32836
|
+
for (const project2 of projects) {
|
|
32837
|
+
for (const repo of (project2.repos ?? []).filter(validRepo2)) {
|
|
32838
|
+
const key = repo.trim().toLowerCase();
|
|
32839
|
+
if (!branchByRepo.has(key)) {
|
|
32840
|
+
branchByRepo.set(key, typeof project2.branch === "string" ? project2.branch : void 0);
|
|
32841
|
+
}
|
|
32842
|
+
}
|
|
32843
|
+
}
|
|
32844
|
+
const [releaseProbes, pluginProbes] = await Promise.all([
|
|
32845
|
+
mapBounded2(repos, 6, (repo) => readOrgVersionProbe(deps.query, repo)),
|
|
32846
|
+
mapBounded2(repos, 6, (repo) => readFleetPluginProbe(deps.query, repo, branchByRepo.get(repo.toLowerCase())))
|
|
32847
|
+
]);
|
|
32848
|
+
return buildFleetTrackInventory(projects, releaseProbes, pluginProbes, deps.now().toISOString());
|
|
32849
|
+
}
|
|
32850
|
+
function formatFleetTrackInventory(report) {
|
|
32851
|
+
const { counts } = report;
|
|
32852
|
+
const lines = [
|
|
32853
|
+
`fleet version-track inventory \u2014 ${counts.registeredRepos} registered repositories (${counts.projects} registry projects)`,
|
|
32854
|
+
`source: live Hub registry + live GitHub releases/contents \xB7 read ${report.generatedAt} \xB7 committed inventory: never`,
|
|
32855
|
+
`tracks: ${counts.tracks.full} full \xB7 ${counts.tracks.direct} direct \xB7 ${counts.tracks.trunk} trunk`,
|
|
32856
|
+
`classified: ${counts.noBoard} no-board \xB7 ${counts.noVault} no-vault \xB7 ${counts.noPlugin} no-plugin \xB7 ${counts.noDeploySurface} no-deploy-surface \xB7 ${counts.unknownRows} rows with unknowns`,
|
|
32857
|
+
"",
|
|
32858
|
+
"repository | track | deploy model | released | plugin | classifications"
|
|
32859
|
+
];
|
|
32860
|
+
for (const row of report.repos) {
|
|
32861
|
+
lines.push(
|
|
32862
|
+
`${row.repo} | ${row.track}${row.declaredTrack ? "" : " (derived)"} | ${row.deployModel ?? "UNDECLARED"} | ${row.release.version ?? "UNKNOWN"} | ${row.pluginVersion ?? (row.surfaces.plugin === "present" ? "present" : row.surfaces.plugin)} | ${row.classifications.join(", ") || "none"}`
|
|
32863
|
+
);
|
|
32864
|
+
for (const unknown of row.unknowns) lines.push(` ? ${unknown}`);
|
|
32865
|
+
}
|
|
32866
|
+
if (report.anomalies.length) {
|
|
32867
|
+
lines.push("", "anomalies:");
|
|
32868
|
+
for (const anomaly of report.anomalies) {
|
|
32869
|
+
lines.push(` ? ${anomaly.slug} (${anomaly.name}) \u2014 registry project has no registered repository`);
|
|
32870
|
+
}
|
|
32871
|
+
}
|
|
32872
|
+
return lines.join("\n");
|
|
32873
|
+
}
|
|
32874
|
+
function registerFleetTrackInventory(program3) {
|
|
32875
|
+
const train = program3.commands.find((c) => c.name() === "train");
|
|
32876
|
+
if (!train) throw new Error("train inventory registration requires the train command group");
|
|
32877
|
+
train.command("inventory").description("live fleet version-track inventory (#4451) \u2014 every registered repo's track, deploy model, CI contract, release evidence, and explicit no-board/no-vault/no-plugin/no-deploy-surface classification").option("--json", "machine-readable output (the typed D2b input shape)").action(async (o) => {
|
|
32878
|
+
try {
|
|
32879
|
+
const report = await readFleetTrackInventory();
|
|
32880
|
+
console.log(o.json ? JSON.stringify(report, null, 2) : formatFleetTrackInventory(report));
|
|
32881
|
+
} catch (e) {
|
|
32882
|
+
return failGraceful(`train inventory: ${e.message}`);
|
|
32883
|
+
}
|
|
32884
|
+
});
|
|
32885
|
+
}
|
|
32886
|
+
|
|
32887
|
+
// ../infra/src/version-gate.ts
|
|
32888
|
+
var HUB_REPO5 = "mutmutco/mmi-hub";
|
|
32889
|
+
var NO_RELEASE_UNKNOWN = "no published GitHub Release";
|
|
32890
|
+
function semverEqual(a, b) {
|
|
32891
|
+
return versionAtLeast(a, b) && versionAtLeast(b, a);
|
|
32892
|
+
}
|
|
32893
|
+
function semverRelation(a, b) {
|
|
32894
|
+
if (!parseSemver(a) || !parseSemver(b)) return null;
|
|
32895
|
+
if (semverEqual(a, b)) return "equal";
|
|
32896
|
+
return versionAtLeast(a, b) ? "ahead" : "behind";
|
|
32897
|
+
}
|
|
32898
|
+
function classifyRow(row) {
|
|
32899
|
+
const findings = [];
|
|
32900
|
+
const reasons = /* @__PURE__ */ new Set();
|
|
32901
|
+
const releaseRequired = row.track !== "trunk";
|
|
32902
|
+
for (const unknown of row.unknowns) {
|
|
32903
|
+
if (!releaseRequired && unknown === NO_RELEASE_UNKNOWN) continue;
|
|
32904
|
+
reasons.add(unknown);
|
|
32905
|
+
}
|
|
32906
|
+
const { version, coordinatedVersion, coordinatedSource, tag } = row.release;
|
|
32907
|
+
if (releaseRequired && !version && ![...reasons].some((r) => /release/i.test(r))) {
|
|
32908
|
+
reasons.add(NO_RELEASE_UNKNOWN);
|
|
32909
|
+
}
|
|
32910
|
+
if (releaseRequired && version && !coordinatedVersion && ![...reasons].some((r) => /version lock|distribution-bom|package\.json/i.test(r))) {
|
|
32911
|
+
reasons.add(`coordinated version pointer unknown at ${tag ?? "the release tag"}`);
|
|
32912
|
+
}
|
|
32913
|
+
if (version && coordinatedVersion) {
|
|
32914
|
+
const relation = semverRelation(version, coordinatedVersion);
|
|
32915
|
+
if (relation === null) {
|
|
32916
|
+
reasons.add(`released ${version} and ${coordinatedSource ?? "version lock"} ${coordinatedVersion} are not comparable semver`);
|
|
32917
|
+
} else if (relation !== "equal") {
|
|
32918
|
+
findings.push(`released ${version} is ${relation === "ahead" ? "ahead of" : "behind"} the tag-scoped version lock ${coordinatedVersion} (${coordinatedSource ?? "unknown source"})`);
|
|
32919
|
+
}
|
|
32920
|
+
}
|
|
32921
|
+
if (row.pluginVersion && version) {
|
|
32922
|
+
const relation = semverRelation(row.pluginVersion, version);
|
|
32923
|
+
if (relation === null) {
|
|
32924
|
+
reasons.add(`plugin surface version ${row.pluginVersion} and released ${version} are not comparable semver`);
|
|
32925
|
+
} else if (relation === "behind") {
|
|
32926
|
+
findings.push(`plugin surface ships ${row.pluginVersion}, behind the released ${version}`);
|
|
32927
|
+
}
|
|
32928
|
+
}
|
|
32929
|
+
const unknownReasons = [...reasons].sort((a, b) => a.localeCompare(b));
|
|
32930
|
+
return {
|
|
32931
|
+
repo: row.repo,
|
|
32932
|
+
track: row.track,
|
|
32933
|
+
deployModel: row.deployModel,
|
|
32934
|
+
verdict: findings.length > 0 ? "drift" : unknownReasons.length > 0 ? "unknown" : "conforming",
|
|
32935
|
+
findings,
|
|
32936
|
+
unknownReasons,
|
|
32937
|
+
checkedAt: row.freshness.readAt
|
|
32938
|
+
};
|
|
32939
|
+
}
|
|
32940
|
+
function canaryOrder(a, b) {
|
|
32941
|
+
const rank = { conforming: 0, unknown: 1, drift: 2 };
|
|
32942
|
+
if (rank[a.result.verdict] !== rank[b.result.verdict]) return rank[a.result.verdict] - rank[b.result.verdict];
|
|
32943
|
+
const aAt = a.releasedAt ? Date.parse(a.releasedAt) : Number.NEGATIVE_INFINITY;
|
|
32944
|
+
const bAt = b.releasedAt ? Date.parse(b.releasedAt) : Number.NEGATIVE_INFINITY;
|
|
32945
|
+
if (aAt !== bAt) return bAt - aAt;
|
|
32946
|
+
return a.result.repo.localeCompare(b.result.repo);
|
|
32947
|
+
}
|
|
32948
|
+
function buildCanaryClasses(rows, resultByRepo) {
|
|
32949
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
32950
|
+
for (const row of rows) {
|
|
32951
|
+
const model = row.deployModel ?? "undeclared";
|
|
32952
|
+
const result = resultByRepo.get(row.repo);
|
|
32953
|
+
if (!result) continue;
|
|
32954
|
+
const bucket = byModel.get(model) ?? [];
|
|
32955
|
+
bucket.push({ result, releasedAt: row.freshness.releasedAt });
|
|
32956
|
+
byModel.set(model, bucket);
|
|
32957
|
+
}
|
|
32958
|
+
return [...byModel.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([deployModel, members]) => {
|
|
32959
|
+
const ordered = [...members].sort(canaryOrder);
|
|
32960
|
+
return {
|
|
32961
|
+
deployModel,
|
|
32962
|
+
canary: ordered[0]?.result.repo ?? null,
|
|
32963
|
+
cohort: ordered.slice(1).map((m) => m.result.repo),
|
|
32964
|
+
conforming: members.filter((m) => m.result.verdict === "conforming").length,
|
|
32965
|
+
drift: members.filter((m) => m.result.verdict === "drift").length,
|
|
32966
|
+
unknown: members.filter((m) => m.result.verdict === "unknown").length
|
|
32967
|
+
};
|
|
32968
|
+
});
|
|
32969
|
+
}
|
|
32970
|
+
function evaluateFleetLockstepGate(rows, evaluatedAt) {
|
|
32971
|
+
const results = rows.map(classifyRow);
|
|
32972
|
+
const resultByRepo = new Map(results.map((r) => [r.repo, r]));
|
|
32973
|
+
const drift = results.filter((r) => r.verdict === "drift").map((r) => ({ repo: r.repo, findings: [...r.findings] }));
|
|
32974
|
+
const unknowns = results.filter((r) => r.verdict === "unknown").map((r) => ({ repo: r.repo, reasons: [...r.unknownReasons] }));
|
|
32975
|
+
const conformingRepos = results.filter((r) => r.verdict === "conforming").map((r) => r.repo);
|
|
32976
|
+
const hubOnly = rows.length > 0 && rows.every((row) => row.repo.trim().toLowerCase() === HUB_REPO5);
|
|
32977
|
+
const failReasons = [];
|
|
32978
|
+
if (rows.length === 0) failReasons.push("empty roster: no registered repositories were evaluated");
|
|
32979
|
+
if (hubOnly) failReasons.push("hub-only coverage: the fleet gate refuses a result covering only mutmutco/MMI-Hub");
|
|
32980
|
+
if (drift.length > 0) failReasons.push(`${drift.length} repositor${drift.length === 1 ? "y" : "ies"} with drift`);
|
|
32981
|
+
if (unknowns.length > 0) failReasons.push(`${unknowns.length} repositor${unknowns.length === 1 ? "y" : "ies"} with unknown evidence`);
|
|
32982
|
+
return {
|
|
32983
|
+
gate: "fleet-lockstep-conformance",
|
|
32984
|
+
mode: "dry-run",
|
|
32985
|
+
enforcement: "none",
|
|
32986
|
+
verdict: failReasons.length === 0 && conformingRepos.length === rows.length ? "pass" : "fail",
|
|
32987
|
+
failReasons,
|
|
32988
|
+
denominator: rows.length,
|
|
32989
|
+
conforming: conformingRepos.length,
|
|
32990
|
+
conformingRepos,
|
|
32991
|
+
drift,
|
|
32992
|
+
unknowns,
|
|
32993
|
+
canaryClasses: buildCanaryClasses(rows, resultByRepo),
|
|
32994
|
+
hubOnly,
|
|
32995
|
+
repos: results,
|
|
32996
|
+
evaluatedAt
|
|
32997
|
+
};
|
|
32998
|
+
}
|
|
32999
|
+
|
|
33000
|
+
// src/fleet-lockstep-gate.ts
|
|
33001
|
+
async function runFleetLockstepGate(deps = defaultFleetTrackInventoryDeps()) {
|
|
33002
|
+
const inventory = await readFleetTrackInventory(deps);
|
|
33003
|
+
return evaluateFleetLockstepGate(inventory.repos, inventory.generatedAt);
|
|
33004
|
+
}
|
|
33005
|
+
function fleetGateConformanceEvidence(result) {
|
|
33006
|
+
return result.repos.map((row) => ({
|
|
33007
|
+
repo: row.repo,
|
|
33008
|
+
verdict: row.verdict,
|
|
33009
|
+
checkedAt: row.checkedAt,
|
|
33010
|
+
findings: row.findings.length ? [...row.findings] : void 0,
|
|
33011
|
+
unknownEvidence: row.unknownReasons.length ? [...row.unknownReasons] : void 0
|
|
33012
|
+
}));
|
|
33013
|
+
}
|
|
33014
|
+
function formatFleetLockstepGate(result) {
|
|
33015
|
+
const lines = [
|
|
33016
|
+
`fleet lockstep gate (dry-run) \u2014 ${result.conforming}/${result.denominator} conforming \xB7 ${result.verdict.toUpperCase()}`,
|
|
33017
|
+
`evaluated ${result.evaluatedAt} \xB7 live D2a inventory \xB7 enforcement: none (D2c #4453)`
|
|
33018
|
+
];
|
|
33019
|
+
if (result.failReasons.length) {
|
|
33020
|
+
lines.push("", "fail reasons:");
|
|
33021
|
+
for (const reason of result.failReasons) lines.push(` ! ${reason}`);
|
|
33022
|
+
}
|
|
33023
|
+
if (result.conformingRepos.length) {
|
|
33024
|
+
lines.push("", `conforming set (${result.conformingRepos.length}):`);
|
|
33025
|
+
for (const repo of result.conformingRepos) lines.push(` = ${repo}`);
|
|
33026
|
+
}
|
|
33027
|
+
if (result.drift.length) {
|
|
33028
|
+
lines.push("", `drift (${result.drift.length}):`);
|
|
33029
|
+
for (const row of result.drift) {
|
|
33030
|
+
lines.push(` x ${row.repo}`);
|
|
33031
|
+
for (const finding of row.findings) lines.push(` ${finding}`);
|
|
33032
|
+
}
|
|
33033
|
+
}
|
|
33034
|
+
if (result.unknowns.length) {
|
|
33035
|
+
lines.push("", `unknown evidence (${result.unknowns.length}):`);
|
|
33036
|
+
for (const row of result.unknowns) {
|
|
33037
|
+
lines.push(` ? ${row.repo}`);
|
|
33038
|
+
for (const reason of row.reasons) lines.push(` ${reason}`);
|
|
33039
|
+
}
|
|
33040
|
+
}
|
|
33041
|
+
lines.push("", "canary classes by deploy model (canary -> cohort):");
|
|
33042
|
+
for (const cls of result.canaryClasses) {
|
|
33043
|
+
lines.push(
|
|
33044
|
+
` ${cls.deployModel}: canary ${cls.canary ?? "NONE"}` + (cls.cohort.length ? ` -> ${cls.cohort.join(", ")}` : "") + ` (${cls.conforming} conforming \xB7 ${cls.drift} drift \xB7 ${cls.unknown} unknown)`
|
|
33045
|
+
);
|
|
33046
|
+
}
|
|
33047
|
+
return lines.join("\n");
|
|
33048
|
+
}
|
|
33049
|
+
function registerFleetLockstepGate(program3) {
|
|
33050
|
+
const train = program3.commands.find((c) => c.name() === "train");
|
|
33051
|
+
if (!train) throw new Error("train gate registration requires the train command group");
|
|
33052
|
+
train.command("gate").description("fleet lockstep N-of-N conformance DRY-RUN gate (#4452) \u2014 live D2a inventory in, fail-closed verdict out: denominator, conforming set, exact drift, canary classes by deploy model, named unknowns; no ruleset enforcement (D2c #4453)").option("--json", "machine-readable output (the typed gate result)").action(async (o) => {
|
|
33053
|
+
try {
|
|
33054
|
+
const result = await runFleetLockstepGate();
|
|
33055
|
+
console.log(o.json ? JSON.stringify(result, null, 2) : formatFleetLockstepGate(result));
|
|
33056
|
+
process.exitCode = result.verdict === "pass" ? 0 : 1;
|
|
33057
|
+
} catch (e) {
|
|
33058
|
+
return failGraceful(`train gate: ${e.message}`);
|
|
33059
|
+
}
|
|
33060
|
+
});
|
|
33061
|
+
}
|
|
33062
|
+
|
|
32638
33063
|
// src/deploy-status.ts
|
|
32639
33064
|
function buildDeployStatusReport(input) {
|
|
32640
33065
|
const deploy = input.deployFacts?.stages[input.stage] ?? null;
|
|
@@ -33619,6 +34044,12 @@ function parseWorktreePorcelain2(stdout) {
|
|
|
33619
34044
|
var CLOSING_MENTION_RE = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi;
|
|
33620
34045
|
var NEGATION_RE = /\b(?:not|never|cannot|can't|don't|doesn't|didn't|won't|wouldn't|shouldn't|mustn't|without)\b/i;
|
|
33621
34046
|
var CLAUSE_WINDOW = 120;
|
|
34047
|
+
var NEGATED_CLOSING_PHRASE_RE = /\b(?:(?:does|do|did|will|would|should|must|can)\s+not|(?:is|was|are|were)\s+not|doesn't|don't|didn't|won't|wouldn't|shouldn't|mustn't|can't|cannot|never|without|not)\s+(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi;
|
|
34048
|
+
function rewriteNegatedClosingPhrases(text) {
|
|
34049
|
+
if (!text.includes("#")) return text;
|
|
34050
|
+
const segments = text.split(/(```[\s\S]*?```|~~~[\s\S]*?~~~|`[^`\n]*`)/g);
|
|
34051
|
+
return segments.map((seg, i) => i % 2 === 1 ? seg : seg.replace(NEGATED_CLOSING_PHRASE_RE, (_m, n) => `leaves #${n} open`)).join("");
|
|
34052
|
+
}
|
|
33622
34053
|
function findClosingMentions(text) {
|
|
33623
34054
|
const mentions = [];
|
|
33624
34055
|
for (const match of text.matchAll(CLOSING_MENTION_RE)) {
|
|
@@ -33692,11 +34123,12 @@ ${typeof body === "string" ? body : ""}`;
|
|
|
33692
34123
|
function negatedClosingRefusalMessage(negated, context = "pr merge") {
|
|
33693
34124
|
const named = negated.map((n) => `#${n}`).join(", ");
|
|
33694
34125
|
const first = `#${negated[0] ?? "N"}`;
|
|
33695
|
-
return `${context}: REFUSED \u2014 GitHub will close ${named} on merge, but the PR body says it does not (GitHub's closing-keyword parser is negation-blind: "does not close ${first}" still closes it). Reword
|
|
34126
|
+
return `${context}: REFUSED \u2014 GitHub will close ${named} on merge, but the PR body says it does not (GitHub's closing-keyword parser is negation-blind: "does not close ${first}" still closes it). Reword so the keyword is gone \u2014 use "Part of ${first}", "Refs ${first}", or "leaves ${first} open"; never "does not close ${first}". Or re-run with --force to merge anyway and let ${named} close.`;
|
|
33696
34127
|
}
|
|
33697
34128
|
function commitClosingRefusalMessage(closing, context = "pr merge") {
|
|
33698
34129
|
const named = closing.map((n) => `#${n}`).join(", ");
|
|
33699
|
-
|
|
34130
|
+
const first = `#${closing[0] ?? "N"}`;
|
|
34131
|
+
return `${context}: REFUSED \u2014 commit messages will close ${named} through the squash body, but GitHub omitted ${named} from closingIssuesReferences. Remove close/fix/resolve + ${first} from the commit message (if the issue must stay open, use "Part of ${first}" / "Refs ${first}" / "leaves ${first} open" \u2014 never "does not close ${first}"), or re-run with --force to merge anyway and let ${named} close.`;
|
|
33700
34132
|
}
|
|
33701
34133
|
function alreadyClosedCommitClosingMessage(closed, context = "pr merge") {
|
|
33702
34134
|
const named = closed.map((n) => `#${n}`).join(", ");
|
|
@@ -33887,6 +34319,297 @@ function registerSessionReport(program3) {
|
|
|
33887
34319
|
});
|
|
33888
34320
|
}
|
|
33889
34321
|
|
|
34322
|
+
// src/learning-closure-rate.ts
|
|
34323
|
+
var CLOSURE_RATE_TIMEOUT_MS = 2e4;
|
|
34324
|
+
var LOOP_ITEM_CAP = 100;
|
|
34325
|
+
var EVIDENCE_CONCURRENCY = 4;
|
|
34326
|
+
var RED_TEAM_FINDING_LABEL2 = "red-team";
|
|
34327
|
+
var RULING_LOOP_STATE_LABELS = ["loop-state:closed-deferred", "loop-state:closed-invalid"];
|
|
34328
|
+
var CLOSED_MERGED_LOOP_STATE_LABEL = "loop-state:closed-merged";
|
|
34329
|
+
var LOOP_KINDS2 = [
|
|
34330
|
+
{ kind: "friction", label: REPORT_LABEL },
|
|
34331
|
+
{ kind: "lesson", label: SKILL_LESSON_LABEL },
|
|
34332
|
+
{ kind: "red-team", label: RED_TEAM_FINDING_LABEL2 }
|
|
34333
|
+
];
|
|
34334
|
+
function prIsMerged(pr2) {
|
|
34335
|
+
return String(pr2.state ?? "").toUpperCase() === "MERGED";
|
|
34336
|
+
}
|
|
34337
|
+
function mergedPrsOf(linkedPrs) {
|
|
34338
|
+
return linkedPrs.filter(prIsMerged);
|
|
34339
|
+
}
|
|
34340
|
+
function hasRecordedRuling(labels) {
|
|
34341
|
+
return labels.some((label) => RULING_LOOP_STATE_LABELS.includes(label));
|
|
34342
|
+
}
|
|
34343
|
+
function classifyLoopClosure(item, evidence) {
|
|
34344
|
+
if (String(item.state ?? "").toUpperCase() !== "CLOSED") return "open";
|
|
34345
|
+
if (mergedPrsOf(evidence.linkedPrs).length > 0) return "closed";
|
|
34346
|
+
if (item.labels.includes(CLOSED_MERGED_LOOP_STATE_LABEL)) return "closed";
|
|
34347
|
+
if (hasRecordedRuling(item.labels)) return "closed";
|
|
34348
|
+
return "closed-no-evidence";
|
|
34349
|
+
}
|
|
34350
|
+
var round3 = (x) => Math.round(x * 1e3) / 1e3;
|
|
34351
|
+
function computeLoopKindRate(spec, verdicts) {
|
|
34352
|
+
const numerator = verdicts.filter((v) => v === "closed").length;
|
|
34353
|
+
const unknownEvidence = verdicts.filter((v) => v === "closed-no-evidence").length;
|
|
34354
|
+
const denominator = verdicts.length;
|
|
34355
|
+
return {
|
|
34356
|
+
kind: spec.kind,
|
|
34357
|
+
label: spec.label,
|
|
34358
|
+
numerator,
|
|
34359
|
+
denominator,
|
|
34360
|
+
unknownEvidence,
|
|
34361
|
+
rate: denominator > 0 ? round3(numerator / denominator) : null
|
|
34362
|
+
};
|
|
34363
|
+
}
|
|
34364
|
+
function fmtRate(rate) {
|
|
34365
|
+
return rate === null ? "\u2014" : rate.toFixed(3);
|
|
34366
|
+
}
|
|
34367
|
+
function formatClosureSummary(report) {
|
|
34368
|
+
const kindWidth = Math.max(...report.kinds.map((k) => k.kind.length));
|
|
34369
|
+
const labelWidth = Math.max(...report.kinds.map((k) => k.label.length));
|
|
34370
|
+
const lines = [`learning closure rate \u2014 ${report.repo} (computed at read; #4440)`];
|
|
34371
|
+
for (const k of report.kinds) {
|
|
34372
|
+
const head = `${k.kind.padEnd(kindWidth)} (label ${k.label.padEnd(labelWidth)})`;
|
|
34373
|
+
const body = k.denominator === 0 ? "no loop items" : `${k.numerator}/${k.denominator} closed \xB7 ${k.unknownEvidence} unknown evidence \xB7 rate ${fmtRate(k.rate)}`;
|
|
34374
|
+
lines.push(` ${head}: ${body}`);
|
|
34375
|
+
}
|
|
34376
|
+
const t = report.totals;
|
|
34377
|
+
lines.push(
|
|
34378
|
+
` ${"total".padEnd(kindWidth)}: ${t.numerator}/${t.denominator} closed \xB7 ${t.unknownEvidence} unknown evidence \xB7 rate ${fmtRate(t.rate)}`,
|
|
34379
|
+
"A closed loop without a merged PR or a recorded ruling reads as open."
|
|
34380
|
+
);
|
|
34381
|
+
if (report.evidencePartial) {
|
|
34382
|
+
lines.push("note: at least one closed item had an unreadable linked-PR read \u2014 counted open (unknown evidence).");
|
|
34383
|
+
}
|
|
34384
|
+
return lines.join("\n");
|
|
34385
|
+
}
|
|
34386
|
+
function buildClosureReport(repo, perKind, evidencePartial) {
|
|
34387
|
+
const kinds = perKind.map(({ spec, verdicts }) => computeLoopKindRate(spec, verdicts));
|
|
34388
|
+
const numerator = kinds.reduce((acc, k) => acc + k.numerator, 0);
|
|
34389
|
+
const denominator = kinds.reduce((acc, k) => acc + k.denominator, 0);
|
|
34390
|
+
const unknownEvidence = kinds.reduce((acc, k) => acc + k.unknownEvidence, 0);
|
|
34391
|
+
const report = {
|
|
34392
|
+
repo,
|
|
34393
|
+
kinds,
|
|
34394
|
+
totals: {
|
|
34395
|
+
numerator,
|
|
34396
|
+
denominator,
|
|
34397
|
+
unknownEvidence,
|
|
34398
|
+
rate: denominator > 0 ? round3(numerator / denominator) : null
|
|
34399
|
+
},
|
|
34400
|
+
evidencePartial,
|
|
34401
|
+
summary: ""
|
|
34402
|
+
};
|
|
34403
|
+
report.summary = formatClosureSummary(report);
|
|
34404
|
+
return report;
|
|
34405
|
+
}
|
|
34406
|
+
function splitRepo2(repo) {
|
|
34407
|
+
const parts = repo.split("/");
|
|
34408
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
34409
|
+
throw new QueryReadError("BAD_INPUT", `invalid repo "${repo}" (expected owner/repo)`);
|
|
34410
|
+
}
|
|
34411
|
+
return { owner: parts[0], name: parts[1] };
|
|
34412
|
+
}
|
|
34413
|
+
async function mapPooled(items, limit, worker) {
|
|
34414
|
+
const out = new Array(items.length);
|
|
34415
|
+
let next = 0;
|
|
34416
|
+
const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
34417
|
+
while (next < items.length) {
|
|
34418
|
+
const index = next;
|
|
34419
|
+
next += 1;
|
|
34420
|
+
out[index] = await worker(items[index]);
|
|
34421
|
+
}
|
|
34422
|
+
});
|
|
34423
|
+
await Promise.all(runners);
|
|
34424
|
+
return out;
|
|
34425
|
+
}
|
|
34426
|
+
async function readEvidence(deps, owner, name, item) {
|
|
34427
|
+
try {
|
|
34428
|
+
const resp = await deps.ghJson(prForIssueGraphqlArgs(owner, name, item.number), CLOSURE_RATE_TIMEOUT_MS);
|
|
34429
|
+
return { linkedPrs: extractPrForIssueResponse(resp), readFailed: false };
|
|
34430
|
+
} catch (e) {
|
|
34431
|
+
if (e instanceof QueryReadError && e.code === "NOT_FOUND") {
|
|
34432
|
+
return { linkedPrs: [], readFailed: false };
|
|
34433
|
+
}
|
|
34434
|
+
return { linkedPrs: [], readFailed: true };
|
|
34435
|
+
}
|
|
34436
|
+
}
|
|
34437
|
+
async function runLearningClosureRate(deps, opts = {}) {
|
|
34438
|
+
const repo = opts.repo ?? HUB_REPO3;
|
|
34439
|
+
const { owner, name } = splitRepo2(repo);
|
|
34440
|
+
let evidencePartial = false;
|
|
34441
|
+
const perKind = [];
|
|
34442
|
+
for (const spec of LOOP_KINDS2) {
|
|
34443
|
+
const rows = await deps.ghJson(
|
|
34444
|
+
buildIssueListArgs({ label: spec.label, state: "all", limit: LOOP_ITEM_CAP }, repo),
|
|
34445
|
+
CLOSURE_RATE_TIMEOUT_MS
|
|
34446
|
+
);
|
|
34447
|
+
const items = shapeIssueList(rows);
|
|
34448
|
+
const closed = items.filter((item) => String(item.state ?? "").toUpperCase() === "CLOSED");
|
|
34449
|
+
const evidence = await mapPooled(closed, EVIDENCE_CONCURRENCY, (item) => readEvidence(deps, owner, name, item));
|
|
34450
|
+
if (evidence.some((ev) => ev.readFailed)) evidencePartial = true;
|
|
34451
|
+
const evidenceByNumber = new Map(closed.map((item, i) => [item.number, evidence[i]]));
|
|
34452
|
+
const verdicts = items.map(
|
|
34453
|
+
(item) => classifyLoopClosure(item, evidenceByNumber.get(item.number) ?? { linkedPrs: [], readFailed: false })
|
|
34454
|
+
);
|
|
34455
|
+
perKind.push({ spec, verdicts });
|
|
34456
|
+
}
|
|
34457
|
+
return buildClosureReport(repo, perKind, evidencePartial);
|
|
34458
|
+
}
|
|
34459
|
+
function closureRateFail(e) {
|
|
34460
|
+
if (e instanceof QueryReadError) {
|
|
34461
|
+
const code = e.code === "NOT_FOUND" ? ERROR_CODES.ERR_NOT_FOUND : e.code === "NO_AUTH" ? ERROR_CODES.ERR_NO_AUTH : ERROR_CODES.ERR_BAD_ENUM;
|
|
34462
|
+
return fail(`closure-rate: ${e.message}`, code === ERROR_CODES.ERR_BAD_ENUM ? void 0 : { code });
|
|
34463
|
+
}
|
|
34464
|
+
const err = e;
|
|
34465
|
+
return fail(`closure-rate: ${(err.stderr || err.message || String(e)).trim()}`);
|
|
34466
|
+
}
|
|
34467
|
+
function registerLearningClosureRateCommand(program3) {
|
|
34468
|
+
const deps = queryDeps();
|
|
34469
|
+
withExamples(
|
|
34470
|
+
program3.command("closure-rate").description(
|
|
34471
|
+
"learning closure rate per loop kind (friction, lesson, red-team) computed at read from board + linked-PR/ruling evidence \u2014 a closed loop without a merged PR or a recorded ruling reads as open (#4440)"
|
|
34472
|
+
).option("--repo <owner/repo>", `repo holding the loop items (defaults to ${HUB_REPO3}, the central Hub board)`).option("--json", "print the structured LearningClosureReport JSON (default is the human summary)").action(async (o) => {
|
|
34473
|
+
try {
|
|
34474
|
+
const report = await runLearningClosureRate(deps, { repo: o.repo });
|
|
34475
|
+
if (o.json) console.log(JSON.stringify(report, null, 2));
|
|
34476
|
+
else console.log(report.summary);
|
|
34477
|
+
} catch (e) {
|
|
34478
|
+
closureRateFail(e);
|
|
34479
|
+
}
|
|
34480
|
+
}),
|
|
34481
|
+
[
|
|
34482
|
+
"mmi-cli learning closure-rate",
|
|
34483
|
+
"mmi-cli learning closure-rate --repo mutmutco/MMI-Hub --json"
|
|
34484
|
+
],
|
|
34485
|
+
"loop items are read newest-first with a bounded cap of 100 per kind \u2014 a rate over a longer history needs the cap raised, never an unbounded scan"
|
|
34486
|
+
);
|
|
34487
|
+
}
|
|
34488
|
+
|
|
34489
|
+
// ../infra/src/fleet-health.ts
|
|
34490
|
+
function uniqueRepos(repos) {
|
|
34491
|
+
const unique = /* @__PURE__ */ new Map();
|
|
34492
|
+
for (const raw of repos) {
|
|
34493
|
+
const repo = raw.trim();
|
|
34494
|
+
if (repo) unique.set(repo.toLowerCase(), unique.get(repo.toLowerCase()) ?? repo);
|
|
34495
|
+
}
|
|
34496
|
+
return [...unique.values()].sort((a, b) => a.localeCompare(b));
|
|
34497
|
+
}
|
|
34498
|
+
function buildFleetHealthReport(registeredRepos, evidence, asOf, maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
34499
|
+
const repos = uniqueRepos(registeredRepos);
|
|
34500
|
+
const evidenceByRepo = new Map(evidence.map((row) => [row.repo.trim().toLowerCase(), row]));
|
|
34501
|
+
const nowMs = Date.parse(asOf);
|
|
34502
|
+
const drift = [];
|
|
34503
|
+
const unknownEvidence = [];
|
|
34504
|
+
let conforming = 0;
|
|
34505
|
+
let fresh = 0;
|
|
34506
|
+
let stale = 0;
|
|
34507
|
+
let freshnessUnknown = 0;
|
|
34508
|
+
for (const repo of repos) {
|
|
34509
|
+
const row = evidenceByRepo.get(repo.toLowerCase());
|
|
34510
|
+
const reasons = [...row?.unknownEvidence ?? []];
|
|
34511
|
+
let isFresh = false;
|
|
34512
|
+
if (!row) {
|
|
34513
|
+
reasons.push("no #4318 dry-run conformance evidence");
|
|
34514
|
+
freshnessUnknown += 1;
|
|
34515
|
+
} else if (!row.checkedAt) {
|
|
34516
|
+
reasons.push("conformance freshness is unknown");
|
|
34517
|
+
freshnessUnknown += 1;
|
|
34518
|
+
} else {
|
|
34519
|
+
const checkedMs = Date.parse(row.checkedAt);
|
|
34520
|
+
if (!Number.isFinite(checkedMs) || !Number.isFinite(nowMs) || checkedMs > nowMs) {
|
|
34521
|
+
reasons.push(`invalid conformance timestamp: ${row.checkedAt}`);
|
|
34522
|
+
freshnessUnknown += 1;
|
|
34523
|
+
} else if (nowMs - checkedMs > maxAgeMs) {
|
|
34524
|
+
reasons.push(`conformance evidence is stale (${row.checkedAt})`);
|
|
34525
|
+
stale += 1;
|
|
34526
|
+
} else {
|
|
34527
|
+
isFresh = true;
|
|
34528
|
+
fresh += 1;
|
|
34529
|
+
}
|
|
34530
|
+
}
|
|
34531
|
+
if (row?.verdict === "drift") {
|
|
34532
|
+
drift.push({
|
|
34533
|
+
repo,
|
|
34534
|
+
findings: row.findings?.length ? [...row.findings] : ["#4318 verdict reported drift without a finding"]
|
|
34535
|
+
});
|
|
34536
|
+
}
|
|
34537
|
+
if (row?.verdict === "unknown" && reasons.length === 0) reasons.push("#4318 verdict is unknown");
|
|
34538
|
+
if (row?.verdict === "conforming" && isFresh && reasons.length === 0) conforming += 1;
|
|
34539
|
+
if (reasons.length > 0) unknownEvidence.push({ repo, reasons });
|
|
34540
|
+
}
|
|
34541
|
+
const verdict = drift.length > 0 ? "drift" : repos.length > 0 && conforming === repos.length && unknownEvidence.length === 0 ? "conforming" : "unknown";
|
|
34542
|
+
return {
|
|
34543
|
+
verdict,
|
|
34544
|
+
source: "#4318-dry-run-conformance",
|
|
34545
|
+
denominator: repos.length,
|
|
34546
|
+
conforming,
|
|
34547
|
+
drift,
|
|
34548
|
+
freshness: {
|
|
34549
|
+
asOf,
|
|
34550
|
+
maxAgeSeconds: Math.floor(maxAgeMs / 1e3),
|
|
34551
|
+
fresh,
|
|
34552
|
+
stale,
|
|
34553
|
+
unknown: freshnessUnknown
|
|
34554
|
+
},
|
|
34555
|
+
unknownEvidence,
|
|
34556
|
+
readOnly: true
|
|
34557
|
+
};
|
|
34558
|
+
}
|
|
34559
|
+
function fleetHealthExitCode(report) {
|
|
34560
|
+
return report.verdict === "conforming" ? 0 : 1;
|
|
34561
|
+
}
|
|
34562
|
+
|
|
34563
|
+
// src/org-health-query.ts
|
|
34564
|
+
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
34565
|
+
function defaultOrgHealthQueryDeps() {
|
|
34566
|
+
return {
|
|
34567
|
+
registeredRepos: async () => {
|
|
34568
|
+
const cfg = await loadConfig();
|
|
34569
|
+
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
34570
|
+
if (!projects) throw new Error("live Hub registry roster is unavailable");
|
|
34571
|
+
return orgVersionRepos(projects);
|
|
34572
|
+
},
|
|
34573
|
+
conformanceEvidence: async () => fleetGateConformanceEvidence(await runFleetLockstepGate()),
|
|
34574
|
+
now: () => /* @__PURE__ */ new Date()
|
|
34575
|
+
};
|
|
34576
|
+
}
|
|
34577
|
+
async function runOrgHealthQuery(deps, maxAgeMs = DEFAULT_MAX_AGE_MS) {
|
|
34578
|
+
const repos = await deps.registeredRepos();
|
|
34579
|
+
const evidence = await deps.conformanceEvidence(repos);
|
|
34580
|
+
return buildFleetHealthReport(repos, evidence, deps.now().toISOString(), maxAgeMs);
|
|
34581
|
+
}
|
|
34582
|
+
function formatOrgHealth(report) {
|
|
34583
|
+
const lines = [
|
|
34584
|
+
`org health \u2014 ${report.conforming}/${report.denominator} conforming \xB7 ${report.verdict.toUpperCase()}`,
|
|
34585
|
+
`source: ${report.source} \xB7 read ${report.freshness.asOf}`,
|
|
34586
|
+
`freshness: ${report.freshness.fresh} fresh \xB7 ${report.freshness.stale} stale \xB7 ${report.freshness.unknown} unknown`
|
|
34587
|
+
];
|
|
34588
|
+
if (report.drift.length) {
|
|
34589
|
+
lines.push("", "drift:");
|
|
34590
|
+
for (const row of report.drift) lines.push(` ${row.repo}: ${row.findings.join("; ")}`);
|
|
34591
|
+
}
|
|
34592
|
+
if (report.unknownEvidence.length) {
|
|
34593
|
+
lines.push("", "unknown evidence:");
|
|
34594
|
+
for (const row of report.unknownEvidence) lines.push(` ${row.repo}: ${row.reasons.join("; ")}`);
|
|
34595
|
+
}
|
|
34596
|
+
return lines.join("\n");
|
|
34597
|
+
}
|
|
34598
|
+
function registerOrgHealthQuery(program3, deps = defaultOrgHealthQueryDeps()) {
|
|
34599
|
+
const org = program3.commands.find((command) => command.name() === "org");
|
|
34600
|
+
if (!org) throw new Error("org health registration requires consolidated command namespaces");
|
|
34601
|
+
org.command("health").description("live read of #4318 fleet lockstep evidence \u2014 N-of-N, named drift, freshness, and unknowns; never writes state").option("--json", "machine-readable output").action(async (options) => {
|
|
34602
|
+
try {
|
|
34603
|
+
const report = await runOrgHealthQuery(deps);
|
|
34604
|
+
console.log(options.json ? JSON.stringify(report, null, 2) : formatOrgHealth(report));
|
|
34605
|
+
process.exitCode = fleetHealthExitCode(report);
|
|
34606
|
+
} catch (error) {
|
|
34607
|
+
console.error(`org health: ${error.message}`);
|
|
34608
|
+
process.exitCode = 1;
|
|
34609
|
+
}
|
|
34610
|
+
});
|
|
34611
|
+
}
|
|
34612
|
+
|
|
33890
34613
|
// src/plugin-release-catchup.ts
|
|
33891
34614
|
var import_node_fs43 = require("node:fs");
|
|
33892
34615
|
var import_node_path40 = require("node:path");
|
|
@@ -36838,9 +37561,9 @@ program2.name("mmi-cli").description("MMI Future Hub CLI ? the org control plane
|
|
|
36838
37561
|
program2.addHelpText(
|
|
36839
37562
|
"before",
|
|
36840
37563
|
`Houses (canonical roots): oracle \xB7 harbour \xB7 devops \xB7 vault \xB7 learning
|
|
36841
|
-
\`mmi-cli <house> <command> \u2026\` is the
|
|
36842
|
-
|
|
36843
|
-
house-shaped tree; \`mmi-cli <house> --help\` lists one house.
|
|
37564
|
+
\`mmi-cli <house> <command> \u2026\` is the only invocation form for the paths below \u2014 their flat
|
|
37565
|
+
\`mmi-cli <command> \u2026\` aliases were removed in ${COMPAT_SHIM_DEATH_WAVE} (#4316, #4438); core commands stay
|
|
37566
|
+
unprefixed. \`mmi-cli commands\` shows the house-shaped tree; \`mmi-cli <house> --help\` lists one house.
|
|
36844
37567
|
`
|
|
36845
37568
|
);
|
|
36846
37569
|
function appActorDeps() {
|
|
@@ -37722,7 +38445,11 @@ docs.command("index").description("regenerate docs/index.md from the docs/ tree
|
|
|
37722
38445
|
docs.command("refs").description("deterministic doc reference gate: every backticked repo path, relative .md link, `mmi-cli` command ref, and `<!-- pinned by ? -->` comment across docs/** + README.md + architecture.md must resolve, or exit 1 (fleet-portable port of the Hub gate scripts/check-doc-refs.mjs, #3339)").option("--json", "machine-readable findings list: { ok, docCount, findings[], warnings[] }").action(async (o) => {
|
|
37723
38446
|
try {
|
|
37724
38447
|
const root = await repoRoot();
|
|
37725
|
-
const commandPaths = new Set(
|
|
38448
|
+
const commandPaths = new Set(
|
|
38449
|
+
buildCommandManifest(program2).index.map(
|
|
38450
|
+
(entry) => entry.house && entry.house !== "core" ? `${entry.house} ${entry.path}` : entry.path
|
|
38451
|
+
)
|
|
38452
|
+
);
|
|
37726
38453
|
const result = runDocRefs(root, { commandPaths });
|
|
37727
38454
|
if (o.json) {
|
|
37728
38455
|
consoleIo.log(JSON.stringify({ ok: result.ok, docCount: result.docCount, findings: result.findings, warnings: result.warnings }, null, 2));
|
|
@@ -38749,6 +39476,7 @@ withExamples(pr.command("create").description("create a PR and print {number,url
|
|
|
38749
39476
|
return fail(`pr create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
|
|
38750
39477
|
}
|
|
38751
39478
|
body = normalizeClosingDirectives(body);
|
|
39479
|
+
body = rewriteNegatedClosingPhrases(body);
|
|
38752
39480
|
const docsCheck = await checkDocsIndexAtHead({ repo: o.repo, head: o.head }, createPrCreateDocsIndexDeps());
|
|
38753
39481
|
if (docsCheck && !docsCheck.ok) {
|
|
38754
39482
|
return fail(`pr create: ${docsCheck.detail} ? ${docsCheck.fix}`);
|
|
@@ -39216,11 +39944,14 @@ registerQueryCommands(program2);
|
|
|
39216
39944
|
registerWorktreeCommands(program2);
|
|
39217
39945
|
registerIssueLifecycleCommands(program2, { attach: attachToProject });
|
|
39218
39946
|
registerTrainCommands(program2);
|
|
39947
|
+
registerFleetTrackInventory(program2);
|
|
39948
|
+
registerFleetLockstepGate(program2);
|
|
39219
39949
|
registerDeployCommands(program2);
|
|
39220
39950
|
registerDiscoveryCommands(program2);
|
|
39221
39951
|
registerExplainCommand(program2);
|
|
39222
39952
|
registerPrLifecycleCommands(program2);
|
|
39223
39953
|
registerSessionReport(program2);
|
|
39954
|
+
registerLearningClosureRateCommand(program2);
|
|
39224
39955
|
registerBoardCommands(program2);
|
|
39225
39956
|
registerStageCommands(program2);
|
|
39226
39957
|
var GH_TRAIN_TIMEOUT_MS = 3e4;
|
|
@@ -39237,7 +39968,10 @@ function trainApplyDeps() {
|
|
|
39237
39968
|
throw surfaceTrainSubprocessFailure(e, file, args);
|
|
39238
39969
|
}
|
|
39239
39970
|
},
|
|
39240
|
-
|
|
39971
|
+
// canonicalArgvFor (#4438): callers hand this seam house-relative paths (`org project get`,
|
|
39972
|
+
// `secrets preflight`); since the Wave 3 flat-alias cut only the canonical house-prefixed form
|
|
39973
|
+
// parses, so the ONE self-spawn seam derives the prefix through the same rule the refusal names.
|
|
39974
|
+
runSelf: async (args) => (await execFileP2(process.execPath, [process.argv[1], ...canonicalArgvFor(args)], { timeout: 3e4 })).stdout,
|
|
39241
39975
|
trainAuthority: async (repo) => {
|
|
39242
39976
|
const verdict = await fetchTrainAuthority(repo, registryClientDeps(await loadConfig()));
|
|
39243
39977
|
return verdict.ok ? { ok: true, role: verdict.authority.role, train: verdict.authority.train } : verdict;
|
|
@@ -39917,12 +40651,21 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
39917
40651
|
});
|
|
39918
40652
|
installProcessBackstop();
|
|
39919
40653
|
consolidateCommandNamespaces(program2);
|
|
40654
|
+
registerOrgHealthQuery(program2);
|
|
39920
40655
|
applyCommandTaxonomy(program2);
|
|
39921
40656
|
function resolveHouseShim(argv) {
|
|
39922
40657
|
const houseToken = argv[2];
|
|
39923
40658
|
if (!houseToken) return;
|
|
39924
40659
|
if (!isHouseRoot(houseToken)) {
|
|
39925
|
-
|
|
40660
|
+
const flatTokens = argv.slice(2, 4).filter((tok) => !tok.startsWith("-"));
|
|
40661
|
+
const flatPath = flatTokens.join(" ");
|
|
40662
|
+
const flatHouse = houseForPath(flatPath);
|
|
40663
|
+
if (!flatHouse || flatHouse === "core") return;
|
|
40664
|
+
process.stderr.write(
|
|
40665
|
+
`mmi-cli: the flat '${flatPath}' alias was removed in ${COMPAT_SHIM_DEATH_WAVE} (#4316) \u2014 run: mmi-cli ${canonicalPathFor(flatPath)} \u2026
|
|
40666
|
+
`
|
|
40667
|
+
);
|
|
40668
|
+
hardExit(2);
|
|
39926
40669
|
}
|
|
39927
40670
|
const remainder = argv.slice(3);
|
|
39928
40671
|
const first = remainder[0];
|
|
@@ -39941,10 +40684,9 @@ function resolveHouseShim(argv) {
|
|
|
39941
40684
|
argv.splice(2, 1);
|
|
39942
40685
|
return;
|
|
39943
40686
|
}
|
|
39944
|
-
const canonical = actual ? `mmi-cli ${actual === "core" ? "" : `${actual} `}${lookupPath}`.replace(/\s+/g, " ").trim() : void 0;
|
|
39945
40687
|
if (actual) {
|
|
39946
40688
|
process.stderr.write(
|
|
39947
|
-
`mmi-cli ${houseToken}: '${lookupPath}' lives in house '${actual}' \u2014 run: ${
|
|
40689
|
+
`mmi-cli ${houseToken}: '${lookupPath}' lives in house '${actual}' \u2014 run: mmi-cli ${canonicalPathFor(lookupPath)} \u2026
|
|
39948
40690
|
`
|
|
39949
40691
|
);
|
|
39950
40692
|
} else {
|
|
@@ -39967,8 +40709,8 @@ function printHouseRootHelp(house) {
|
|
|
39967
40709
|
}
|
|
39968
40710
|
lines.push(
|
|
39969
40711
|
"",
|
|
39970
|
-
`Canonical: \`mmi-cli ${house} <command> \u2026\`. The flat \`mmi-cli <command> \u2026\`
|
|
39971
|
-
`
|
|
40712
|
+
`Canonical: \`mmi-cli ${house} <command> \u2026\`. The flat \`mmi-cli <command> \u2026\` compatibility shims`,
|
|
40713
|
+
`were removed in ${COMPAT_SHIM_DEATH_WAVE} (#4316, #4438) \u2014 only the canonical house-prefixed form parses.`
|
|
39972
40714
|
);
|
|
39973
40715
|
consoleIo.log(lines.join("\n"));
|
|
39974
40716
|
}
|
package/package.json
CHANGED