@adhisang/minecraft-modding-mcp 7.0.0 → 7.1.1

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 (95) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +4 -3
  3. package/dist/access-transformer-parser.d.ts +8 -0
  4. package/dist/access-transformer-parser.js +8 -1
  5. package/dist/access-widener-parser.d.ts +17 -0
  6. package/dist/access-widener-parser.js +12 -1
  7. package/dist/cache-registry.js +19 -6
  8. package/dist/entry-tools/analyze-mod-service.d.ts +2 -2
  9. package/dist/entry-tools/analyze-symbol-service.d.ts +2 -2
  10. package/dist/entry-tools/batch-class-members-service.d.ts +3 -2
  11. package/dist/entry-tools/batch-class-members-service.js +20 -6
  12. package/dist/entry-tools/batch-class-source-service.d.ts +3 -2
  13. package/dist/entry-tools/batch-class-source-service.js +10 -0
  14. package/dist/entry-tools/compare-minecraft-service.d.ts +27 -4
  15. package/dist/entry-tools/compare-minecraft-service.js +65 -4
  16. package/dist/entry-tools/entry-tool-schema.d.ts +2 -2
  17. package/dist/entry-tools/inspect-minecraft/handlers/versions.js +15 -9
  18. package/dist/entry-tools/inspect-minecraft-service.d.ts +2 -2
  19. package/dist/entry-tools/manage-cache-service.d.ts +2 -2
  20. package/dist/entry-tools/manage-cache-service.js +10 -14
  21. package/dist/entry-tools/validate-project/cases/access-transformer.js +22 -3
  22. package/dist/entry-tools/validate-project/cases/access-widener.js +31 -4
  23. package/dist/entry-tools/validate-project/cases/mixin.js +11 -3
  24. package/dist/entry-tools/validate-project/cases/project-summary.js +94 -16
  25. package/dist/entry-tools/validate-project-service.d.ts +2 -2
  26. package/dist/entry-tools/verify-mixin-target-service.js +19 -8
  27. package/dist/index.js +38 -15
  28. package/dist/java-process.d.ts +1 -0
  29. package/dist/java-process.js +14 -0
  30. package/dist/mapping/lookup.js +16 -1
  31. package/dist/mapping-service.d.ts +14 -0
  32. package/dist/mapping-service.js +35 -15
  33. package/dist/minecraft-explorer-service.js +70 -8
  34. package/dist/mixin/access-validators.js +38 -2
  35. package/dist/mixin/annotation-validators.js +137 -43
  36. package/dist/mixin/parsed-validator.js +21 -7
  37. package/dist/mixin-parser.d.ts +52 -0
  38. package/dist/mixin-parser.js +709 -130
  39. package/dist/mod-decompile-service.js +11 -1
  40. package/dist/mod-remap-service.js +6 -6
  41. package/dist/nbt/java-nbt-codec.js +7 -1
  42. package/dist/source/access-validate.js +10 -0
  43. package/dist/source/artifact-resolver.d.ts +27 -3
  44. package/dist/source/artifact-resolver.js +235 -30
  45. package/dist/source/class-source/members-builder.d.ts +7 -0
  46. package/dist/source/class-source/members-builder.js +4 -1
  47. package/dist/source/class-source.d.ts +9 -2
  48. package/dist/source/class-source.js +186 -27
  49. package/dist/source/indexer.js +69 -1
  50. package/dist/source/lifecycle/mapping-helpers.d.ts +20 -1
  51. package/dist/source/lifecycle/mapping-helpers.js +29 -3
  52. package/dist/source/lifecycle/runtime-check.d.ts +25 -0
  53. package/dist/source/lifecycle/runtime-check.js +68 -39
  54. package/dist/source/nested-jars.d.ts +15 -1
  55. package/dist/source/nested-jars.js +14 -5
  56. package/dist/source/search.d.ts +10 -2
  57. package/dist/source/search.js +60 -13
  58. package/dist/source/symbol-resolver.js +88 -0
  59. package/dist/source/validate-mixin/pipeline/mapping-health.js +20 -1
  60. package/dist/source/validate-mixin/pipeline/target-lookup.js +16 -7
  61. package/dist/source/validate-mixin.d.ts +5 -0
  62. package/dist/source/validate-mixin.js +136 -21
  63. package/dist/source/workspace-target.js +75 -7
  64. package/dist/source-jar-reader.d.ts +48 -1
  65. package/dist/source-jar-reader.js +93 -3
  66. package/dist/source-resolver.d.ts +7 -0
  67. package/dist/source-resolver.js +22 -14
  68. package/dist/source-service.d.ts +5 -0
  69. package/dist/source-service.js +7 -0
  70. package/dist/stdio-supervisor.d.ts +35 -1
  71. package/dist/stdio-supervisor.js +77 -2
  72. package/dist/storage/db.d.ts +62 -2
  73. package/dist/storage/db.js +181 -20
  74. package/dist/storage/files-repo.d.ts +7 -0
  75. package/dist/storage/files-repo.js +17 -4
  76. package/dist/storage/sqlite.d.ts +31 -1
  77. package/dist/storage/sqlite.js +125 -16
  78. package/dist/tool-contract-manifest.js +2 -2
  79. package/dist/tool-execution-gate.js +2 -1
  80. package/dist/tool-guidance.js +4 -1
  81. package/dist/tool-schemas.d.ts +64 -52
  82. package/dist/tool-schemas.js +9 -7
  83. package/dist/types.d.ts +9 -0
  84. package/dist/v1-parity-schemas.js +36 -2
  85. package/dist/version-diff-service.d.ts +23 -0
  86. package/dist/version-diff-service.js +101 -0
  87. package/dist/version-service.d.ts +14 -0
  88. package/dist/version-service.js +52 -3
  89. package/dist/workspace-context-cache.d.ts +25 -0
  90. package/dist/workspace-context-cache.js +52 -2
  91. package/dist/workspace-mapping-service.d.ts +8 -0
  92. package/dist/workspace-mapping-service.js +151 -21
  93. package/docs/README-ja.md +3 -1
  94. package/docs/tool-reference.md +69 -22
  95. package/package.json +1 -1
@@ -108,6 +108,15 @@ function buildPreview(content, query) {
108
108
  function tokenizeIndexedQuery(query) {
109
109
  return query.match(/[\p{L}\p{N}]+/gu) ?? [];
110
110
  }
111
+ /**
112
+ * Quote a token so FTS5 reads it as a string literal instead of grammar. Real
113
+ * Minecraft identifiers tokenize into operator keywords (`BooleanOp.AND` ->
114
+ * `BooleanOp AND`), which unquoted is either a syntax error or - mid-query, as
115
+ * in `a NOT b` - a silent change of meaning.
116
+ */
117
+ function quoteIndexedToken(token) {
118
+ return `"${token.replace(/"/g, '""')}"`;
119
+ }
111
120
  function buildIndexedMatchQuery(query, match) {
112
121
  const tokens = tokenizeIndexedQuery(query.trim());
113
122
  if (tokens.length === 0) {
@@ -118,10 +127,11 @@ function buildIndexedMatchQuery(query, match) {
118
127
  // path while SourceService re-checks hydrated path/content matches before
119
128
  // returning hits. Callers can still use literal mode for exact substring scans.
120
129
  return tokens.map((token, index) => {
130
+ const quoted = quoteIndexedToken(token);
121
131
  if (match === "prefix" && index === tokens.length - 1) {
122
- return `${token}*`;
132
+ return `${quoted}*`;
123
133
  }
124
- return token;
134
+ return quoted;
125
135
  }).join(" ");
126
136
  }
127
137
  /** Escape LIKE wildcards so the needle is matched literally under ESCAPE '\'. */
@@ -177,6 +187,7 @@ export class FilesRepo {
177
187
  SELECT file_path
178
188
  FROM files
179
189
  WHERE artifact_id = ? AND file_path LIKE ? ESCAPE '\\'
190
+ AND (? IS NULL OR file_path LIKE ? ESCAPE '\\')
180
191
  ORDER BY file_path ASC
181
192
  LIMIT ?
182
193
  `);
@@ -191,6 +202,7 @@ export class FilesRepo {
191
202
  SELECT file_path, rank
192
203
  FROM files_fts
193
204
  WHERE artifact_id = ? AND files_fts MATCH ?
205
+ AND (? IS NULL OR file_path LIKE ? ESCAPE '\\')
194
206
  ORDER BY rank
195
207
  LIMIT ?
196
208
  `);
@@ -314,6 +326,7 @@ export class FilesRepo {
314
326
  const cursor = parseSearchCursor(options.cursor);
315
327
  const likeQuery = `%${escapeLikeNeedle(normalized)}%`;
316
328
  const ftsQuery = buildIndexedMatchQuery(normalized, options.match);
329
+ const scopePattern = options.pathPrefix ? `${escapeLikeNeedle(options.pathPrefix)}%` : null;
317
330
  const mode = options.mode ?? "mixed";
318
331
  // Cursor-adaptive fetch limit: when no cursor, use a generous limit;
319
332
  // with cursor + SQL pushdown, we need far fewer rows.
@@ -325,7 +338,7 @@ export class FilesRepo {
325
338
  const includePath = mode !== "text" && !cursorExhausted;
326
339
  const includeContent = mode !== "path" && !cursorExhausted;
327
340
  const pathRows = includePath
328
- ? this.searchPathStmt.all(artifactId, likeQuery, fetchLimit)
341
+ ? this.searchPathStmt.all(artifactId, likeQuery, scopePattern, scopePattern, fetchLimit)
329
342
  : [];
330
343
  const merged = pathRows.map((row) => ({
331
344
  filePath: row.file_path,
@@ -336,7 +349,7 @@ export class FilesRepo {
336
349
  let contentRows = [];
337
350
  if (includeContent && ftsQuery) {
338
351
  try {
339
- contentRows = this.searchFtsStmt.all(artifactId, ftsQuery, fetchLimit);
352
+ contentRows = this.searchFtsStmt.all(artifactId, ftsQuery, scopePattern, scopePattern, fetchLimit);
340
353
  }
341
354
  catch (error) {
342
355
  const message = error instanceof Error ? error.message : String(error);
@@ -1,17 +1,47 @@
1
1
  import { type StatementSync } from "node:sqlite";
2
+ /**
3
+ * Recognizes a raw (non-AppError) SQLite corruption error - the shape
4
+ * node:sqlite throws mid-query, e.g. `{ code: "ERR_SQLITE_ERROR", errcode: 11 }`.
5
+ * Lives here (rather than storage/db.ts, which imports this module) so the
6
+ * Database wrapper below can call it directly without a module cycle; db.ts
7
+ * re-exports it for callers that used to import it from there.
8
+ */
9
+ export declare function isRawSqliteCorruptionError(error: unknown): boolean;
10
+ /**
11
+ * Notified, at most once per `Database` instance, the first time a raw SQLite
12
+ * corruption error is thrown by any statement/pragma/transaction path on that
13
+ * instance. Called BEFORE the triggering error is rethrown unchanged, so it
14
+ * runs regardless of how (or whether) a caller further up the stack wraps or
15
+ * swallows that error - a wrapping catch block elsewhere in the codebase can
16
+ * no longer hide a runtime corruption from this observer.
17
+ */
18
+ export type SqliteCorruptionObserver = (error: unknown) => void;
2
19
  export declare class Statement<T = unknown> {
3
20
  private readonly stmt;
4
- constructor(stmt: StatementSync);
21
+ private readonly notifyCorruption;
22
+ constructor(stmt: StatementSync, notifyCorruption: (error: unknown) => void);
5
23
  run(...params: unknown[]): unknown;
6
24
  get(...params: unknown[]): T | undefined;
7
25
  all(...params: unknown[]): T[];
8
26
  iterate(...params: unknown[]): Iterable<T>;
27
+ private wrapIterable;
9
28
  private invoke;
10
29
  }
11
30
  export default class Database {
12
31
  private readonly inner;
13
32
  private transactionDepth;
33
+ private corruptionObserver;
34
+ private corruptionNotified;
14
35
  constructor(path: string);
36
+ /**
37
+ * Registers (or clears, with `undefined`) the observer notified on this
38
+ * instance's first raw corruption error. Best-effort: an observer that
39
+ * throws is swallowed so it can never mask the real SQLite error being
40
+ * rethrown, and it fires at most once per instance.
41
+ */
42
+ setCorruptionObserver(observer: SqliteCorruptionObserver | undefined): void;
43
+ private notifyCorruption;
44
+ private runRaw;
15
45
  pragma(pragma: string): unknown;
16
46
  prepare<T = Record<string, unknown>>(sql: string): Statement<T>;
17
47
  transaction<T>(fn: () => T): () => T;
@@ -1,4 +1,5 @@
1
1
  import { DatabaseSync } from "node:sqlite";
2
+ import { isAppError } from "../errors.js";
2
3
  function isPlainObject(value) {
3
4
  if (value === null || typeof value !== "object" || Array.isArray(value) || ArrayBuffer.isView(value)) {
4
5
  return false;
@@ -18,10 +19,35 @@ function normalizeParameters(args) {
18
19
  }
19
20
  return { positional: args };
20
21
  }
22
+ const SQLITE_CORRUPT_ERRCODE = 11;
23
+ const SQLITE_NOTADB_ERRCODE = 26;
24
+ /**
25
+ * Recognizes a raw (non-AppError) SQLite corruption error - the shape
26
+ * node:sqlite throws mid-query, e.g. `{ code: "ERR_SQLITE_ERROR", errcode: 11 }`.
27
+ * Lives here (rather than storage/db.ts, which imports this module) so the
28
+ * Database wrapper below can call it directly without a module cycle; db.ts
29
+ * re-exports it for callers that used to import it from there.
30
+ */
31
+ export function isRawSqliteCorruptionError(error) {
32
+ if (isAppError(error)) {
33
+ return false;
34
+ }
35
+ const sqliteError = error;
36
+ if (sqliteError?.code === "SQLITE_CORRUPT" || sqliteError?.code === "SQLITE_NOTADB") {
37
+ return true;
38
+ }
39
+ if (typeof sqliteError?.errcode !== "number") {
40
+ return false;
41
+ }
42
+ const primaryErrcode = sqliteError.errcode & 0xff;
43
+ return primaryErrcode === SQLITE_CORRUPT_ERRCODE || primaryErrcode === SQLITE_NOTADB_ERRCODE;
44
+ }
21
45
  export class Statement {
22
46
  stmt;
23
- constructor(stmt) {
47
+ notifyCorruption;
48
+ constructor(stmt, notifyCorruption) {
24
49
  this.stmt = stmt;
50
+ this.notifyCorruption = notifyCorruption;
25
51
  }
26
52
  run(...params) {
27
53
  return this.invoke("run", params);
@@ -33,34 +59,116 @@ export class Statement {
33
59
  return this.invoke("all", params);
34
60
  }
35
61
  iterate(...params) {
36
- return this.invoke("iterate", params);
62
+ const rawIterable = this.invoke("iterate", params);
63
+ return this.wrapIterable(rawIterable);
64
+ }
65
+ // node:sqlite's iterate() returns lazily: the corrupted page is only ever
66
+ // touched once the consumer actually pulls a row, i.e. inside next(), not
67
+ // at the call above. Wrap the iterator itself so a mid-iteration corruption
68
+ // error still reaches the observer before propagating to the consumer.
69
+ wrapIterable(iterable) {
70
+ const notifyCorruption = this.notifyCorruption;
71
+ return {
72
+ [Symbol.iterator]() {
73
+ const inner = iterable[Symbol.iterator]();
74
+ const wrapped = {
75
+ next() {
76
+ try {
77
+ return inner.next();
78
+ }
79
+ catch (error) {
80
+ notifyCorruption(error);
81
+ throw error;
82
+ }
83
+ }
84
+ };
85
+ // Forward early termination (a `break` out of for...of, or a consuming
86
+ // generator being closed) so the underlying statement is reset instead
87
+ // of being left mid-iteration with its read snapshot open.
88
+ if (typeof inner.return === "function") {
89
+ wrapped.return = (value) => inner.return(value);
90
+ }
91
+ return wrapped;
92
+ }
93
+ };
37
94
  }
38
95
  invoke(method, params) {
39
96
  const normalized = normalizeParameters(params);
40
97
  const target = this.stmt[method];
41
- if (normalized.named !== undefined) {
42
- return target.call(this.stmt, normalized.named);
98
+ try {
99
+ if (normalized.named !== undefined) {
100
+ return target.call(this.stmt, normalized.named);
101
+ }
102
+ return target.call(this.stmt, ...(normalized.positional ?? []));
103
+ }
104
+ catch (error) {
105
+ this.notifyCorruption(error);
106
+ throw error;
43
107
  }
44
- return target.call(this.stmt, ...(normalized.positional ?? []));
45
108
  }
46
109
  }
47
110
  let transactionSerial = 0;
48
111
  export default class Database {
49
112
  inner;
50
113
  transactionDepth = 0;
114
+ corruptionObserver;
115
+ corruptionNotified = false;
51
116
  constructor(path) {
52
117
  this.inner = new DatabaseSync(path);
53
118
  }
119
+ /**
120
+ * Registers (or clears, with `undefined`) the observer notified on this
121
+ * instance's first raw corruption error. Best-effort: an observer that
122
+ * throws is swallowed so it can never mask the real SQLite error being
123
+ * rethrown, and it fires at most once per instance.
124
+ */
125
+ setCorruptionObserver(observer) {
126
+ this.corruptionObserver = observer;
127
+ }
128
+ notifyCorruption(error) {
129
+ if (this.corruptionNotified) {
130
+ return;
131
+ }
132
+ const observer = this.corruptionObserver;
133
+ if (!observer) {
134
+ return;
135
+ }
136
+ if (!isRawSqliteCorruptionError(error)) {
137
+ return;
138
+ }
139
+ this.corruptionNotified = true;
140
+ try {
141
+ observer(error);
142
+ }
143
+ catch {
144
+ // best-effort: never let the observer mask the real error being rethrown
145
+ }
146
+ }
147
+ runRaw(fn) {
148
+ try {
149
+ return fn();
150
+ }
151
+ catch (error) {
152
+ this.notifyCorruption(error);
153
+ throw error;
154
+ }
155
+ }
54
156
  pragma(pragma) {
55
157
  const sql = `PRAGMA ${pragma}`;
56
158
  if (pragma.includes("=")) {
57
- this.inner.exec(sql);
159
+ this.runRaw(() => this.inner.exec(sql));
58
160
  return undefined;
59
161
  }
60
- return this.inner.prepare(sql).all();
162
+ return this.runRaw(() => this.inner.prepare(sql).all());
61
163
  }
62
164
  prepare(sql) {
63
- return new Statement(this.inner.prepare(sql));
165
+ // Wrapped in runRaw: PREPARING a statement can itself throw a raw
166
+ // corruption error - e.g. damage to the schema (sqlite_master) that a
167
+ // fresh connection only discovers while compiling its first statement -
168
+ // not just running one. Without this, that error skipped the observer
169
+ // entirely (reproduced: errcode 11, observer never notified).
170
+ const stmt = this.runRaw(() => this.inner.prepare(sql));
171
+ return new Statement(stmt, (error) => this.notifyCorruption(error));
64
172
  }
65
173
  transaction(fn) {
66
174
  return () => this.runInTransaction(fn);
@@ -74,19 +182,19 @@ export default class Database {
74
182
  const savepoint = `sp_${++transactionSerial}`;
75
183
  try {
76
184
  if (isOutermost) {
77
- this.inner.exec("BEGIN");
185
+ this.runRaw(() => this.inner.exec("BEGIN"));
78
186
  }
79
187
  else {
80
- this.inner.exec(`SAVEPOINT ${savepoint}`);
188
+ this.runRaw(() => this.inner.exec(`SAVEPOINT ${savepoint}`));
81
189
  }
82
190
  this.transactionDepth = initialDepth + 1;
83
191
  const result = fn();
84
192
  this.transactionDepth = initialDepth;
85
193
  if (isOutermost) {
86
- this.inner.exec("COMMIT");
194
+ this.runRaw(() => this.inner.exec("COMMIT"));
87
195
  }
88
196
  else {
89
- this.inner.exec(`RELEASE SAVEPOINT ${savepoint}`);
197
+ this.runRaw(() => this.inner.exec(`RELEASE SAVEPOINT ${savepoint}`));
90
198
  }
91
199
  return result;
92
200
  }
@@ -94,15 +202,16 @@ export default class Database {
94
202
  this.transactionDepth = initialDepth;
95
203
  try {
96
204
  if (isOutermost) {
97
- this.inner.exec("ROLLBACK");
205
+ this.runRaw(() => this.inner.exec("ROLLBACK"));
98
206
  }
99
207
  else {
100
- this.inner.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`);
101
- this.inner.exec(`RELEASE SAVEPOINT ${savepoint}`);
208
+ this.runRaw(() => this.inner.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`));
209
+ this.runRaw(() => this.inner.exec(`RELEASE SAVEPOINT ${savepoint}`));
102
210
  }
103
211
  }
104
212
  catch {
105
- // best-effort rollback cleanup
213
+ // best-effort rollback cleanup - runRaw already notified the
214
+ // observer (if any) before this catch swallows the rollback failure
106
215
  }
107
216
  throw error;
108
217
  }
@@ -17,7 +17,7 @@ const SECTION_ROWS = {
17
17
  "| `compare-minecraft` | Compare version pairs, class diffs, registry diffs, and migration-oriented summaries |",
18
18
  "| `analyze-mod` | Summarize mod metadata, decompile and search mod code, inspect class source, read class members from bytecode, and preview or apply remaps |",
19
19
  "| `validate-project` | Summarize workspaces and run direct Mixin, Access Widener, or Access Transformer validation |",
20
- "| `manage-cache` | List, verify, and preview or apply cache cleanup and rebuild operations |"
20
+ "| `manage-cache` | List, verify, and preview or apply cache cleanup operations |"
21
21
  ],
22
22
  ja: [
23
23
  "| `inspect-minecraft` | バージョン、アーティファクト、クラス、ファイル、ソース本文、ワークスペース文脈の調査フローをまとめて扱う |",
@@ -25,7 +25,7 @@ const SECTION_ROWS = {
25
25
  "| `compare-minecraft` | バージョン差分、クラス差分、レジストリ差分、移行向け概要を比較する |",
26
26
  "| `analyze-mod` | Mod メタデータの要約、Mod コードのデコンパイル / 検索、クラスソース確認、リマップのプレビュー / 実行を扱う |",
27
27
  "| `validate-project` | ワークスペース要約と、Mixin / Access Widener / Access Transformer の直接検証を行う |",
28
- "| `manage-cache` | キャッシュの一覧、検証、クリーンアップ / 再構築のプレビュー / 実行を行う |"
28
+ "| `manage-cache` | キャッシュの一覧、検証、クリーンアップのプレビュー / 実行を行う |"
29
29
  ]
30
30
  },
31
31
  "source-exploration": {
@@ -4,7 +4,8 @@ const DEFAULT_OPTIONS = {
4
4
  maxQueue: 2
5
5
  };
6
6
  // Heavy tools that have a batch equivalent able to collapse many same-kind
7
- // queries into a single gated call. Used to point overflow guidance at the
7
+ // queries into one call. The batch-* tools are NOT in the heavy set, so that
8
+ // one call bypasses this gate entirely. Used to point overflow guidance at the
8
9
  // right batch-* tool instead of just telling the caller to retry serially.
9
10
  const BATCH_EQUIVALENTS = {
10
11
  "find-mapping": "batch-mappings",
@@ -800,7 +800,10 @@ export function buildValidateProjectSuggestedParams(normalizedInput) {
800
800
  for (const field of booleanFields) {
801
801
  const value = record[field];
802
802
  if (typeof value === "boolean" &&
803
- (!Object.prototype.hasOwnProperty.call(SUGGESTED_CALL_DEFAULTS, field) ||
803
+ // project-summary infers an omitted version, so an explicit
804
+ // preferProjectVersion=false is an opt-out, not a droppable default.
805
+ (field === "preferProjectVersion" ||
806
+ !Object.prototype.hasOwnProperty.call(SUGGESTED_CALL_DEFAULTS, field) ||
804
807
  !isSuggestedCallDefault(field, value))) {
805
808
  result[field] = value;
806
809
  }