@skillcap/gdh 0.25.1 → 0.25.2

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 (28) hide show
  1. package/INSTALL-BUNDLE.json +1 -1
  2. package/RELEASE-SPAN-UPDATE-CONTRACTS.json +57 -0
  3. package/node_modules/@gdh/adapters/dist/index.d.ts +10 -9
  4. package/node_modules/@gdh/adapters/dist/index.d.ts.map +1 -1
  5. package/node_modules/@gdh/adapters/dist/index.js +256 -49
  6. package/node_modules/@gdh/adapters/dist/index.js.map +1 -1
  7. package/node_modules/@gdh/adapters/package.json +8 -8
  8. package/node_modules/@gdh/authoring/package.json +2 -2
  9. package/node_modules/@gdh/cli/dist/index.js +7 -7
  10. package/node_modules/@gdh/cli/dist/index.js.map +1 -1
  11. package/node_modules/@gdh/cli/package.json +10 -10
  12. package/node_modules/@gdh/core/dist/index.d.ts +1 -1
  13. package/node_modules/@gdh/core/dist/index.js +1 -1
  14. package/node_modules/@gdh/core/dist/migrations/managed-surface-classes.d.ts +7 -7
  15. package/node_modules/@gdh/core/dist/migrations/managed-surface-classes.js +9 -9
  16. package/node_modules/@gdh/core/dist/migrations/managed-surface-classes.js.map +1 -1
  17. package/node_modules/@gdh/core/dist/migrations/managed-target-surface-inventory.d.ts +4 -4
  18. package/node_modules/@gdh/core/dist/migrations/managed-target-surface-inventory.d.ts.map +1 -1
  19. package/node_modules/@gdh/core/dist/migrations/managed-target-surface-inventory.js +9 -7
  20. package/node_modules/@gdh/core/dist/migrations/managed-target-surface-inventory.js.map +1 -1
  21. package/node_modules/@gdh/core/package.json +1 -1
  22. package/node_modules/@gdh/docs/package.json +2 -2
  23. package/node_modules/@gdh/mcp/package.json +8 -8
  24. package/node_modules/@gdh/observability/package.json +2 -2
  25. package/node_modules/@gdh/runtime/package.json +2 -2
  26. package/node_modules/@gdh/scan/package.json +3 -3
  27. package/node_modules/@gdh/verify/package.json +7 -7
  28. package/package.json +11 -11
@@ -54,7 +54,7 @@ export const CLAUDE_ONBOARD_SKILL_RELATIVE_PATH = ".claude/skills/gdh-onboard/SK
54
54
  export const PROJECT_MCP_RELATIVE_PATH = ".mcp.json";
55
55
  export const CURSOR_MCP_RELATIVE_PATH = ".cursor/mcp.json";
56
56
  export const CURSOR_RULE_RELATIVE_PATH = ".cursor/rules/gdh-agent.mdc";
57
- export const CURSOR_ONBOARD_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-onboard/SKILL.md";
57
+ export const CURSOR_ONBOARD_COMMAND_RELATIVE_PATH = ".cursor/commands/gdh-onboard.md";
58
58
  export const CODEX_ONBOARD_SKILL_RELATIVE_PATH = ".agents/skills/gdh-onboard/SKILL.md";
59
59
  export const CLAUDE_STATUS_SKILL_RELATIVE_PATH = ".claude/skills/gdh-status/SKILL.md";
60
60
  export const CLAUDE_MIGRATE_SKILL_RELATIVE_PATH = ".claude/skills/gdh-migrate/SKILL.md";
@@ -66,11 +66,10 @@ export const CODEX_MIGRATE_SKILL_RELATIVE_PATH = ".agents/skills/gdh-migrate/SKI
66
66
  export const CODEX_CHECK_SKILL_RELATIVE_PATH = ".agents/skills/gdh-check/SKILL.md";
67
67
  export const CODEX_PREPARE_SKILL_RELATIVE_PATH = ".agents/skills/gdh-prepare/SKILL.md";
68
68
  export const CODEX_RUN_GAME_SKILL_RELATIVE_PATH = ".agents/skills/gdh-run-game/SKILL.md";
69
- export const CURSOR_STATUS_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-status/SKILL.md";
70
- export const CURSOR_MIGRATE_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-migrate/SKILL.md";
71
- export const CURSOR_CHECK_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-check/SKILL.md";
72
- export const CURSOR_PREPARE_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-prepare/SKILL.md";
73
- export const CURSOR_RUN_GAME_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-run-game/SKILL.md";
69
+ export const CURSOR_STATUS_COMMAND_RELATIVE_PATH = ".cursor/commands/gdh-status.md";
70
+ export const CURSOR_MIGRATE_COMMAND_RELATIVE_PATH = ".cursor/commands/gdh-migrate.md";
71
+ export const CURSOR_PREPARE_COMMAND_RELATIVE_PATH = ".cursor/commands/gdh-prepare.md";
72
+ export const CURSOR_RUN_GAME_COMMAND_RELATIVE_PATH = ".cursor/commands/gdh-run-game.md";
74
73
  // Phase 13 SELF-01: /gdh-update skill surface path constants. The rendered
75
74
  // bodies shell out to `npx -y @skillcap/gdh@latest self-update` (LITERAL
76
75
  // @latest, not the pinned version — D-10) so the NEW CLI performs the update,
@@ -79,7 +78,7 @@ export const CURSOR_RUN_GAME_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-run-game/
79
78
  // rendered bodies are version-agnostic by design.
80
79
  export const CLAUDE_UPDATE_SKILL_RELATIVE_PATH = ".claude/skills/gdh-update/SKILL.md";
81
80
  export const CODEX_UPDATE_SKILL_RELATIVE_PATH = ".agents/skills/gdh-update/SKILL.md";
82
- export const CURSOR_UPDATE_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-update/SKILL.md";
81
+ export const CURSOR_UPDATE_COMMAND_RELATIVE_PATH = ".cursor/commands/gdh-update.md";
83
82
  export const CLAUDE_SCAN_SKILL_RELATIVE_PATH = ".claude/skills/gdh-scan/SKILL.md";
84
83
  export const CODEX_SCAN_SKILL_RELATIVE_PATH = ".agents/skills/gdh-scan/SKILL.md";
85
84
  export const CLAUDE_ONBOARD_COMMAND_RELATIVE_PATH = CLAUDE_ONBOARD_SKILL_RELATIVE_PATH;
@@ -111,7 +110,36 @@ const LEGACY_CODEX_SKILL_RELATIVE_PATHS = [
111
110
  [".codex/skills/gdh-verify/SKILL.md", "gdh-verify"],
112
111
  [".codex/skills/gdh-scan/SKILL.md", "gdh-scan"],
113
112
  ];
114
- export const CURSOR_SCAN_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-scan/SKILL.md";
113
+ const LEGACY_CURSOR_SKILL_RELATIVE_PATHS = [
114
+ [".cursor/skills/gdh-onboard/SKILL.md", "gdh-onboard"],
115
+ [".cursor/skills/gdh-status/SKILL.md", "gdh-status"],
116
+ [".cursor/skills/gdh-migrate/SKILL.md", "gdh-migrate"],
117
+ [".cursor/skills/gdh-update/SKILL.md", "gdh-update"],
118
+ [".cursor/skills/gdh-check/SKILL.md", "gdh-check"],
119
+ [".cursor/skills/gdh-prepare/SKILL.md", "gdh-prepare"],
120
+ [".cursor/skills/gdh-run-game/SKILL.md", "gdh-run-game"],
121
+ [".cursor/skills/gdh-verify/SKILL.md", "gdh-verify"],
122
+ [".cursor/skills/gdh-scan/SKILL.md", "gdh-scan"],
123
+ ];
124
+ export const CURSOR_SCAN_COMMAND_RELATIVE_PATH = ".cursor/commands/gdh-scan.md";
125
+ const CODEX_SKILL_RELATIVE_PATHS = [
126
+ path.dirname(CODEX_ONBOARD_SKILL_RELATIVE_PATH),
127
+ path.dirname(CODEX_STATUS_SKILL_RELATIVE_PATH),
128
+ path.dirname(CODEX_MIGRATE_SKILL_RELATIVE_PATH),
129
+ path.dirname(CODEX_UPDATE_SKILL_RELATIVE_PATH),
130
+ path.dirname(CODEX_PREPARE_SKILL_RELATIVE_PATH),
131
+ path.dirname(CODEX_RUN_GAME_SKILL_RELATIVE_PATH),
132
+ path.dirname(CODEX_SCAN_SKILL_RELATIVE_PATH),
133
+ ];
134
+ const CURSOR_COMMAND_RELATIVE_PATHS = [
135
+ CURSOR_ONBOARD_COMMAND_RELATIVE_PATH,
136
+ CURSOR_STATUS_COMMAND_RELATIVE_PATH,
137
+ CURSOR_MIGRATE_COMMAND_RELATIVE_PATH,
138
+ CURSOR_UPDATE_COMMAND_RELATIVE_PATH,
139
+ CURSOR_PREPARE_COMMAND_RELATIVE_PATH,
140
+ CURSOR_RUN_GAME_COMMAND_RELATIVE_PATH,
141
+ CURSOR_SCAN_COMMAND_RELATIVE_PATH,
142
+ ];
115
143
  export const LOCAL_PATH_HINTS_RELATIVE_PATH = ".gdh-state/local-paths.json";
116
144
  export const CODEX_PROJECT_CONFIG_RELATIVE_PATH = ".codex/config.toml";
117
145
  export const GDH_MCP_SERVER_NAME = "gdh";
@@ -375,7 +403,15 @@ export async function createGsdSnapshot(targetPath, options = {}) {
375
403
  guidanceResolve,
376
404
  };
377
405
  }
378
- export function renderCursorRule() {
406
+ function normalizePortableRelativePath(relativePath) {
407
+ const normalized = relativePath.split(path.sep).join("/");
408
+ return normalized === "" ? "." : normalized.replace(/\/+$/, "");
409
+ }
410
+ export function renderCursorRule(input) {
411
+ const targetPrefix = input?.targetRelativePath
412
+ ? normalizePortableRelativePath(input.targetRelativePath)
413
+ : ".";
414
+ const prefixed = (relativePath) => targetPrefix === "." ? relativePath : `${targetPrefix}/${relativePath}`;
379
415
  return [
380
416
  "---",
381
417
  "description: GDH visibility reinforcement",
@@ -386,8 +422,8 @@ export function renderCursorRule() {
386
422
  "",
387
423
  "This GDH-managed Godot target uses GDH.",
388
424
  "Apply this rule only for files inside this Godot target or when the user explicitly asks for GDH work on this target.",
389
- "Follow the canonical target-local entrypoint in [AGENTS.md](../../AGENTS.md), then load the canonical guidance index at [.gdh/guidance/README.md](../../.gdh/guidance/README.md) before substantive Godot work.",
390
- "For runtime bridge usage, load [.gdh/guidance/authoring-and-validation.md](../../.gdh/guidance/authoring-and-validation.md) and follow its runtime bridge section.",
425
+ `Follow the canonical target-local entrypoint in [AGENTS.md](../../${prefixed("AGENTS.md")}), then load the canonical guidance index at [.gdh/guidance/README.md](../../${prefixed(".gdh/guidance/README.md")}) before substantive Godot work.`,
426
+ `For runtime bridge usage, load [.gdh/guidance/authoring-and-validation.md](../../${prefixed(".gdh/guidance/authoring-and-validation.md")}) and follow its runtime bridge section.`,
391
427
  "Do not duplicate or override the canonical GDH guidance chain here.",
392
428
  "",
393
429
  ].join("\n");
@@ -419,9 +455,16 @@ async function inspectProjectMcpSupport(targetPath, options) {
419
455
  const managedMcpEntry = buildManagedMcpServerEntry({
420
456
  pinnedVersion: options.pinnedVersion ?? "latest",
421
457
  });
422
- const [projectFile, cursorFile, codexProjectContent, localPathHints] = await Promise.all([
458
+ const nestedTarget = path.resolve(targetPath) !== path.resolve(options.integrationRootPath);
459
+ const [projectFile, cursorFile, targetProjectFile, targetCursorFile, codexProjectContent, localPathHints,] = await Promise.all([
423
460
  inspectJsonFile(path.join(options.integrationRootPath, PROJECT_MCP_RELATIVE_PATH)),
424
461
  inspectJsonFile(path.join(options.integrationRootPath, CURSOR_MCP_RELATIVE_PATH)),
462
+ nestedTarget
463
+ ? inspectJsonFile(path.join(targetPath, PROJECT_MCP_RELATIVE_PATH))
464
+ : inspectJsonFile(path.join(options.integrationRootPath, PROJECT_MCP_RELATIVE_PATH)),
465
+ nestedTarget
466
+ ? inspectJsonFile(path.join(targetPath, CURSOR_MCP_RELATIVE_PATH))
467
+ : inspectJsonFile(path.join(options.integrationRootPath, CURSOR_MCP_RELATIVE_PATH)),
425
468
  fs
426
469
  .readFile(path.join(options.integrationRootPath, CODEX_PROJECT_CONFIG_RELATIVE_PATH), "utf8")
427
470
  .catch(() => null),
@@ -435,6 +478,8 @@ async function inspectProjectMcpSupport(targetPath, options) {
435
478
  localPathHints,
436
479
  projectFile: inspectManagedMcpFile(projectFile, enabled, PROJECT_MCP_RELATIVE_PATH, "Claude project MCP config", managedMcpEntry),
437
480
  cursorFile: inspectManagedMcpFile(cursorFile, enabled, CURSOR_MCP_RELATIVE_PATH, "Cursor project MCP config", managedMcpEntry),
481
+ targetProjectFile: inspectManagedMcpFile(targetProjectFile, enabled, PROJECT_MCP_RELATIVE_PATH, "target-local Claude project MCP config", managedMcpEntry),
482
+ targetCursorFile: inspectManagedMcpFile(targetCursorFile, enabled, CURSOR_MCP_RELATIVE_PATH, "target-local Cursor project MCP config", managedMcpEntry),
438
483
  codexProjectFile: inspectManagedCodexProjectFile(codexProjectContent, enabled, options.pinnedVersion),
439
484
  codexRegistration: options.includeUserLocal
440
485
  ? await inspectCodexRegistration(targetPath, enabled, codexServerName, codexConfigPath, options.integrationRootPath, options.pinnedVersion)
@@ -731,34 +776,34 @@ function inspectClaudeSkillSurface(targetPath, relativePath, content, expectedCo
731
776
  }),
732
777
  ];
733
778
  }
734
- function inspectCursorSkillSurface(targetPath, relativePath, content, expectedContent, skillName) {
779
+ function inspectCursorCommandSurface(targetPath, relativePath, content, expectedContent, commandName) {
735
780
  if (expectedContent === null) {
736
781
  return [
737
782
  createSurfaceStatus({
738
- kind: "skill_file",
783
+ kind: "command_file",
739
784
  scope: "repo",
740
785
  targetPath,
741
786
  relativePath,
742
787
  present: content !== null,
743
788
  state: "missing",
744
- summary: `Cursor ${skillName} skill cannot be validated yet: no \`gdh_version\` is pinned (run \`gdh setup\` or \`gdh migrate --apply\`).`,
789
+ summary: `Cursor ${commandName} command cannot be validated yet: no \`gdh_version\` is pinned (run \`gdh setup\` or \`gdh migrate --apply\`).`,
745
790
  version: null,
746
791
  }),
747
792
  ];
748
793
  }
749
794
  return [
750
795
  createSurfaceStatus({
751
- kind: "skill_file",
796
+ kind: "command_file",
752
797
  scope: "repo",
753
798
  targetPath,
754
799
  relativePath,
755
800
  present: content !== null,
756
801
  state: content === null ? "missing" : content === expectedContent ? "ready" : "misconfigured",
757
802
  summary: content === null
758
- ? `Cursor \`/${skillName}\` skill is missing and should install under .cursor/skills/.`
803
+ ? `Cursor \`/${commandName}\` command is missing and should install under .cursor/commands/.`
759
804
  : content === expectedContent
760
- ? `Cursor can discover the managed \`/${skillName}\` skill.`
761
- : `Cursor \`/${skillName}\` skill exists but no longer matches the expected managed GDH skill.`,
805
+ ? `Cursor can discover the managed \`/${commandName}\` command.`
806
+ : `Cursor \`/${commandName}\` command exists but no longer matches the expected managed GDH command.`,
762
807
  version: null,
763
808
  }),
764
809
  ];
@@ -1012,35 +1057,47 @@ async function inspectClaudeAdapter(targetPath, guidance, projectMcp, pinnedVers
1012
1057
  : projectMcp.projectFile.summary,
1013
1058
  version: null,
1014
1059
  }));
1060
+ if (path.resolve(projectMcp.integrationRootPath) !== path.resolve(targetPath)) {
1061
+ surfaces.push(createSurfaceStatus({
1062
+ kind: "mcp_file",
1063
+ scope: "repo",
1064
+ targetPath,
1065
+ relativePath: PROJECT_MCP_RELATIVE_PATH,
1066
+ present: projectMcp.targetProjectFile.present,
1067
+ state: projectMcp.targetProjectFile.state,
1068
+ summary: projectMcp.targetProjectFile.summary,
1069
+ version: null,
1070
+ }));
1071
+ }
1015
1072
  }
1016
1073
  return createAgentStatus("claude", guidance, surfaces);
1017
1074
  }
1018
1075
  async function inspectCursorAdapter(targetPath, guidance, projectMcp, pinnedVersion) {
1019
1076
  const absolutePath = path.join(targetPath, CURSOR_RULE_RELATIVE_PATH);
1020
- const onboardSkillPath = path.join(targetPath, CURSOR_ONBOARD_SKILL_RELATIVE_PATH);
1077
+ const onboardCommandPath = path.join(targetPath, CURSOR_ONBOARD_COMMAND_RELATIVE_PATH);
1021
1078
  const content = await fs.readFile(absolutePath, "utf8").catch(() => null);
1022
- const onboardSkillContent = await fs.readFile(onboardSkillPath, "utf8").catch(() => null);
1079
+ const onboardCommandContent = await fs.readFile(onboardCommandPath, "utf8").catch(() => null);
1023
1080
  const cursorStatusContent = await fs
1024
- .readFile(path.join(targetPath, CURSOR_STATUS_SKILL_RELATIVE_PATH), "utf8")
1081
+ .readFile(path.join(targetPath, CURSOR_STATUS_COMMAND_RELATIVE_PATH), "utf8")
1025
1082
  .catch(() => null);
1026
1083
  const cursorMigrateContent = await fs
1027
- .readFile(path.join(targetPath, CURSOR_MIGRATE_SKILL_RELATIVE_PATH), "utf8")
1084
+ .readFile(path.join(targetPath, CURSOR_MIGRATE_COMMAND_RELATIVE_PATH), "utf8")
1028
1085
  .catch(() => null);
1029
1086
  // Phase 13 Plan 13-03 deliverable — /gdh-update skill for Cursor. Inspection
1030
1087
  // wiring required so planSkillInstallAction sees the surface state; otherwise
1031
1088
  // the planner returns `unchanged` for a missing file and install becomes a
1032
1089
  // no-op (Plan 13-05 integration-test Rule 2 fix).
1033
1090
  const cursorUpdateContent = await fs
1034
- .readFile(path.join(targetPath, CURSOR_UPDATE_SKILL_RELATIVE_PATH), "utf8")
1091
+ .readFile(path.join(targetPath, CURSOR_UPDATE_COMMAND_RELATIVE_PATH), "utf8")
1035
1092
  .catch(() => null);
1036
1093
  const cursorPrepareContent = await fs
1037
- .readFile(path.join(targetPath, CURSOR_PREPARE_SKILL_RELATIVE_PATH), "utf8")
1094
+ .readFile(path.join(targetPath, CURSOR_PREPARE_COMMAND_RELATIVE_PATH), "utf8")
1038
1095
  .catch(() => null);
1039
1096
  const cursorRunGameContent = await fs
1040
- .readFile(path.resolve(targetPath, CURSOR_RUN_GAME_SKILL_RELATIVE_PATH), "utf8")
1097
+ .readFile(path.resolve(targetPath, CURSOR_RUN_GAME_COMMAND_RELATIVE_PATH), "utf8")
1041
1098
  .catch(() => null);
1042
1099
  const cursorScanContent = await fs
1043
- .readFile(path.join(targetPath, CURSOR_SCAN_SKILL_RELATIVE_PATH), "utf8")
1100
+ .readFile(path.join(targetPath, CURSOR_SCAN_COMMAND_RELATIVE_PATH), "utf8")
1044
1101
  .catch(() => null);
1045
1102
  const expectedContent = renderCursorRule();
1046
1103
  const version = readCursorRuleVersion(content);
@@ -1067,33 +1124,33 @@ async function inspectCursorAdapter(targetPath, guidance, projectMcp, pinnedVers
1067
1124
  version,
1068
1125
  }),
1069
1126
  createSurfaceStatus({
1070
- kind: "skill_file",
1127
+ kind: "command_file",
1071
1128
  scope: "repo",
1072
1129
  targetPath,
1073
- relativePath: CURSOR_ONBOARD_SKILL_RELATIVE_PATH,
1074
- present: onboardSkillContent !== null,
1075
- state: onboardSkillContent === null
1130
+ relativePath: CURSOR_ONBOARD_COMMAND_RELATIVE_PATH,
1131
+ present: onboardCommandContent !== null,
1132
+ state: onboardCommandContent === null
1076
1133
  ? "missing"
1077
1134
  : expectedCursorOnboardSkill === null
1078
1135
  ? "missing"
1079
- : onboardSkillContent === expectedCursorOnboardSkill
1136
+ : onboardCommandContent === expectedCursorOnboardSkill
1080
1137
  ? "ready"
1081
1138
  : "misconfigured",
1082
- summary: onboardSkillContent === null
1083
- ? "Cursor onboarding handoff is missing and should install `/gdh-onboard` under .cursor/skills/."
1139
+ summary: onboardCommandContent === null
1140
+ ? "Cursor onboarding handoff is missing and should install `/gdh-onboard` under .cursor/commands/."
1084
1141
  : expectedCursorOnboardSkill === null
1085
1142
  ? "Cursor onboarding handoff cannot be validated yet: no `gdh_version` is pinned (run `gdh setup` or `gdh migrate --apply`)."
1086
- : onboardSkillContent === expectedCursorOnboardSkill
1087
- ? "Cursor can discover the managed `/gdh-onboard` handoff skill."
1088
- : "Cursor onboarding handoff exists but no longer matches the expected managed GDH skill.",
1143
+ : onboardCommandContent === expectedCursorOnboardSkill
1144
+ ? "Cursor can discover the managed `/gdh-onboard` handoff command."
1145
+ : "Cursor onboarding handoff exists but no longer matches the expected managed GDH command.",
1089
1146
  version: null,
1090
1147
  }),
1091
- ...inspectCursorSkillSurface(targetPath, CURSOR_STATUS_SKILL_RELATIVE_PATH, cursorStatusContent, expectedCursorStatusSkill, "gdh-status"),
1092
- ...inspectCursorSkillSurface(targetPath, CURSOR_MIGRATE_SKILL_RELATIVE_PATH, cursorMigrateContent, expectedCursorMigrateSkill, "gdh-migrate"),
1093
- ...inspectCursorSkillSurface(targetPath, CURSOR_UPDATE_SKILL_RELATIVE_PATH, cursorUpdateContent, expectedCursorUpdateSkill, "gdh-update"),
1094
- ...inspectCursorSkillSurface(targetPath, CURSOR_PREPARE_SKILL_RELATIVE_PATH, cursorPrepareContent, expectedCursorPrepareSkill, "gdh-prepare"),
1095
- ...inspectCursorSkillSurface(targetPath, CURSOR_RUN_GAME_SKILL_RELATIVE_PATH, cursorRunGameContent, expectedCursorRunGameSkill, "gdh-run-game"),
1096
- ...inspectCursorSkillSurface(targetPath, CURSOR_SCAN_SKILL_RELATIVE_PATH, cursorScanContent, expectedCursorScanSkill, "gdh-scan"),
1148
+ ...inspectCursorCommandSurface(targetPath, CURSOR_STATUS_COMMAND_RELATIVE_PATH, cursorStatusContent, expectedCursorStatusSkill, "gdh-status"),
1149
+ ...inspectCursorCommandSurface(targetPath, CURSOR_MIGRATE_COMMAND_RELATIVE_PATH, cursorMigrateContent, expectedCursorMigrateSkill, "gdh-migrate"),
1150
+ ...inspectCursorCommandSurface(targetPath, CURSOR_UPDATE_COMMAND_RELATIVE_PATH, cursorUpdateContent, expectedCursorUpdateSkill, "gdh-update"),
1151
+ ...inspectCursorCommandSurface(targetPath, CURSOR_PREPARE_COMMAND_RELATIVE_PATH, cursorPrepareContent, expectedCursorPrepareSkill, "gdh-prepare"),
1152
+ ...inspectCursorCommandSurface(targetPath, CURSOR_RUN_GAME_COMMAND_RELATIVE_PATH, cursorRunGameContent, expectedCursorRunGameSkill, "gdh-run-game"),
1153
+ ...inspectCursorCommandSurface(targetPath, CURSOR_SCAN_COMMAND_RELATIVE_PATH, cursorScanContent, expectedCursorScanSkill, "gdh-scan"),
1097
1154
  ];
1098
1155
  if (projectMcp.enabled) {
1099
1156
  surfaces.push(createSurfaceStatus({
@@ -1106,6 +1163,18 @@ async function inspectCursorAdapter(targetPath, guidance, projectMcp, pinnedVers
1106
1163
  summary: projectMcp.cursorFile.summary,
1107
1164
  version: null,
1108
1165
  }));
1166
+ if (path.resolve(projectMcp.integrationRootPath) !== path.resolve(targetPath)) {
1167
+ surfaces.push(createSurfaceStatus({
1168
+ kind: "mcp_file",
1169
+ scope: "repo",
1170
+ targetPath,
1171
+ relativePath: CURSOR_MCP_RELATIVE_PATH,
1172
+ present: projectMcp.targetCursorFile.present,
1173
+ state: projectMcp.targetCursorFile.state,
1174
+ summary: projectMcp.targetCursorFile.summary,
1175
+ version: null,
1176
+ }));
1177
+ }
1109
1178
  }
1110
1179
  return createAgentStatus("cursor", guidance, surfaces);
1111
1180
  }
@@ -1198,10 +1267,15 @@ async function planInstallActions(targetPath, adapters, requestedAgents, options
1198
1267
  continue;
1199
1268
  }
1200
1269
  if (adapter.agent === "cursor") {
1201
- actions.push(...planCursorInstallActions(targetPath, adapter, options.pinnedVersion));
1270
+ actions.push(...planCursorInstallActions(targetPath, adapter, options.pinnedVersion, options.integrationRootPath));
1202
1271
  }
1203
1272
  }
1204
1273
  actions.push(...planRetiredManagedSurfaceCleanupActions(targetPath));
1274
+ if (path.resolve(options.integrationRootPath) !== path.resolve(targetPath)) {
1275
+ actions.push(...planRetiredManagedSurfaceCleanupActions(options.integrationRootPath));
1276
+ actions.push(...planLegacyCodexSkillCleanupActions(options.integrationRootPath, options.pinnedVersion));
1277
+ actions.push(...planLegacyCursorSkillCleanupActions(options.integrationRootPath));
1278
+ }
1205
1279
  return dedupeInstallActions(actions);
1206
1280
  }
1207
1281
  function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDevRepoPath, integrationRootPath, pinnedVersion) {
@@ -1209,6 +1283,8 @@ function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDe
1209
1283
  return [];
1210
1284
  }
1211
1285
  const actions = [];
1286
+ const managedMcpEntry = buildManagedMcpServerEntry({ pinnedVersion });
1287
+ const nestedTarget = path.resolve(targetPath) !== path.resolve(integrationRootPath);
1212
1288
  if (projectMcp.projectFile.state !== "ready") {
1213
1289
  actions.push(createInstallAction({
1214
1290
  agent,
@@ -1221,7 +1297,7 @@ function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDe
1221
1297
  summary: projectMcp.projectFile.present
1222
1298
  ? "Replace the managed `gdh` MCP entry in .mcp.json while preserving any non-GDH servers."
1223
1299
  : "Create .mcp.json with the managed `gdh` MCP entry.",
1224
- content: renderManagedMcpConfig(path.join(integrationRootPath, PROJECT_MCP_RELATIVE_PATH), buildManagedMcpServerEntry({ pinnedVersion })),
1300
+ content: renderManagedMcpConfig(path.join(integrationRootPath, PROJECT_MCP_RELATIVE_PATH), managedMcpEntry),
1225
1301
  }));
1226
1302
  }
1227
1303
  if (projectMcp.cursorFile.state !== "ready") {
@@ -1236,7 +1312,37 @@ function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDe
1236
1312
  summary: projectMcp.cursorFile.present
1237
1313
  ? "Replace the managed `gdh` MCP entry in .cursor/mcp.json while preserving any non-GDH servers."
1238
1314
  : "Create .cursor/mcp.json with the managed `gdh` MCP entry.",
1239
- content: renderManagedMcpConfig(path.join(integrationRootPath, CURSOR_MCP_RELATIVE_PATH), buildManagedMcpServerEntry({ pinnedVersion })),
1315
+ content: renderManagedMcpConfig(path.join(integrationRootPath, CURSOR_MCP_RELATIVE_PATH), managedMcpEntry),
1316
+ }));
1317
+ }
1318
+ if (nestedTarget && projectMcp.targetProjectFile.state !== "ready") {
1319
+ actions.push(createInstallAction({
1320
+ agent,
1321
+ kind: "write_file",
1322
+ scope: "repo",
1323
+ targetPath,
1324
+ relativePath: PROJECT_MCP_RELATIVE_PATH,
1325
+ state: "planned",
1326
+ mode: projectMcp.targetProjectFile.present ? "replace" : "create",
1327
+ summary: projectMcp.targetProjectFile.present
1328
+ ? "Replace the managed `gdh` MCP entry in target-local .mcp.json while preserving any non-GDH servers."
1329
+ : "Create target-local .mcp.json so Claude can discover GDH when launched from the Godot target.",
1330
+ content: renderManagedMcpConfig(path.join(targetPath, PROJECT_MCP_RELATIVE_PATH), managedMcpEntry),
1331
+ }));
1332
+ }
1333
+ if (nestedTarget && projectMcp.targetCursorFile.state !== "ready") {
1334
+ actions.push(createInstallAction({
1335
+ agent,
1336
+ kind: "write_file",
1337
+ scope: "repo",
1338
+ targetPath,
1339
+ relativePath: CURSOR_MCP_RELATIVE_PATH,
1340
+ state: "planned",
1341
+ mode: projectMcp.targetCursorFile.present ? "replace" : "create",
1342
+ summary: projectMcp.targetCursorFile.present
1343
+ ? "Replace the managed `gdh` MCP entry in target-local .cursor/mcp.json while preserving any non-GDH servers."
1344
+ : "Create target-local .cursor/mcp.json so Cursor can discover GDH when opened at the Godot target.",
1345
+ content: renderManagedMcpConfig(path.join(targetPath, CURSOR_MCP_RELATIVE_PATH), managedMcpEntry),
1240
1346
  }));
1241
1347
  }
1242
1348
  if (projectMcp.localPathHints.gdhDevRepoPath !== effectiveDevRepoPath) {
@@ -1296,6 +1402,35 @@ function planSkillInstallAction(agent, targetPath, adapter, relativePath, render
1296
1402
  content: renderFn(pinnedVersion),
1297
1403
  });
1298
1404
  }
1405
+ function planCommandInstallAction(agent, targetPath, adapter, relativePath, renderFn, commandName, pinnedVersion) {
1406
+ const surface = adapter.surfaces.find((s) => s.relativePath === relativePath);
1407
+ if (!surface || surface.state === "ready") {
1408
+ return createInstallAction({
1409
+ agent,
1410
+ kind: "write_file",
1411
+ scope: "repo",
1412
+ targetPath,
1413
+ relativePath,
1414
+ state: "unchanged",
1415
+ mode: "unchanged",
1416
+ summary: `The managed ${agentLabel(agent)} \`/${commandName}\` command already matches the expected GDH content.`,
1417
+ content: renderFn(pinnedVersion),
1418
+ });
1419
+ }
1420
+ return createInstallAction({
1421
+ agent,
1422
+ kind: "write_file",
1423
+ scope: "repo",
1424
+ targetPath,
1425
+ relativePath,
1426
+ state: "planned",
1427
+ mode: surface.present ? "replace" : "create",
1428
+ summary: surface.present
1429
+ ? `Replace the existing ${agentLabel(agent)} \`/${commandName}\` command with the managed GDH command.`
1430
+ : `Create the managed ${agentLabel(agent)} \`/${commandName}\` command.`,
1431
+ content: renderFn(pinnedVersion),
1432
+ });
1433
+ }
1299
1434
  function planHookInstallAction(agent, targetPath, adapter, relativePath, renderFn, hookName, pinnedVersion) {
1300
1435
  const surface = adapter.surfaces.find((entry) => entry.relativePath === relativePath);
1301
1436
  if (!surface || surface.state === "ready") {
@@ -1369,6 +1504,15 @@ function planCodexRepoInstallActions(targetPath, adapter, pinnedVersion, project
1369
1504
  content: renderManagedCodexProjectConfig(existingContent, pinnedVersion),
1370
1505
  }));
1371
1506
  }
1507
+ if (path.resolve(integrationRootPath) !== path.resolve(targetPath)) {
1508
+ actions.push(...planRootSymlinkActions({
1509
+ agent: "codex",
1510
+ integrationRootPath,
1511
+ targetPath,
1512
+ relativePaths: CODEX_SKILL_RELATIVE_PATHS,
1513
+ noun: "Codex skill",
1514
+ }));
1515
+ }
1372
1516
  actions.push(...planLegacyCodexSkillCleanupActions(targetPath, pinnedVersion));
1373
1517
  return actions;
1374
1518
  }
@@ -1387,6 +1531,10 @@ function isManagedLegacyCodexSkillContent(content) {
1387
1531
  return (content.includes(GDH_MANAGED_AGENT_SKILL_MARKER) ||
1388
1532
  content.includes(LEGACY_CODEX_MANAGED_SKILL_MARKER));
1389
1533
  }
1534
+ function isManagedLegacyCursorSkillContent(content) {
1535
+ return (content.includes(GDH_MANAGED_AGENT_SKILL_MARKER) ||
1536
+ content.includes(LEGACY_CURSOR_MANAGED_SKILL_MARKER));
1537
+ }
1390
1538
  function isManagedLegacyClaudeCommandContent(content, skillName) {
1391
1539
  const slashName = skillName.replace("gdh-", "gdh:");
1392
1540
  return (content.includes(`name: ${slashName}`) &&
@@ -1471,6 +1619,7 @@ function planRetiredManagedSurfaceCleanupActions(targetPath) {
1471
1619
  summary: `Remove retired managed \`${entry.skillName}\` surface. ${entry.replacementSummary}`,
1472
1620
  }));
1473
1621
  }
1622
+ actions.push(...planLegacyCursorSkillCleanupActions(targetPath));
1474
1623
  return actions;
1475
1624
  }
1476
1625
  function planLegacyCodexSkillCleanupActions(targetPath, _pinnedVersion) {
@@ -1502,6 +1651,29 @@ function planLegacyCodexSkillCleanupActions(targetPath, _pinnedVersion) {
1502
1651
  }
1503
1652
  return actions;
1504
1653
  }
1654
+ function planLegacyCursorSkillCleanupActions(targetPath) {
1655
+ const actions = [];
1656
+ for (const [relativePath, skillName] of LEGACY_CURSOR_SKILL_RELATIVE_PATHS) {
1657
+ const absolutePath = path.join(targetPath, relativePath);
1658
+ const content = fsSync.existsSync(absolutePath)
1659
+ ? fsSync.readFileSync(absolutePath, "utf8")
1660
+ : null;
1661
+ if (content === null || !isManagedLegacyCursorSkillContent(content)) {
1662
+ continue;
1663
+ }
1664
+ actions.push(createInstallAction({
1665
+ agent: "cursor",
1666
+ kind: "remove_file",
1667
+ scope: "repo",
1668
+ targetPath,
1669
+ relativePath,
1670
+ state: "planned",
1671
+ mode: "delete",
1672
+ summary: `Remove legacy managed Cursor \`/${skillName}\` skill from .cursor/skills after installing the documented .cursor/commands location.`,
1673
+ }));
1674
+ }
1675
+ return actions;
1676
+ }
1505
1677
  function planLegacyClaudeCommandCleanupActions(targetPath) {
1506
1678
  const actions = [];
1507
1679
  for (const [relativePath, skillName] of LEGACY_CLAUDE_SKILL_COMMAND_RELATIVE_PATHS) {
@@ -1705,6 +1877,22 @@ function planDirectSymlinkAction(input) {
1705
1877
  expectedTarget: input.expectedTarget,
1706
1878
  });
1707
1879
  }
1880
+ function planRootSymlinkActions(input) {
1881
+ return input.relativePaths.map((relativePath) => {
1882
+ const rootLinkDirectory = path.dirname(path.join(input.integrationRootPath, relativePath));
1883
+ const targetFilePath = path.join(input.targetPath, relativePath);
1884
+ const expectedTarget = normalizePortableRelativePath(path.relative(rootLinkDirectory, targetFilePath));
1885
+ return planDirectSymlinkAction({
1886
+ agent: input.agent,
1887
+ targetPath: input.integrationRootPath,
1888
+ relativePath,
1889
+ expectedTarget,
1890
+ readySummary: `Root-launched ${input.noun} ${relativePath} already points to the target-local managed surface.`,
1891
+ createSummary: `Create root-launched ${input.noun} symlink ${relativePath} to the target-local managed surface.`,
1892
+ replaceSummary: `Replace root-launched ${input.noun} ${relativePath} with a symlink to the target-local managed surface.`,
1893
+ });
1894
+ });
1895
+ }
1708
1896
  function planDirectWriteFileAction(input) {
1709
1897
  const absolutePath = path.resolve(input.targetPath, input.relativePath);
1710
1898
  let existingContent = null;
@@ -1758,9 +1946,10 @@ function planRootClaudeAuthoringSettingsAction(integrationRootPath) {
1758
1946
  content: patchedSettings,
1759
1947
  });
1760
1948
  }
1761
- function planCursorInstallActions(targetPath, adapter, pinnedVersion) {
1949
+ function planCursorInstallActions(targetPath, adapter, pinnedVersion, integrationRootPath) {
1762
1950
  const actions = [];
1763
1951
  const cursorRuleSurface = adapter.surfaces.find((surface) => surface.relativePath === CURSOR_RULE_RELATIVE_PATH);
1952
+ const targetRelativePath = path.relative(integrationRootPath, targetPath) || ".";
1764
1953
  if (!cursorRuleSurface || cursorRuleSurface.state === "ready") {
1765
1954
  actions.push(createInstallAction({
1766
1955
  agent: "cursor",
@@ -1791,7 +1980,24 @@ function planCursorInstallActions(targetPath, adapter, pinnedVersion) {
1791
1980
  content: renderCursorRule(),
1792
1981
  }));
1793
1982
  }
1794
- actions.push(planSkillInstallAction("cursor", targetPath, adapter, CURSOR_ONBOARD_SKILL_RELATIVE_PATH, renderCursorOnboardSkill, "gdh-onboard", pinnedVersion), planSkillInstallAction("cursor", targetPath, adapter, CURSOR_STATUS_SKILL_RELATIVE_PATH, renderCursorStatusSkill, "gdh-status", pinnedVersion), planSkillInstallAction("cursor", targetPath, adapter, CURSOR_MIGRATE_SKILL_RELATIVE_PATH, renderCursorMigrateSkill, "gdh-migrate", pinnedVersion), planSkillInstallAction("cursor", targetPath, adapter, CURSOR_UPDATE_SKILL_RELATIVE_PATH, renderCursorUpdateSkill, "gdh-update", pinnedVersion), planSkillInstallAction("cursor", targetPath, adapter, CURSOR_PREPARE_SKILL_RELATIVE_PATH, renderCursorPrepareSkill, "gdh-prepare", pinnedVersion), planSkillInstallAction("cursor", targetPath, adapter, CURSOR_RUN_GAME_SKILL_RELATIVE_PATH, renderCursorRunGameSkill, "gdh-run-game", pinnedVersion), planSkillInstallAction("cursor", targetPath, adapter, CURSOR_SCAN_SKILL_RELATIVE_PATH, renderCursorScanSkill, "gdh-scan", pinnedVersion));
1983
+ actions.push(planCommandInstallAction("cursor", targetPath, adapter, CURSOR_ONBOARD_COMMAND_RELATIVE_PATH, renderCursorOnboardSkill, "gdh-onboard", pinnedVersion), planCommandInstallAction("cursor", targetPath, adapter, CURSOR_STATUS_COMMAND_RELATIVE_PATH, renderCursorStatusSkill, "gdh-status", pinnedVersion), planCommandInstallAction("cursor", targetPath, adapter, CURSOR_MIGRATE_COMMAND_RELATIVE_PATH, renderCursorMigrateSkill, "gdh-migrate", pinnedVersion), planCommandInstallAction("cursor", targetPath, adapter, CURSOR_UPDATE_COMMAND_RELATIVE_PATH, renderCursorUpdateSkill, "gdh-update", pinnedVersion), planCommandInstallAction("cursor", targetPath, adapter, CURSOR_PREPARE_COMMAND_RELATIVE_PATH, renderCursorPrepareSkill, "gdh-prepare", pinnedVersion), planCommandInstallAction("cursor", targetPath, adapter, CURSOR_RUN_GAME_COMMAND_RELATIVE_PATH, renderCursorRunGameSkill, "gdh-run-game", pinnedVersion), planCommandInstallAction("cursor", targetPath, adapter, CURSOR_SCAN_COMMAND_RELATIVE_PATH, renderCursorScanSkill, "gdh-scan", pinnedVersion));
1984
+ if (path.resolve(integrationRootPath) !== path.resolve(targetPath)) {
1985
+ actions.push(planDirectWriteFileAction({
1986
+ agent: "cursor",
1987
+ targetPath: integrationRootPath,
1988
+ relativePath: CURSOR_RULE_RELATIVE_PATH,
1989
+ content: renderCursorRule({ targetRelativePath }),
1990
+ readySummary: "Root-launched Cursor rule already routes to the managed Godot target.",
1991
+ createSummary: "Create root-launched Cursor rule that routes to the managed Godot target.",
1992
+ replaceSummary: "Replace root-launched Cursor rule with target-aware links to the managed Godot target.",
1993
+ }), ...planRootSymlinkActions({
1994
+ agent: "cursor",
1995
+ integrationRootPath,
1996
+ targetPath,
1997
+ relativePaths: CURSOR_COMMAND_RELATIVE_PATHS,
1998
+ noun: "Cursor command",
1999
+ }));
2000
+ }
1795
2001
  return actions;
1796
2002
  }
1797
2003
  function dedupeInstallActions(actions) {
@@ -1837,7 +2043,8 @@ async function applySymlinkAction(action) {
1837
2043
  if (action.absolutePath === null) {
1838
2044
  throw new Error("Symlink install action is missing an absolute path.");
1839
2045
  }
1840
- await fs.rm(action.absolutePath, { force: true });
2046
+ await fs.mkdir(path.dirname(action.absolutePath), { recursive: true });
2047
+ await fs.rm(action.absolutePath, { recursive: true, force: true });
1841
2048
  await fs.symlink(action.expectedTarget ?? "AGENTS.md", action.absolutePath);
1842
2049
  }
1843
2050
  async function applyWriteFileAction(action) {