@mutmutco/cli 3.139.1 → 3.139.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 +191 -81
- package/dist/repo-index-v4.cjs +28 -15
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -7078,9 +7078,15 @@ function gateSeedVars(cls, releaseTrack, runtime = "node") {
|
|
|
7078
7078
|
GATE_BUDGET_SHA: BLESSED_RUN_WITH_BUDGET_SHA
|
|
7079
7079
|
};
|
|
7080
7080
|
const track = releaseTrack ?? (cls === "content" ? "trunk" : "full");
|
|
7081
|
+
const windowsCompat = {
|
|
7082
|
+
// #5113: opt-in informational windows-latest proof. Default OFF — the CLI fills the rendered job
|
|
7083
|
+
// YAML (or '') at the final layering step; never hand-passed.
|
|
7084
|
+
GATE_WINDOWS_COMPAT_JOB_YAML: ""
|
|
7085
|
+
};
|
|
7081
7086
|
if (track === "trunk") {
|
|
7082
7087
|
return {
|
|
7083
7088
|
...runtimeVars,
|
|
7089
|
+
...windowsCompat,
|
|
7084
7090
|
GATE_PUSH_BRANCHES_YAML: "[main]",
|
|
7085
7091
|
GATE_FULL_RUN_BRANCH: "main",
|
|
7086
7092
|
GATE_RULESET_BRANCH_REFS_JSON: '["refs/heads/main"]'
|
|
@@ -7089,6 +7095,7 @@ function gateSeedVars(cls, releaseTrack, runtime = "node") {
|
|
|
7089
7095
|
if (track === "direct") {
|
|
7090
7096
|
return {
|
|
7091
7097
|
...runtimeVars,
|
|
7098
|
+
...windowsCompat,
|
|
7092
7099
|
GATE_PUSH_BRANCHES_YAML: "[development, main]",
|
|
7093
7100
|
GATE_FULL_RUN_BRANCH: "development",
|
|
7094
7101
|
GATE_RULESET_BRANCH_REFS_JSON: '["refs/heads/development", "refs/heads/main"]'
|
|
@@ -7096,6 +7103,7 @@ function gateSeedVars(cls, releaseTrack, runtime = "node") {
|
|
|
7096
7103
|
}
|
|
7097
7104
|
return {
|
|
7098
7105
|
...runtimeVars,
|
|
7106
|
+
...windowsCompat,
|
|
7099
7107
|
GATE_PUSH_BRANCHES_YAML: "[development, rc, main]",
|
|
7100
7108
|
GATE_FULL_RUN_BRANCH: "development",
|
|
7101
7109
|
GATE_RULESET_BRANCH_REFS_JSON: '["refs/heads/development", "refs/heads/rc", "refs/heads/main"]'
|
|
@@ -7112,8 +7120,38 @@ function withDerivedRepoVars(vars, parsed, cls, releaseTrack) {
|
|
|
7112
7120
|
for (const [key, value] of Object.entries(gateSeedVars(cls, track, runtime))) {
|
|
7113
7121
|
out[key] ??= value;
|
|
7114
7122
|
}
|
|
7123
|
+
if (out.GATE_WINDOWS_COMPAT === "true" && !out.GATE_WINDOWS_COMPAT_JOB_YAML) {
|
|
7124
|
+
out.GATE_WINDOWS_COMPAT_JOB_YAML = windowsCompatJobYaml(out);
|
|
7125
|
+
}
|
|
7115
7126
|
return out;
|
|
7116
7127
|
}
|
|
7128
|
+
function windowsCompatJobYaml(vars) {
|
|
7129
|
+
const workdir = vars.GATE_WORKDIR ?? ".";
|
|
7130
|
+
const cmd = vars.GATE_CMD ?? DEFAULT_GATE_CMD;
|
|
7131
|
+
const install = vars.GATE_INSTALL_CMD ?? "npm ci";
|
|
7132
|
+
const fullRunBranch = vars.GATE_FULL_RUN_BRANCH ?? "development";
|
|
7133
|
+
const runtime = vars.GATE_RUNTIME === "python" ? "python" : "node";
|
|
7134
|
+
const setup = runtime === "python" ? ` - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
7135
|
+
with: { python-version: '${vars.GATE_PY_VERSION ?? DEFAULT_GATE_PY_VERSION}' }` : ` - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
7136
|
+
with: { node-version: 24, cache: npm, cache-dependency-path: ${vars.GATE_CACHE_DEP_PATH ?? "package-lock.json"} }`;
|
|
7137
|
+
return ` # MMI-Hub#5113: opt-in Windows compatibility proof \u2014 informational only. Do NOT add this job to a
|
|
7138
|
+
# required-contexts ruleset without a deliberate repo decision: the required gate stays the Linux lane
|
|
7139
|
+
# (faster, cheaper, where autonomous agents run). GitHub-hosted Windows minutes cost more than Linux,
|
|
7140
|
+
# hence opt-in. defaults.run.shell=bash is Git for Windows bash on windows-latest, so the check syntax
|
|
7141
|
+
# the Linux gate runs keeps working here.
|
|
7142
|
+
windows-compat:
|
|
7143
|
+
if: \${{ github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref_name == '${fullRunBranch}' || github.ref_name == 'main')) }}
|
|
7144
|
+
runs-on: windows-latest
|
|
7145
|
+
defaults:
|
|
7146
|
+
run: { working-directory: ${workdir}, shell: bash }
|
|
7147
|
+
steps:
|
|
7148
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
7149
|
+
${setup}
|
|
7150
|
+
- run: ${install}
|
|
7151
|
+
# Fast proof only \u2014 the full suite stays on the Linux gate.
|
|
7152
|
+
- run: ${cmd}
|
|
7153
|
+
`;
|
|
7154
|
+
}
|
|
7117
7155
|
function gateConfigToVars(gate) {
|
|
7118
7156
|
const out = {};
|
|
7119
7157
|
if (!gate || typeof gate !== "object") return out;
|
|
@@ -7124,6 +7162,7 @@ function gateConfigToVars(gate) {
|
|
|
7124
7162
|
if (typeof gate.pyVersion === "string" && gate.pyVersion.trim()) out.GATE_PY_VERSION = gate.pyVersion;
|
|
7125
7163
|
const seconds = typeof gate.maxSeconds === "number" ? String(gate.maxSeconds) : gate.maxSeconds;
|
|
7126
7164
|
if (typeof seconds === "string" && /^\d+$/.test(seconds.trim()) && Number(seconds) > 0) out.GATE_MAX_SECONDS = seconds.trim();
|
|
7165
|
+
if (gate.windowsCompat === true) out.GATE_WINDOWS_COMPAT = "true";
|
|
7127
7166
|
return out;
|
|
7128
7167
|
}
|
|
7129
7168
|
function seedMatchesDeployModel(seed, deployModel) {
|
|
@@ -10707,10 +10746,10 @@ var rollout_plan_default = {
|
|
|
10707
10746
|
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)."
|
|
10708
10747
|
},
|
|
10709
10748
|
baseline: {
|
|
10710
|
-
version: "3.139.
|
|
10711
|
-
tag: "v3.139.
|
|
10712
|
-
commit: "
|
|
10713
|
-
npm: "@mutmutco/cli@3.139.
|
|
10749
|
+
version: "3.139.3",
|
|
10750
|
+
tag: "v3.139.3",
|
|
10751
|
+
commit: "19258f64de5f",
|
|
10752
|
+
npm: "@mutmutco/cli@3.139.3"
|
|
10714
10753
|
},
|
|
10715
10754
|
exitCriterion: "fleet-n-of-n",
|
|
10716
10755
|
hubOnlyShortcut: "forbidden",
|
|
@@ -10727,14 +10766,14 @@ var rollout_plan_default = {
|
|
|
10727
10766
|
repo: "mutmutco/mmi-hub",
|
|
10728
10767
|
role: "canary",
|
|
10729
10768
|
schedule: "train",
|
|
10730
|
-
v3Target: "v3.139.
|
|
10769
|
+
v3Target: "v3.139.3"
|
|
10731
10770
|
}
|
|
10732
10771
|
],
|
|
10733
10772
|
rollbackTrigger: "Any red inside the post-cut soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a v3 client refused while the compat window must still admit it (SUPPORTED_MINOR_WINDOW=2, MIN_CLIENT_VERSION 0.0.0 \u2014 D6a), or npm consumer install/doctor failure on the v4 dist.",
|
|
10734
10773
|
rollback: {
|
|
10735
10774
|
independent: true,
|
|
10736
|
-
mechanism: "npm dist-tag latest -> 3.139.
|
|
10737
|
-
v3Target: "v3.139.
|
|
10775
|
+
mechanism: "npm dist-tag latest -> 3.139.3 and redeploy the Hub Lambda from tag v3.139.3 (19258f64de5f); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
10776
|
+
v3Target: "v3.139.3 (@mutmutco/cli@3.139.3, tag commit 19258f64de5f \u2014 the preserved latest-v3 distribution, D6b)"
|
|
10738
10777
|
}
|
|
10739
10778
|
},
|
|
10740
10779
|
{
|
|
@@ -22696,8 +22735,9 @@ var import_node_crypto5 = require("node:crypto");
|
|
|
22696
22735
|
var import_node_child_process12 = require("node:child_process");
|
|
22697
22736
|
var import_node_fs23 = require("node:fs");
|
|
22698
22737
|
var import_node_path21 = require("node:path");
|
|
22699
|
-
|
|
22700
|
-
|
|
22738
|
+
|
|
22739
|
+
// ../infra/repo-index-path-policy.mjs
|
|
22740
|
+
var HARD_DENY = Object.freeze([
|
|
22701
22741
|
/(^|\/)\.env(\.|$)/i,
|
|
22702
22742
|
/(^|\/)\.env\./i,
|
|
22703
22743
|
/credentials/i,
|
|
@@ -22708,7 +22748,20 @@ var HARD_DENY = [
|
|
|
22708
22748
|
/(^|\/)id_rsa/i,
|
|
22709
22749
|
/(^|\/)id_ed25519/i,
|
|
22710
22750
|
/\.keystore$/i
|
|
22711
|
-
];
|
|
22751
|
+
]);
|
|
22752
|
+
function isHardDeniedRepoIndexPath(value) {
|
|
22753
|
+
return typeof value !== "string" || HARD_DENY.some((pattern) => pattern.test(value));
|
|
22754
|
+
}
|
|
22755
|
+
function isSafeRepoIndexPath(value) {
|
|
22756
|
+
if (typeof value !== "string" || !value || value.length > 1024) return false;
|
|
22757
|
+
if (value.startsWith("/") || value.includes("\\") || /[\u0000-\u001f\u007f]/u.test(value)) return false;
|
|
22758
|
+
const segments = value.split("/");
|
|
22759
|
+
if (segments.some((segment) => !segment || segment === "." || segment === "..")) return false;
|
|
22760
|
+
return !isHardDeniedRepoIndexPath(value);
|
|
22761
|
+
}
|
|
22762
|
+
|
|
22763
|
+
// src/repo-index.ts
|
|
22764
|
+
var REPO_INDEX_SCHEMA = 1;
|
|
22712
22765
|
var INDEXABLE_EXT = /* @__PURE__ */ new Set([
|
|
22713
22766
|
".ts",
|
|
22714
22767
|
".tsx",
|
|
@@ -22742,10 +22795,10 @@ function repoIndexStorePath(cwd) {
|
|
|
22742
22795
|
return repoRuntimeStatePath(cwd, "repo-index", "index.json");
|
|
22743
22796
|
}
|
|
22744
22797
|
function isHardDeniedPath(relPosix) {
|
|
22745
|
-
return
|
|
22798
|
+
return isHardDeniedRepoIndexPath(relPosix);
|
|
22746
22799
|
}
|
|
22747
22800
|
function isIndexablePath(relPosix) {
|
|
22748
|
-
if (
|
|
22801
|
+
if (!isSafeRepoIndexPath(relPosix)) return false;
|
|
22749
22802
|
if (relPosix.startsWith(".git/")) return false;
|
|
22750
22803
|
if (relPosix.includes("node_modules/")) return false;
|
|
22751
22804
|
if (relPosix.includes("dist/")) return false;
|
|
@@ -23215,6 +23268,7 @@ ${chunk.symbol ?? ""}
|
|
|
23215
23268
|
${chunk.blurb ?? ""}`;
|
|
23216
23269
|
}
|
|
23217
23270
|
}
|
|
23271
|
+
var V4_EMBED_TIMEOUT_MS = 15 * 6e4;
|
|
23218
23272
|
function runEmbedder(cwd, chunks, modelDirectory, createdAt) {
|
|
23219
23273
|
if (!chunks.length) return { embeddings: [] };
|
|
23220
23274
|
const runner = (0, import_node_path23.join)(cwd, "repo-indexer", "src", "batch.mjs");
|
|
@@ -23223,7 +23277,7 @@ function runEmbedder(cwd, chunks, modelDirectory, createdAt) {
|
|
|
23223
23277
|
if (!(0, import_node_fs25.existsSync)(file)) return { embeddings: [], reason: "embeddings-unavailable" };
|
|
23224
23278
|
const request = { texts: chunks.map((chunk) => ({ id: chunk.id, text: embeddingInput(cwd, chunk) })), maxBatch: V4_EMBED_BATCH };
|
|
23225
23279
|
const env = { ...process.env, ...modelDirectory ? { MMI_REPO_INDEXER_MODEL_DIR: modelDirectory } : {} };
|
|
23226
|
-
const result = (0, import_node_child_process13.spawnSync)(process.execPath, [file], { input: JSON.stringify(request), encoding: "utf8", windowsHide: true, timeout:
|
|
23280
|
+
const result = (0, import_node_child_process13.spawnSync)(process.execPath, [file], { input: JSON.stringify(request), encoding: "utf8", windowsHide: true, timeout: V4_EMBED_TIMEOUT_MS, maxBuffer: V4_MAX_ARTIFACT_BYTES, env });
|
|
23227
23281
|
if (result.error || result.status !== 0) return { embeddings: [], reason: "embeddings-unavailable" };
|
|
23228
23282
|
try {
|
|
23229
23283
|
const response = JSON.parse(result.stdout);
|
|
@@ -23297,6 +23351,9 @@ async function buildRepoIndexV4(cwd, repo, opts = {}) {
|
|
|
23297
23351
|
`, "utf8");
|
|
23298
23352
|
return envelope;
|
|
23299
23353
|
}
|
|
23354
|
+
function repoIndexV4StorePath(cwd) {
|
|
23355
|
+
return statePath(cwd);
|
|
23356
|
+
}
|
|
23300
23357
|
|
|
23301
23358
|
// src/repo-index-v4/shards.ts
|
|
23302
23359
|
var V4_STAGE_MAX_BODY_BYTES = 750 * 1024;
|
|
@@ -32371,7 +32428,7 @@ var surfaces_default = {
|
|
|
32371
32428
|
ownership: {
|
|
32372
32429
|
trust: "host",
|
|
32373
32430
|
cache: "host",
|
|
32374
|
-
repair: "mmi-
|
|
32431
|
+
repair: "mmi-hub"
|
|
32375
32432
|
},
|
|
32376
32433
|
certification: {
|
|
32377
32434
|
hostCommand: "claude",
|
|
@@ -32506,7 +32563,7 @@ var surfaces_default = {
|
|
|
32506
32563
|
ownership: {
|
|
32507
32564
|
trust: "operator",
|
|
32508
32565
|
cache: "host",
|
|
32509
|
-
repair: "mmi-
|
|
32566
|
+
repair: "mmi-hub"
|
|
32510
32567
|
},
|
|
32511
32568
|
certification: {
|
|
32512
32569
|
hostCommand: "codex",
|
|
@@ -32767,8 +32824,8 @@ var surfaces_default = {
|
|
|
32767
32824
|
},
|
|
32768
32825
|
ownership: {
|
|
32769
32826
|
trust: "host",
|
|
32770
|
-
cache: "mmi-
|
|
32771
|
-
repair: "mmi-
|
|
32827
|
+
cache: "mmi-hub",
|
|
32828
|
+
repair: "mmi-hub"
|
|
32772
32829
|
},
|
|
32773
32830
|
certification: {
|
|
32774
32831
|
hostCommand: "cursor-agent",
|
|
@@ -32879,7 +32936,7 @@ var surfaces_default = {
|
|
|
32879
32936
|
ownership: {
|
|
32880
32937
|
trust: "host",
|
|
32881
32938
|
cache: "host",
|
|
32882
|
-
repair: "mmi-
|
|
32939
|
+
repair: "mmi-hub"
|
|
32883
32940
|
},
|
|
32884
32941
|
certification: {
|
|
32885
32942
|
hostCommand: "kilo",
|
|
@@ -32997,7 +33054,7 @@ var surfaces_default = {
|
|
|
32997
33054
|
ownership: {
|
|
32998
33055
|
trust: "host",
|
|
32999
33056
|
cache: "host",
|
|
33000
|
-
repair: "mmi-
|
|
33057
|
+
repair: "mmi-hub"
|
|
33001
33058
|
},
|
|
33002
33059
|
certification: {
|
|
33003
33060
|
hostCommand: "jervcode",
|
|
@@ -34125,19 +34182,14 @@ function checkPluginCache(input) {
|
|
|
34125
34182
|
parts.push(`${input.staging.length} orphaned staging dir(s)${size}`);
|
|
34126
34183
|
}
|
|
34127
34184
|
return {
|
|
34128
|
-
ok:
|
|
34185
|
+
ok: true,
|
|
34186
|
+
warn: true,
|
|
34129
34187
|
id: "plugin-cache",
|
|
34130
34188
|
label: "plugin cache",
|
|
34131
|
-
//
|
|
34132
|
-
//
|
|
34133
|
-
// `mmi-cli plugin prune --apply` clears it in one command and a re-run goes green, so this is an
|
|
34134
|
-
// ordinary red the operator is expected to act on. "The doctor cannot heal it automatically" was never
|
|
34135
|
-
// the bar — see the contract for why report-only is a per-row ruling rather than a rule you can apply
|
|
34136
|
-
// from here.
|
|
34137
|
-
// Lead with the versioned-cache framing when there are stale versions; otherwise report the staging litter
|
|
34138
|
-
// on its own so a cache with zero versioned dirs still gets an honest ✗.
|
|
34189
|
+
// Installed-surface convergence belongs to mmi-hub; the host owns its cache lifecycle. Doctor
|
|
34190
|
+
// reports old generations but never races either writer by deleting them.
|
|
34139
34191
|
detail: input.stale.length ? `${input.cached} versions cached (${parts.join("; ")})` : parts.join("; "),
|
|
34140
|
-
fix: "run `mmi-
|
|
34192
|
+
fix: "run `mmi-hub status` and `mmi-hub update`; if only superseded cache remains, let the host retire it after all older sessions close",
|
|
34141
34193
|
verbose: evidence
|
|
34142
34194
|
};
|
|
34143
34195
|
}
|
|
@@ -34242,24 +34294,34 @@ function checkRepoIndexCloud(probe) {
|
|
|
34242
34294
|
probe.builtAt ? `builtAt: ${probe.builtAt}` : "builtAt: n/a",
|
|
34243
34295
|
typeof probe.fileCount === "number" ? `fileCount: ${probe.fileCount}` : "fileCount: n/a",
|
|
34244
34296
|
typeof probe.embCount === "number" ? `embCount: ${probe.embCount}` : "embCount: n/a",
|
|
34245
|
-
typeof probe.localPresent === "boolean" ? `local cache: ${probe.localPresent ? "present" : "absent"}` : "local cache: n/a"
|
|
34297
|
+
typeof probe.localPresent === "boolean" ? `local v3 cache: ${probe.localPresent ? "present" : "absent"}` : "local v3 cache: n/a",
|
|
34298
|
+
`local v4: ${probe.localV4State ?? "unknown"}${probe.localV4Commit ? ` @ ${probe.localV4Commit.slice(0, 12)}` : ""}${typeof probe.localV4EmbeddingCoverage === "number" ? ` (${Math.round(probe.localV4EmbeddingCoverage * 100)}% embeddings)` : ""}`,
|
|
34299
|
+
`cloud v4: ${probe.cloudV4State ?? "unknown"}${probe.cloudV4Commit ? ` @ ${probe.cloudV4Commit.slice(0, 12)}` : ""}`,
|
|
34300
|
+
`v4 cohort: ${probe.v4ReadPercent ?? 0}%`,
|
|
34301
|
+
`v4 shadows: ${probe.v4ShadowComparisons ?? 0}/${probe.v4ShadowMinimum ?? 50} \u2014 ${probe.v4ShadowCutoverReady ? "cutover-ready" : "not ready"}`,
|
|
34302
|
+
...Object.entries(probe.v4ShadowErrors ?? {}).map(([kind, count]) => `v4 shadow error ${kind}: ${count}`)
|
|
34246
34303
|
];
|
|
34247
34304
|
switch (probe.kind) {
|
|
34248
|
-
case "healthy":
|
|
34305
|
+
case "healthy": {
|
|
34306
|
+
const v4Unready = probe.cloudV4State === "invalid" || probe.cloudV4State === "tombstoned" || probe.cloudV4State === "degraded" && (probe.localV4EmbeddingCoverage ?? 1) < 0.95;
|
|
34307
|
+
const detail = probe.detail ?? `cloud v3 ok${typeof probe.fileCount === "number" ? ` \u2014 ${probe.fileCount} files` : ""}; v4 ${probe.cloudV4State ?? "absent"}, cohort ${probe.v4ReadPercent ?? 0}%`;
|
|
34249
34308
|
return {
|
|
34250
|
-
ok:
|
|
34309
|
+
ok: !v4Unready || (probe.v4ReadPercent ?? 0) === 0,
|
|
34310
|
+
...v4Unready && (probe.v4ReadPercent ?? 0) === 0 ? { warn: true, verified: false } : {},
|
|
34251
34311
|
id: "repo-index",
|
|
34252
34312
|
label: "repo-index",
|
|
34253
|
-
detail
|
|
34313
|
+
detail,
|
|
34314
|
+
...v4Unready ? { fix: "run `mmi-hub update`, then dispatch `mmi-cli harbour org schedules run MMI-Hub/repo-index-reconcile`; keep the v4 cohort at 0 until shadows pass" } : {},
|
|
34254
34315
|
verbose: evidence
|
|
34255
34316
|
};
|
|
34317
|
+
}
|
|
34256
34318
|
case "command-absent":
|
|
34257
34319
|
return {
|
|
34258
34320
|
ok: false,
|
|
34259
34321
|
id: "repo-index",
|
|
34260
34322
|
label: "repo-index",
|
|
34261
34323
|
detail: "this mmi-cli build has no repo-index command",
|
|
34262
|
-
fix: "run `mmi-
|
|
34324
|
+
fix: "run `mmi-hub status`, then `mmi-hub update` to converge the CLI and installed host surfaces",
|
|
34263
34325
|
verbose: evidence
|
|
34264
34326
|
};
|
|
34265
34327
|
case "deploy-lag":
|
|
@@ -34278,7 +34340,7 @@ function checkRepoIndexCloud(probe) {
|
|
|
34278
34340
|
id: "repo-index",
|
|
34279
34341
|
label: "repo-index",
|
|
34280
34342
|
detail: probe.detail ?? "no cloud projection for this repo",
|
|
34281
|
-
fix: "
|
|
34343
|
+
fix: "dispatch `mmi-cli harbour org schedules run MMI-Hub/repo-index-reconcile` or run the authenticated per-repo sync",
|
|
34282
34344
|
verbose: evidence
|
|
34283
34345
|
};
|
|
34284
34346
|
case "stale":
|
|
@@ -34287,7 +34349,7 @@ function checkRepoIndexCloud(probe) {
|
|
|
34287
34349
|
id: "repo-index",
|
|
34288
34350
|
label: "repo-index",
|
|
34289
34351
|
detail: probe.detail ?? "cloud projection looks stale vs recent pushes",
|
|
34290
|
-
fix: "
|
|
34352
|
+
fix: "dispatch `mmi-cli harbour org schedules run MMI-Hub/repo-index-reconcile` or wait for the registered 6h schedule",
|
|
34291
34353
|
verbose: evidence
|
|
34292
34354
|
};
|
|
34293
34355
|
case "auth":
|
|
@@ -34305,7 +34367,7 @@ function checkRepoIndexCloud(probe) {
|
|
|
34305
34367
|
id: "repo-index",
|
|
34306
34368
|
label: "repo-index",
|
|
34307
34369
|
detail: probe.detail ?? "Hub API URL not configured",
|
|
34308
|
-
fix: "run `mmi-
|
|
34370
|
+
fix: "run `mmi-hub status` and `mmi-hub update`; Hub URL wiring is installer-owned",
|
|
34309
34371
|
verbose: evidence
|
|
34310
34372
|
};
|
|
34311
34373
|
case "network":
|
|
@@ -34335,6 +34397,39 @@ function planGitignore(current) {
|
|
|
34335
34397
|
const { content, changed } = upsertManagedGitignoreBlock(current);
|
|
34336
34398
|
return changed ? { ok: false, content } : { ok: true };
|
|
34337
34399
|
}
|
|
34400
|
+
function checkLineEndings(probe) {
|
|
34401
|
+
if (probe.error) {
|
|
34402
|
+
return {
|
|
34403
|
+
ok: true,
|
|
34404
|
+
verified: false,
|
|
34405
|
+
id: "line-endings",
|
|
34406
|
+
label: "line endings",
|
|
34407
|
+
detail: `could not inspect tracked shell scripts: ${probe.error}`,
|
|
34408
|
+
fix: "run `git ls-files --eol -- '*.sh'` from the repository root, then re-run `mmi-cli doctor`"
|
|
34409
|
+
};
|
|
34410
|
+
}
|
|
34411
|
+
const problems = [
|
|
34412
|
+
...probe.attributesPresent ? [] : [".gitattributes is missing"],
|
|
34413
|
+
...probe.crlfShellScripts.map((path2) => `${path2} is CRLF in the index`)
|
|
34414
|
+
];
|
|
34415
|
+
if (!problems.length) {
|
|
34416
|
+
return {
|
|
34417
|
+
ok: true,
|
|
34418
|
+
id: "line-endings",
|
|
34419
|
+
label: "line endings",
|
|
34420
|
+
detail: ".gitattributes present; tracked *.sh index entries are LF"
|
|
34421
|
+
};
|
|
34422
|
+
}
|
|
34423
|
+
return {
|
|
34424
|
+
ok: false,
|
|
34425
|
+
id: "line-endings",
|
|
34426
|
+
label: "line endings",
|
|
34427
|
+
detail: problems.join("; "),
|
|
34428
|
+
fix: "add the canonical .gitattributes if missing, then run `git add --renormalize .`",
|
|
34429
|
+
command: "git add --renormalize .",
|
|
34430
|
+
verbose: probe.crlfShellScripts.map((path2) => `i/crlf: ${path2}`)
|
|
34431
|
+
};
|
|
34432
|
+
}
|
|
34338
34433
|
async function runDoctorClean(opts, io, deps) {
|
|
34339
34434
|
const full = !opts.fast && !opts.banner && !opts.preflight;
|
|
34340
34435
|
const applyEnv = full;
|
|
@@ -34468,6 +34563,11 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
34468
34563
|
const aws = checkAwsIdentity({ isOrgRepo, probed: probeAws, callerArn });
|
|
34469
34564
|
if (aws) emitNow(aws);
|
|
34470
34565
|
}
|
|
34566
|
+
async function runLineEndingsRow() {
|
|
34567
|
+
const root = await deps.repoRoot();
|
|
34568
|
+
const state = deps.lineEndingState?.(root);
|
|
34569
|
+
if (state) emitNow(checkLineEndings(state));
|
|
34570
|
+
}
|
|
34471
34571
|
async function runGitignoreRow() {
|
|
34472
34572
|
const current = deps.readGitignore();
|
|
34473
34573
|
const gi = planGitignore(current);
|
|
@@ -34487,42 +34587,6 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
34487
34587
|
}
|
|
34488
34588
|
}
|
|
34489
34589
|
async function runPluginCacheRow() {
|
|
34490
|
-
if (applyEnv && deps.prunePluginCache && spendOnce("cache-prune")) {
|
|
34491
|
-
const cache = deps.pluginCache();
|
|
34492
|
-
if (cache.stale.length === 0 && cache.staging.length === 0) {
|
|
34493
|
-
emitNow(checkPluginCache(cache));
|
|
34494
|
-
return;
|
|
34495
|
-
}
|
|
34496
|
-
healIntent(`plugin cache \u2014 pruning ${cache.stale.length} superseded version(s) (guarded)`);
|
|
34497
|
-
traceHeal("cache-prune");
|
|
34498
|
-
const outcome = deps.prunePluginCache();
|
|
34499
|
-
if (outcome.removed.length) markHealChanged();
|
|
34500
|
-
const evidence = [
|
|
34501
|
-
...outcome.removed.map((v) => `pruned: ${v}`),
|
|
34502
|
-
...outcome.held.map((h) => `still held: ${h.version} (${h.error}) \u2014 never forced`),
|
|
34503
|
-
...outcome.kept.map((k) => `kept: ${k.version} \u2014 ${k.reason}`)
|
|
34504
|
-
];
|
|
34505
|
-
if (outcome.held.length === 0) {
|
|
34506
|
-
emitNow({
|
|
34507
|
-
id: "plugin-cache",
|
|
34508
|
-
ok: true,
|
|
34509
|
-
label: "plugin cache",
|
|
34510
|
-
detail: outcome.removed.length ? `pruned ${outcome.removed.length} superseded version(s)` : "nothing provably superseded (guards kept every candidate)",
|
|
34511
|
-
verbose: evidence
|
|
34512
|
-
});
|
|
34513
|
-
} else {
|
|
34514
|
-
emitNow({
|
|
34515
|
-
id: "plugin-cache",
|
|
34516
|
-
ok: false,
|
|
34517
|
-
reportOnly: true,
|
|
34518
|
-
label: "plugin cache",
|
|
34519
|
-
detail: `${outcome.removed.length} pruned; ${outcome.held.length} still held by a live session`,
|
|
34520
|
-
fix: "close the session holding it and re-run `mmi-cli doctor`",
|
|
34521
|
-
verbose: evidence
|
|
34522
|
-
});
|
|
34523
|
-
}
|
|
34524
|
-
return;
|
|
34525
|
-
}
|
|
34526
34590
|
emitNow(checkPluginCache(deps.pluginCache()));
|
|
34527
34591
|
}
|
|
34528
34592
|
async function runClaudeBinaryRow() {
|
|
@@ -34851,6 +34915,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
34851
34915
|
{ id: "aws-identity", when: true, run: runAwsRow },
|
|
34852
34916
|
{ id: "sessionstart-payload", when: true, run: runSessionPayloadRow },
|
|
34853
34917
|
{ id: "claude-binary", when: true, run: runClaudeBinaryRow },
|
|
34918
|
+
{ id: "line-endings", when: isOrgRepo, run: runLineEndingsRow },
|
|
34854
34919
|
{ id: "gitignore-block", when: isOrgRepo, run: runGitignoreRow }
|
|
34855
34920
|
];
|
|
34856
34921
|
const maxPasses = 3;
|
|
@@ -35143,6 +35208,20 @@ function writeGitignore(content) {
|
|
|
35143
35208
|
return false;
|
|
35144
35209
|
}
|
|
35145
35210
|
}
|
|
35211
|
+
function lineEndingState(root) {
|
|
35212
|
+
const attributesPresent = (0, import_node_fs41.existsSync)((0, import_node_path38.join)(root, ".gitattributes"));
|
|
35213
|
+
try {
|
|
35214
|
+
const output = (0, import_node_child_process18.execFileSync)("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
|
|
35215
|
+
windowsHide: true,
|
|
35216
|
+
encoding: "utf8",
|
|
35217
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
35218
|
+
});
|
|
35219
|
+
const crlfShellScripts = output.split(/\r?\n/).filter((line) => line.startsWith("i/crlf ")).map((line) => line.slice(line.indexOf(" ") + 1)).filter(Boolean);
|
|
35220
|
+
return { attributesPresent, crlfShellScripts };
|
|
35221
|
+
} catch (error) {
|
|
35222
|
+
return { attributesPresent, crlfShellScripts: [], error: error.message || "git ls-files --eol failed" };
|
|
35223
|
+
}
|
|
35224
|
+
}
|
|
35146
35225
|
async function ghInstalled() {
|
|
35147
35226
|
try {
|
|
35148
35227
|
await execFileP6("gh", ["--version"]);
|
|
@@ -35352,6 +35431,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
35352
35431
|
currentCliVersion: resolveClientVersion,
|
|
35353
35432
|
readGitignore,
|
|
35354
35433
|
writeGitignore,
|
|
35434
|
+
lineEndingState,
|
|
35355
35435
|
repoRoot,
|
|
35356
35436
|
executeScratchGc: (root, o) => executeScratchGc(root, { apply: o.apply }),
|
|
35357
35437
|
// #2759: same fetch+ff-only sync SessionStart runs, wired here too so an on-demand `mmi-cli doctor`
|
|
@@ -35520,6 +35600,21 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
35520
35600
|
// #4156: short-timeout Hub status for the current repo. Fail-soft — never throws to doctor.
|
|
35521
35601
|
repoIndexCloudState: async (root) => {
|
|
35522
35602
|
const local = repoIndexStatus(root);
|
|
35603
|
+
let localV4 = { state: "absent" };
|
|
35604
|
+
try {
|
|
35605
|
+
const parsed = JSON.parse((0, import_node_fs42.readFileSync)(repoIndexV4StorePath(root), "utf8"));
|
|
35606
|
+
const state = parsed.status?.state;
|
|
35607
|
+
if (parsed.schemaVersion === 4 && (state === "ready" || state === "degraded")) {
|
|
35608
|
+
const chunks = parsed.manifest?.chunks?.length ?? 0;
|
|
35609
|
+
localV4 = {
|
|
35610
|
+
state,
|
|
35611
|
+
...typeof parsed.manifest?.commit === "string" ? { commit: parsed.manifest.commit } : {},
|
|
35612
|
+
embeddingCoverage: chunks ? (parsed.manifest?.embeddings?.length ?? 0) / chunks : 1
|
|
35613
|
+
};
|
|
35614
|
+
} else localV4 = { state: "invalid" };
|
|
35615
|
+
} catch {
|
|
35616
|
+
}
|
|
35617
|
+
const localFields = { localPresent: local.present, localV4State: localV4.state, localV4Commit: localV4.commit, localV4EmbeddingCoverage: localV4.embeddingCoverage };
|
|
35523
35618
|
try {
|
|
35524
35619
|
const cfg = await loadConfig();
|
|
35525
35620
|
const repo = inferRepoSlug(root);
|
|
@@ -35528,17 +35623,32 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
35528
35623
|
timeoutMs: 4e3
|
|
35529
35624
|
});
|
|
35530
35625
|
if (isRepoIndexStatusError(st)) {
|
|
35531
|
-
if (st.code === "deploy-lag") return { kind: "deploy-lag", repo,
|
|
35532
|
-
if (st.code === "auth") return { kind: "auth", repo,
|
|
35533
|
-
if (st.code === "config") return { kind: "config", repo, detail: st.error,
|
|
35534
|
-
return { kind: "network", repo, detail: st.error,
|
|
35626
|
+
if (st.code === "deploy-lag") return { kind: "deploy-lag", repo, ...localFields };
|
|
35627
|
+
if (st.code === "auth") return { kind: "auth", repo, ...localFields };
|
|
35628
|
+
if (st.code === "config") return { kind: "config", repo, detail: st.error, ...localFields };
|
|
35629
|
+
return { kind: "network", repo, detail: st.error, ...localFields };
|
|
35535
35630
|
}
|
|
35536
35631
|
const present = st.present;
|
|
35537
35632
|
const builtAt = typeof st.builtAt === "string" ? st.builtAt : void 0;
|
|
35538
35633
|
const fileCount = typeof st.fileCount === "number" ? st.fileCount : void 0;
|
|
35539
35634
|
const embCount = typeof st.embCount === "number" ? st.embCount : void 0;
|
|
35635
|
+
const v4 = st.v4;
|
|
35636
|
+
const migration = st.v4Migration;
|
|
35637
|
+
const shadow = st.v4Shadow;
|
|
35638
|
+
const shadowThresholds = shadow?.thresholds;
|
|
35639
|
+
const cloudV4State = typeof v4?.state === "string" && ["ready", "degraded", "invalid", "tombstoned"].includes(v4.state) ? v4.state : "absent";
|
|
35640
|
+
const v4Fields = {
|
|
35641
|
+
...localFields,
|
|
35642
|
+
cloudV4State,
|
|
35643
|
+
...typeof v4?.commit === "string" ? { cloudV4Commit: v4.commit } : {},
|
|
35644
|
+
v4ReadPercent: typeof migration?.readPercent === "number" ? migration.readPercent : 0,
|
|
35645
|
+
v4ShadowComparisons: typeof shadow?.comparisonCount === "number" ? shadow.comparisonCount : 0,
|
|
35646
|
+
v4ShadowMinimum: typeof shadowThresholds?.minimumComparisons === "number" ? shadowThresholds.minimumComparisons : 50,
|
|
35647
|
+
v4ShadowCutoverReady: shadow?.cutoverReady === true,
|
|
35648
|
+
v4ShadowErrors: shadow?.errors && typeof shadow.errors === "object" ? shadow.errors : {}
|
|
35649
|
+
};
|
|
35540
35650
|
if (present === false) {
|
|
35541
|
-
return { kind: "missing-projection", repo,
|
|
35651
|
+
return { kind: "missing-projection", repo, ...v4Fields, fileCount: 0, embCount: 0 };
|
|
35542
35652
|
}
|
|
35543
35653
|
if (builtAt) {
|
|
35544
35654
|
const ageMs = Date.now() - Date.parse(builtAt);
|
|
@@ -35549,7 +35659,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
35549
35659
|
builtAt,
|
|
35550
35660
|
fileCount,
|
|
35551
35661
|
embCount,
|
|
35552
|
-
|
|
35662
|
+
...v4Fields,
|
|
35553
35663
|
detail: `projection builtAt ${builtAt} (>48h old)`
|
|
35554
35664
|
};
|
|
35555
35665
|
}
|
|
@@ -35560,13 +35670,13 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
35560
35670
|
builtAt,
|
|
35561
35671
|
fileCount,
|
|
35562
35672
|
embCount,
|
|
35563
|
-
|
|
35673
|
+
...v4Fields
|
|
35564
35674
|
};
|
|
35565
35675
|
} catch (e) {
|
|
35566
35676
|
return {
|
|
35567
35677
|
kind: "network",
|
|
35568
35678
|
detail: e instanceof Error ? e.message : String(e),
|
|
35569
|
-
|
|
35679
|
+
...localFields
|
|
35570
35680
|
};
|
|
35571
35681
|
}
|
|
35572
35682
|
}
|
package/dist/repo-index-v4.cjs
CHANGED
|
@@ -85,6 +85,30 @@ function defaultIsIgnored(root, relPaths, exec = import_node_child_process.execF
|
|
|
85
85
|
var import_node_child_process2 = require("node:child_process");
|
|
86
86
|
var import_node_path2 = require("node:path");
|
|
87
87
|
|
|
88
|
+
// ../infra/repo-index-path-policy.mjs
|
|
89
|
+
var HARD_DENY = Object.freeze([
|
|
90
|
+
/(^|\/)\.env(\.|$)/i,
|
|
91
|
+
/(^|\/)\.env\./i,
|
|
92
|
+
/credentials/i,
|
|
93
|
+
/secrets?\.json$/i,
|
|
94
|
+
/\.pem$/i,
|
|
95
|
+
/\.p12$/i,
|
|
96
|
+
/\.key$/i,
|
|
97
|
+
/(^|\/)id_rsa/i,
|
|
98
|
+
/(^|\/)id_ed25519/i,
|
|
99
|
+
/\.keystore$/i
|
|
100
|
+
]);
|
|
101
|
+
function isHardDeniedRepoIndexPath(value) {
|
|
102
|
+
return typeof value !== "string" || HARD_DENY.some((pattern) => pattern.test(value));
|
|
103
|
+
}
|
|
104
|
+
function isSafeRepoIndexPath(value) {
|
|
105
|
+
if (typeof value !== "string" || !value || value.length > 1024) return false;
|
|
106
|
+
if (value.startsWith("/") || value.includes("\\") || /[\u0000-\u001f\u007f]/u.test(value)) return false;
|
|
107
|
+
const segments = value.split("/");
|
|
108
|
+
if (segments.some((segment) => !segment || segment === "." || segment === "..")) return false;
|
|
109
|
+
return !isHardDeniedRepoIndexPath(value);
|
|
110
|
+
}
|
|
111
|
+
|
|
88
112
|
// src/repo-runtime-state.ts
|
|
89
113
|
var import_node_crypto = require("node:crypto");
|
|
90
114
|
var import_node_fs = require("node:fs");
|
|
@@ -120,18 +144,6 @@ function repoRuntimeStatePath(cwd, ...parts) {
|
|
|
120
144
|
}
|
|
121
145
|
|
|
122
146
|
// src/repo-index.ts
|
|
123
|
-
var HARD_DENY = [
|
|
124
|
-
/(^|\/)\.env(\.|$)/i,
|
|
125
|
-
/(^|\/)\.env\./i,
|
|
126
|
-
/credentials/i,
|
|
127
|
-
/secrets?\.json$/i,
|
|
128
|
-
/\.pem$/i,
|
|
129
|
-
/\.p12$/i,
|
|
130
|
-
/\.key$/i,
|
|
131
|
-
/(^|\/)id_rsa/i,
|
|
132
|
-
/(^|\/)id_ed25519/i,
|
|
133
|
-
/\.keystore$/i
|
|
134
|
-
];
|
|
135
147
|
var INDEXABLE_EXT = /* @__PURE__ */ new Set([
|
|
136
148
|
".ts",
|
|
137
149
|
".tsx",
|
|
@@ -155,10 +167,10 @@ var INDEXABLE_EXT = /* @__PURE__ */ new Set([
|
|
|
155
167
|
".html"
|
|
156
168
|
]);
|
|
157
169
|
function isHardDeniedPath(relPosix) {
|
|
158
|
-
return
|
|
170
|
+
return isHardDeniedRepoIndexPath(relPosix);
|
|
159
171
|
}
|
|
160
172
|
function isIndexablePath(relPosix) {
|
|
161
|
-
if (
|
|
173
|
+
if (!isSafeRepoIndexPath(relPosix)) return false;
|
|
162
174
|
if (relPosix.startsWith(".git/")) return false;
|
|
163
175
|
if (relPosix.includes("node_modules/")) return false;
|
|
164
176
|
if (relPosix.includes("dist/")) return false;
|
|
@@ -418,6 +430,7 @@ ${chunk.symbol ?? ""}
|
|
|
418
430
|
${chunk.blurb ?? ""}`;
|
|
419
431
|
}
|
|
420
432
|
}
|
|
433
|
+
var V4_EMBED_TIMEOUT_MS = 15 * 6e4;
|
|
421
434
|
function runEmbedder(cwd, chunks, modelDirectory, createdAt) {
|
|
422
435
|
if (!chunks.length) return { embeddings: [] };
|
|
423
436
|
const runner = (0, import_node_path4.join)(cwd, "repo-indexer", "src", "batch.mjs");
|
|
@@ -426,7 +439,7 @@ function runEmbedder(cwd, chunks, modelDirectory, createdAt) {
|
|
|
426
439
|
if (!(0, import_node_fs3.existsSync)(file)) return { embeddings: [], reason: "embeddings-unavailable" };
|
|
427
440
|
const request = { texts: chunks.map((chunk) => ({ id: chunk.id, text: embeddingInput(cwd, chunk) })), maxBatch: V4_EMBED_BATCH };
|
|
428
441
|
const env = { ...process.env, ...modelDirectory ? { MMI_REPO_INDEXER_MODEL_DIR: modelDirectory } : {} };
|
|
429
|
-
const result = (0, import_node_child_process3.spawnSync)(process.execPath, [file], { input: JSON.stringify(request), encoding: "utf8", windowsHide: true, timeout:
|
|
442
|
+
const result = (0, import_node_child_process3.spawnSync)(process.execPath, [file], { input: JSON.stringify(request), encoding: "utf8", windowsHide: true, timeout: V4_EMBED_TIMEOUT_MS, maxBuffer: V4_MAX_ARTIFACT_BYTES, env });
|
|
430
443
|
if (result.error || result.status !== 0) return { embeddings: [], reason: "embeddings-unavailable" };
|
|
431
444
|
try {
|
|
432
445
|
const response = JSON.parse(result.stdout);
|
package/package.json
CHANGED