@adhisang/minecraft-modding-mcp 2.1.0 → 3.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 (32) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/README.md +224 -802
  3. package/dist/cache-registry.d.ts +95 -0
  4. package/dist/cache-registry.js +541 -0
  5. package/dist/entry-tools/analyze-mod-service.d.ts +207 -0
  6. package/dist/entry-tools/analyze-mod-service.js +309 -0
  7. package/dist/entry-tools/analyze-symbol-service.d.ts +209 -0
  8. package/dist/entry-tools/analyze-symbol-service.js +359 -0
  9. package/dist/entry-tools/compare-minecraft-service.d.ts +210 -0
  10. package/dist/entry-tools/compare-minecraft-service.js +429 -0
  11. package/dist/entry-tools/entry-tool-schema.d.ts +6 -0
  12. package/dist/entry-tools/entry-tool-schema.js +10 -0
  13. package/dist/entry-tools/inspect-minecraft-service.d.ts +1954 -0
  14. package/dist/entry-tools/inspect-minecraft-service.js +1030 -0
  15. package/dist/entry-tools/manage-cache-service.d.ts +130 -0
  16. package/dist/entry-tools/manage-cache-service.js +264 -0
  17. package/dist/entry-tools/request-normalizers.d.ts +10 -0
  18. package/dist/entry-tools/request-normalizers.js +36 -0
  19. package/dist/entry-tools/response-contract.d.ts +45 -0
  20. package/dist/entry-tools/response-contract.js +99 -0
  21. package/dist/entry-tools/validate-project-service.d.ts +543 -0
  22. package/dist/entry-tools/validate-project-service.js +414 -0
  23. package/dist/index.js +183 -59
  24. package/dist/observability.d.ts +18 -2
  25. package/dist/observability.js +47 -10
  26. package/dist/source-service.d.ts +0 -1
  27. package/dist/source-service.js +44 -54
  28. package/dist/storage/files-repo.d.ts +1 -0
  29. package/dist/storage/files-repo.js +29 -5
  30. package/dist/tool-contract-manifest.d.ts +4 -0
  31. package/dist/tool-contract-manifest.js +139 -0
  32. package/package.json +1 -1
@@ -0,0 +1,95 @@
1
+ import { type PathRuntimeInfo } from "./path-converter.js";
2
+ export declare const PUBLIC_CACHE_KINDS: readonly ["artifact-index", "downloads", "mapping", "registry", "decompiled-source", "mod-remap"];
3
+ export type PublicCacheKind = (typeof PUBLIC_CACHE_KINDS)[number];
4
+ export declare const CACHE_HEALTH_STATES: readonly ["healthy", "partial", "stale", "orphaned", "corrupt", "in_use"];
5
+ export type CacheHealthState = (typeof CACHE_HEALTH_STATES)[number];
6
+ export type CacheSelector = {
7
+ artifactId?: string;
8
+ version?: string;
9
+ jarPath?: string;
10
+ entryId?: string;
11
+ status?: CacheHealthState;
12
+ olderThan?: string;
13
+ mapping?: string;
14
+ scope?: string;
15
+ projectPath?: string;
16
+ };
17
+ export type CacheKindSummary = {
18
+ cacheKind: PublicCacheKind;
19
+ entryCount: number;
20
+ totalBytes: number;
21
+ status: CacheHealthState;
22
+ };
23
+ export type CacheEntry = {
24
+ cacheKind: PublicCacheKind;
25
+ entryId: string;
26
+ path: string;
27
+ sizeBytes: number;
28
+ status: CacheHealthState;
29
+ owner?: string;
30
+ meta?: Record<string, unknown>;
31
+ };
32
+ type CacheEntryPage = {
33
+ entries: CacheEntry[];
34
+ nextCursor?: string;
35
+ };
36
+ export type CacheRegistryConfig = {
37
+ cacheDir: string;
38
+ sqlitePath: string;
39
+ pathRuntimeInfo?: PathRuntimeInfo;
40
+ };
41
+ export interface CacheRegistry {
42
+ summarize(input: {
43
+ cacheKinds?: PublicCacheKind[];
44
+ selector?: CacheSelector;
45
+ }): Promise<{
46
+ kinds: Partial<Record<PublicCacheKind, CacheKindSummary>>;
47
+ }>;
48
+ listEntries(input: {
49
+ cacheKinds?: PublicCacheKind[];
50
+ selector?: CacheSelector;
51
+ limit?: number;
52
+ cursor?: string;
53
+ }): Promise<CacheEntryPage>;
54
+ inspectEntries(input: {
55
+ cacheKinds?: PublicCacheKind[];
56
+ selector?: CacheSelector;
57
+ limit?: number;
58
+ }): Promise<CacheEntry[]>;
59
+ verifyEntries(input: {
60
+ cacheKinds?: PublicCacheKind[];
61
+ selector?: CacheSelector;
62
+ }): Promise<{
63
+ checkedEntries: number;
64
+ unhealthyEntries: number;
65
+ warnings: string[];
66
+ }>;
67
+ deleteEntries(input: {
68
+ cacheKinds?: PublicCacheKind[];
69
+ selector?: CacheSelector;
70
+ executionMode: "preview" | "apply";
71
+ }): Promise<{
72
+ deletedEntries: number;
73
+ deletedBytes: number;
74
+ warnings: string[];
75
+ }>;
76
+ pruneEntries(input: {
77
+ cacheKinds?: PublicCacheKind[];
78
+ selector?: CacheSelector;
79
+ executionMode: "preview" | "apply";
80
+ }): Promise<{
81
+ deletedEntries: number;
82
+ deletedBytes: number;
83
+ warnings: string[];
84
+ }>;
85
+ rebuildEntries(input: {
86
+ cacheKinds?: PublicCacheKind[];
87
+ selector?: CacheSelector;
88
+ executionMode: "preview" | "apply";
89
+ }): Promise<{
90
+ rebuiltEntries: number;
91
+ warnings: string[];
92
+ }>;
93
+ }
94
+ export declare function createCacheRegistry(config: CacheRegistryConfig): CacheRegistry;
95
+ export {};
@@ -0,0 +1,541 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { readdir, rm, stat } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ import { createError, ERROR_CODES } from "./errors.js";
5
+ import { normalizeOptionalPathForHost } from "./path-converter.js";
6
+ import Database from "./storage/sqlite.js";
7
+ export const PUBLIC_CACHE_KINDS = [
8
+ "artifact-index",
9
+ "downloads",
10
+ "mapping",
11
+ "registry",
12
+ "decompiled-source",
13
+ "mod-remap"
14
+ ];
15
+ export const CACHE_HEALTH_STATES = [
16
+ "healthy",
17
+ "partial",
18
+ "stale",
19
+ "orphaned",
20
+ "corrupt",
21
+ "in_use"
22
+ ];
23
+ const STALE_ENTRY_AGE_MS = 30 * 24 * 60 * 60 * 1000;
24
+ const CURSOR_VERSION = 1;
25
+ const STATUS_PRIORITY = ["in_use", "corrupt", "orphaned", "stale", "partial", "healthy"];
26
+ function kindRoot(config, cacheKind) {
27
+ switch (cacheKind) {
28
+ case "artifact-index":
29
+ return resolve(config.sqlitePath);
30
+ case "downloads":
31
+ return join(config.cacheDir, "downloads");
32
+ case "mapping":
33
+ return join(config.cacheDir, "mappings");
34
+ case "registry":
35
+ return join(config.cacheDir, "registries");
36
+ case "decompiled-source":
37
+ return join(config.cacheDir, "decompiled");
38
+ case "mod-remap":
39
+ return join(config.cacheDir, "remapped-mods");
40
+ }
41
+ }
42
+ async function listFilesRecursive(root) {
43
+ if (!existsSync(root)) {
44
+ return [];
45
+ }
46
+ const output = [];
47
+ const pending = [root];
48
+ while (pending.length > 0) {
49
+ const current = pending.pop();
50
+ const entries = await readdir(current, { withFileTypes: true });
51
+ for (const entry of entries) {
52
+ const fullPath = join(current, entry.name);
53
+ if (entry.isDirectory()) {
54
+ pending.push(fullPath);
55
+ continue;
56
+ }
57
+ if (entry.isFile()) {
58
+ output.push(fullPath);
59
+ }
60
+ }
61
+ }
62
+ return output.sort((left, right) => left.localeCompare(right));
63
+ }
64
+ function normalizePathKey(pathValue, runtimeInfo) {
65
+ const normalized = normalizeOptionalPathForHost(pathValue, runtimeInfo, "jarPath");
66
+ if (!normalized) {
67
+ return undefined;
68
+ }
69
+ return normalized.replace(/\\/g, "/").replace(/\/+$/, "");
70
+ }
71
+ function parseStringArray(value) {
72
+ if (!value) {
73
+ return [];
74
+ }
75
+ try {
76
+ const parsed = JSON.parse(value);
77
+ return Array.isArray(parsed) ? parsed.filter((entry) => typeof entry === "string") : [];
78
+ }
79
+ catch {
80
+ return [];
81
+ }
82
+ }
83
+ function inferVersion(...candidates) {
84
+ for (const candidate of candidates) {
85
+ if (!candidate) {
86
+ continue;
87
+ }
88
+ const match = candidate.match(/\b(\d+\.\d+(?:\.\d+)?(?:-[A-Za-z0-9.]+)?)\b/);
89
+ if (match?.[1]) {
90
+ return match[1];
91
+ }
92
+ }
93
+ return undefined;
94
+ }
95
+ function inferMapping(...candidates) {
96
+ for (const candidate of candidates) {
97
+ const normalized = candidate?.toLowerCase();
98
+ if (!normalized) {
99
+ continue;
100
+ }
101
+ if (normalized.includes("intermediary")) {
102
+ return "intermediary";
103
+ }
104
+ if (normalized.includes("mojang")) {
105
+ return "mojang";
106
+ }
107
+ if (normalized.includes("yarn")) {
108
+ return "yarn";
109
+ }
110
+ if (normalized.includes("obfuscated")) {
111
+ return "obfuscated";
112
+ }
113
+ }
114
+ return undefined;
115
+ }
116
+ function inferScope(...candidates) {
117
+ for (const candidate of candidates) {
118
+ const normalized = candidate?.toLowerCase();
119
+ if (!normalized) {
120
+ continue;
121
+ }
122
+ if (normalized.includes("loader")) {
123
+ return "loader";
124
+ }
125
+ if (normalized.includes("merged")) {
126
+ return "merged";
127
+ }
128
+ if (normalized.includes("vanilla")) {
129
+ return "vanilla";
130
+ }
131
+ }
132
+ return undefined;
133
+ }
134
+ function inferProjectPath(pathValue, runtimeInfo) {
135
+ const normalized = normalizePathKey(pathValue, runtimeInfo);
136
+ if (!normalized) {
137
+ return undefined;
138
+ }
139
+ for (const marker of ["/.gradle/", "/build/", "/src/"]) {
140
+ const index = normalized.indexOf(marker);
141
+ if (index > 0) {
142
+ return normalized.slice(0, index);
143
+ }
144
+ }
145
+ return undefined;
146
+ }
147
+ function isCorruptRegistryJson(filePath) {
148
+ if (!filePath.endsWith(".json")) {
149
+ return false;
150
+ }
151
+ try {
152
+ JSON.parse(readFileSync(filePath, "utf8"));
153
+ return false;
154
+ }
155
+ catch {
156
+ return true;
157
+ }
158
+ }
159
+ function parseOlderThan(value) {
160
+ if (!value) {
161
+ return undefined;
162
+ }
163
+ const trimmed = value.trim();
164
+ if (!trimmed) {
165
+ return undefined;
166
+ }
167
+ const match = trimmed.match(/^P(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/);
168
+ if (!match) {
169
+ throw createError({
170
+ code: ERROR_CODES.INVALID_INPUT,
171
+ message: `olderThan must be an ISO-8601 duration like "P30D" or "PT12H".`
172
+ });
173
+ }
174
+ const weeks = Number(match[1] ?? 0);
175
+ const days = Number(match[2] ?? 0);
176
+ const hours = Number(match[3] ?? 0);
177
+ const minutes = Number(match[4] ?? 0);
178
+ const seconds = Number(match[5] ?? 0);
179
+ const totalMs = (((weeks * 7 + days) * 24 + hours) * 60 * 60 + minutes * 60 + seconds) * 1000;
180
+ if (totalMs <= 0) {
181
+ throw createError({
182
+ code: ERROR_CODES.INVALID_INPUT,
183
+ message: "olderThan must be greater than zero."
184
+ });
185
+ }
186
+ return totalMs;
187
+ }
188
+ function prepareSelector(selector, runtimeInfo) {
189
+ if (!selector) {
190
+ return undefined;
191
+ }
192
+ return {
193
+ ...selector,
194
+ olderThanMs: parseOlderThan(selector.olderThan),
195
+ normalizedJarPath: normalizePathKey(selector.jarPath, runtimeInfo),
196
+ normalizedProjectPath: normalizePathKey(selector.projectPath, runtimeInfo)
197
+ };
198
+ }
199
+ function openDb(sqlitePath) {
200
+ if (!existsSync(sqlitePath)) {
201
+ return undefined;
202
+ }
203
+ return new Database(sqlitePath);
204
+ }
205
+ function candidatePathsForEntry(entry) {
206
+ const paths = new Set();
207
+ const maybeMeta = entry.meta ?? {};
208
+ for (const candidate of [
209
+ entry.path,
210
+ typeof maybeMeta.jarPath === "string" ? maybeMeta.jarPath : undefined,
211
+ typeof maybeMeta.binaryJarPath === "string" ? maybeMeta.binaryJarPath : undefined,
212
+ typeof maybeMeta.sourceJarPath === "string" ? maybeMeta.sourceJarPath : undefined,
213
+ typeof maybeMeta.projectPath === "string" ? maybeMeta.projectPath : undefined
214
+ ]) {
215
+ if (candidate) {
216
+ paths.add(candidate);
217
+ }
218
+ }
219
+ return [...paths];
220
+ }
221
+ function entryUpdatedAt(entry) {
222
+ return typeof entry.meta?.updatedAt === "string" ? entry.meta.updatedAt : undefined;
223
+ }
224
+ function deriveEntryStatus(entry, config, now) {
225
+ const maybeMeta = entry.meta ?? {};
226
+ if (maybeMeta.inUse === true) {
227
+ return "in_use";
228
+ }
229
+ if (maybeMeta.corrupt === true) {
230
+ return "corrupt";
231
+ }
232
+ const candidatePaths = candidatePathsForEntry(entry);
233
+ const existingPaths = candidatePaths.filter((candidate) => existsSync(candidate));
234
+ if (entry.cacheKind === "artifact-index" && !existsSync(config.sqlitePath)) {
235
+ return "orphaned";
236
+ }
237
+ if (candidatePaths.length > 0 && existingPaths.length === 0) {
238
+ return "orphaned";
239
+ }
240
+ if (candidatePaths.length > 1 && existingPaths.length > 0 && existingPaths.length < candidatePaths.length) {
241
+ return "partial";
242
+ }
243
+ if (maybeMeta.partial === true) {
244
+ return "partial";
245
+ }
246
+ const updatedAt = entryUpdatedAt(entry);
247
+ if (updatedAt) {
248
+ const updatedAtMs = Date.parse(updatedAt);
249
+ if (Number.isFinite(updatedAtMs) && now - updatedAtMs >= STALE_ENTRY_AGE_MS) {
250
+ return "stale";
251
+ }
252
+ }
253
+ return "healthy";
254
+ }
255
+ function sortEntries(entries) {
256
+ return [...entries].sort((left, right) => {
257
+ if (left.cacheKind !== right.cacheKind) {
258
+ return left.cacheKind.localeCompare(right.cacheKind);
259
+ }
260
+ return left.entryId.localeCompare(right.entryId);
261
+ });
262
+ }
263
+ function entrySortKey(entry) {
264
+ return `${entry.cacheKind}\u0000${entry.entryId}`;
265
+ }
266
+ function encodeCursor(entry) {
267
+ return Buffer.from(JSON.stringify({ version: CURSOR_VERSION, key: entrySortKey(entry) }), "utf8").toString("base64");
268
+ }
269
+ function decodeCursor(cursor) {
270
+ if (!cursor) {
271
+ return undefined;
272
+ }
273
+ try {
274
+ const decoded = JSON.parse(Buffer.from(cursor, "base64").toString("utf8"));
275
+ if (decoded.version !== CURSOR_VERSION || typeof decoded.key !== "string" || !decoded.key) {
276
+ throw new Error("invalid");
277
+ }
278
+ return decoded.key;
279
+ }
280
+ catch {
281
+ throw createError({
282
+ code: ERROR_CODES.INVALID_INPUT,
283
+ message: "Invalid pagination cursor."
284
+ });
285
+ }
286
+ }
287
+ function paginateEntries(entries, limit, cursor) {
288
+ const cursorKey = decodeCursor(cursor);
289
+ const pageSource = cursorKey
290
+ ? entries.filter((entry) => entrySortKey(entry) > cursorKey)
291
+ : entries;
292
+ const pageEntries = pageSource.slice(0, limit);
293
+ return {
294
+ entries: pageEntries,
295
+ nextCursor: pageSource.length > limit && pageEntries.length > 0 ? encodeCursor(pageEntries[pageEntries.length - 1]) : undefined
296
+ };
297
+ }
298
+ function rollupStatus(entries, rootExists) {
299
+ if (!rootExists || entries.length === 0) {
300
+ return "partial";
301
+ }
302
+ for (const status of STATUS_PRIORITY) {
303
+ if (entries.some((entry) => entry.status === status)) {
304
+ return status;
305
+ }
306
+ }
307
+ return "healthy";
308
+ }
309
+ function matchesSelector(entry, selector, runtimeInfo) {
310
+ if (!selector) {
311
+ return true;
312
+ }
313
+ const maybeMeta = entry.meta ?? {};
314
+ if (selector.entryId && selector.entryId !== entry.entryId) {
315
+ return false;
316
+ }
317
+ if (selector.artifactId && maybeMeta.artifactId !== selector.artifactId) {
318
+ return false;
319
+ }
320
+ if (selector.status && selector.status !== entry.status) {
321
+ return false;
322
+ }
323
+ if (selector.version) {
324
+ const version = typeof maybeMeta.version === "string" ? maybeMeta.version : undefined;
325
+ if (version !== selector.version && !entry.path.includes(selector.version)) {
326
+ return false;
327
+ }
328
+ }
329
+ if (selector.mapping) {
330
+ const mappings = new Set([maybeMeta.mapping, maybeMeta.requestedMapping, maybeMeta.mappingApplied]
331
+ .filter((value) => typeof value === "string"));
332
+ if (!mappings.has(selector.mapping)) {
333
+ return false;
334
+ }
335
+ }
336
+ if (selector.scope) {
337
+ const scope = typeof maybeMeta.scope === "string" ? maybeMeta.scope : undefined;
338
+ if (scope !== selector.scope) {
339
+ return false;
340
+ }
341
+ }
342
+ if (selector.olderThanMs != null) {
343
+ const updatedAt = entryUpdatedAt(entry);
344
+ const updatedAtMs = updatedAt ? Date.parse(updatedAt) : Number.NaN;
345
+ if (!Number.isFinite(updatedAtMs) || Date.now() - updatedAtMs < selector.olderThanMs) {
346
+ return false;
347
+ }
348
+ }
349
+ const normalizedPaths = candidatePathsForEntry(entry)
350
+ .map((candidate) => normalizePathKey(candidate, runtimeInfo))
351
+ .filter((candidate) => Boolean(candidate));
352
+ if (selector.normalizedJarPath && !normalizedPaths.includes(selector.normalizedJarPath)) {
353
+ return false;
354
+ }
355
+ if (selector.normalizedProjectPath) {
356
+ const projectMatch = normalizedPaths.some((candidate) => candidate === selector.normalizedProjectPath || candidate.startsWith(`${selector.normalizedProjectPath}/`));
357
+ if (!projectMatch) {
358
+ return false;
359
+ }
360
+ }
361
+ return true;
362
+ }
363
+ async function artifactIndexEntries(config) {
364
+ const db = openDb(config.sqlitePath);
365
+ if (!db) {
366
+ return [];
367
+ }
368
+ try {
369
+ const rows = db.prepare(`
370
+ SELECT
371
+ artifacts.artifact_id,
372
+ artifacts.updated_at,
373
+ COALESCE(artifact_content_bytes.total_content_bytes, 0) AS total_content_bytes,
374
+ artifacts.version,
375
+ artifacts.binary_jar_path,
376
+ artifacts.source_jar_path,
377
+ artifacts.requested_mapping,
378
+ artifacts.mapping_applied,
379
+ artifacts.quality_flags_json
380
+ FROM artifacts
381
+ LEFT JOIN artifact_content_bytes
382
+ ON artifact_content_bytes.artifact_id = artifacts.artifact_id
383
+ ORDER BY artifacts.updated_at DESC
384
+ `).all();
385
+ const dbInUse = existsSync(`${config.sqlitePath}-wal`) || existsSync(`${config.sqlitePath}-journal`);
386
+ return rows.map((row) => {
387
+ const qualityFlags = parseStringArray(row.quality_flags_json);
388
+ const binaryJarPath = row.binary_jar_path ?? undefined;
389
+ const sourceJarPath = row.source_jar_path ?? undefined;
390
+ return {
391
+ cacheKind: "artifact-index",
392
+ entryId: row.artifact_id,
393
+ path: binaryJarPath ?? sourceJarPath ?? config.sqlitePath,
394
+ sizeBytes: Math.max(0, row.total_content_bytes),
395
+ status: "healthy",
396
+ meta: {
397
+ artifactId: row.artifact_id,
398
+ updatedAt: row.updated_at,
399
+ version: row.version ?? inferVersion(binaryJarPath, sourceJarPath),
400
+ requestedMapping: row.requested_mapping ?? undefined,
401
+ mappingApplied: row.mapping_applied ?? undefined,
402
+ mapping: row.mapping_applied ?? row.requested_mapping ?? inferMapping(binaryJarPath, sourceJarPath, ...qualityFlags),
403
+ binaryJarPath,
404
+ sourceJarPath,
405
+ projectPath: inferProjectPath(binaryJarPath ?? sourceJarPath, config.pathRuntimeInfo),
406
+ scope: inferScope(binaryJarPath, sourceJarPath, ...qualityFlags) ?? "vanilla",
407
+ partial: qualityFlags.some((flag) => flag.includes("partial")),
408
+ inUse: dbInUse
409
+ }
410
+ };
411
+ });
412
+ }
413
+ finally {
414
+ db.close();
415
+ }
416
+ }
417
+ async function fileBackedEntries(config, cacheKind) {
418
+ const root = kindRoot(config, cacheKind);
419
+ const files = await listFilesRecursive(root);
420
+ const entries = [];
421
+ for (const filePath of files) {
422
+ const fileStat = await stat(filePath);
423
+ const normalizedEntryId = filePath.slice(root.length + 1);
424
+ const inferredScope = inferScope(filePath, normalizedEntryId) ?? (cacheKind === "decompiled-source" ? "vanilla" : undefined);
425
+ entries.push({
426
+ cacheKind,
427
+ entryId: normalizedEntryId,
428
+ path: filePath,
429
+ sizeBytes: fileStat.size,
430
+ status: "healthy",
431
+ meta: {
432
+ updatedAt: fileStat.mtime.toISOString(),
433
+ version: inferVersion(filePath, normalizedEntryId),
434
+ mapping: inferMapping(filePath, normalizedEntryId),
435
+ scope: inferredScope,
436
+ projectPath: inferProjectPath(filePath, config.pathRuntimeInfo),
437
+ partial: fileStat.size === 0,
438
+ corrupt: cacheKind === "registry" ? isCorruptRegistryJson(filePath) : false,
439
+ inUse: filePath.endsWith(".lock") ||
440
+ filePath.endsWith(".wal") ||
441
+ filePath.endsWith(".journal"),
442
+ ...(cacheKind === "downloads" || cacheKind === "mod-remap" ? { jarPath: filePath } : {})
443
+ }
444
+ });
445
+ }
446
+ return entries;
447
+ }
448
+ export function createCacheRegistry(config) {
449
+ async function collectEntries(cacheKinds, selector) {
450
+ const selectedKinds = cacheKinds?.length ? cacheKinds : [...PUBLIC_CACHE_KINDS];
451
+ const preparedSelector = prepareSelector(selector, config.pathRuntimeInfo);
452
+ const now = Date.now();
453
+ const entries = await Promise.all(selectedKinds.map((cacheKind) => cacheKind === "artifact-index"
454
+ ? artifactIndexEntries(config)
455
+ : fileBackedEntries(config, cacheKind)));
456
+ const enriched = entries
457
+ .flat()
458
+ .map((entry) => ({
459
+ ...entry,
460
+ status: deriveEntryStatus(entry, config, now)
461
+ }));
462
+ return sortEntries(enriched.filter((entry) => matchesSelector(entry, preparedSelector, config.pathRuntimeInfo)));
463
+ }
464
+ return {
465
+ async summarize(input) {
466
+ const selectedKinds = input.cacheKinds?.length ? input.cacheKinds : [...PUBLIC_CACHE_KINDS];
467
+ const entries = await collectEntries(selectedKinds, input.selector);
468
+ const kinds = {};
469
+ for (const cacheKind of selectedKinds) {
470
+ const root = kindRoot(config, cacheKind);
471
+ const rows = entries.filter((entry) => entry.cacheKind === cacheKind);
472
+ kinds[cacheKind] = {
473
+ cacheKind,
474
+ entryCount: rows.length,
475
+ totalBytes: rows.reduce((total, entry) => total + entry.sizeBytes, 0),
476
+ status: rollupStatus(rows, existsSync(root))
477
+ };
478
+ }
479
+ return { kinds };
480
+ },
481
+ async listEntries(input) {
482
+ const entries = await collectEntries(input.cacheKinds, input.selector);
483
+ const limit = Math.max(1, input.limit ?? 50);
484
+ return paginateEntries(entries, limit, input.cursor);
485
+ },
486
+ async inspectEntries(input) {
487
+ const entries = await collectEntries(input.cacheKinds, input.selector);
488
+ const limit = Math.max(1, input.limit ?? 50);
489
+ return entries.slice(0, limit);
490
+ },
491
+ async verifyEntries(input) {
492
+ const entries = await collectEntries(input.cacheKinds, input.selector);
493
+ const unhealthy = entries.filter((entry) => entry.status !== "healthy");
494
+ const warningStatuses = [...new Set(unhealthy.map((entry) => entry.status))];
495
+ return {
496
+ checkedEntries: entries.length,
497
+ unhealthyEntries: unhealthy.length,
498
+ warnings: warningStatuses.length > 0
499
+ ? [`Detected cache entries with health states: ${warningStatuses.join(", ")}.`]
500
+ : []
501
+ };
502
+ },
503
+ async deleteEntries(input) {
504
+ const entries = await collectEntries(input.cacheKinds, input.selector);
505
+ const selectedBytes = entries.reduce((total, entry) => total + entry.sizeBytes, 0);
506
+ if (input.executionMode === "apply") {
507
+ const db = openDb(config.sqlitePath);
508
+ try {
509
+ for (const entry of entries) {
510
+ if (entry.cacheKind === "artifact-index") {
511
+ db?.prepare("DELETE FROM artifacts WHERE artifact_id = ?").run([entry.entryId]);
512
+ continue;
513
+ }
514
+ if (existsSync(entry.path)) {
515
+ await rm(entry.path, { force: true });
516
+ }
517
+ }
518
+ }
519
+ finally {
520
+ db?.close();
521
+ }
522
+ }
523
+ return {
524
+ deletedEntries: entries.length,
525
+ deletedBytes: selectedBytes,
526
+ warnings: []
527
+ };
528
+ },
529
+ async pruneEntries(input) {
530
+ return this.deleteEntries(input);
531
+ },
532
+ async rebuildEntries(input) {
533
+ const entries = await collectEntries(input.cacheKinds, input.selector);
534
+ return {
535
+ rebuiltEntries: entries.length,
536
+ warnings: []
537
+ };
538
+ }
539
+ };
540
+ }
541
+ //# sourceMappingURL=cache-registry.js.map