@mutmutco/cli 4.1.2 → 4.1.3
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 +248 -48
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -12868,10 +12868,10 @@ var rollout_plan_default = {
|
|
|
12868
12868
|
note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
|
|
12869
12869
|
},
|
|
12870
12870
|
baseline: {
|
|
12871
|
-
version: "4.1.
|
|
12872
|
-
tag: "v4.1.
|
|
12873
|
-
commit: "
|
|
12874
|
-
npm: "@mutmutco/cli@4.1.
|
|
12871
|
+
version: "4.1.3",
|
|
12872
|
+
tag: "v4.1.3",
|
|
12873
|
+
commit: "3164484f3f37",
|
|
12874
|
+
npm: "@mutmutco/cli@4.1.3"
|
|
12875
12875
|
},
|
|
12876
12876
|
exitCriterion: "fleet-n-of-n",
|
|
12877
12877
|
hubOnlyShortcut: "forbidden",
|
|
@@ -12888,14 +12888,14 @@ var rollout_plan_default = {
|
|
|
12888
12888
|
repo: "mutmutco/mmi-hub",
|
|
12889
12889
|
role: "canary",
|
|
12890
12890
|
schedule: "train",
|
|
12891
|
-
v3Target: "v4.1.
|
|
12891
|
+
v3Target: "v4.1.3"
|
|
12892
12892
|
}
|
|
12893
12893
|
],
|
|
12894
12894
|
rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
|
|
12895
12895
|
rollback: {
|
|
12896
12896
|
independent: true,
|
|
12897
|
-
mechanism: "npm dist-tag latest -> 4.1.
|
|
12898
|
-
v3Target: "v4.1.
|
|
12897
|
+
mechanism: "npm dist-tag latest -> 4.1.3 and redeploy the Hub Lambda from tag v4.1.3 (3164484f3f37); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
12898
|
+
v3Target: "v4.1.3 (@mutmutco/cli@4.1.3, tag commit 3164484f3f37 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
12899
12899
|
}
|
|
12900
12900
|
},
|
|
12901
12901
|
{
|
|
@@ -19389,8 +19389,10 @@ init_client_version();
|
|
|
19389
19389
|
var BOARD_SNAPSHOT_TIMEOUT_MS = 25e3;
|
|
19390
19390
|
function isSnapshotShape(body) {
|
|
19391
19391
|
const b = body;
|
|
19392
|
+
const rate = b?.github?.rateLimit;
|
|
19393
|
+
const githubValid = b?.github === void 0 || b.github.credential === "app_installation" && Number.isFinite(rate?.limit) && Number.isFinite(rate?.remaining) && Number.isFinite(rate?.cost) && typeof rate?.resetAt === "string";
|
|
19392
19394
|
return Boolean(
|
|
19393
|
-
b && typeof b === "object" && typeof b.project?.id === "string" && typeof b.project?.title === "string" && typeof b.viewer === "string" && b.viewer.length > 0 && Array.isArray(b.nodes) && Array.isArray(b.writableRepos) && Array.isArray(b.unreadableRepos) && Array.isArray(b.pullRequests) && Array.isArray(b.warnings) && typeof b.partial === "boolean"
|
|
19395
|
+
b && typeof b === "object" && typeof b.project?.id === "string" && typeof b.project?.title === "string" && typeof b.viewer === "string" && b.viewer.length > 0 && Array.isArray(b.nodes) && Array.isArray(b.writableRepos) && Array.isArray(b.unreadableRepos) && Array.isArray(b.pullRequests) && Array.isArray(b.warnings) && typeof b.partial === "boolean" && githubValid
|
|
19394
19396
|
);
|
|
19395
19397
|
}
|
|
19396
19398
|
async function fetchHubBoardSnapshot(request, deps) {
|
|
@@ -20173,6 +20175,10 @@ function renderBoardItem(item) {
|
|
|
20173
20175
|
}
|
|
20174
20176
|
function renderBoardReport(report) {
|
|
20175
20177
|
const lines = [`Board \xB7 ${report.project.title} \xB7 @${report.viewer}`, renderBoardSource()];
|
|
20178
|
+
if (report.github) {
|
|
20179
|
+
const { limit, remaining, cost, resetAt } = report.github.rateLimit;
|
|
20180
|
+
lines.push(`github: app installation \xB7 GraphQL ${remaining}/${limit} remaining \xB7 cost ${cost} \xB7 resets ${resetAt}`);
|
|
20181
|
+
}
|
|
20176
20182
|
renderScope(lines, "PRIMARY", report.repo, report.primary, report.viewer);
|
|
20177
20183
|
renderScope(lines, "SECONDARY", "Other repos on this project", report.secondary, report.viewer);
|
|
20178
20184
|
if (report.warnings.length) {
|
|
@@ -20285,6 +20291,7 @@ async function readBoard(options, deps = {}) {
|
|
|
20285
20291
|
let collected;
|
|
20286
20292
|
let writable;
|
|
20287
20293
|
let pullRequests;
|
|
20294
|
+
let github;
|
|
20288
20295
|
let snapshotFallback;
|
|
20289
20296
|
const attempt = deps.snapshot ? await fetchHubBoardSnapshot(
|
|
20290
20297
|
{
|
|
@@ -20329,6 +20336,7 @@ async function readBoard(options, deps = {}) {
|
|
|
20329
20336
|
unknown: new Set(snapshot.unreadableRepos.map((entry) => entry.repo.toLowerCase()))
|
|
20330
20337
|
};
|
|
20331
20338
|
pullRequests = snapshot.pullRequests;
|
|
20339
|
+
github = snapshot.github;
|
|
20332
20340
|
} else {
|
|
20333
20341
|
if (attempt?.state === "unavailable") snapshotFallback = attempt.reason;
|
|
20334
20342
|
collected = await collectBoardItems(cfg, { repo: options.repo, allowPartial: options.allowPartial, activeOnly: true }, deps);
|
|
@@ -20349,7 +20357,8 @@ async function readBoard(options, deps = {}) {
|
|
|
20349
20357
|
warnings: collected.warnings,
|
|
20350
20358
|
partial: collected.partial,
|
|
20351
20359
|
source: "live",
|
|
20352
|
-
...pullRequests ? { pullRequests } : {}
|
|
20360
|
+
...pullRequests ? { pullRequests } : {},
|
|
20361
|
+
...github ? { github } : {}
|
|
20353
20362
|
};
|
|
20354
20363
|
if (options.includeBundleDetails || options.includeAllBodies) {
|
|
20355
20364
|
await attachBundleDetails(report, client, options.allowPartial ?? false, { all: options.includeAllBodies });
|
|
@@ -25323,6 +25332,7 @@ function repoIndexV4BucketDigests(repo, chunks, embeddings) {
|
|
|
25323
25332
|
|
|
25324
25333
|
// src/repo-index-cloud-client.ts
|
|
25325
25334
|
var RETRY_ATTEMPTS2 = 3;
|
|
25335
|
+
var REPO_INDEX_GC_TIMEOUT_MS = 12e4;
|
|
25326
25336
|
async function repoIndexSourceHostHeaders() {
|
|
25327
25337
|
const { detectSurface: detectSurface2 } = await Promise.resolve().then(() => (init_plugin_guard_io(), plugin_guard_io_exports));
|
|
25328
25338
|
return { [SOURCE_HOST_HEADER]: detectSurface2(process.env) };
|
|
@@ -25728,10 +25738,22 @@ async function statusRepoIndexCloud(repo, deps) {
|
|
|
25728
25738
|
return { ok: false, error: e.message, code: "network" };
|
|
25729
25739
|
}
|
|
25730
25740
|
}
|
|
25741
|
+
function normalizeGcV4Repos(raw) {
|
|
25742
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
25743
|
+
const out = {};
|
|
25744
|
+
for (const [repo, value] of Object.entries(raw)) {
|
|
25745
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
25746
|
+
const removed = Array.isArray(value.removed) ? value.removed.filter((k) => typeof k === "string") : [];
|
|
25747
|
+
const kept = Number(value.kept);
|
|
25748
|
+
out[repo] = { removed, kept: Number.isFinite(kept) ? kept : 0 };
|
|
25749
|
+
}
|
|
25750
|
+
return out;
|
|
25751
|
+
}
|
|
25731
25752
|
async function gcRepoIndexCloud(deps) {
|
|
25732
25753
|
if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
|
|
25733
25754
|
const token = await deps.token();
|
|
25734
25755
|
if (!token) return { ok: false, error: "no Hub session token (run `gh auth login`)" };
|
|
25756
|
+
const timeoutMs = deps.timeoutMs ?? REPO_INDEX_GC_TIMEOUT_MS;
|
|
25735
25757
|
try {
|
|
25736
25758
|
const res = await fetchWithRetry(
|
|
25737
25759
|
deps.fetch ?? fetch,
|
|
@@ -25741,13 +25763,32 @@ async function gcRepoIndexCloud(deps) {
|
|
|
25741
25763
|
headers: { ...clientVersionHeaders(), Authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
25742
25764
|
body: "{}"
|
|
25743
25765
|
},
|
|
25744
|
-
|
|
25766
|
+
// Single attempt: GC tombstones authorities and deletes S3 objects. A timed-out POST may still
|
|
25767
|
+
// finish on Hub; retrying would re-issue a destructive sweep without an authoritative receipt.
|
|
25768
|
+
{ attempts: 1, timeoutMs, sleep: deps.retrySleep }
|
|
25745
25769
|
);
|
|
25746
25770
|
const body = await res.json().catch(() => ({}));
|
|
25747
25771
|
if (!res.ok) return { ok: false, error: body.error ?? `gc HTTP ${res.status}` };
|
|
25748
|
-
|
|
25772
|
+
const removed = Array.isArray(body.removed) ? body.removed.filter((repo) => typeof repo === "string") : [];
|
|
25773
|
+
const kept = Number(body.kept);
|
|
25774
|
+
return {
|
|
25775
|
+
ok: true,
|
|
25776
|
+
removed,
|
|
25777
|
+
kept: Number.isFinite(kept) ? kept : 0,
|
|
25778
|
+
v4: {
|
|
25779
|
+
dryRun: body.v4?.dryRun === true,
|
|
25780
|
+
repos: normalizeGcV4Repos(body.v4?.repos)
|
|
25781
|
+
}
|
|
25782
|
+
};
|
|
25749
25783
|
} catch (e) {
|
|
25750
|
-
|
|
25784
|
+
const msg = e.message || String(e);
|
|
25785
|
+
if (/abort|timeout/i.test(msg)) {
|
|
25786
|
+
return {
|
|
25787
|
+
ok: false,
|
|
25788
|
+
error: `repo-index gc timed out after ${timeoutMs}ms \u2014 Hub may still have completed the destructive sweep; run \`mmi-cli oracle repo-index status --cloud --json\` and verify before retrying`
|
|
25789
|
+
};
|
|
25790
|
+
}
|
|
25791
|
+
return { ok: false, error: msg };
|
|
25751
25792
|
}
|
|
25752
25793
|
}
|
|
25753
25794
|
|
|
@@ -25894,6 +25935,16 @@ function buildGraphEdges(cwd, repo, commit, rosterRepos2) {
|
|
|
25894
25935
|
}
|
|
25895
25936
|
|
|
25896
25937
|
// src/repo-index-sync.ts
|
|
25938
|
+
function execFileUtf8(file, args) {
|
|
25939
|
+
return new Promise((resolve5, reject) => {
|
|
25940
|
+
(0, import_node_child_process15.execFile)(file, args, { encoding: "utf8", windowsHide: true }, (error, stdout) => {
|
|
25941
|
+
if (error) reject(error);
|
|
25942
|
+
else resolve5(String(stdout ?? ""));
|
|
25943
|
+
});
|
|
25944
|
+
});
|
|
25945
|
+
}
|
|
25946
|
+
var ESTATE_CLASSIFY_CONCURRENCY = 8;
|
|
25947
|
+
var ESTATE_HEALTHY_CLASSIFY_BUDGET_MS = 5 * 601e3;
|
|
25897
25948
|
var COMMIT3 = /^[a-f0-9]{40}$/;
|
|
25898
25949
|
var SHA256 = /^[a-f0-9]{64}$/;
|
|
25899
25950
|
var UTC_MILLIS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
@@ -25929,17 +25980,28 @@ function checkoutExactCommit(repo, dest, token, commit) {
|
|
|
25929
25980
|
const head = git3(["rev-parse", "HEAD"]).trim().toLowerCase();
|
|
25930
25981
|
if (head !== commit) throw new Error(`checkout of ${repo} resolved ${head}, not the requested commit ${commit}`);
|
|
25931
25982
|
}
|
|
25932
|
-
function remoteHead(repo, token) {
|
|
25983
|
+
async function remoteHead(repo, token) {
|
|
25933
25984
|
const basic = Buffer.from(`x-access-token:${token}`, "utf8").toString("base64");
|
|
25934
|
-
const
|
|
25985
|
+
const stdout = await execFileUtf8(
|
|
25935
25986
|
"git",
|
|
25936
|
-
["-c", `http.extraHeader=Authorization: Basic ${basic}`, "ls-remote", "--exit-code", `https://github.com/${repo}.git`, "HEAD"]
|
|
25937
|
-
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
|
|
25987
|
+
["-c", `http.extraHeader=Authorization: Basic ${basic}`, "ls-remote", "--exit-code", `https://github.com/${repo}.git`, "HEAD"]
|
|
25938
25988
|
);
|
|
25939
|
-
const match =
|
|
25989
|
+
const match = stdout.match(/^([a-f0-9]{40})\s+HEAD$/m);
|
|
25940
25990
|
if (!match) throw new Error(`could not resolve remote HEAD for ${repo}`);
|
|
25941
25991
|
return match[1];
|
|
25942
25992
|
}
|
|
25993
|
+
async function mapLimit(items, limit, fn) {
|
|
25994
|
+
const out = new Array(items.length);
|
|
25995
|
+
let next = 0;
|
|
25996
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
25997
|
+
while (next < items.length) {
|
|
25998
|
+
const i = next++;
|
|
25999
|
+
out[i] = await fn(items[i], i);
|
|
26000
|
+
}
|
|
26001
|
+
});
|
|
26002
|
+
await Promise.all(workers);
|
|
26003
|
+
return out;
|
|
26004
|
+
}
|
|
25943
26005
|
function verifiedReadyBase(statusValue, repo) {
|
|
25944
26006
|
if (!statusValue || typeof statusValue !== "object" || Array.isArray(statusValue)) return null;
|
|
25945
26007
|
const status = statusValue;
|
|
@@ -26041,11 +26103,18 @@ async function syncEstateRepoIndex(opts) {
|
|
|
26041
26103
|
const busy = new Set(
|
|
26042
26104
|
(opts.skipRepos ?? []).map((repo) => normalizeRepoIndexRepo(repo)).filter((repo) => repo !== null)
|
|
26043
26105
|
);
|
|
26044
|
-
|
|
26106
|
+
const emit = (progress) => {
|
|
26107
|
+
try {
|
|
26108
|
+
opts.onClassify?.(progress);
|
|
26109
|
+
} catch {
|
|
26110
|
+
}
|
|
26111
|
+
};
|
|
26112
|
+
const classified = await mapLimit(repos, ESTATE_CLASSIFY_CONCURRENCY, async (repo) => {
|
|
26045
26113
|
if (busy.has(repo)) {
|
|
26046
|
-
|
|
26047
|
-
|
|
26048
|
-
|
|
26114
|
+
const row2 = { repo, reason: "busy-elsewhere", action: "skip" };
|
|
26115
|
+
const warning = `${repo}: a per-repo reconcile run is already publishing it`;
|
|
26116
|
+
emit({ row: row2, warning });
|
|
26117
|
+
return { repo, row: row2, warning, base: null, expectedActiveDigest: void 0 };
|
|
26049
26118
|
}
|
|
26050
26119
|
let base = null;
|
|
26051
26120
|
let expectedActiveDigest;
|
|
@@ -26068,7 +26137,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
26068
26137
|
if (base && !forceFull) {
|
|
26069
26138
|
if (!targetCommit) {
|
|
26070
26139
|
try {
|
|
26071
|
-
targetCommit = remoteHead(repo, opts.githubToken);
|
|
26140
|
+
targetCommit = await remoteHead(repo, opts.githubToken);
|
|
26072
26141
|
} catch {
|
|
26073
26142
|
targetCommit = void 0;
|
|
26074
26143
|
}
|
|
@@ -26077,25 +26146,43 @@ async function syncEstateRepoIndex(opts) {
|
|
|
26077
26146
|
(error) => ({ ok: false, error: error.message })
|
|
26078
26147
|
);
|
|
26079
26148
|
if (provenance.ok && !provenance.deltaCompatible) {
|
|
26080
|
-
|
|
26081
|
-
|
|
26082
|
-
|
|
26083
|
-
|
|
26149
|
+
const row2 = {
|
|
26150
|
+
repo,
|
|
26151
|
+
reason: "incompatible-provenance",
|
|
26152
|
+
action: "needs-full-rebuild",
|
|
26153
|
+
activeCommit: base.commit,
|
|
26154
|
+
...targetCommit ? { targetCommit } : {}
|
|
26155
|
+
};
|
|
26156
|
+
const warning = `${repo}: DRIFT incompatible index provenance \u2014 the active authority at ${base.commit} was not built by ${CURRENT_REPO_INDEX_PROVENANCE_TOKEN}; migrate it explicitly with \`oracle repo-index sync-estate --repo ${repo} --full-rebuild ${CURRENT_REPO_INDEX_PROVENANCE_TOKEN}\``;
|
|
26157
|
+
emit({ row: row2, warning });
|
|
26158
|
+
return { repo, row: row2, warning, base, expectedActiveDigest };
|
|
26084
26159
|
}
|
|
26085
26160
|
if (targetCommit && targetCommit === base.commit) {
|
|
26086
|
-
|
|
26087
|
-
|
|
26088
|
-
|
|
26161
|
+
const row2 = { repo, reason: "healthy", action: "skip", activeCommit: base.commit, targetCommit };
|
|
26162
|
+
const warning = `${repo}: unchanged verified-ready authority at ${targetCommit}`;
|
|
26163
|
+
emit({ row: row2, warning });
|
|
26164
|
+
return { repo, row: row2, warning, base, expectedActiveDigest };
|
|
26089
26165
|
}
|
|
26090
26166
|
}
|
|
26091
|
-
|
|
26167
|
+
const row = {
|
|
26092
26168
|
repo,
|
|
26093
26169
|
reason,
|
|
26094
26170
|
action: "build",
|
|
26095
26171
|
...base ? { activeCommit: base.commit } : {},
|
|
26096
26172
|
...targetCommit ? { targetCommit } : {}
|
|
26097
|
-
}
|
|
26098
|
-
|
|
26173
|
+
};
|
|
26174
|
+
emit({ row });
|
|
26175
|
+
return { repo, row, base, expectedActiveDigest };
|
|
26176
|
+
});
|
|
26177
|
+
for (const entry of classified) {
|
|
26178
|
+
drift.push(entry.row);
|
|
26179
|
+
if (entry.warning) skipped.push(entry.warning);
|
|
26180
|
+
if (entry.row.action === "needs-full-rebuild") needsFullRebuild.push(entry.repo);
|
|
26181
|
+
}
|
|
26182
|
+
if (opts.plan) return answer();
|
|
26183
|
+
for (const entry of classified) {
|
|
26184
|
+
if (entry.row.action !== "build") continue;
|
|
26185
|
+
const { repo, base, expectedActiveDigest } = entry;
|
|
26099
26186
|
const dir = (0, import_node_fs27.mkdtempSync)((0, import_node_path25.join)((0, import_node_os13.tmpdir)(), "mmi-repo-index-"));
|
|
26100
26187
|
try {
|
|
26101
26188
|
shallowClone(repo, dir, opts.githubToken);
|
|
@@ -26536,6 +26623,79 @@ function runSpawnPolicy(root) {
|
|
|
26536
26623
|
var import_node_child_process17 = require("node:child_process");
|
|
26537
26624
|
var import_node_fs30 = require("node:fs");
|
|
26538
26625
|
var import_node_path27 = require("node:path");
|
|
26626
|
+
|
|
26627
|
+
// ../scripts/test-command-policy-core.mjs
|
|
26628
|
+
var TEST_COMMAND_CLASS = "test";
|
|
26629
|
+
function translateGlob(glob) {
|
|
26630
|
+
let out = "";
|
|
26631
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
26632
|
+
const char = glob[i];
|
|
26633
|
+
if (char === "*") {
|
|
26634
|
+
if (glob[i + 1] === "*") {
|
|
26635
|
+
if (glob[i + 2] === "/") {
|
|
26636
|
+
out += "(?:.*/)?";
|
|
26637
|
+
i += 2;
|
|
26638
|
+
} else {
|
|
26639
|
+
out += ".*";
|
|
26640
|
+
i += 1;
|
|
26641
|
+
}
|
|
26642
|
+
} else out += "[^/]*";
|
|
26643
|
+
} else if (char === "{") {
|
|
26644
|
+
const close = glob.indexOf("}", i);
|
|
26645
|
+
if (close === -1) out += "\\{";
|
|
26646
|
+
else {
|
|
26647
|
+
out += `(?:${glob.slice(i + 1, close).split(",").map(translateGlob).join("|")})`;
|
|
26648
|
+
i = close;
|
|
26649
|
+
}
|
|
26650
|
+
} else {
|
|
26651
|
+
out += /[.+?^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
|
|
26652
|
+
}
|
|
26653
|
+
}
|
|
26654
|
+
return out;
|
|
26655
|
+
}
|
|
26656
|
+
function globToRegExp(glob) {
|
|
26657
|
+
return new RegExp(`^${translateGlob(glob)}$`);
|
|
26658
|
+
}
|
|
26659
|
+
function mandatoryGlobList(mandatory) {
|
|
26660
|
+
if (!Array.isArray(mandatory)) return [];
|
|
26661
|
+
return mandatory.map((entry) => typeof entry === "string" ? entry : entry?.glob).filter((glob) => typeof glob === "string");
|
|
26662
|
+
}
|
|
26663
|
+
function matchedMandatoryGlobs(paths, mandatory) {
|
|
26664
|
+
const globs = mandatoryGlobList(mandatory);
|
|
26665
|
+
const list = Array.isArray(paths) ? paths : [];
|
|
26666
|
+
return globs.filter((glob) => {
|
|
26667
|
+
const re = globToRegExp(glob);
|
|
26668
|
+
return list.some((path2) => re.test(path2));
|
|
26669
|
+
});
|
|
26670
|
+
}
|
|
26671
|
+
function evaluateTestCommandPolicy({ paths, mandatory, regulated = true } = {}) {
|
|
26672
|
+
const configuredMandatoryCount = mandatoryGlobList(mandatory).length;
|
|
26673
|
+
if (!regulated) {
|
|
26674
|
+
return {
|
|
26675
|
+
configuredMandatoryCount: 0,
|
|
26676
|
+
matchedMandatoryGlobs: [],
|
|
26677
|
+
matchedMandatoryCount: 0,
|
|
26678
|
+
testCommandsAllowed: true,
|
|
26679
|
+
reasonId: null,
|
|
26680
|
+
commandClasses: { allowed: [TEST_COMMAND_CLASS], refused: [] }
|
|
26681
|
+
};
|
|
26682
|
+
}
|
|
26683
|
+
const matched = matchedMandatoryGlobs(paths, mandatory);
|
|
26684
|
+
const testCommandsAllowed = matched.length > 0;
|
|
26685
|
+
return {
|
|
26686
|
+
configuredMandatoryCount,
|
|
26687
|
+
matchedMandatoryGlobs: matched,
|
|
26688
|
+
matchedMandatoryCount: matched.length,
|
|
26689
|
+
testCommandsAllowed,
|
|
26690
|
+
reasonId: testCommandsAllowed ? null : "test-command-outside-mandatory-zone",
|
|
26691
|
+
commandClasses: {
|
|
26692
|
+
allowed: testCommandsAllowed ? [TEST_COMMAND_CLASS] : [],
|
|
26693
|
+
refused: testCommandsAllowed ? [] : [TEST_COMMAND_CLASS]
|
|
26694
|
+
}
|
|
26695
|
+
};
|
|
26696
|
+
}
|
|
26697
|
+
|
|
26698
|
+
// src/test-policy-core.ts
|
|
26539
26699
|
var POLICY_FILE = "test-policy.json";
|
|
26540
26700
|
var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
26541
26701
|
var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
|
|
@@ -26581,7 +26741,7 @@ function translate(glob) {
|
|
|
26581
26741
|
}
|
|
26582
26742
|
return out;
|
|
26583
26743
|
}
|
|
26584
|
-
function
|
|
26744
|
+
function globToRegExp2(glob) {
|
|
26585
26745
|
return new RegExp(`^${translate(glob)}$`);
|
|
26586
26746
|
}
|
|
26587
26747
|
function isTestPath(path2) {
|
|
@@ -26815,7 +26975,7 @@ function isMeaningfulChange(path2, before, after) {
|
|
|
26815
26975
|
return !a.every((t, k) => t.text === b[k].text && t.nl === b[k].nl);
|
|
26816
26976
|
}
|
|
26817
26977
|
function annotateChangeMeaning(changed, policy, read) {
|
|
26818
|
-
const matchers = (policy.mandatory ?? []).map((m) =>
|
|
26978
|
+
const matchers = (policy.mandatory ?? []).map((m) => globToRegExp2(m.glob));
|
|
26819
26979
|
return changed.map((file) => {
|
|
26820
26980
|
if (file.status !== "M" || !SUPPORTED_SOURCE.test(file.path)) return file;
|
|
26821
26981
|
if (!matchers.some((re) => re.test(file.path)) && !isTestPath(file.path)) return file;
|
|
@@ -26853,7 +27013,7 @@ function removedPaths2(changed) {
|
|
|
26853
27013
|
);
|
|
26854
27014
|
}
|
|
26855
27015
|
function classify(changed, policy, present = () => false) {
|
|
26856
|
-
const matchers = (policy.mandatory ?? []).map((m) => ({ ...m, re:
|
|
27016
|
+
const matchers = (policy.mandatory ?? []).map((m) => ({ ...m, re: globToRegExp2(m.glob) }));
|
|
26857
27017
|
const mandatoryHits = changed.filter((f) => matchers.some((m) => m.re.test(f.path)));
|
|
26858
27018
|
const testChanges = changed.filter((f) => isTestPath(f.path));
|
|
26859
27019
|
const addedTests = testChanges.filter((f) => f.status === "A");
|
|
@@ -27103,7 +27263,23 @@ function runTestPolicy(root, deps = {}) {
|
|
|
27103
27263
|
};
|
|
27104
27264
|
const blocking = sift([...refusal ? [refusal] : [], ...lookup.refusals, ...staleFindings]);
|
|
27105
27265
|
const findings = blocking.length > 0 ? blocking : sift(evaluate(changed, policy, present));
|
|
27106
|
-
const
|
|
27266
|
+
const commandPolicy = evaluateTestCommandPolicy({
|
|
27267
|
+
paths: changed.map((f) => f.path),
|
|
27268
|
+
mandatory: policy.mandatory,
|
|
27269
|
+
regulated: policy.declared !== false
|
|
27270
|
+
});
|
|
27271
|
+
const result = {
|
|
27272
|
+
ok: findings.length === 0,
|
|
27273
|
+
findings,
|
|
27274
|
+
changedCount: changed.length,
|
|
27275
|
+
base,
|
|
27276
|
+
...counts,
|
|
27277
|
+
matchedMandatoryGlobs: commandPolicy.matchedMandatoryGlobs,
|
|
27278
|
+
matchedMandatoryCount: commandPolicy.matchedMandatoryCount,
|
|
27279
|
+
testCommandsAllowed: commandPolicy.testCommandsAllowed,
|
|
27280
|
+
testCommandReasonId: commandPolicy.reasonId,
|
|
27281
|
+
commandClasses: commandPolicy.commandClasses
|
|
27282
|
+
};
|
|
27107
27283
|
if (override) {
|
|
27108
27284
|
result.overriddenBy = override;
|
|
27109
27285
|
result.waived = waived;
|
|
@@ -35568,6 +35744,7 @@ var surfaces_default = {
|
|
|
35568
35744
|
targetPath: "packages/claude-plugin/scripts",
|
|
35569
35745
|
include: [
|
|
35570
35746
|
"pretooluse-shell-gates.mjs",
|
|
35747
|
+
"test-command-policy-core.mjs",
|
|
35571
35748
|
"vault-edit-gate.mjs",
|
|
35572
35749
|
"deny-gate-crash.mjs",
|
|
35573
35750
|
"secret-echo-lint.mjs",
|
|
@@ -35694,6 +35871,7 @@ var surfaces_default = {
|
|
|
35694
35871
|
targetPath: "packages/codex-plugin/scripts",
|
|
35695
35872
|
include: [
|
|
35696
35873
|
"pretooluse-shell-gates.mjs",
|
|
35874
|
+
"test-command-policy-core.mjs",
|
|
35697
35875
|
"vault-edit-gate.mjs",
|
|
35698
35876
|
"deny-gate-crash.mjs",
|
|
35699
35877
|
"secret-echo-lint.mjs",
|
|
@@ -35812,6 +35990,7 @@ var surfaces_default = {
|
|
|
35812
35990
|
targetPath: "packages/kimi-plugin/scripts",
|
|
35813
35991
|
include: [
|
|
35814
35992
|
"pretooluse-shell-gates.mjs",
|
|
35993
|
+
"test-command-policy-core.mjs",
|
|
35815
35994
|
"vault-edit-gate.mjs",
|
|
35816
35995
|
"deny-gate-crash.mjs",
|
|
35817
35996
|
"secret-echo-lint.mjs",
|
|
@@ -35938,6 +36117,7 @@ var surfaces_default = {
|
|
|
35938
36117
|
targetPath: "packages/cursor-plugin/scripts",
|
|
35939
36118
|
include: [
|
|
35940
36119
|
"pretooluse-shell-gates.mjs",
|
|
36120
|
+
"test-command-policy-core.mjs",
|
|
35941
36121
|
"vault-edit-gate.mjs",
|
|
35942
36122
|
"deny-gate-crash.mjs",
|
|
35943
36123
|
"secret-echo-lint.mjs",
|
|
@@ -36060,6 +36240,7 @@ var surfaces_default = {
|
|
|
36060
36240
|
targetPath: ".kilo-plugin/scripts",
|
|
36061
36241
|
include: [
|
|
36062
36242
|
"pretooluse-shell-gates.mjs",
|
|
36243
|
+
"test-command-policy-core.mjs",
|
|
36063
36244
|
"vault-edit-gate.mjs",
|
|
36064
36245
|
"deny-gate-crash.mjs",
|
|
36065
36246
|
"secret-echo-lint.mjs",
|
|
@@ -36169,6 +36350,7 @@ var surfaces_default = {
|
|
|
36169
36350
|
targetPath: ".pi-plugin/scripts",
|
|
36170
36351
|
include: [
|
|
36171
36352
|
"pretooluse-shell-gates.mjs",
|
|
36353
|
+
"test-command-policy-core.mjs",
|
|
36172
36354
|
"vault-edit-gate.mjs",
|
|
36173
36355
|
"deny-gate-crash.mjs",
|
|
36174
36356
|
"secret-echo-lint.mjs",
|
|
@@ -36268,6 +36450,7 @@ var surfaces_default = {
|
|
|
36268
36450
|
targetPath: "packages/hermes-plugin/scripts",
|
|
36269
36451
|
include: [
|
|
36270
36452
|
"pretooluse-shell-gates.mjs",
|
|
36453
|
+
"test-command-policy-core.mjs",
|
|
36271
36454
|
"vault-edit-gate.mjs",
|
|
36272
36455
|
"deny-gate-crash.mjs",
|
|
36273
36456
|
"secret-echo-lint.mjs",
|
|
@@ -39553,7 +39736,15 @@ repoIndex.command("gc").description("remove v4 cloud authority material for repo
|
|
|
39553
39736
|
consoleIo.log(JSON.stringify(res, null, 2));
|
|
39554
39737
|
return;
|
|
39555
39738
|
}
|
|
39556
|
-
|
|
39739
|
+
let artifactsRemoved = 0;
|
|
39740
|
+
let artifactsKept = 0;
|
|
39741
|
+
for (const receipt of Object.values(res.v4.repos)) {
|
|
39742
|
+
artifactsRemoved += receipt.removed.length;
|
|
39743
|
+
artifactsKept += receipt.kept;
|
|
39744
|
+
}
|
|
39745
|
+
console.log(
|
|
39746
|
+
`repo-index: gc tombstoned ${res.removed.length} orphan authorit${res.removed.length === 1 ? "y" : "ies"}, removed ${artifactsRemoved} artifact(s); kept ${res.kept} authorit${res.kept === 1 ? "y" : "ies"}, ${artifactsKept} artifact(s)`
|
|
39747
|
+
);
|
|
39557
39748
|
} catch (e) {
|
|
39558
39749
|
return await failGraceful(e.message);
|
|
39559
39750
|
}
|
|
@@ -39563,6 +39754,10 @@ repoIndex.command("sync-estate").description("Hub indexer: publish a pushed comm
|
|
|
39563
39754
|
const cfg = await loadConfig();
|
|
39564
39755
|
const gh = process.env.GH_TOKEN || process.env.GITHUB_TOKEN || "";
|
|
39565
39756
|
if (!gh) await failGraceful("sync-estate needs GH_TOKEN or GITHUB_TOKEN with contents:read on target repos");
|
|
39757
|
+
if (!o.json) {
|
|
39758
|
+
console.log(`repo-index: pipeline provenance ${CURRENT_REPO_INDEX_PROVENANCE_TOKEN} \u2014 delta is the normal path${o.plan ? " (plan only: nothing was cloned, embedded or published)" : ""}`);
|
|
39759
|
+
}
|
|
39760
|
+
const streamedWarnings = /* @__PURE__ */ new Set();
|
|
39566
39761
|
const res = await syncEstateRepoIndex({
|
|
39567
39762
|
deps: registryClientDeps(cfg),
|
|
39568
39763
|
repo: o.repo,
|
|
@@ -39570,26 +39765,30 @@ repoIndex.command("sync-estate").description("Hub indexer: publish a pushed comm
|
|
|
39570
39765
|
fullRebuild: o.fullRebuild,
|
|
39571
39766
|
skipRepos: o.skipRepo,
|
|
39572
39767
|
plan: Boolean(o.plan),
|
|
39573
|
-
githubToken: gh
|
|
39768
|
+
githubToken: gh,
|
|
39769
|
+
onClassify: o.json ? void 0 : ({ row, warning }) => {
|
|
39770
|
+
const at = row.targetCommit ? ` target=${row.targetCommit.slice(0, 12)}` : "";
|
|
39771
|
+
const active = row.activeCommit ? ` active=${row.activeCommit.slice(0, 12)}` : "";
|
|
39772
|
+
console.log(`repo-index: ${row.repo} ${row.reason} \u2192 ${row.action}${active}${at}`);
|
|
39773
|
+
if (warning) {
|
|
39774
|
+
streamedWarnings.add(warning);
|
|
39775
|
+
console.error(`repo-index: WARN ${warning}`);
|
|
39776
|
+
}
|
|
39777
|
+
}
|
|
39574
39778
|
});
|
|
39575
39779
|
if (o.json) {
|
|
39576
39780
|
consoleIo.log(JSON.stringify(res, null, 2));
|
|
39577
39781
|
if (!res.ok) process.exitCode = 1;
|
|
39578
39782
|
return;
|
|
39579
39783
|
}
|
|
39580
|
-
console.log(`repo-index: pipeline provenance ${res.provenanceToken} \u2014 delta is the normal path${o.plan ? " (plan only: nothing was cloned, embedded or published)" : ""}`);
|
|
39581
|
-
for (const row of res.drift) {
|
|
39582
|
-
if (row.reason === "healthy") continue;
|
|
39583
|
-
const at = row.targetCommit ? ` target=${row.targetCommit.slice(0, 12)}` : "";
|
|
39584
|
-
const active = row.activeCommit ? ` active=${row.activeCommit.slice(0, 12)}` : "";
|
|
39585
|
-
console.log(`repo-index: ${row.repo} ${row.reason} \u2192 ${row.action}${active}${at}`);
|
|
39586
|
-
}
|
|
39587
39784
|
for (const p of res.published) {
|
|
39588
39785
|
const gap = p.embGap ?? Math.max(0, p.fileCount - (p.embCount ?? 0));
|
|
39589
39786
|
console.log(`repo-index: published v4 ${p.repo} \u2014 ${p.fileCount} chunks emb=${p.embCount ?? 0}/${p.fileCount} gap=${gap} graph=${p.graphEdges ?? "unavailable"}`);
|
|
39590
39787
|
if (p.metrics) console.log(`repo-index: ${formatV4BuildMetrics(p.repo, p.metrics)}`);
|
|
39591
39788
|
}
|
|
39592
|
-
for (const warning of res.skipped)
|
|
39789
|
+
for (const warning of res.skipped) {
|
|
39790
|
+
if (!streamedWarnings.has(warning)) console.error(`repo-index: WARN ${warning}`);
|
|
39791
|
+
}
|
|
39593
39792
|
if (res.needsFullRebuild.length) {
|
|
39594
39793
|
console.error(`repo-index: ${res.needsFullRebuild.length} repo(s) need an explicit tokened migration: ${res.needsFullRebuild.join(", ")}`);
|
|
39595
39794
|
}
|
|
@@ -39668,7 +39867,7 @@ spawnCmd.command("policy").description("enforce the windowsHide contract across
|
|
|
39668
39867
|
}
|
|
39669
39868
|
});
|
|
39670
39869
|
var tests = program2.command("tests").description("a repo's test-policy.json \u2014 the opt-in test contract and its enforcement");
|
|
39671
|
-
tests.command("policy").description("enforce this repo's test-policy.json against the diff: a mandatory-zone change must carry a test, an unrequested new test file is refused, a `protected` test file may not be deleted or renamed away, and a `protected` entry naming a missing file is refused. Override any of them with a `Test-Policy-Override: <reason>` commit trailer (#3605)").option("--json", "machine-readable result: { ok, base, changedCount, findings[] }").option("--base <ref>", "comparison base (default: TEST_POLICY_BASE, then origin/development, then origin/main)").action(async (o) => {
|
|
39870
|
+
tests.command("policy").description("enforce this repo's test-policy.json against the diff: a mandatory-zone change must carry a test, an unrequested new test file is refused, a `protected` test file may not be deleted or renamed away, and a `protected` entry naming a missing file is refused. Override any of them with a `Test-Policy-Override: <reason>` commit trailer (#3605)").option("--json", "machine-readable result: { ok, base, changedCount, mandatoryCount, matchedMandatoryCount, matchedMandatoryGlobs, testCommandsAllowed, commandClasses, findings[] }").option("--base <ref>", "comparison base (default: TEST_POLICY_BASE, then origin/development, then origin/main)").action(async (o) => {
|
|
39672
39871
|
try {
|
|
39673
39872
|
const root = await repoRoot();
|
|
39674
39873
|
const result = runTestPolicy(root, { base: o.base });
|
|
@@ -39684,8 +39883,9 @@ tests.command("policy").description("enforce this repo's test-policy.json agains
|
|
|
39684
39883
|
return;
|
|
39685
39884
|
}
|
|
39686
39885
|
if (result.ok) {
|
|
39886
|
+
const commandVerdict = result.testCommandsAllowed ? "test commands: allowed" : `test commands: refused [${result.testCommandReasonId}]`;
|
|
39687
39887
|
console.log(
|
|
39688
|
-
`tests policy: OK (${result.changedCount} changed file(s); ${result.mandatoryCount} mandatory glob(s), ${result.protectedCount} protected file(s)).`
|
|
39888
|
+
`tests policy: OK (${result.changedCount} changed file(s); ${result.matchedMandatoryCount} of ${result.mandatoryCount} mandatory glob(s) matched, ${result.protectedCount} protected file(s); ${commandVerdict}).`
|
|
39689
39889
|
);
|
|
39690
39890
|
return;
|
|
39691
39891
|
}
|
package/package.json
CHANGED