gdharness 1.1.38 → 1.1.39

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/build/cli.js +125 -46
  2. package/build/index.js +123 -49
  3. package/package.json +1 -1
package/build/cli.js CHANGED
@@ -16541,6 +16541,16 @@ var init_types = __esm(() => {
16541
16541
  };
16542
16542
  });
16543
16543
 
16544
+ // src/answer-json.ts
16545
+ function answerJson(value) {
16546
+ return JSON.stringify(value, null, 2).replace(SCALAR_ARRAY, (_whole, items) => `[${items.split(/,\n\s+/).join(", ")}]`);
16547
+ }
16548
+ var SCALAR, SCALAR_ARRAY;
16549
+ var init_answer_json = __esm(() => {
16550
+ SCALAR = String.raw`(?:-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)`;
16551
+ SCALAR_ARRAY = new RegExp(String.raw`\[\n\s+(${SCALAR}(?:,\n\s+${SCALAR})*)\n\s*\]`, "g");
16552
+ });
16553
+
16544
16554
  // src/resources.ts
16545
16555
  import { readFileSync as readFileSync8, realpathSync as realpathSync3 } from "node:fs";
16546
16556
  import { extname, resolve as resolve3 } from "node:path";
@@ -16727,7 +16737,7 @@ function readResourceText(uri, getProjectPath) {
16727
16737
  const parsedProject = parseProjectGodot(rawProject);
16728
16738
  return {
16729
16739
  mimeType: "application/json",
16730
- text: JSON.stringify(parsedProject, null, 2)
16740
+ text: answerJson(parsedProject)
16731
16741
  };
16732
16742
  }
16733
16743
  const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
@@ -16770,6 +16780,7 @@ function setupResourceHandlers(mcp, getProjectPath) {
16770
16780
  var STATIC_RESOURCES, RESOURCE_TEMPLATES, RESOURCE_COUNT;
16771
16781
  var init_resources = __esm(() => {
16772
16782
  init_types();
16783
+ init_answer_json();
16773
16784
  init_errors();
16774
16785
  init_paths();
16775
16786
  STATIC_RESOURCES = [
@@ -17758,7 +17769,7 @@ var init_tool_definitions = __esm(() => {
17758
17769
  },
17759
17770
  {
17760
17771
  name: "script_edit",
17761
- description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read, and a script declaring a class_name is put in the project's class list, with registered saying whether it is there. modify makes every addition or none, and answers with the line each one is on in the file written. rename renames a class or a member wherever the engine uses it and writes every file or none. A new name already taken is refused with nothing written, a member's included when a class inheriting it already uses the name in code, as a local or a parameter. changed lists each file with its changed lines as they now read, and leftAlone lists every other whole-word occurrence of the old name with what the tokenizer makes of it (code, string, comment, doc, nodePath, scene, text, or ignored for a file under a .gdignore directory, which is never changed) and, where it is not plain, why: a string that is the old name whole, such as find_children's type or a call's method name, which nothing renames or compiles and which is listed first; a node named like the class; a call on a value whose type the analyser could not tell. A member rename answers under languageServer how many requests it made and how long they took. A comment, documentation line, string or text file line with nothing more to say is given under leftAloneInProse by file, kind and line instead, and code that is certainly another symbol of the same name, a bare name in a file where nothing inherits the member or one the language server resolved to another declaration, under leftAloneOtherSymbols as each why with the files and lines it covers. summary.leftAlone counts each of the three lists beside the total. Read both before calling the rename done; nothing in them was changed. Afterwards an open editor scans the changed files and reloads every changed script, which is also the compile check, and its answer comes back under editor; with no editor the class cache is rebuilt and the changed scripts are compiled in a fresh engine, under classCache and compiled. written is true once the files are written, and ok is false when what came after the write failed: the files are left changed, and note says so.",
17772
+ description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read, and a script declaring a class_name is put in the project's class list, with registered saying whether it is there. modify makes every addition or none, and answers with the line each one is on in the file written. rename renames a class or a member wherever the engine uses it and writes every file or none. A new name already taken is refused with nothing written, a member's included when a class inheriting it already uses the name in code, as a local or a parameter. changed lists each file with its changed lines as they now read, and leftAlone lists every other whole-word occurrence of the old name with what the tokenizer makes of it (code, string, comment, doc, nodePath, scene, text, or ignored for a file under a .gdignore directory, which is never changed) and, where it is not plain, why: a string that is the old name whole, such as find_children's type or a call's method name, which nothing renames or compiles and which is listed first; a node named like the class; a call on a value whose type the analyser could not tell. A member rename answers under languageServer how many requests it made and how long they took. A comment, documentation line, string or text file line with nothing more to say is given under leftAloneInProse by file, kind and line instead, and code that is certainly another symbol of the same name, a bare name in a file where nothing inherits the member or one the language server resolved to another declaration, under leftAloneOtherSymbols as each why with the files and lines it covers. summary.leftAlone counts each of the three lists beside the total. When those two grouped lists run past 300 lines, they are written with each line's text to a file under the project's .godot directory instead, and leftAloneDetail names the file and counts what is in it. Read both before calling the rename done; nothing in them was changed. Afterwards an open editor scans the changed files and reloads every changed script, which is also the compile check, and its answer comes back under editor; with no editor the class cache is rebuilt and the changed scripts are compiled in a fresh engine, under classCache and compiled. written is true once the files are written, and ok is false when what came after the write failed: the files are left changed, and note says so.",
17762
17773
  parameters: {
17763
17774
  projectPath: PROJECT_PATH,
17764
17775
  scriptPath: SCRIPT_PATH,
@@ -34666,7 +34677,7 @@ async function handleDAPTool(client, toolName, args) {
34666
34677
  }
34667
34678
  const result = await client.setBreakpoint(safeArgs.scriptPath, safeArgs.line);
34668
34679
  return {
34669
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
34680
+ content: [{ type: "text", text: answerJson(result) }]
34670
34681
  };
34671
34682
  }
34672
34683
  case "dap_remove_breakpoint": {
@@ -34675,7 +34686,7 @@ async function handleDAPTool(client, toolName, args) {
34675
34686
  }
34676
34687
  const result = await client.removeBreakpoint(safeArgs.scriptPath, safeArgs.line);
34677
34688
  return {
34678
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
34689
+ content: [{ type: "text", text: answerJson(result) }]
34679
34690
  };
34680
34691
  }
34681
34692
  case "dap_continue": {
@@ -34690,12 +34701,12 @@ async function handleDAPTool(client, toolName, args) {
34690
34701
  heldAgain,
34691
34702
  note: `The game had not stopped again ${client.stepPatienceMs / 1000}s after the step: the step is still running, it went on without stopping anywhere, or the game was not held when it was asked. debug_state stack answers once it stops.`
34692
34703
  };
34693
- return { content: [{ type: "text", text: JSON.stringify(answer, null, 2) }] };
34704
+ return { content: [{ type: "text", text: answerJson(answer) }] };
34694
34705
  }
34695
34706
  case "dap_get_stack_trace": {
34696
34707
  const stack = await client.getStackTrace();
34697
34708
  return {
34698
- content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
34709
+ content: [{ type: "text", text: answerJson(stack) }]
34699
34710
  };
34700
34711
  }
34701
34712
  case "dap_get_variables": {
@@ -34708,7 +34719,7 @@ async function handleDAPTool(client, toolName, args) {
34708
34719
  note: `The values of ${missing.join(", ")} did not arrive from the game within ${client.scopesPatienceMs / 1000}s, so they are unknown rather than empty. Ask again.`
34709
34720
  };
34710
34721
  return {
34711
- content: [{ type: "text", text: JSON.stringify(answer, null, 2) }]
34722
+ content: [{ type: "text", text: answerJson(answer) }]
34712
34723
  };
34713
34724
  }
34714
34725
  default:
@@ -34727,6 +34738,7 @@ async function handleDAPTool(client, toolName, args) {
34727
34738
  }
34728
34739
  var DEFAULT_DAP_PORT = 6006, DAP_REQUEST_TIMEOUT_MS = 1e4, SCOPES_TIMEOUT_MS = 20000, STEP_LANDS_WITHIN_MS = 1e4, VARIABLES_POLL_TIMEOUT_MS = 2000, ReadRefusal;
34729
34740
  var init_dap_client = __esm(() => {
34741
+ init_answer_json();
34730
34742
  init_errors();
34731
34743
  init_framing();
34732
34744
  init_ports();
@@ -39341,7 +39353,7 @@ function asToolResponse(payload) {
39341
39353
  content: [
39342
39354
  {
39343
39355
  type: "text",
39344
- text: JSON.stringify(payload, null, 2)
39356
+ text: answerJson(payload)
39345
39357
  }
39346
39358
  ]
39347
39359
  };
@@ -39438,6 +39450,7 @@ async function handleLSPTool(client, toolName, args) {
39438
39450
  }
39439
39451
  var DEFAULT_LSP_PORT = 6005, DIAGNOSTICS_TIMEOUT_MS = 5000, REQUEST_TIMEOUT_MS2 = 1e4, REFERENCES_TIMEOUT_MS = 180000, LSPTimeout, ArgumentRefusal, AnotherProjectsServer;
39440
39452
  var init_lsp_client = __esm(() => {
39453
+ init_answer_json();
39441
39454
  init_errors();
39442
39455
  init_framing();
39443
39456
  init_paths();
@@ -40074,7 +40087,14 @@ class PlanBuilder {
40074
40087
  }
40075
40088
  function isWholeString(text, offset, name) {
40076
40089
  const before = text[offset - 1];
40077
- return (before === '"' || before === "'") && text[offset + name.length] === before;
40090
+ if (before !== '"' && before !== "'" || text[offset + name.length] !== before) {
40091
+ return false;
40092
+ }
40093
+ let after = offset + name.length + 1;
40094
+ while (text[after] === " " || text[after] === "\t") {
40095
+ after += 1;
40096
+ }
40097
+ return text[after] !== ":";
40078
40098
  }
40079
40099
  function declaresItsOwn(script, name, except) {
40080
40100
  return script.occurrences(name).some((occurrence) => occurrence.kind === "code" && occurrence.offset !== except && occurrence.declaredAs !== null && occurrence.declaredAs !== "class_name");
@@ -40536,7 +40556,7 @@ function applyRewrites(rewrites) {
40536
40556
  throw error;
40537
40557
  }
40538
40558
  }
40539
- var DOC_LINK_KINDS, DOC_KIND, WHOLE_CLASS_NAME = "the whole string is the class's name, as find_children's type, a comparison with get_global_name() or a list of accepted type names takes it, and it was not changed: if it names this class, it no longer matches anything after the rename, and no compile says so", WHOLE_MEMBER_NAME = "the whole string is the member's name, as call, connect, has_method, get or set take it, and it was not changed: if it names this member, it no longer finds it after the rename, and no compile says so", NOT_INHERITED = "no class in this file inherits the member, so a bare name here is that class's own local, parameter or member, or a global", RESOLVED_ELSEWHERE = "the language server resolved this to another declaration of the name";
40559
+ var DOC_LINK_KINDS, DOC_KIND, WHOLE_CLASS_NAME = "the whole string is the class's name, as find_children's type, a comparison with get_global_name() or a list of accepted type names takes it, and it was not changed: if it names this class, it no longer matches anything after the rename, and no compile says so", WHOLE_MEMBER_NAME = "the whole string is the member's name, as call, connect, has_method, get, set or a subscript on an object take it, and it was not changed: if it names this member, it no longer finds it after the rename, and no compile says so", NOT_INHERITED = "no class in this file inherits the member, so a bare name here is that class's own local, parameter or member, or a global", RESOLVED_ELSEWHERE = "the language server resolved this to another declaration of the name";
40540
40560
  var init_rename = __esm(() => {
40541
40561
  init_gdscript_source();
40542
40562
  DOC_LINK_KINDS = new Set([
@@ -40560,7 +40580,7 @@ var init_rename = __esm(() => {
40560
40580
  });
40561
40581
 
40562
40582
  // src/rename-symbol.ts
40563
- import { existsSync as existsSync18, realpathSync as realpathSync5 } from "node:fs";
40583
+ import { existsSync as existsSync18, mkdirSync as mkdirSync11, readdirSync as readdirSync8, realpathSync as realpathSync5, rmSync as rmSync9, writeFileSync as writeFileSync13 } from "node:fs";
40564
40584
  import { basename as basename5, dirname as dirname15, join as join21, relative as relative4, resolve as resolve6 } from "node:path";
40565
40585
  function resourcePathOf(projectPath, absolute) {
40566
40586
  const inside = relative4(onDiskSpelling(projectPath), onDiskSpelling(absolute));
@@ -40818,6 +40838,7 @@ async function renameSymbol(request, services) {
40818
40838
  moved: moveTo === null ? null : { from: declaringPath, to: moveTo },
40819
40839
  scriptsInOrder,
40820
40840
  rewrites,
40841
+ detail: left.detail,
40821
40842
  report: {
40822
40843
  renamed: { what, from: symbol, to: newName, declaredIn: declaringPath, resolvedBy },
40823
40844
  ...took === null ? {} : { languageServer: took },
@@ -40930,6 +40951,10 @@ function leftAloneReport(mentions) {
40930
40951
  lines.push(line);
40931
40952
  }
40932
40953
  };
40954
+ const textOf = new Map;
40955
+ for (const mention of mentions) {
40956
+ textOf.set(`${mention.file}:${String(mention.line)}`, mention.text);
40957
+ }
40933
40958
  for (const mention of mentions) {
40934
40959
  if (PROSE_KINDS.has(mention.kind) && mention.why === undefined) {
40935
40960
  const key = `${mention.file}
@@ -40951,20 +40976,64 @@ ${mention.kind}`;
40951
40976
  }
40952
40977
  }
40953
40978
  const loudFirst = (mention) => mention.kind === "string" && mention.why !== undefined ? 0 : 1;
40979
+ const leftAlone = full.sort((a, b) => loudFirst(a) - loudFirst(b));
40980
+ const inProse = [...prose.values()];
40981
+ const otherSymbols = [...others.entries()].map(([why, files]) => ({
40982
+ why,
40983
+ files: [...files.entries()].map(([file, lines]) => ({ file, lines }))
40984
+ }));
40985
+ const grouped = inProse.reduce((sum, group) => sum + group.lines.length, 0) + otherSymbols.reduce((sum, reason) => sum + reason.files.reduce((n, one) => n + one.lines.length, 0), 0);
40986
+ if (grouped <= LEFT_ALONE_ANSWER_LINES) {
40987
+ return {
40988
+ lists: {
40989
+ leftAlone,
40990
+ ...inProse.length > 0 ? { leftAloneInProse: inProse } : {},
40991
+ ...otherSymbols.length > 0 ? { leftAloneOtherSymbols: otherSymbols } : {}
40992
+ },
40993
+ counts,
40994
+ detail: null
40995
+ };
40996
+ }
40997
+ const withText = (file, lines) => lines.map((line) => ({ line, text: textOf.get(`${file}:${String(line)}`) ?? "" }));
40954
40998
  return {
40955
40999
  lists: {
40956
- leftAlone: full.sort((a, b) => loudFirst(a) - loudFirst(b)),
40957
- ...prose.size > 0 ? { leftAloneInProse: [...prose.values()] } : {},
40958
- ...others.size > 0 ? {
40959
- leftAloneOtherSymbols: [...others.entries()].map(([why, files]) => ({
40960
- why,
40961
- files: [...files.entries()].map(([file, lines]) => ({ file, lines }))
41000
+ leftAlone,
41001
+ leftAloneDetail: {
41002
+ inProse: { files: new Set(inProse.map((group) => group.file)).size, occurrences: counts.inProse },
41003
+ otherSymbols: otherSymbols.map((reason) => ({
41004
+ why: reason.why,
41005
+ files: reason.files.length,
41006
+ occurrences: reason.files.reduce((n, one) => n + one.lines.length, 0)
40962
41007
  }))
40963
- } : {}
41008
+ }
40964
41009
  },
40965
- counts
41010
+ counts,
41011
+ detail: {
41012
+ leftAloneInProse: inProse.map((group) => ({
41013
+ file: group.file,
41014
+ kind: group.kind,
41015
+ lines: withText(group.file, group.lines)
41016
+ })),
41017
+ leftAloneOtherSymbols: otherSymbols.map((reason) => ({
41018
+ why: reason.why,
41019
+ files: reason.files.map((one) => ({ file: one.file, lines: withText(one.file, one.lines) }))
41020
+ }))
41021
+ }
40966
41022
  };
40967
41023
  }
41024
+ function writeLeftAloneDetail(projectPath, symbol, detail) {
41025
+ const directory = join21(projectPath, ".godot", "gdharness-renames");
41026
+ mkdirSync11(directory, { recursive: true });
41027
+ detailWrites += 1;
41028
+ const path = join21(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
41029
+ writeFileSync13(path, `${JSON.stringify(detail, null, 2)}
41030
+ `, "utf8");
41031
+ const kept = readdirSync8(directory).filter((name) => name.endsWith(".json")).sort().reverse();
41032
+ for (const old of kept.slice(10)) {
41033
+ rmSync9(join21(directory, old), { force: true });
41034
+ }
41035
+ return path;
41036
+ }
40968
41037
  function summaryOf(edits, mentions, rewrites, left) {
40969
41038
  const count = (values) => {
40970
41039
  const counted = {};
@@ -40981,7 +41050,7 @@ function summaryOf(edits, mentions, rewrites, left) {
40981
41050
  leftAloneByKind: count(mentions.map((mention) => mention.kind))
40982
41051
  };
40983
41052
  }
40984
- var WHAT, PROSE_KINDS;
41053
+ var WHAT, LEFT_ALONE_ANSWER_LINES = 300, detailWrites = 0, PROSE_KINDS;
40985
41054
  var init_rename_symbol = __esm(() => {
40986
41055
  init_gdscript_source();
40987
41056
  init_lsp_client();
@@ -41001,7 +41070,7 @@ var init_rename_symbol = __esm(() => {
41001
41070
  });
41002
41071
 
41003
41072
  // src/restart-note.ts
41004
- import { mkdirSync as mkdirSync11, readFileSync as readFileSync19, rmSync as rmSync9, writeFileSync as writeFileSync13 } from "node:fs";
41073
+ import { mkdirSync as mkdirSync12, readFileSync as readFileSync19, rmSync as rmSync10, writeFileSync as writeFileSync14 } from "node:fs";
41005
41074
  import { dirname as dirname16, join as join22 } from "node:path";
41006
41075
  function restartNotePath(projectPath) {
41007
41076
  return join22(projectPath, ".godot", "gdharness-restart.json");
@@ -41009,8 +41078,8 @@ function restartNotePath(projectPath) {
41009
41078
  function noteRestartBegun(note) {
41010
41079
  const path = restartNotePath(note.projectPath);
41011
41080
  try {
41012
- mkdirSync11(dirname16(path), { recursive: true });
41013
- writeFileSync13(path, `${JSON.stringify(note, null, 2)}
41081
+ mkdirSync12(dirname16(path), { recursive: true });
41082
+ writeFileSync14(path, `${JSON.stringify(note, null, 2)}
41014
41083
  `, "utf8");
41015
41084
  } catch (error) {
41016
41085
  console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
@@ -41033,7 +41102,7 @@ function restartOwed(projectPath) {
41033
41102
  }
41034
41103
  function restartSettled(projectPath) {
41035
41104
  try {
41036
- rmSync9(restartNotePath(projectPath), { force: true });
41105
+ rmSync10(restartNotePath(projectPath), { force: true });
41037
41106
  } catch (error) {
41038
41107
  console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
41039
41108
  }
@@ -41046,14 +41115,14 @@ var init_restart_note = __esm(() => {
41046
41115
  import { execFile as execFile4 } from "node:child_process";
41047
41116
  import { createHash as createHash4 } from "node:crypto";
41048
41117
  import {
41049
- mkdirSync as mkdirSync12,
41118
+ mkdirSync as mkdirSync13,
41050
41119
  openSync as openSync2,
41051
- readdirSync as readdirSync8,
41120
+ readdirSync as readdirSync9,
41052
41121
  readFileSync as readFileSync20,
41053
41122
  realpathSync as realpathSync6,
41054
- rmSync as rmSync10,
41123
+ rmSync as rmSync11,
41055
41124
  statSync as statSync8,
41056
- writeFileSync as writeFileSync14
41125
+ writeFileSync as writeFileSync15
41057
41126
  } from "node:fs";
41058
41127
  import { basename as basename6, dirname as dirname17, join as join23, resolve as resolve7 } from "node:path";
41059
41128
  import { promisify as promisify4 } from "node:util";
@@ -41085,7 +41154,7 @@ function runRecordPath(projectPath, directory = runtimeDirectory()) {
41085
41154
  return join23(directory, "runs", `note-${projectKey(projectPath)}.json`);
41086
41155
  }
41087
41156
  function openTranscript(startedAt) {
41088
- mkdirSync12(runsDirectory(), { recursive: true });
41157
+ mkdirSync13(runsDirectory(), { recursive: true });
41089
41158
  const path = join23(runsDirectory(), `run-${startedAt}.log`);
41090
41159
  return { path, fd: openSync2(path, "a") };
41091
41160
  }
@@ -41096,8 +41165,8 @@ function editorNotePath(projectPath, directory = runtimeDirectory()) {
41096
41165
  return join23(directory, "runs", `editor-${projectKey(projectPath)}.json`);
41097
41166
  }
41098
41167
  function writeEditorRunNote(note) {
41099
- mkdirSync12(runsDirectory(), { recursive: true });
41100
- writeFileSync14(editorNotePath(note.projectPath), JSON.stringify(note, null, 2), "utf8");
41168
+ mkdirSync13(runsDirectory(), { recursive: true });
41169
+ writeFileSync15(editorNotePath(note.projectPath), JSON.stringify(note, null, 2), "utf8");
41101
41170
  }
41102
41171
  function readEditorRunNote(projectPath) {
41103
41172
  for (const directory of runtimeDirectories()) {
@@ -41147,7 +41216,7 @@ function everyRunRecord() {
41147
41216
  for (const directory of runtimeDirectories()) {
41148
41217
  let entries;
41149
41218
  try {
41150
- entries = readdirSync8(join23(directory, "runs"));
41219
+ entries = readdirSync9(join23(directory, "runs"));
41151
41220
  } catch {
41152
41221
  continue;
41153
41222
  }
@@ -41366,7 +41435,7 @@ function clearRunRecord(projectPath) {
41366
41435
  const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
41367
41436
  for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
41368
41437
  try {
41369
- rmSync10(path, { force: true });
41438
+ rmSync11(path, { force: true });
41370
41439
  } catch {}
41371
41440
  }
41372
41441
  }
@@ -41383,7 +41452,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
41383
41452
  const kept = new Set(everyRunRecord().map((record) => record.transcript));
41384
41453
  let entries;
41385
41454
  try {
41386
- entries = readdirSync8(directory);
41455
+ entries = readdirSync9(directory);
41387
41456
  } catch {
41388
41457
  return;
41389
41458
  }
@@ -41396,7 +41465,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
41396
41465
  continue;
41397
41466
  }
41398
41467
  try {
41399
- rmSync10(path);
41468
+ rmSync11(path);
41400
41469
  } catch {}
41401
41470
  }
41402
41471
  }
@@ -41420,9 +41489,9 @@ import {
41420
41489
  copyFileSync,
41421
41490
  existsSync as existsSync19,
41422
41491
  fstatSync,
41423
- mkdirSync as mkdirSync13,
41492
+ mkdirSync as mkdirSync14,
41424
41493
  openSync as openSync3,
41425
- readdirSync as readdirSync9,
41494
+ readdirSync as readdirSync10,
41426
41495
  readFileSync as readFileSync21,
41427
41496
  readSync as readSync2,
41428
41497
  rmdirSync as rmdirSync2,
@@ -41876,7 +41945,7 @@ function exportAnswer(ending, asked, before, after) {
41876
41945
  ...problems.omitted === 0 ? {} : { entriesOmitted: problems.omitted }
41877
41946
  };
41878
41947
  if (verdict.exported) {
41879
- return { content: [{ type: "text", text: JSON.stringify(verdict, null, 2) }] };
41948
+ return { content: [{ type: "text", text: answerJson(verdict) }] };
41880
41949
  }
41881
41950
  const stale = !written && after !== null ? ` The ${asked.outputPath} there is from before this export, which did not write it.` : "";
41882
41951
  const why = ending.exitSignal === null ? stale : ` The engine was ${howItEnded(ending)}, so it has no exit code.${stale}`;
@@ -41886,7 +41955,7 @@ function exportAnswer(ending, asked, before, after) {
41886
41955
  type: "text",
41887
41956
  text: `Export with preset '${asked.preset}' did not produce ${asked.outputPath}.${why}`
41888
41957
  },
41889
- { type: "text", text: JSON.stringify(verdict, null, 2) }
41958
+ { type: "text", text: answerJson(verdict) }
41890
41959
  ],
41891
41960
  isError: true
41892
41961
  };
@@ -41990,7 +42059,7 @@ function elsewhereIn(projectPath, asked) {
41990
42059
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
41991
42060
  let near = [];
41992
42061
  try {
41993
- near = readdirSync9(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
42062
+ near = readdirSync10(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
41994
42063
  } catch {
41995
42064
  return "";
41996
42065
  }
@@ -42092,7 +42161,7 @@ class GodotServer {
42092
42161
  return response;
42093
42162
  }
42094
42163
  jsonTextResponse(payload) {
42095
- return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
42164
+ return { content: [{ type: "text", text: answerJson(payload) }] };
42096
42165
  }
42097
42166
  async run() {
42098
42167
  const located = await this.locator.find();
@@ -42425,7 +42494,7 @@ class GodotServer {
42425
42494
  saying(answer, block) {
42426
42495
  return {
42427
42496
  ...answer,
42428
- content: [...answer.content, { type: "text", text: JSON.stringify(block, null, 2) }]
42497
+ content: [...answer.content, { type: "text", text: answerJson(block) }]
42429
42498
  };
42430
42499
  }
42431
42500
  projectHasMovedOn() {
@@ -43398,7 +43467,7 @@ class GodotServer {
43398
43467
  if (!outcome.ok) {
43399
43468
  const response = this.createErrorResponse(outcome.message);
43400
43469
  if (Object.keys(extras).length > 0) {
43401
- response.content.push({ type: "text", text: JSON.stringify(extras, null, 2) });
43470
+ response.content.push({ type: "text", text: answerJson(extras) });
43402
43471
  }
43403
43472
  return response;
43404
43473
  }
@@ -43476,7 +43545,7 @@ class GodotServer {
43476
43545
  }
43477
43546
  const folder = dirname18(output.absolutePath);
43478
43547
  try {
43479
- mkdirSync13(folder, { recursive: true });
43548
+ mkdirSync14(folder, { recursive: true });
43480
43549
  } catch (error) {
43481
43550
  return this.createErrorResponse(`Cannot create ${dirname18(output.relativePath)} to export into: ${errorMessage(error)}`);
43482
43551
  }
@@ -43587,7 +43656,7 @@ class GodotServer {
43587
43656
  let report = null;
43588
43657
  let reportProblem = null;
43589
43658
  try {
43590
- const written = existsSync19(reportsDir) ? readdirSync9(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
43659
+ const written = existsSync19(reportsDir) ? readdirSync10(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
43591
43660
  const newest = written.at(-1);
43592
43661
  if (newest !== undefined) {
43593
43662
  report = parseJUnit(readFileSync21(join24(reportsDir, newest, "results.xml"), "utf8"));
@@ -46098,6 +46167,15 @@ class GodotServer {
46098
46167
  const said = outcome.conflicts === undefined ? "" : ` ${outcome.conflicts.join("; ")}.`;
46099
46168
  return this.createErrorResponse(`${outcome.reason}${said}`, outcome.advice ?? []);
46100
46169
  }
46170
+ if (outcome.detail !== null) {
46171
+ const file = writeLeftAloneDetail(projectPath, symbol, outcome.detail);
46172
+ const folded = asParams(outcome.report["leftAloneDetail"]);
46173
+ outcome.report["leftAloneDetail"] = {
46174
+ file,
46175
+ note: `Too many lines were left alone to list here, and none of them is a use: every comment, documentation line, string and text file line that only shares the word, and every other symbol of the same name, is in ${file} with its text.`,
46176
+ ...folded
46177
+ };
46178
+ }
46101
46179
  if (readBoolean(args, "preview") === true) {
46102
46180
  return this.jsonTextResponse({ preview: true, ...outcome.report });
46103
46181
  }
@@ -46561,6 +46639,7 @@ var init_server3 = __esm(() => {
46561
46639
  init_stdio2();
46562
46640
  init_types();
46563
46641
  init_alive();
46642
+ init_answer_json();
46564
46643
  init_boot_note();
46565
46644
  init_breakpoint_note();
46566
46645
  init_bridge_announce();
@@ -47831,7 +47910,7 @@ every other occurrence of the word for you to read: a string that is the name wh
47831
47910
  \`find_children\` type, first under \`leftAlone\` since nothing compiles it, a file under a
47832
47911
  \`.gdignore\` directory there too, a word in a comment, a doc line, a string or a text file by
47833
47912
  line under \`leftAloneInProse\`, and a local or another class's member of the same name by line
47834
- under \`leftAloneOtherSymbols\`. Pass \`preview\` first on a large rename. If the answer has \`ok: false\`
47913
+ under \`leftAloneOtherSymbols\`; a common name's lines go to the file \`leftAloneDetail\` names. Pass \`preview\` first on a large rename. If the answer has \`ok: false\`
47835
47914
  beside \`written: true\`, the files were changed and a script did not compile afterwards.
47836
47915
 
47837
47916
  Editing a file while a game is running does not change the running game. \`auto_reload\` is an
package/build/index.js CHANGED
@@ -10238,9 +10238,9 @@ import {
10238
10238
  copyFileSync,
10239
10239
  existsSync as existsSync16,
10240
10240
  fstatSync,
10241
- mkdirSync as mkdirSync11,
10241
+ mkdirSync as mkdirSync12,
10242
10242
  openSync as openSync3,
10243
- readdirSync as readdirSync8,
10243
+ readdirSync as readdirSync9,
10244
10244
  readFileSync as readFileSync19,
10245
10245
  readSync as readSync2,
10246
10246
  rmdirSync as rmdirSync2,
@@ -24828,6 +24828,13 @@ function alive(pid) {
24828
24828
  }
24829
24829
  }
24830
24830
 
24831
+ // src/answer-json.ts
24832
+ var SCALAR = String.raw`(?:-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)`;
24833
+ var SCALAR_ARRAY = new RegExp(String.raw`\[\n\s+(${SCALAR}(?:,\n\s+${SCALAR})*)\n\s*\]`, "g");
24834
+ function answerJson(value) {
24835
+ return JSON.stringify(value, null, 2).replace(SCALAR_ARRAY, (_whole, items) => `[${items.split(/,\n\s+/).join(", ")}]`);
24836
+ }
24837
+
24831
24838
  // src/boot-note.ts
24832
24839
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
24833
24840
  import { dirname as dirname2, join as join3 } from "node:path";
@@ -27580,7 +27587,7 @@ async function handleDAPTool(client, toolName, args) {
27580
27587
  }
27581
27588
  const result = await client.setBreakpoint(safeArgs.scriptPath, safeArgs.line);
27582
27589
  return {
27583
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
27590
+ content: [{ type: "text", text: answerJson(result) }]
27584
27591
  };
27585
27592
  }
27586
27593
  case "dap_remove_breakpoint": {
@@ -27589,7 +27596,7 @@ async function handleDAPTool(client, toolName, args) {
27589
27596
  }
27590
27597
  const result = await client.removeBreakpoint(safeArgs.scriptPath, safeArgs.line);
27591
27598
  return {
27592
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
27599
+ content: [{ type: "text", text: answerJson(result) }]
27593
27600
  };
27594
27601
  }
27595
27602
  case "dap_continue": {
@@ -27604,12 +27611,12 @@ async function handleDAPTool(client, toolName, args) {
27604
27611
  heldAgain,
27605
27612
  note: `The game had not stopped again ${client.stepPatienceMs / 1000}s after the step: the step is still running, it went on without stopping anywhere, or the game was not held when it was asked. debug_state stack answers once it stops.`
27606
27613
  };
27607
- return { content: [{ type: "text", text: JSON.stringify(answer, null, 2) }] };
27614
+ return { content: [{ type: "text", text: answerJson(answer) }] };
27608
27615
  }
27609
27616
  case "dap_get_stack_trace": {
27610
27617
  const stack = await client.getStackTrace();
27611
27618
  return {
27612
- content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
27619
+ content: [{ type: "text", text: answerJson(stack) }]
27613
27620
  };
27614
27621
  }
27615
27622
  case "dap_get_variables": {
@@ -27622,7 +27629,7 @@ async function handleDAPTool(client, toolName, args) {
27622
27629
  note: `The values of ${missing.join(", ")} did not arrive from the game within ${client.scopesPatienceMs / 1000}s, so they are unknown rather than empty. Ask again.`
27623
27630
  };
27624
27631
  return {
27625
- content: [{ type: "text", text: JSON.stringify(answer, null, 2) }]
27632
+ content: [{ type: "text", text: answerJson(answer) }]
27626
27633
  };
27627
27634
  }
27628
27635
  default:
@@ -30082,7 +30089,7 @@ function asToolResponse(payload) {
30082
30089
  content: [
30083
30090
  {
30084
30091
  type: "text",
30085
- text: JSON.stringify(payload, null, 2)
30092
+ text: answerJson(payload)
30086
30093
  }
30087
30094
  ]
30088
30095
  };
@@ -30798,10 +30805,17 @@ class PlanBuilder {
30798
30805
  }
30799
30806
  function isWholeString(text, offset, name) {
30800
30807
  const before = text[offset - 1];
30801
- return (before === '"' || before === "'") && text[offset + name.length] === before;
30808
+ if (before !== '"' && before !== "'" || text[offset + name.length] !== before) {
30809
+ return false;
30810
+ }
30811
+ let after = offset + name.length + 1;
30812
+ while (text[after] === " " || text[after] === "\t") {
30813
+ after += 1;
30814
+ }
30815
+ return text[after] !== ":";
30802
30816
  }
30803
30817
  var WHOLE_CLASS_NAME = "the whole string is the class's name, as find_children's type, a comparison with get_global_name() or a list of accepted type names takes it, and it was not changed: if it names this class, it no longer matches anything after the rename, and no compile says so";
30804
- var WHOLE_MEMBER_NAME = "the whole string is the member's name, as call, connect, has_method, get or set take it, and it was not changed: if it names this member, it no longer finds it after the rename, and no compile says so";
30818
+ var WHOLE_MEMBER_NAME = "the whole string is the member's name, as call, connect, has_method, get, set or a subscript on an object take it, and it was not changed: if it names this member, it no longer finds it after the rename, and no compile says so";
30805
30819
  function declaresItsOwn(script, name, except) {
30806
30820
  return script.occurrences(name).some((occurrence) => occurrence.kind === "code" && occurrence.offset !== except && occurrence.declaredAs !== null && occurrence.declaredAs !== "class_name");
30807
30821
  }
@@ -31266,7 +31280,7 @@ function applyRewrites(rewrites) {
31266
31280
  }
31267
31281
 
31268
31282
  // src/rename-symbol.ts
31269
- import { existsSync as existsSync14, realpathSync as realpathSync5 } from "node:fs";
31283
+ import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync6, realpathSync as realpathSync5, rmSync as rmSync6, writeFileSync as writeFileSync10 } from "node:fs";
31270
31284
  import { basename as basename3, dirname as dirname10, join as join16, relative as relative3, resolve as resolve5 } from "node:path";
31271
31285
 
31272
31286
  // src/resources.ts
@@ -31484,7 +31498,7 @@ function readResourceText(uri, getProjectPath) {
31484
31498
  const parsedProject = parseProjectGodot(rawProject);
31485
31499
  return {
31486
31500
  mimeType: "application/json",
31487
- text: JSON.stringify(parsedProject, null, 2)
31501
+ text: answerJson(parsedProject)
31488
31502
  };
31489
31503
  }
31490
31504
  const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
@@ -31791,6 +31805,7 @@ async function renameSymbol(request, services) {
31791
31805
  moved: moveTo === null ? null : { from: declaringPath, to: moveTo },
31792
31806
  scriptsInOrder,
31793
31807
  rewrites,
31808
+ detail: left.detail,
31794
31809
  report: {
31795
31810
  renamed: { what, from: symbol, to: newName, declaredIn: declaringPath, resolvedBy },
31796
31811
  ...took === null ? {} : { languageServer: took },
@@ -31903,6 +31918,10 @@ function leftAloneReport(mentions) {
31903
31918
  lines.push(line);
31904
31919
  }
31905
31920
  };
31921
+ const textOf = new Map;
31922
+ for (const mention of mentions) {
31923
+ textOf.set(`${mention.file}:${String(mention.line)}`, mention.text);
31924
+ }
31906
31925
  for (const mention of mentions) {
31907
31926
  if (PROSE_KINDS.has(mention.kind) && mention.why === undefined) {
31908
31927
  const key = `${mention.file}
@@ -31924,20 +31943,66 @@ ${mention.kind}`;
31924
31943
  }
31925
31944
  }
31926
31945
  const loudFirst = (mention) => mention.kind === "string" && mention.why !== undefined ? 0 : 1;
31946
+ const leftAlone = full.sort((a, b) => loudFirst(a) - loudFirst(b));
31947
+ const inProse = [...prose.values()];
31948
+ const otherSymbols = [...others.entries()].map(([why, files]) => ({
31949
+ why,
31950
+ files: [...files.entries()].map(([file, lines]) => ({ file, lines }))
31951
+ }));
31952
+ const grouped = inProse.reduce((sum, group) => sum + group.lines.length, 0) + otherSymbols.reduce((sum, reason) => sum + reason.files.reduce((n, one) => n + one.lines.length, 0), 0);
31953
+ if (grouped <= LEFT_ALONE_ANSWER_LINES) {
31954
+ return {
31955
+ lists: {
31956
+ leftAlone,
31957
+ ...inProse.length > 0 ? { leftAloneInProse: inProse } : {},
31958
+ ...otherSymbols.length > 0 ? { leftAloneOtherSymbols: otherSymbols } : {}
31959
+ },
31960
+ counts,
31961
+ detail: null
31962
+ };
31963
+ }
31964
+ const withText = (file, lines) => lines.map((line) => ({ line, text: textOf.get(`${file}:${String(line)}`) ?? "" }));
31927
31965
  return {
31928
31966
  lists: {
31929
- leftAlone: full.sort((a, b) => loudFirst(a) - loudFirst(b)),
31930
- ...prose.size > 0 ? { leftAloneInProse: [...prose.values()] } : {},
31931
- ...others.size > 0 ? {
31932
- leftAloneOtherSymbols: [...others.entries()].map(([why, files]) => ({
31933
- why,
31934
- files: [...files.entries()].map(([file, lines]) => ({ file, lines }))
31967
+ leftAlone,
31968
+ leftAloneDetail: {
31969
+ inProse: { files: new Set(inProse.map((group) => group.file)).size, occurrences: counts.inProse },
31970
+ otherSymbols: otherSymbols.map((reason) => ({
31971
+ why: reason.why,
31972
+ files: reason.files.length,
31973
+ occurrences: reason.files.reduce((n, one) => n + one.lines.length, 0)
31935
31974
  }))
31936
- } : {}
31975
+ }
31937
31976
  },
31938
- counts
31977
+ counts,
31978
+ detail: {
31979
+ leftAloneInProse: inProse.map((group) => ({
31980
+ file: group.file,
31981
+ kind: group.kind,
31982
+ lines: withText(group.file, group.lines)
31983
+ })),
31984
+ leftAloneOtherSymbols: otherSymbols.map((reason) => ({
31985
+ why: reason.why,
31986
+ files: reason.files.map((one) => ({ file: one.file, lines: withText(one.file, one.lines) }))
31987
+ }))
31988
+ }
31939
31989
  };
31940
31990
  }
31991
+ var LEFT_ALONE_ANSWER_LINES = 300;
31992
+ var detailWrites = 0;
31993
+ function writeLeftAloneDetail(projectPath, symbol, detail) {
31994
+ const directory = join16(projectPath, ".godot", "gdharness-renames");
31995
+ mkdirSync8(directory, { recursive: true });
31996
+ detailWrites += 1;
31997
+ const path = join16(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
31998
+ writeFileSync10(path, `${JSON.stringify(detail, null, 2)}
31999
+ `, "utf8");
32000
+ const kept = readdirSync6(directory).filter((name) => name.endsWith(".json")).sort().reverse();
32001
+ for (const old of kept.slice(10)) {
32002
+ rmSync6(join16(directory, old), { force: true });
32003
+ }
32004
+ return path;
32005
+ }
31941
32006
  var PROSE_KINDS = new Set(["comment", "doc", "string", "text"]);
31942
32007
  function summaryOf(edits, mentions, rewrites, left) {
31943
32008
  const count = (values) => {
@@ -31957,7 +32022,7 @@ function summaryOf(edits, mentions, rewrites, left) {
31957
32022
  }
31958
32023
 
31959
32024
  // src/restart-note.ts
31960
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync16, rmSync as rmSync6, writeFileSync as writeFileSync10 } from "node:fs";
32025
+ import { mkdirSync as mkdirSync9, readFileSync as readFileSync16, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "node:fs";
31961
32026
  import { dirname as dirname11, join as join17 } from "node:path";
31962
32027
  function restartNotePath(projectPath) {
31963
32028
  return join17(projectPath, ".godot", "gdharness-restart.json");
@@ -31965,8 +32030,8 @@ function restartNotePath(projectPath) {
31965
32030
  function noteRestartBegun(note) {
31966
32031
  const path = restartNotePath(note.projectPath);
31967
32032
  try {
31968
- mkdirSync8(dirname11(path), { recursive: true });
31969
- writeFileSync10(path, `${JSON.stringify(note, null, 2)}
32033
+ mkdirSync9(dirname11(path), { recursive: true });
32034
+ writeFileSync11(path, `${JSON.stringify(note, null, 2)}
31970
32035
  `, "utf8");
31971
32036
  } catch (error) {
31972
32037
  console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
@@ -31989,7 +32054,7 @@ function restartOwed(projectPath) {
31989
32054
  }
31990
32055
  function restartSettled(projectPath) {
31991
32056
  try {
31992
- rmSync6(restartNotePath(projectPath), { force: true });
32057
+ rmSync7(restartNotePath(projectPath), { force: true });
31993
32058
  } catch (error) {
31994
32059
  console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
31995
32060
  }
@@ -31999,14 +32064,14 @@ function restartSettled(projectPath) {
31999
32064
  import { execFile as execFile4 } from "node:child_process";
32000
32065
  import { createHash as createHash3 } from "node:crypto";
32001
32066
  import {
32002
- mkdirSync as mkdirSync9,
32067
+ mkdirSync as mkdirSync10,
32003
32068
  openSync as openSync2,
32004
- readdirSync as readdirSync6,
32069
+ readdirSync as readdirSync7,
32005
32070
  readFileSync as readFileSync17,
32006
32071
  realpathSync as realpathSync6,
32007
- rmSync as rmSync7,
32072
+ rmSync as rmSync8,
32008
32073
  statSync as statSync6,
32009
- writeFileSync as writeFileSync11
32074
+ writeFileSync as writeFileSync12
32010
32075
  } from "node:fs";
32011
32076
  import { basename as basename4, dirname as dirname12, join as join18, resolve as resolve6 } from "node:path";
32012
32077
  import { promisify as promisify4 } from "node:util";
@@ -32040,7 +32105,7 @@ function runRecordPath(projectPath, directory = runtimeDirectory()) {
32040
32105
  }
32041
32106
  var SHARED_NOTE = "run.json";
32042
32107
  function openTranscript(startedAt) {
32043
- mkdirSync9(runsDirectory(), { recursive: true });
32108
+ mkdirSync10(runsDirectory(), { recursive: true });
32044
32109
  const path = join18(runsDirectory(), `run-${startedAt}.log`);
32045
32110
  return { path, fd: openSync2(path, "a") };
32046
32111
  }
@@ -32051,8 +32116,8 @@ function editorNotePath(projectPath, directory = runtimeDirectory()) {
32051
32116
  return join18(directory, "runs", `editor-${projectKey(projectPath)}.json`);
32052
32117
  }
32053
32118
  function writeEditorRunNote(note) {
32054
- mkdirSync9(runsDirectory(), { recursive: true });
32055
- writeFileSync11(editorNotePath(note.projectPath), JSON.stringify(note, null, 2), "utf8");
32119
+ mkdirSync10(runsDirectory(), { recursive: true });
32120
+ writeFileSync12(editorNotePath(note.projectPath), JSON.stringify(note, null, 2), "utf8");
32056
32121
  }
32057
32122
  function readEditorRunNote(projectPath) {
32058
32123
  for (const directory of runtimeDirectories()) {
@@ -32102,7 +32167,7 @@ function everyRunRecord() {
32102
32167
  for (const directory of runtimeDirectories()) {
32103
32168
  let entries;
32104
32169
  try {
32105
- entries = readdirSync6(join18(directory, "runs"));
32170
+ entries = readdirSync7(join18(directory, "runs"));
32106
32171
  } catch {
32107
32172
  continue;
32108
32173
  }
@@ -32322,7 +32387,7 @@ function clearRunRecord(projectPath) {
32322
32387
  const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
32323
32388
  for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
32324
32389
  try {
32325
- rmSync7(path, { force: true });
32390
+ rmSync8(path, { force: true });
32326
32391
  } catch {}
32327
32392
  }
32328
32393
  }
@@ -32339,7 +32404,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
32339
32404
  const kept = new Set(everyRunRecord().map((record) => record.transcript));
32340
32405
  let entries;
32341
32406
  try {
32342
- entries = readdirSync6(directory);
32407
+ entries = readdirSync7(directory);
32343
32408
  } catch {
32344
32409
  return;
32345
32410
  }
@@ -32352,7 +32417,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
32352
32417
  continue;
32353
32418
  }
32354
32419
  try {
32355
- rmSync7(path);
32420
+ rmSync8(path);
32356
32421
  } catch {}
32357
32422
  }
32358
32423
  }
@@ -32363,13 +32428,13 @@ import { createHash as createHash4 } from "node:crypto";
32363
32428
  import {
32364
32429
  cpSync,
32365
32430
  existsSync as existsSync15,
32366
- mkdirSync as mkdirSync10,
32367
- readdirSync as readdirSync7,
32431
+ mkdirSync as mkdirSync11,
32432
+ readdirSync as readdirSync8,
32368
32433
  readFileSync as readFileSync18,
32369
32434
  rmdirSync,
32370
- rmSync as rmSync8,
32435
+ rmSync as rmSync9,
32371
32436
  statSync as statSync7,
32372
- writeFileSync as writeFileSync12
32437
+ writeFileSync as writeFileSync13
32373
32438
  } from "node:fs";
32374
32439
  import { dirname as dirname13, join as join19 } from "node:path";
32375
32440
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -32391,7 +32456,7 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
32391
32456
  function addonsDigest(names, from) {
32392
32457
  const hash = createHash4("sha256");
32393
32458
  const walk = (directory, relative) => {
32394
- for (const name of readdirSync7(directory).sort()) {
32459
+ for (const name of readdirSync8(directory).sort()) {
32395
32460
  if (name === VERSION_MARKER || name === DIGEST_MARKER) {
32396
32461
  continue;
32397
32462
  }
@@ -33125,7 +33190,7 @@ var TOOL_SPECS = [
33125
33190
  },
33126
33191
  {
33127
33192
  name: "script_edit",
33128
- description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read, and a script declaring a class_name is put in the project's class list, with registered saying whether it is there. modify makes every addition or none, and answers with the line each one is on in the file written. rename renames a class or a member wherever the engine uses it and writes every file or none. A new name already taken is refused with nothing written, a member's included when a class inheriting it already uses the name in code, as a local or a parameter. changed lists each file with its changed lines as they now read, and leftAlone lists every other whole-word occurrence of the old name with what the tokenizer makes of it (code, string, comment, doc, nodePath, scene, text, or ignored for a file under a .gdignore directory, which is never changed) and, where it is not plain, why: a string that is the old name whole, such as find_children's type or a call's method name, which nothing renames or compiles and which is listed first; a node named like the class; a call on a value whose type the analyser could not tell. A member rename answers under languageServer how many requests it made and how long they took. A comment, documentation line, string or text file line with nothing more to say is given under leftAloneInProse by file, kind and line instead, and code that is certainly another symbol of the same name, a bare name in a file where nothing inherits the member or one the language server resolved to another declaration, under leftAloneOtherSymbols as each why with the files and lines it covers. summary.leftAlone counts each of the three lists beside the total. Read both before calling the rename done; nothing in them was changed. Afterwards an open editor scans the changed files and reloads every changed script, which is also the compile check, and its answer comes back under editor; with no editor the class cache is rebuilt and the changed scripts are compiled in a fresh engine, under classCache and compiled. written is true once the files are written, and ok is false when what came after the write failed: the files are left changed, and note says so.",
33193
+ description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read, and a script declaring a class_name is put in the project's class list, with registered saying whether it is there. modify makes every addition or none, and answers with the line each one is on in the file written. rename renames a class or a member wherever the engine uses it and writes every file or none. A new name already taken is refused with nothing written, a member's included when a class inheriting it already uses the name in code, as a local or a parameter. changed lists each file with its changed lines as they now read, and leftAlone lists every other whole-word occurrence of the old name with what the tokenizer makes of it (code, string, comment, doc, nodePath, scene, text, or ignored for a file under a .gdignore directory, which is never changed) and, where it is not plain, why: a string that is the old name whole, such as find_children's type or a call's method name, which nothing renames or compiles and which is listed first; a node named like the class; a call on a value whose type the analyser could not tell. A member rename answers under languageServer how many requests it made and how long they took. A comment, documentation line, string or text file line with nothing more to say is given under leftAloneInProse by file, kind and line instead, and code that is certainly another symbol of the same name, a bare name in a file where nothing inherits the member or one the language server resolved to another declaration, under leftAloneOtherSymbols as each why with the files and lines it covers. summary.leftAlone counts each of the three lists beside the total. When those two grouped lists run past 300 lines, they are written with each line's text to a file under the project's .godot directory instead, and leftAloneDetail names the file and counts what is in it. Read both before calling the rename done; nothing in them was changed. Afterwards an open editor scans the changed files and reloads every changed script, which is also the compile check, and its answer comes back under editor; with no editor the class cache is rebuilt and the changed scripts are compiled in a fresh engine, under classCache and compiled. written is true once the files are written, and ok is false when what came after the write failed: the files are left changed, and note says so.",
33129
33194
  parameters: {
33130
33195
  projectPath: PROJECT_PATH,
33131
33196
  scriptPath: SCRIPT_PATH,
@@ -34411,7 +34476,7 @@ function exportAnswer(ending, asked, before, after) {
34411
34476
  ...problems.omitted === 0 ? {} : { entriesOmitted: problems.omitted }
34412
34477
  };
34413
34478
  if (verdict.exported) {
34414
- return { content: [{ type: "text", text: JSON.stringify(verdict, null, 2) }] };
34479
+ return { content: [{ type: "text", text: answerJson(verdict) }] };
34415
34480
  }
34416
34481
  const stale = !written && after !== null ? ` The ${asked.outputPath} there is from before this export, which did not write it.` : "";
34417
34482
  const why = ending.exitSignal === null ? stale : ` The engine was ${howItEnded(ending)}, so it has no exit code.${stale}`;
@@ -34421,7 +34486,7 @@ function exportAnswer(ending, asked, before, after) {
34421
34486
  type: "text",
34422
34487
  text: `Export with preset '${asked.preset}' did not produce ${asked.outputPath}.${why}`
34423
34488
  },
34424
- { type: "text", text: JSON.stringify(verdict, null, 2) }
34489
+ { type: "text", text: answerJson(verdict) }
34425
34490
  ],
34426
34491
  isError: true
34427
34492
  };
@@ -34568,7 +34633,7 @@ function elsewhereIn(projectPath, asked) {
34568
34633
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
34569
34634
  let near = [];
34570
34635
  try {
34571
- near = readdirSync8(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
34636
+ near = readdirSync9(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
34572
34637
  } catch {
34573
34638
  return "";
34574
34639
  }
@@ -34670,7 +34735,7 @@ class GodotServer {
34670
34735
  return response;
34671
34736
  }
34672
34737
  jsonTextResponse(payload) {
34673
- return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
34738
+ return { content: [{ type: "text", text: answerJson(payload) }] };
34674
34739
  }
34675
34740
  async run() {
34676
34741
  const located = await this.locator.find();
@@ -35003,7 +35068,7 @@ class GodotServer {
35003
35068
  saying(answer, block) {
35004
35069
  return {
35005
35070
  ...answer,
35006
- content: [...answer.content, { type: "text", text: JSON.stringify(block, null, 2) }]
35071
+ content: [...answer.content, { type: "text", text: answerJson(block) }]
35007
35072
  };
35008
35073
  }
35009
35074
  projectHasMovedOn() {
@@ -35976,7 +36041,7 @@ class GodotServer {
35976
36041
  if (!outcome.ok) {
35977
36042
  const response = this.createErrorResponse(outcome.message);
35978
36043
  if (Object.keys(extras).length > 0) {
35979
- response.content.push({ type: "text", text: JSON.stringify(extras, null, 2) });
36044
+ response.content.push({ type: "text", text: answerJson(extras) });
35980
36045
  }
35981
36046
  return response;
35982
36047
  }
@@ -36054,7 +36119,7 @@ class GodotServer {
36054
36119
  }
36055
36120
  const folder = dirname14(output.absolutePath);
36056
36121
  try {
36057
- mkdirSync11(folder, { recursive: true });
36122
+ mkdirSync12(folder, { recursive: true });
36058
36123
  } catch (error) {
36059
36124
  return this.createErrorResponse(`Cannot create ${dirname14(output.relativePath)} to export into: ${errorMessage(error)}`);
36060
36125
  }
@@ -36165,7 +36230,7 @@ class GodotServer {
36165
36230
  let report = null;
36166
36231
  let reportProblem = null;
36167
36232
  try {
36168
- const written = existsSync16(reportsDir) ? readdirSync8(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
36233
+ const written = existsSync16(reportsDir) ? readdirSync9(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
36169
36234
  const newest = written.at(-1);
36170
36235
  if (newest !== undefined) {
36171
36236
  report = parseJUnit(readFileSync19(join20(reportsDir, newest, "results.xml"), "utf8"));
@@ -38676,6 +38741,15 @@ class GodotServer {
38676
38741
  const said = outcome.conflicts === undefined ? "" : ` ${outcome.conflicts.join("; ")}.`;
38677
38742
  return this.createErrorResponse(`${outcome.reason}${said}`, outcome.advice ?? []);
38678
38743
  }
38744
+ if (outcome.detail !== null) {
38745
+ const file = writeLeftAloneDetail(projectPath, symbol, outcome.detail);
38746
+ const folded = asParams(outcome.report["leftAloneDetail"]);
38747
+ outcome.report["leftAloneDetail"] = {
38748
+ file,
38749
+ note: `Too many lines were left alone to list here, and none of them is a use: every comment, documentation line, string and text file line that only shares the word, and every other symbol of the same name, is in ${file} with its text.`,
38750
+ ...folded
38751
+ };
38752
+ }
38679
38753
  if (readBoolean(args, "preview") === true) {
38680
38754
  return this.jsonTextResponse({ preview: true, ...outcome.report });
38681
38755
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.1.38",
3
+ "version": "1.1.39",
4
4
  "mcpName": "io.github.Aureliolo/gdharness",
5
5
  "description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
6
6
  "type": "module",