@mutmutco/cli 4.0.17 → 4.1.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.
Files changed (3) hide show
  1. package/dist/main.cjs +19581 -16889
  2. package/dist/repo-index-v4.cjs +392 -59
  3. package/package.json +1 -1
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  REPO_INDEX_V4_SCHEMA: () => REPO_INDEX_V4_SCHEMA,
34
+ V4_DELTA_DEEPEN_STEPS: () => V4_DELTA_DEEPEN_STEPS,
34
35
  V4_GRAPH_MAX_EDGES: () => V4_GRAPH_MAX_EDGES,
35
36
  V4_GRAPH_MAX_EDGES_PER_FILE: () => V4_GRAPH_MAX_EDGES_PER_FILE,
36
37
  V4_MAX_ARTIFACT_BYTES: () => V4_MAX_ARTIFACT_BYTES,
@@ -39,10 +40,23 @@ __export(index_exports, {
39
40
  V4_STAGE_MAX_RECORDS: () => V4_STAGE_MAX_RECORDS,
40
41
  buildGraphEdges: () => buildGraphEdges,
41
42
  buildRepoIndexV4: () => buildRepoIndexV4,
43
+ buildRepoIndexV4Detailed: () => buildRepoIndexV4Detailed,
42
44
  buildStructuralChunks: () => buildStructuralChunks,
45
+ buildStructuralChunksForPaths: () => buildStructuralChunksForPaths,
46
+ canonicalRepoIndexPaths: () => canonicalRepoIndexPaths,
47
+ changedPaths: () => changedPaths,
48
+ compareV4Chunks: () => compareV4Chunks,
43
49
  encodedStageBytes: () => encodedStageBytes,
50
+ formatV4BuildMetrics: () => formatV4BuildMetrics,
51
+ gitRunner: () => gitRunner,
44
52
  graphEdgesForSource: () => graphEdgesForSource,
53
+ isRepoIndexDeltaCompatible: () => isRepoIndexDeltaCompatible,
45
54
  languageForPath: () => languageForPath,
55
+ parseNameStatusZ: () => parseNameStatusZ,
56
+ planRepoIndexV4Delta: () => planRepoIndexV4Delta,
57
+ readRepoIndexProvenance: () => readRepoIndexProvenance,
58
+ removedPaths: () => removedPaths,
59
+ repoIndexV4DeltaBase: () => repoIndexV4DeltaBase,
46
60
  repoIndexV4StorePath: () => repoIndexV4StorePath,
47
61
  shardRepoIndexV4: () => shardRepoIndexV4,
48
62
  structuralChunksForSource: () => structuralChunksForSource
@@ -50,10 +64,189 @@ __export(index_exports, {
50
64
  module.exports = __toCommonJS(index_exports);
51
65
 
52
66
  // src/repo-index-v4/chunks.ts
53
- var import_node_crypto2 = require("node:crypto");
67
+ var import_node_crypto3 = require("node:crypto");
54
68
  var import_node_fs2 = require("node:fs");
55
69
  var import_node_path3 = require("node:path");
56
70
 
71
+ // ../infra/repo-index-path-policy.mjs
72
+ var HARD_DENY = Object.freeze([
73
+ /(^|\/)\.env(\.|$)/i,
74
+ /(^|\/)\.env\./i,
75
+ /credentials/i,
76
+ /secrets?\.json$/i,
77
+ /\.pem$/i,
78
+ /\.p12$/i,
79
+ /\.key$/i,
80
+ /(^|\/)id_rsa/i,
81
+ /(^|\/)id_ed25519/i,
82
+ /\.keystore$/i
83
+ ]);
84
+ function isHardDeniedRepoIndexPath(value) {
85
+ return typeof value !== "string" || HARD_DENY.some((pattern) => pattern.test(value));
86
+ }
87
+ function isSafeRepoIndexPath(value) {
88
+ if (typeof value !== "string" || !value || value.length > 1024) return false;
89
+ if (value.startsWith("/") || value.includes("\\") || /[\u0000-\u001f\u007f]/u.test(value)) return false;
90
+ const segments = value.split("/");
91
+ if (segments.some((segment) => !segment || segment === "." || segment === "..")) return false;
92
+ return !isHardDeniedRepoIndexPath(value);
93
+ }
94
+ var REPO_INDEX_ALGORITHM_VERSION = 1;
95
+ var REPO_INDEX_FILTER_VERSION = 2;
96
+ var REPO_INDEX_MATERIAL_LAYOUT_VERSION = 2;
97
+ var LEGACY_REPO_INDEX_PROVENANCE = Object.freeze({
98
+ algorithmVersion: 1,
99
+ filterVersion: 1,
100
+ materialLayoutVersion: 1
101
+ });
102
+ var CURRENT_REPO_INDEX_PROVENANCE = Object.freeze({
103
+ algorithmVersion: REPO_INDEX_ALGORITHM_VERSION,
104
+ filterVersion: REPO_INDEX_FILTER_VERSION,
105
+ materialLayoutVersion: REPO_INDEX_MATERIAL_LAYOUT_VERSION
106
+ });
107
+ function repoIndexProvenanceToken(provenance = CURRENT_REPO_INDEX_PROVENANCE) {
108
+ const resolved = provenance && typeof provenance === "object" ? provenance : {};
109
+ return `a${resolved.algorithmVersion}-f${resolved.filterVersion}-l${resolved.materialLayoutVersion}`;
110
+ }
111
+ var CURRENT_REPO_INDEX_PROVENANCE_TOKEN = repoIndexProvenanceToken(CURRENT_REPO_INDEX_PROVENANCE);
112
+ var GENERATED_PROJECTION = Object.freeze([
113
+ // Host plugin packages: the assembled skills/hooks/scripts/bin payload and the copied host
114
+ // manifest directory. `packages/<host>-plugin/<file>` at the root stays admitted.
115
+ /^packages\/(?:claude|codex|kimi|cursor|hermes)-plugin\/(?:skills|hooks|scripts|bin)\//,
116
+ /^packages\/(?:claude|codex|kimi|cursor|hermes)-plugin\/\.[A-Za-z0-9-]+-plugin\//,
117
+ // In-repo plugin roots assembled by the same declaration.
118
+ /^\.kilo-plugin\/(?:skills|scripts)\//,
119
+ /^\.pi-plugin\/(?:skills|scripts|extensions)\//,
120
+ // Release metadata regenerated from the tracked tree at every prepare.
121
+ /^distribution-bom\.json$/,
122
+ // Cross-repo generator outputs the estate audit named.
123
+ /^product\/connector\/generated\//,
124
+ /^packages\/jerv-pi\/generated\/roster\//,
125
+ /^docs\/\.release-inbox\//
126
+ ]);
127
+ function isGeneratedRepoIndexProjection(value) {
128
+ return typeof value === "string" && GENERATED_PROJECTION.some((pattern) => pattern.test(value));
129
+ }
130
+ function isCanonicalRepoIndexPath(value) {
131
+ return isSafeRepoIndexPath(value) && !isGeneratedRepoIndexProjection(value);
132
+ }
133
+ function readRepoIndexProvenance(manifest) {
134
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) return null;
135
+ const provenance = manifest.indexProvenance;
136
+ if (provenance === void 0) return { ...LEGACY_REPO_INDEX_PROVENANCE };
137
+ if (!provenance || typeof provenance !== "object" || Array.isArray(provenance)) return null;
138
+ const keys = ["algorithmVersion", "filterVersion", "materialLayoutVersion"];
139
+ if (Object.keys(provenance).length !== keys.length) return null;
140
+ for (const key of keys) {
141
+ const value = provenance[key];
142
+ if (!Number.isInteger(value) || value < 1) return null;
143
+ }
144
+ return {
145
+ algorithmVersion: provenance.algorithmVersion,
146
+ filterVersion: provenance.filterVersion,
147
+ materialLayoutVersion: provenance.materialLayoutVersion
148
+ };
149
+ }
150
+ function isRepoIndexDeltaCompatible(manifest) {
151
+ const provenance = readRepoIndexProvenance(manifest);
152
+ return provenance !== null && provenance.algorithmVersion === REPO_INDEX_ALGORITHM_VERSION && provenance.filterVersion === REPO_INDEX_FILTER_VERSION && provenance.materialLayoutVersion === REPO_INDEX_MATERIAL_LAYOUT_VERSION;
153
+ }
154
+
155
+ // ../infra/repo-index-material-buckets.mjs
156
+ var import_node_crypto = require("node:crypto");
157
+ var REPO_INDEX_MATERIAL_BUCKET_COUNT = 2;
158
+ var REPO_INDEX_MATERIAL_KINDS = Object.freeze(["chunks", "embeddings"]);
159
+ var REPO_INDEX_MATERIAL_PREFIX = "material-v2";
160
+ function canonicalMaterialJson(value) {
161
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
162
+ if (Array.isArray(value)) return `[${value.map(canonicalMaterialJson).join(",")}]`;
163
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalMaterialJson(value[key])}`).join(",")}}`;
164
+ }
165
+ function repoIndexMaterialDigest(value) {
166
+ return (0, import_node_crypto.createHash)("sha256").update(canonicalMaterialJson(value)).digest("hex");
167
+ }
168
+ function normalizeRepoIndexMaterialPath(path) {
169
+ return String(path).replace(/^\.\//, "").normalize("NFC");
170
+ }
171
+ function repoIndexMaterialBucket(path) {
172
+ const hash = (0, import_node_crypto.createHash)("sha256").update(normalizeRepoIndexMaterialPath(path), "utf8").digest();
173
+ return hash.readUInt32BE(0) % REPO_INDEX_MATERIAL_BUCKET_COUNT;
174
+ }
175
+ function compareRepoIndexChunks(a, b) {
176
+ const aPath = String(a?.path ?? "");
177
+ const bPath = String(b?.path ?? "");
178
+ if (aPath !== bPath) return aPath < bPath ? -1 : 1;
179
+ const aLine = Number(a?.citations?.[0]?.startLine ?? 0);
180
+ const bLine = Number(b?.citations?.[0]?.startLine ?? 0);
181
+ if (aLine !== bLine) return aLine - bLine;
182
+ const aId = String(a?.id ?? "");
183
+ const bId = String(b?.id ?? "");
184
+ return aId === bId ? 0 : aId < bId ? -1 : 1;
185
+ }
186
+ function compareRepoIndexEmbeddings(a, b) {
187
+ const aId = String(a?.chunkId ?? "");
188
+ const bId = String(b?.chunkId ?? "");
189
+ return aId === bId ? 0 : aId < bId ? -1 : 1;
190
+ }
191
+ function stripRepoIndexChunkCommit(chunk) {
192
+ const citations = Array.isArray(chunk?.citations) ? chunk.citations : [];
193
+ return {
194
+ ...chunk,
195
+ citations: citations.map((citation2) => {
196
+ const rest = { ...citation2 };
197
+ delete rest.commit;
198
+ return rest;
199
+ })
200
+ };
201
+ }
202
+ function repoIndexMaterialIndexKey(repoBase, digest) {
203
+ return `${repoBase}/${REPO_INDEX_MATERIAL_PREFIX}/index/${digest}.json`;
204
+ }
205
+ function repoIndexMaterialIndexUri(repo, digest) {
206
+ return `s3://${repoIndexMaterialIndexKey(`repo-index/v4/${repo}`, digest)}`;
207
+ }
208
+ function repoIndexMaterialBucketBody(kind, bucket, records) {
209
+ return { schemaVersion: 4, materialLayoutVersion: 2, kind, bucket, bucketCount: REPO_INDEX_MATERIAL_BUCKET_COUNT, count: records.length, records };
210
+ }
211
+ var bucketBody = repoIndexMaterialBucketBody;
212
+ function buildRepoIndexMaterialLayout(repo, chunks, embeddings) {
213
+ const chunkList = Array.isArray(chunks) ? chunks : [];
214
+ const embeddingList = Array.isArray(embeddings) ? embeddings : [];
215
+ const pathByChunkId = new Map(chunkList.map((chunk) => [String(chunk?.id), String(chunk?.path)]));
216
+ const grouped = /* @__PURE__ */ new Map();
217
+ for (const kind of REPO_INDEX_MATERIAL_KINDS) {
218
+ for (let bucket = 0; bucket < REPO_INDEX_MATERIAL_BUCKET_COUNT; bucket++) grouped.set(`${kind}:${bucket}`, []);
219
+ }
220
+ for (const chunk of chunkList) {
221
+ grouped.get(`chunks:${repoIndexMaterialBucket(chunk?.path)}`).push(stripRepoIndexChunkCommit(chunk));
222
+ }
223
+ for (const embedding of embeddingList) {
224
+ const path = pathByChunkId.get(String(embedding?.chunkId));
225
+ if (path === void 0) throw new Error("repo-index material embeds a chunk that is not in the corpus");
226
+ grouped.get(`embeddings:${repoIndexMaterialBucket(path)}`).push(embedding);
227
+ }
228
+ const buckets = [];
229
+ for (const kind of REPO_INDEX_MATERIAL_KINDS) {
230
+ for (let bucket = 0; bucket < REPO_INDEX_MATERIAL_BUCKET_COUNT; bucket++) {
231
+ const records = grouped.get(`${kind}:${bucket}`);
232
+ records.sort(kind === "chunks" ? compareRepoIndexChunks : compareRepoIndexEmbeddings);
233
+ const body = bucketBody(kind, bucket, records);
234
+ buckets.push({ kind, bucket, count: records.length, digest: repoIndexMaterialDigest(body), body });
235
+ }
236
+ }
237
+ const index = {
238
+ schemaVersion: 4,
239
+ materialLayoutVersion: 2,
240
+ repo,
241
+ bucketCount: REPO_INDEX_MATERIAL_BUCKET_COUNT,
242
+ chunkCount: chunkList.length,
243
+ embeddingCount: embeddingList.length,
244
+ // Ordered descriptors: chunks buckets ascending, then embeddings buckets ascending.
245
+ buckets: buckets.map(({ kind, bucket, count, digest }) => ({ kind, bucket, count, digest }))
246
+ };
247
+ return { bucketCount: REPO_INDEX_MATERIAL_BUCKET_COUNT, buckets, index, indexDigest: repoIndexMaterialDigest(index) };
248
+ }
249
+
57
250
  // src/doc-refs-core.ts
58
251
  var import_node_child_process = require("node:child_process");
59
252
  var CHECK_IGNORE_MAX_BUFFER = 32 * 1024 * 1024;
@@ -85,38 +278,14 @@ function defaultIsIgnored(root, relPaths, exec = import_node_child_process.execF
85
278
  var import_node_child_process2 = require("node:child_process");
86
279
  var import_node_path2 = require("node:path");
87
280
 
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
-
112
281
  // src/repo-runtime-state.ts
113
- var import_node_crypto = require("node:crypto");
282
+ var import_node_crypto2 = require("node:crypto");
114
283
  var import_node_fs = require("node:fs");
115
284
  var import_node_os = require("node:os");
116
285
  var import_node_path = require("node:path");
117
286
  var RUNTIME_DIR = "mmi-runtime";
118
287
  function hashPath(path) {
119
- return (0, import_node_crypto.createHash)("sha256").update((0, import_node_path.resolve)(path)).digest("hex").slice(0, 16);
288
+ return (0, import_node_crypto2.createHash)("sha256").update((0, import_node_path.resolve)(path)).digest("hex").slice(0, 16);
120
289
  }
121
290
  function resolveGitDir(cwd) {
122
291
  const dotGit = (0, import_node_path.join)(cwd, ".git");
@@ -257,7 +426,7 @@ async function parserFor(language) {
257
426
  return parser;
258
427
  }
259
428
  function sha256(value) {
260
- return (0, import_node_crypto2.createHash)("sha256").update(value).digest("hex");
429
+ return (0, import_node_crypto3.createHash)("sha256").update(value).digest("hex");
261
430
  }
262
431
  function pointerId(path, kind, contentHash, start, end, symbol) {
263
432
  return sha256(["repo-index-v4", path, kind, contentHash, String(start), String(end), symbol ?? ""].join("\0"));
@@ -345,12 +514,17 @@ async function structuralChunksForSource(repo, commit, path, source) {
345
514
  parser?.delete();
346
515
  }
347
516
  }
348
- async function buildStructuralChunks(cwd, repo, commit) {
517
+ function canonicalRepoIndexPaths(cwd) {
349
518
  const candidates = listCandidatePaths(cwd).filter(isIndexablePath).sort((a, b) => a.localeCompare(b));
350
519
  const ignored = defaultIsIgnored(cwd, candidates);
520
+ return candidates.filter((path) => !ignored.has(path) && !isHardDeniedPath(path) && isCanonicalRepoIndexPath(path));
521
+ }
522
+ function compareV4Chunks(a, b) {
523
+ return compareRepoIndexChunks(a, b);
524
+ }
525
+ async function buildStructuralChunksForPaths(cwd, repo, commit, paths) {
351
526
  const chunks = [];
352
- for (const path of candidates) {
353
- if (ignored.has(path) || isHardDeniedPath(path)) continue;
527
+ for (const path of paths) {
354
528
  const absolute = (0, import_node_path3.join)(cwd, ...path.split("/"));
355
529
  if (!(0, import_node_fs2.existsSync)(absolute)) continue;
356
530
  let source;
@@ -361,23 +535,102 @@ async function buildStructuralChunks(cwd, repo, commit) {
361
535
  }
362
536
  chunks.push(...await structuralChunksForSource(repo, commit, path, source));
363
537
  }
364
- return chunks.sort((a, b) => a.path.localeCompare(b.path) || a.citations[0].startLine - b.citations[0].startLine || a.id.localeCompare(b.id));
538
+ return chunks.sort(compareV4Chunks);
539
+ }
540
+ async function buildStructuralChunks(cwd, repo, commit) {
541
+ return buildStructuralChunksForPaths(cwd, repo, commit, canonicalRepoIndexPaths(cwd));
365
542
  }
366
543
 
367
544
  // src/repo-index-v4/types.ts
368
545
  var REPO_INDEX_V4_SCHEMA = 4;
369
546
 
370
547
  // src/repo-index-v4/builder.ts
371
- var import_node_crypto3 = require("node:crypto");
372
- var import_node_child_process3 = require("node:child_process");
548
+ var import_node_crypto4 = require("node:crypto");
549
+ var import_node_child_process4 = require("node:child_process");
373
550
  var import_node_fs3 = require("node:fs");
374
551
  var import_node_os2 = require("node:os");
375
552
  var import_node_path4 = require("node:path");
553
+
554
+ // src/repo-index-v4/delta.ts
555
+ var import_node_child_process3 = require("node:child_process");
556
+ var COMMIT = /^[a-f0-9]{40}$/;
557
+ var V4_DELTA_DEEPEN_STEPS = [50, 250, 1e3];
558
+ function gitRunner(cwd) {
559
+ return (args) => {
560
+ const result = (0, import_node_child_process3.spawnSync)("git", [...args], { cwd, encoding: "utf8", windowsHide: true, maxBuffer: 64 * 1024 * 1024 });
561
+ if (result.error) return { status: -1, stdout: "" };
562
+ return { status: typeof result.status === "number" ? result.status : -1, stdout: String(result.stdout ?? "") };
563
+ };
564
+ }
565
+ function parseNameStatusZ(output) {
566
+ const fields = output.split("\0");
567
+ if (fields.length && fields[fields.length - 1] === "") fields.pop();
568
+ const changes = [];
569
+ for (let i = 0; i < fields.length; ) {
570
+ const raw = fields[i++] ?? "";
571
+ const letter = raw[0];
572
+ if (!letter || !/^[AMDTRC]\d*$/.test(raw)) return null;
573
+ if (letter === "R" || letter === "C") {
574
+ const oldPath = fields[i++];
575
+ const path2 = fields[i++];
576
+ if (!oldPath || !path2) return null;
577
+ changes.push(letter === "C" ? { status: "A", path: path2 } : { status: "R", path: path2, oldPath });
578
+ continue;
579
+ }
580
+ const path = fields[i++];
581
+ if (!path) return null;
582
+ changes.push({ status: letter, path });
583
+ }
584
+ return changes;
585
+ }
586
+ function changedPaths(changes) {
587
+ return [...new Set(changes.filter((change) => change.status !== "D").map((change) => change.path))].sort();
588
+ }
589
+ function removedPaths(changes) {
590
+ const removed = changes.flatMap((change) => change.status === "D" ? [change.path] : change.status === "R" && change.oldPath ? [change.oldPath] : []);
591
+ return [...new Set(removed)].sort();
592
+ }
593
+ function commitPresent(git2, commit) {
594
+ return git2(["cat-file", "-e", `${commit}^{commit}`]).status === 0;
595
+ }
596
+ function isAncestor(git2, base, head) {
597
+ return git2(["merge-base", "--is-ancestor", base, head]).status === 0;
598
+ }
599
+ function planRepoIndexV4Delta(opts) {
600
+ const headCommit = opts.headCommit.toLowerCase();
601
+ const baseCommit = opts.baseCommit ? opts.baseCommit.toLowerCase() : null;
602
+ const full = (fallbackReason) => ({ mode: "full", headCommit, fallbackReason, ...baseCommit ? { baseCommit } : {} });
603
+ if (opts.forceFull) return full("explicit-full-rebuild");
604
+ if (!baseCommit || !COMMIT.test(baseCommit) || baseCommit === headCommit) return full("no-active-authority");
605
+ if (opts.basePipelineCompatible === false) return full("incompatible-base-provenance");
606
+ if (opts.hasPriorMaterial === false) return full("no-prior-material");
607
+ const { git: git2 } = opts;
608
+ let usable = commitPresent(git2, baseCommit) && isAncestor(git2, baseCommit, headCommit);
609
+ for (const depth of V4_DELTA_DEEPEN_STEPS) {
610
+ if (usable || !opts.deepen) break;
611
+ try {
612
+ opts.deepen(depth);
613
+ } catch {
614
+ break;
615
+ }
616
+ usable = commitPresent(git2, baseCommit) && isAncestor(git2, baseCommit, headCommit);
617
+ }
618
+ if (!usable) {
619
+ return full(commitPresent(git2, baseCommit) ? "base-not-ancestor" : "base-commit-unavailable");
620
+ }
621
+ const diff = git2(["diff", "--name-status", "-z", "--find-renames", baseCommit, headCommit]);
622
+ if (diff.status !== 0) return full("diff-unreadable");
623
+ const changes = parseNameStatusZ(diff.stdout);
624
+ if (!changes) return full("unsupported-change-status");
625
+ return { mode: "delta", baseCommit, headCommit, changes };
626
+ }
627
+
628
+ // src/repo-index-v4/builder.ts
376
629
  var V4_MAX_CHUNKS = 1e4;
377
630
  var V4_MAX_ARTIFACT_BYTES = 64 * 1024 * 1024;
378
631
  var V4_EMBED_BATCH = 32;
379
632
  var V4_VECTOR_DECIMAL_PLACES = 6;
380
- var COMMIT = /^[a-f0-9]{40}$/;
633
+ var COMMIT2 = /^[a-f0-9]{40}$/;
381
634
  function canonicalJson(value) {
382
635
  if (value === null || typeof value !== "object") return JSON.stringify(value);
383
636
  if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
@@ -385,7 +638,7 @@ function canonicalJson(value) {
385
638
  return `{${Object.keys(record).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(record[k])}`).join(",")}}`;
386
639
  }
387
640
  function sha2562(value) {
388
- return (0, import_node_crypto3.createHash)("sha256").update(canonicalJson(value)).digest("hex");
641
+ return (0, import_node_crypto4.createHash)("sha256").update(canonicalJson(value)).digest("hex");
389
642
  }
390
643
  function quantizeV4Vector(vector) {
391
644
  return vector.map((value) => Number(value.toFixed(V4_VECTOR_DECIMAL_PLACES)));
@@ -394,11 +647,11 @@ function statePath(cwd) {
394
647
  return repoRuntimeStatePath(cwd, "repo-index", "v4.json");
395
648
  }
396
649
  function git(cwd, args) {
397
- return String((0, import_node_child_process3.execFileSync)("git", args, { cwd, encoding: "utf8", windowsHide: true, stdio: ["ignore", "pipe", "ignore"] })).trim();
650
+ return String((0, import_node_child_process4.execFileSync)("git", args, { cwd, encoding: "utf8", windowsHide: true, stdio: ["ignore", "pipe", "ignore"] })).trim();
398
651
  }
399
652
  function gitInfo(cwd) {
400
653
  const commit = git(cwd, ["rev-parse", "HEAD"]).toLowerCase();
401
- if (!COMMIT.test(commit)) throw new Error("repo-index v4 requires an exact git HEAD commit");
654
+ if (!COMMIT2.test(commit)) throw new Error("repo-index v4 requires an exact git HEAD commit");
402
655
  let defaultBranch = "main";
403
656
  try {
404
657
  defaultBranch = git(cwd, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]).replace(/^origin\//, "") || defaultBranch;
@@ -420,6 +673,9 @@ function prior(cwd) {
420
673
  return null;
421
674
  }
422
675
  }
676
+ function repoIndexV4DeltaBase(envelope) {
677
+ return envelope && isRepoIndexDeltaCompatible(envelope.manifest) ? envelope : null;
678
+ }
423
679
  function tombstone(repo, commit, path, createdAt) {
424
680
  const base = { repo, commit, path, reason: "deleted", createdAt };
425
681
  return { ...base, id: sha2562(base) };
@@ -454,7 +710,7 @@ function runEmbedderOnce(cwd, chunks, modelDirectory, createdAt) {
454
710
  (0, import_node_fs3.writeFileSync)(requestFile, JSON.stringify(request));
455
711
  let result;
456
712
  try {
457
- result = (0, import_node_child_process3.spawnSync)(process.execPath, [file, requestFile], { encoding: "utf8", windowsHide: true, timeout: V4_EMBED_TIMEOUT_MS, maxBuffer: V4_MAX_ARTIFACT_BYTES, env });
713
+ result = (0, import_node_child_process4.spawnSync)(process.execPath, [file, requestFile], { encoding: "utf8", windowsHide: true, timeout: V4_EMBED_TIMEOUT_MS, maxBuffer: V4_MAX_ARTIFACT_BYTES, env });
458
714
  } finally {
459
715
  (0, import_node_fs3.rmSync)(requestDir, { recursive: true, force: true });
460
716
  }
@@ -510,23 +766,53 @@ function runEmbedder(cwd, chunks, modelDirectory, createdAt) {
510
766
  const stderr = runner.stderrTail ? ` stderr=${runner.stderrTail}` : "";
511
767
  throw new Error(`repo-index v4 embedding runner failed: ${runner.detail ?? "unknown"}${code}${stdout}${stderr}`);
512
768
  }
769
+ function restampCitations(chunk, repo, commit) {
770
+ return { ...chunk, citations: chunk.citations.map((citation2) => ({ ...citation2, repo, commit })) };
771
+ }
513
772
  async function buildRepoIndexV4(cwd, repo, opts = {}) {
773
+ return (await buildRepoIndexV4Detailed(cwd, repo, opts)).envelope;
774
+ }
775
+ async function buildRepoIndexV4Detailed(cwd, repo, opts = {}) {
514
776
  const { commit, defaultBranch, createdAt } = gitInfo(cwd);
515
- const chunks = await buildStructuralChunks(cwd, repo, commit);
516
- if (chunks.length > V4_MAX_CHUNKS) throw new Error(`repo-index v4 exceeds ${V4_MAX_CHUNKS} chunk ceiling`);
777
+ const delta = opts.delta;
517
778
  const old = prior(cwd);
779
+ const deltaBase = repoIndexV4DeltaBase(old);
780
+ let chunks;
781
+ let carried = 0;
782
+ let built = 0;
783
+ let changedCount = 0;
784
+ let removedCount = 0;
785
+ if (delta) {
786
+ const admitted = new Set(canonicalRepoIndexPaths(cwd));
787
+ const changed = new Set(changedPaths(delta.changes));
788
+ const removed = new Set(removedPaths(delta.changes));
789
+ changedCount = changed.size;
790
+ removedCount = removed.size;
791
+ const rebuilt = await buildStructuralChunksForPaths(cwd, repo, commit, [...changed].filter((path2) => admitted.has(path2)).sort());
792
+ const kept = delta.prior.chunks.filter((chunk) => !changed.has(chunk.path) && !removed.has(chunk.path) && admitted.has(chunk.path)).map((chunk) => restampCitations(chunk, repo, commit));
793
+ carried = kept.length;
794
+ built = rebuilt.length;
795
+ chunks = [...kept, ...rebuilt].sort(compareV4Chunks);
796
+ } else {
797
+ chunks = await buildStructuralChunks(cwd, repo, commit);
798
+ built = chunks.length;
799
+ }
800
+ if (chunks.length > V4_MAX_CHUNKS) throw new Error(`repo-index v4 exceeds ${V4_MAX_CHUNKS} chunk ceiling`);
518
801
  const currentPaths = new Set(chunks.map((c) => c.path));
802
+ const priorChunks = delta ? delta.prior.chunks : old?.manifest.chunks ?? [];
803
+ const priorTombstones = delta ? delta.prior.tombstones ?? [] : old?.manifest.tombstones ?? [];
519
804
  const tombstonePaths = [
520
- ...(old?.manifest.tombstones ?? []).map((t) => t.path),
521
- ...(old?.manifest.chunks ?? []).map((c) => c.path).filter((path2) => !currentPaths.has(path2))
805
+ ...priorTombstones.map((t) => t.path),
806
+ ...priorChunks.map((c) => c.path).filter((path2) => !currentPaths.has(path2))
522
807
  ];
523
808
  const tombstones = [...new Set(tombstonePaths)].sort().map((path2) => tombstone(repo, commit, path2, createdAt));
524
- const oldChunkHashById = new Map((old?.manifest.chunks ?? []).map((c) => [c.id, c.contentHash]));
809
+ const embeddingSource = delta ? delta.prior : { chunks: deltaBase?.manifest.chunks ?? [], embeddings: deltaBase?.manifest.embeddings ?? [] };
810
+ const oldChunkHashById = new Map(embeddingSource.chunks.map((c) => [c.id, c.contentHash]));
525
811
  const reusableEmbedding = (embedding) => {
526
812
  const norm = Math.hypot(...embedding.vector);
527
813
  return embedding.vector.length === 384 && embedding.vector.every(Number.isFinite) && norm >= 0.98 && norm <= 1.02 && embedding.provenance.provider === "local" && embedding.provenance.modelDigest === "828e1496d7fabb79cfa4dcd84fa38625c0d3d21da474a00f08db0f559940cf35" && embedding.provenance.dimensions === 384 && embedding.provenance.input === "pointer-safe-structural-chunk";
528
814
  };
529
- const oldEmbeddingByHash = new Map((old?.manifest.embeddings ?? []).filter(reusableEmbedding).map((e) => [oldChunkHashById.get(e.chunkId), e]));
815
+ const oldEmbeddingByHash = new Map(embeddingSource.embeddings.filter(reusableEmbedding).map((e) => [oldChunkHashById.get(e.chunkId), e]));
530
816
  const reusable = chunks.flatMap((chunk) => {
531
817
  const priorEmbedding = oldEmbeddingByHash.get(chunk.contentHash);
532
818
  return priorEmbedding ? [{ ...priorEmbedding, chunkId: chunk.id }] : [];
@@ -534,21 +820,21 @@ async function buildRepoIndexV4(cwd, repo, opts = {}) {
534
820
  const reusableIds = new Set(reusable.map((e) => e.chunkId));
535
821
  const missing = chunks.filter((chunk) => !reusableIds.has(chunk.id));
536
822
  const generated = missing.length === 0 ? { embeddings: [] } : opts.embed === false ? { embeddings: [], reason: "embeddings-unavailable" } : runEmbedder(cwd, missing, opts.modelDirectory, createdAt);
537
- const embeddings = [...reusable, ...generated.embeddings].map((embedding) => ({ ...embedding, vector: quantizeV4Vector(embedding.vector) })).sort((a, b) => a.chunkId.localeCompare(b.chunkId));
538
- const chunksDigest = sha2562(chunks);
539
- const embeddingsDigest = sha2562(embeddings);
540
- const artifact = (kind, digest) => {
541
- const identity2 = { repo, commit, immutable: true, kind, uri: `s3://repo-index/v4/${repo}/${commit}/${digest}.${kind}.json`, sha256: digest, createdAt };
823
+ const embeddings = [...reusable, ...generated.embeddings].map((embedding) => ({ ...embedding, vector: quantizeV4Vector(embedding.vector) })).sort(compareRepoIndexEmbeddings);
824
+ const materialIndexDigest = buildRepoIndexMaterialLayout(repo, chunks, embeddings).indexDigest;
825
+ const artifact = (kind, digest, uri) => {
826
+ const identity2 = { repo, commit, immutable: true, kind, uri, sha256: digest, createdAt };
542
827
  return { ...identity2, id: sha2562(identity2) };
543
828
  };
544
- const artifacts = [artifact("chunks", chunksDigest), artifact("embeddings", embeddingsDigest)];
829
+ const artifacts = [artifact("material-index", materialIndexDigest, repoIndexMaterialIndexUri(repo, materialIndexDigest))];
545
830
  const grammarProvenance = {
546
831
  runtime: { name: "web-tree-sitter", version: "0.26.12", license: "MIT" },
547
832
  grammarPack: { name: "tree-sitter-wasm", version: "1.1.4", license: "MIT", digest: "f3089ddf2c9615a423783b645c4dfb23ccda30807bbd059746f583952357c489" },
548
833
  languages: ["go", "java", "javascript", "jsx", "kotlin", "python", "rust", "tsx", "typescript"]
549
834
  };
550
835
  const rrf = { algorithm: "reciprocal-rank-fusion", k: 60, lexicalWeight: 1, semanticWeight: 1 };
551
- const identity = { repo, commit, defaultBranch, immutable: true, createdAt, grammarProvenance, chunks, embeddings, artifacts, tombstones, rrf };
836
+ const indexProvenance = { ...CURRENT_REPO_INDEX_PROVENANCE };
837
+ const identity = { repo, commit, defaultBranch, immutable: true, ...delta ? { parentCommit: delta.baseCommit } : {}, createdAt, grammarProvenance, indexProvenance, chunks, embeddings, artifacts, tombstones, rrf };
552
838
  const manifest = { ...identity, id: sha2562(identity) };
553
839
  const complete = embeddings.length === chunks.length;
554
840
  const envelope = {
@@ -562,7 +848,23 @@ async function buildRepoIndexV4(cwd, repo, opts = {}) {
562
848
  (0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(path), { recursive: true });
563
849
  (0, import_node_fs3.writeFileSync)(path, `${JSON.stringify(envelope, null, 2)}
564
850
  `, "utf8");
565
- return envelope;
851
+ const metrics = {
852
+ mode: delta ? "delta" : "full",
853
+ ...delta ? { baseCommit: delta.baseCommit } : {},
854
+ changedPaths: changedCount,
855
+ removedPaths: removedCount,
856
+ chunksTotal: chunks.length,
857
+ chunksCarried: carried,
858
+ chunksBuilt: built,
859
+ embeddingsReused: reusable.length,
860
+ embeddingsEmbedded: generated.embeddings.length,
861
+ tombstones: tombstones.length
862
+ };
863
+ return { envelope, metrics };
864
+ }
865
+ function formatV4BuildMetrics(repo, metrics) {
866
+ const base = metrics.baseCommit ? ` base=${metrics.baseCommit.slice(0, 12)}` : "";
867
+ return `${repo}: ${metrics.mode}${base} changed=${metrics.changedPaths} removed=${metrics.removedPaths} chunks=${metrics.chunksTotal} (carried=${metrics.chunksCarried} built=${metrics.chunksBuilt}) embeddings(reused=${metrics.embeddingsReused} new=${metrics.embeddingsEmbedded}) tombstones=${metrics.tombstones}`;
566
868
  }
567
869
  function repoIndexV4StorePath(cwd) {
568
870
  return statePath(cwd);
@@ -575,12 +877,12 @@ var V4_STAGE_MAX_SHARDS = 128;
575
877
  function encodedBytes(value) {
576
878
  return Buffer.byteLength(JSON.stringify(value));
577
879
  }
578
- function split(kind, records, manifest) {
880
+ function split(kind, records, manifest, bucket) {
579
881
  const groups = [];
580
882
  let group = [];
581
883
  for (const record of records) {
582
884
  const next = [...group, record];
583
- const probe = { repo: manifest.repo, commit: manifest.commit, manifestId: manifest.id, kind, index: 127, total: 128, digest: "0".repeat(64), records: next };
885
+ const probe = { repo: manifest.repo, commit: manifest.commit, manifestId: manifest.id, kind, ...bucket === void 0 ? {} : { bucket }, index: 127, total: 128, digest: "0".repeat(64), records: next };
584
886
  if (group.length && (group.length >= V4_STAGE_MAX_RECORDS || encodedBytes(probe) > V4_STAGE_MAX_BODY_BYTES)) {
585
887
  groups.push(group);
586
888
  group = [record];
@@ -596,6 +898,7 @@ function split(kind, records, manifest) {
596
898
  commit: manifest.commit,
597
899
  manifestId: manifest.id,
598
900
  kind,
901
+ ...bucket === void 0 ? {} : { bucket },
599
902
  index,
600
903
  total: groups.length,
601
904
  digest: sha2562(items),
@@ -605,11 +908,27 @@ function split(kind, records, manifest) {
605
908
  return request;
606
909
  });
607
910
  }
608
- function shardRepoIndexV4(envelope) {
911
+ function shardRepoIndexV4(envelope, opts = {}) {
609
912
  const { chunks, embeddings, ...header } = envelope.manifest;
610
- const stages = [...split("chunks", chunks, envelope.manifest), ...split("embeddings", embeddings ?? [], envelope.manifest)];
611
- const shards = stages.map(({ kind, index, total, digest, records }) => ({ kind, index, total, digest, count: records.length }));
612
- return { stages, finalize: { repo: header.repo, commit: header.commit, manifestId: header.id, header, status: envelope.status, shards } };
913
+ const identity = { repo: header.repo, commit: header.commit, manifestId: header.id, header, status: envelope.status };
914
+ if ((header.indexProvenance?.materialLayoutVersion ?? 1) < 2) {
915
+ const stages2 = [...split("chunks", chunks, envelope.manifest), ...split("embeddings", embeddings ?? [], envelope.manifest)];
916
+ const shards2 = stages2.map(({ kind, index, total, digest, records }) => ({ kind, index, total, digest, count: records.length }));
917
+ return { stages: stages2, finalize: { ...identity, shards: shards2 } };
918
+ }
919
+ const reusable = new Set(opts.priorBucketDigests ?? []);
920
+ const layout = buildRepoIndexMaterialLayout(header.repo, chunks, embeddings ?? []);
921
+ const stages = [];
922
+ const shards = [];
923
+ const buckets = layout.buckets.map((bucket) => {
924
+ if (reusable.has(bucket.digest)) return { kind: bucket.kind, bucket: bucket.bucket, count: bucket.count, digest: bucket.digest, shards: 0 };
925
+ const fragments = split(bucket.kind, bucket.body.records, envelope.manifest, bucket.bucket);
926
+ stages.push(...fragments);
927
+ shards.push(...fragments.map(({ kind, index, total, digest, records }) => ({ kind, bucket: bucket.bucket, index, total, digest, count: records.length })));
928
+ return { kind: bucket.kind, bucket: bucket.bucket, count: bucket.count, digest: bucket.digest, shards: fragments.length };
929
+ });
930
+ if (buckets.length !== 2 * REPO_INDEX_MATERIAL_BUCKET_COUNT) throw new Error("repo-index v4 material layout is incomplete");
931
+ return { stages, finalize: { ...identity, shards, materialLayout: 2, buckets } };
613
932
  }
614
933
  function encodedStageBytes(stage) {
615
934
  return encodedBytes(stage);
@@ -718,6 +1037,7 @@ function buildGraphEdges(cwd, repo, commit, rosterRepos) {
718
1037
  // Annotate the CommonJS export names for ESM import in node:
719
1038
  0 && (module.exports = {
720
1039
  REPO_INDEX_V4_SCHEMA,
1040
+ V4_DELTA_DEEPEN_STEPS,
721
1041
  V4_GRAPH_MAX_EDGES,
722
1042
  V4_GRAPH_MAX_EDGES_PER_FILE,
723
1043
  V4_MAX_ARTIFACT_BYTES,
@@ -726,10 +1046,23 @@ function buildGraphEdges(cwd, repo, commit, rosterRepos) {
726
1046
  V4_STAGE_MAX_RECORDS,
727
1047
  buildGraphEdges,
728
1048
  buildRepoIndexV4,
1049
+ buildRepoIndexV4Detailed,
729
1050
  buildStructuralChunks,
1051
+ buildStructuralChunksForPaths,
1052
+ canonicalRepoIndexPaths,
1053
+ changedPaths,
1054
+ compareV4Chunks,
730
1055
  encodedStageBytes,
1056
+ formatV4BuildMetrics,
1057
+ gitRunner,
731
1058
  graphEdgesForSource,
1059
+ isRepoIndexDeltaCompatible,
732
1060
  languageForPath,
1061
+ parseNameStatusZ,
1062
+ planRepoIndexV4Delta,
1063
+ readRepoIndexProvenance,
1064
+ removedPaths,
1065
+ repoIndexV4DeltaBase,
733
1066
  repoIndexV4StorePath,
734
1067
  shardRepoIndexV4,
735
1068
  structuralChunksForSource
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.0.17",
3
+ "version": "4.1.0",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",