gdharness 0.12.10 → 0.12.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/cli.js CHANGED
@@ -7583,6 +7583,8 @@ async function runOperation2(engine, operation, params, projectPath) {
7583
7583
  writeFileSync4(paramsFile, JSON.stringify(snakeCased2(params)), "utf8");
7584
7584
  const args = [
7585
7585
  "--headless",
7586
+ "--log-file",
7587
+ join7(paramsDir, "engine.log"),
7586
7588
  "--path",
7587
7589
  projectPath,
7588
7590
  "--script",
@@ -15324,7 +15326,7 @@ var init_tool_definitions = __esm(() => {
15324
15326
  },
15325
15327
  {
15326
15328
  name: "project_search",
15327
- description: "Searches text or a regular expression across project files and returns file paths with line numbers.",
15329
+ description: "Searches text or a regular expression across project files and returns file paths with line numbers. What the engine steps over is not searched: directories spelled with a dot, node_modules, and any directory holding a .gdignore. A vendored engine or an export directory is therefore absent from the results rather than matching the code you are looking for in a copy nobody runs.",
15328
15330
  parameters: {
15329
15331
  projectPath: PROJECT_PATH,
15330
15332
  query: { type: "string", description: "The text or pattern to find." },
@@ -35786,13 +35788,22 @@ var init_lsp_client = __esm(() => {
35786
35788
  });
35787
35789
 
35788
35790
  // src/project-scan.ts
35789
- import { readdirSync as readdirSync4, readFileSync as readFileSync10 } from "node:fs";
35791
+ import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync10 } from "node:fs";
35790
35792
  import { join as join12 } from "node:path";
35793
+ function skipped(name) {
35794
+ return name.startsWith(".") || SKIPPED.has(name);
35795
+ }
35796
+ function steppedOver(directory) {
35797
+ return existsSync10(join12(directory, ".gdignore"));
35798
+ }
35791
35799
  function projectStructure(projectPath) {
35792
35800
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
35793
35801
  const visit = (directory) => {
35802
+ if (steppedOver(directory)) {
35803
+ return;
35804
+ }
35794
35805
  for (const entry of readdirSync4(directory, { withFileTypes: true })) {
35795
- if (entry.name.startsWith(".")) {
35806
+ if (skipped(entry.name)) {
35796
35807
  continue;
35797
35808
  }
35798
35809
  if (entry.isDirectory()) {
@@ -35831,11 +35842,14 @@ function searchProject(projectPath, options) {
35831
35842
  return false;
35832
35843
  };
35833
35844
  const visit = (directory) => {
35845
+ if (steppedOver(directory)) {
35846
+ return;
35847
+ }
35834
35848
  for (const entry of readdirSync4(directory, { withFileTypes: true })) {
35835
35849
  if (full()) {
35836
35850
  return;
35837
35851
  }
35838
- if (SKIPPED.has(entry.name)) {
35852
+ if (skipped(entry.name)) {
35839
35853
  continue;
35840
35854
  }
35841
35855
  const entryPath = join12(directory, entry.name);
@@ -35872,12 +35886,12 @@ function searchProject(projectPath, options) {
35872
35886
  }
35873
35887
  var SKIPPED, ASSET_EXTENSIONS;
35874
35888
  var init_project_scan = __esm(() => {
35875
- SKIPPED = new Set([".git", ".godot", ".import", "node_modules"]);
35889
+ SKIPPED = new Set(["node_modules"]);
35876
35890
  ASSET_EXTENSIONS = new Set(["png", "jpg", "jpeg", "webp", "svg", "ttf", "otf", "wav", "mp3", "ogg"]);
35877
35891
  });
35878
35892
 
35879
35893
  // src/runtime-client.ts
35880
- import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync11, unlinkSync } from "node:fs";
35894
+ import { existsSync as existsSync11, readdirSync as readdirSync5, readFileSync as readFileSync11, unlinkSync } from "node:fs";
35881
35895
  import { createConnection as createConnection3 } from "node:net";
35882
35896
  import { tmpdir as tmpdir4 } from "node:os";
35883
35897
  import { join as join13, resolve as resolve4 } from "node:path";
@@ -35948,7 +35962,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
35948
35962
  return runtimesAnnounced(directories).running;
35949
35963
  }
35950
35964
  function announcedIn(directory) {
35951
- if (!existsSync10(directory)) {
35965
+ if (!existsSync11(directory)) {
35952
35966
  return [];
35953
35967
  }
35954
35968
  const found = [];
@@ -36285,11 +36299,11 @@ var init_run_record = __esm(() => {
36285
36299
  });
36286
36300
 
36287
36301
  // src/setup.ts
36288
- import { cpSync as cpSync2, existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync13, rmSync as rmSync8, writeFileSync as writeFileSync9 } from "node:fs";
36302
+ import { cpSync as cpSync2, existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync13, rmSync as rmSync8, writeFileSync as writeFileSync9 } from "node:fs";
36289
36303
  import { dirname as dirname8, join as join15 } from "node:path";
36290
36304
  function installedAddonVersion(projectPath) {
36291
36305
  const marker = join15(projectPath, "addons", ADDONS2[0], VERSION_MARKER2);
36292
- if (!existsSync11(marker)) {
36306
+ if (!existsSync12(marker)) {
36293
36307
  return null;
36294
36308
  }
36295
36309
  const said = readFileSync13(marker, "utf8").trim();
@@ -36312,7 +36326,7 @@ var init_setup = __esm(() => {
36312
36326
  import { execFile as execFile5, spawn } from "node:child_process";
36313
36327
  import {
36314
36328
  closeSync,
36315
- existsSync as existsSync12,
36329
+ existsSync as existsSync13,
36316
36330
  fstatSync,
36317
36331
  mkdirSync as mkdirSync8,
36318
36332
  mkdtempSync as mkdtempSync3,
@@ -36460,7 +36474,7 @@ function aboveTheRunner(entry) {
36460
36474
  }
36461
36475
  function elsewhereIn(projectPath, asked) {
36462
36476
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
36463
- if (wanted === "" || existsSync12(join16(projectPath, wanted))) {
36477
+ if (wanted === "" || existsSync13(join16(projectPath, wanted))) {
36464
36478
  return "";
36465
36479
  }
36466
36480
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -37077,7 +37091,7 @@ class GodotServer {
37077
37091
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
37078
37092
  }
37079
37093
  const file = join16(path, "project.godot");
37080
- if (!existsSync12(file)) {
37094
+ if (!existsSync13(file)) {
37081
37095
  return {
37082
37096
  ok: false,
37083
37097
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -37341,7 +37355,7 @@ class GodotServer {
37341
37355
  log.finish();
37342
37356
  const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
37343
37357
  const verdict = {
37344
- exported: exitCode === 0 && log.count("error") === 0 && existsSync12(output.absolutePath),
37358
+ exported: exitCode === 0 && log.count("error") === 0 && existsSync13(output.absolutePath),
37345
37359
  preset,
37346
37360
  outputPath: output.relativePath,
37347
37361
  debug,
@@ -37371,7 +37385,7 @@ class GodotServer {
37371
37385
  return contained.response;
37372
37386
  }
37373
37387
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
37374
- if (!existsSync12(join16(project.value.path, runner))) {
37388
+ if (!existsSync13(join16(project.value.path, runner))) {
37375
37389
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
37376
37390
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
37377
37391
  ]);
@@ -37425,7 +37439,7 @@ class GodotServer {
37425
37439
  let report = null;
37426
37440
  let reportProblem = null;
37427
37441
  try {
37428
- const written = existsSync12(reportsDir) ? readdirSync7(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
37442
+ const written = existsSync13(reportsDir) ? readdirSync7(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
37429
37443
  const newest = written.at(-1);
37430
37444
  if (newest !== undefined) {
37431
37445
  report = parseJUnit(readFileSync14(join16(reportsDir, newest, "results.xml"), "utf8"));
@@ -37845,7 +37859,7 @@ class GodotServer {
37845
37859
  return runIsUp(run, editorSays);
37846
37860
  }
37847
37861
  async runtimeUp(projectPath, before, budgetMs) {
37848
- if (!existsSync12(join16(projectPath, RUNTIME_AUTOLOAD2.path))) {
37862
+ if (!existsSync13(join16(projectPath, RUNTIME_AUTOLOAD2.path))) {
37849
37863
  return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false });
37850
37864
  }
37851
37865
  const endpoint = await announcedSince(projectPath, before, {
@@ -38475,7 +38489,7 @@ var init_server3 = __esm(() => {
38475
38489
  });
38476
38490
 
38477
38491
  // src/cli.ts
38478
- import { existsSync as existsSync13 } from "fs";
38492
+ import { existsSync as existsSync14 } from "fs";
38479
38493
  import { join as join17, resolve as resolve5 } from "path";
38480
38494
 
38481
38495
  // src/errors.ts
@@ -39407,6 +39421,8 @@ async function runOperation(engine, operation, params, projectPath) {
39407
39421
  writeFileSync2(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
39408
39422
  const args = [
39409
39423
  "--headless",
39424
+ "--log-file",
39425
+ join4(paramsDir, "engine.log"),
39410
39426
  "--path",
39411
39427
  projectPath,
39412
39428
  "--script",
@@ -39887,9 +39903,17 @@ breakpoint on the line you want instead.
39887
39903
  \`scene_signal\` and \`resource_edit\` change it through the editor, so the files keep their
39888
39904
  formatting. \`script_diagnostics\` and \`script_info\` come from the editor's own language server.
39889
39905
 
39890
- \`project_info\`, \`project_dependencies\`, \`project_import\`, \`project_test\` and
39906
+ \`project_info\`, \`project_dependencies\`, \`project_settings\`, \`project_import\`,
39907
+ \`project_export\`, \`project_test\`, \`script_edit\`, \`script_info structure\` and
39891
39908
  \`editor_classes\` need nothing open: they run a short headless engine and are gone before the
39892
- answer is printed.
39909
+ answer is printed. \`project_settings get\` is on that list too, reading as it does like a look at
39910
+ a config file: it starts an engine so that a setting nobody wrote down still answers with the
39911
+ default the engine registers for it.
39912
+
39913
+ Editing a file while a game is running does not change the running game. \`auto_reload\` is an
39914
+ editor plugin and reloads into the editor's own process; the game is a separate process holding
39915
+ its own copy. So a long run is not a reason to stop editing, and the new code arrives on the next
39916
+ \`editor_run start\`.
39893
39917
 
39894
39918
  A wall of "Could not find type" on the project's own classes means the editor is not holding
39895
39919
  them. \`editor_rescan\` asks it to scan and names any class it still cannot resolve under
@@ -40018,7 +40042,7 @@ class UsageError extends Refusal {
40018
40042
  function projectArgument(at) {
40019
40043
  const named = args.slice(at).find((value) => !value.startsWith("--"));
40020
40044
  const projectPath = resolve5(named ?? ".");
40021
- if (!existsSync13(join17(projectPath, "project.godot"))) {
40045
+ if (!existsSync14(join17(projectPath, "project.godot"))) {
40022
40046
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
40023
40047
  }
40024
40048
  return projectPath;
@@ -40052,7 +40076,7 @@ async function registerRuntime(godot, projectPath) {
40052
40076
  return;
40053
40077
  }
40054
40078
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
40055
- if (!existsSync13(join17(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
40079
+ if (!existsSync14(join17(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
40056
40080
  console.log(` that file is not in this project, so it boots with a missing script: restore it, or point the entry at res://${RUNTIME_AUTOLOAD.path}`);
40057
40081
  }
40058
40082
  }
@@ -40184,7 +40208,7 @@ async function setup() {
40184
40208
  console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
40185
40209
  }
40186
40210
  if (!args.includes("--no-skill")) {
40187
- const directories = skillDirectories(chosen.harnesses, projectPath, existsSync13);
40211
+ const directories = skillDirectories(chosen.harnesses, projectPath, existsSync14);
40188
40212
  for (const written of writeSkill(directories, getLocalVersion())) {
40189
40213
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
40190
40214
  }
@@ -40273,7 +40297,7 @@ async function upgrade() {
40273
40297
  if (already.length === 0) {
40274
40298
  console.log("no harness config names gdharness, so none was re-pinned");
40275
40299
  }
40276
- for (const written of writeSkill(skillDirectories(already, projectPath, existsSync13), version)) {
40300
+ for (const written of writeSkill(skillDirectories(already, projectPath, existsSync14), version)) {
40277
40301
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
40278
40302
  }
40279
40303
  console.log(`
package/build/index.js CHANGED
@@ -10134,7 +10134,7 @@ function defectReport(where, error, godotVersion) {
10134
10134
  import { execFile as execFile3, spawn } from "node:child_process";
10135
10135
  import {
10136
10136
  closeSync,
10137
- existsSync as existsSync7,
10137
+ existsSync as existsSync8,
10138
10138
  fstatSync,
10139
10139
  mkdirSync as mkdirSync5,
10140
10140
  mkdtempSync as mkdtempSync2,
@@ -26299,6 +26299,8 @@ async function runOperation(engine, operation, params, projectPath) {
26299
26299
  writeFileSync3(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
26300
26300
  const args = [
26301
26301
  "--headless",
26302
+ "--log-file",
26303
+ join5(paramsDir, "engine.log"),
26302
26304
  "--path",
26303
26305
  projectPath,
26304
26306
  "--script",
@@ -27196,15 +27198,24 @@ async function handleLSPTool(client, toolName, args) {
27196
27198
  }
27197
27199
 
27198
27200
  // src/project-scan.ts
27199
- import { readdirSync as readdirSync3, readFileSync as readFileSync5 } from "node:fs";
27201
+ import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "node:fs";
27200
27202
  import { join as join6 } from "node:path";
27201
- var SKIPPED = new Set([".git", ".godot", ".import", "node_modules"]);
27203
+ var SKIPPED = new Set(["node_modules"]);
27204
+ function skipped(name) {
27205
+ return name.startsWith(".") || SKIPPED.has(name);
27206
+ }
27207
+ function steppedOver(directory) {
27208
+ return existsSync5(join6(directory, ".gdignore"));
27209
+ }
27202
27210
  var ASSET_EXTENSIONS = new Set(["png", "jpg", "jpeg", "webp", "svg", "ttf", "otf", "wav", "mp3", "ogg"]);
27203
27211
  function projectStructure(projectPath) {
27204
27212
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
27205
27213
  const visit = (directory) => {
27214
+ if (steppedOver(directory)) {
27215
+ return;
27216
+ }
27206
27217
  for (const entry of readdirSync3(directory, { withFileTypes: true })) {
27207
- if (entry.name.startsWith(".")) {
27218
+ if (skipped(entry.name)) {
27208
27219
  continue;
27209
27220
  }
27210
27221
  if (entry.isDirectory()) {
@@ -27243,11 +27254,14 @@ function searchProject(projectPath, options) {
27243
27254
  return false;
27244
27255
  };
27245
27256
  const visit = (directory) => {
27257
+ if (steppedOver(directory)) {
27258
+ return;
27259
+ }
27246
27260
  for (const entry of readdirSync3(directory, { withFileTypes: true })) {
27247
27261
  if (full()) {
27248
27262
  return;
27249
27263
  }
27250
- if (SKIPPED.has(entry.name)) {
27264
+ if (skipped(entry.name)) {
27251
27265
  continue;
27252
27266
  }
27253
27267
  const entryPath = join6(directory, entry.name);
@@ -27506,7 +27520,7 @@ import { mkdirSync as mkdirSync3, openSync, readdirSync as readdirSync5, readFil
27506
27520
  import { basename, join as join8 } from "node:path";
27507
27521
 
27508
27522
  // src/runtime-client.ts
27509
- import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync7, unlinkSync } from "node:fs";
27523
+ import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync7, unlinkSync } from "node:fs";
27510
27524
  import { createConnection as createConnection3 } from "node:net";
27511
27525
  import { tmpdir as tmpdir3 } from "node:os";
27512
27526
  import { join as join7, resolve as resolve4 } from "node:path";
@@ -27632,7 +27646,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
27632
27646
  return runtimesAnnounced(directories).running;
27633
27647
  }
27634
27648
  function announcedIn(directory) {
27635
- if (!existsSync5(directory)) {
27649
+ if (!existsSync6(directory)) {
27636
27650
  return [];
27637
27651
  }
27638
27652
  const found = [];
@@ -27962,7 +27976,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
27962
27976
  }
27963
27977
 
27964
27978
  // src/setup.ts
27965
- import { cpSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync9, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "node:fs";
27979
+ import { cpSync, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync9, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "node:fs";
27966
27980
  import { dirname as dirname3, join as join9 } from "node:path";
27967
27981
  var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
27968
27982
  var RUNTIME_AUTOLOAD = {
@@ -27972,7 +27986,7 @@ var RUNTIME_AUTOLOAD = {
27972
27986
  var VERSION_MARKER = ".gdharness-version";
27973
27987
  function installedAddonVersion(projectPath) {
27974
27988
  const marker = join9(projectPath, "addons", ADDONS[0], VERSION_MARKER);
27975
- if (!existsSync6(marker)) {
27989
+ if (!existsSync7(marker)) {
27976
27990
  return null;
27977
27991
  }
27978
27992
  const said = readFileSync9(marker, "utf8").trim();
@@ -28199,7 +28213,7 @@ var TOOL_SPECS = [
28199
28213
  },
28200
28214
  {
28201
28215
  name: "project_search",
28202
- description: "Searches text or a regular expression across project files and returns file paths with line numbers.",
28216
+ description: "Searches text or a regular expression across project files and returns file paths with line numbers. What the engine steps over is not searched: directories spelled with a dot, node_modules, and any directory holding a .gdignore. A vendored engine or an export directory is therefore absent from the results rather than matching the code you are looking for in a copy nobody runs.",
28203
28217
  parameters: {
28204
28218
  projectPath: PROJECT_PATH,
28205
28219
  query: { type: "string", description: "The text or pattern to find." },
@@ -29242,7 +29256,7 @@ function aboveTheRunner(entry) {
29242
29256
  }
29243
29257
  function elsewhereIn(projectPath, asked) {
29244
29258
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
29245
- if (wanted === "" || existsSync7(join10(projectPath, wanted))) {
29259
+ if (wanted === "" || existsSync8(join10(projectPath, wanted))) {
29246
29260
  return "";
29247
29261
  }
29248
29262
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -29859,7 +29873,7 @@ class GodotServer {
29859
29873
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
29860
29874
  }
29861
29875
  const file = join10(path, "project.godot");
29862
- if (!existsSync7(file)) {
29876
+ if (!existsSync8(file)) {
29863
29877
  return {
29864
29878
  ok: false,
29865
29879
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -30123,7 +30137,7 @@ class GodotServer {
30123
30137
  log.finish();
30124
30138
  const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
30125
30139
  const verdict = {
30126
- exported: exitCode === 0 && log.count("error") === 0 && existsSync7(output.absolutePath),
30140
+ exported: exitCode === 0 && log.count("error") === 0 && existsSync8(output.absolutePath),
30127
30141
  preset,
30128
30142
  outputPath: output.relativePath,
30129
30143
  debug,
@@ -30153,7 +30167,7 @@ class GodotServer {
30153
30167
  return contained.response;
30154
30168
  }
30155
30169
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
30156
- if (!existsSync7(join10(project.value.path, runner))) {
30170
+ if (!existsSync8(join10(project.value.path, runner))) {
30157
30171
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
30158
30172
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
30159
30173
  ]);
@@ -30207,7 +30221,7 @@ class GodotServer {
30207
30221
  let report = null;
30208
30222
  let reportProblem = null;
30209
30223
  try {
30210
- const written = existsSync7(reportsDir) ? readdirSync6(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
30224
+ const written = existsSync8(reportsDir) ? readdirSync6(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
30211
30225
  const newest = written.at(-1);
30212
30226
  if (newest !== undefined) {
30213
30227
  report = parseJUnit(readFileSync10(join10(reportsDir, newest, "results.xml"), "utf8"));
@@ -30627,7 +30641,7 @@ class GodotServer {
30627
30641
  return runIsUp(run, editorSays);
30628
30642
  }
30629
30643
  async runtimeUp(projectPath, before, budgetMs) {
30630
- if (!existsSync7(join10(projectPath, RUNTIME_AUTOLOAD.path))) {
30644
+ if (!existsSync8(join10(projectPath, RUNTIME_AUTOLOAD.path))) {
30631
30645
  return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false });
30632
30646
  }
30633
30647
  const endpoint = await announcedSince(projectPath, before, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "0.12.10",
3
+ "version": "0.12.12",
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",