@skillcap/gdh 0.25.1 → 0.25.3

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 (30) hide show
  1. package/INSTALL-BUNDLE.json +1 -1
  2. package/RELEASE-SPAN-UPDATE-CONTRACTS.json +112 -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 +298 -66
  6. package/node_modules/@gdh/adapters/dist/index.js.map +1 -1
  7. package/node_modules/@gdh/adapters/dist/skill-rendering.js +1 -1
  8. package/node_modules/@gdh/adapters/dist/skill-rendering.js.map +1 -1
  9. package/node_modules/@gdh/adapters/package.json +8 -8
  10. package/node_modules/@gdh/authoring/package.json +2 -2
  11. package/node_modules/@gdh/cli/dist/index.js +7 -7
  12. package/node_modules/@gdh/cli/dist/index.js.map +1 -1
  13. package/node_modules/@gdh/cli/package.json +10 -10
  14. package/node_modules/@gdh/core/dist/index.d.ts +2 -2
  15. package/node_modules/@gdh/core/dist/index.js +2 -2
  16. package/node_modules/@gdh/core/dist/migrations/managed-surface-classes.d.ts +7 -7
  17. package/node_modules/@gdh/core/dist/migrations/managed-surface-classes.js +9 -9
  18. package/node_modules/@gdh/core/dist/migrations/managed-surface-classes.js.map +1 -1
  19. package/node_modules/@gdh/core/dist/migrations/managed-target-surface-inventory.d.ts +4 -4
  20. package/node_modules/@gdh/core/dist/migrations/managed-target-surface-inventory.d.ts.map +1 -1
  21. package/node_modules/@gdh/core/dist/migrations/managed-target-surface-inventory.js +9 -7
  22. package/node_modules/@gdh/core/dist/migrations/managed-target-surface-inventory.js.map +1 -1
  23. package/node_modules/@gdh/core/package.json +1 -1
  24. package/node_modules/@gdh/docs/package.json +2 -2
  25. package/node_modules/@gdh/mcp/package.json +8 -8
  26. package/node_modules/@gdh/observability/package.json +2 -2
  27. package/node_modules/@gdh/runtime/package.json +2 -2
  28. package/node_modules/@gdh/scan/package.json +3 -3
  29. package/node_modules/@gdh/verify/package.json +7 -7
  30. 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";
@@ -122,23 +150,23 @@ const PREVIEW_HEADER_LITERAL = "Preview (dry-run) — not applied";
122
150
  const execFile = promisify(execFileCallback);
123
151
  export async function getSupportedAgentAdaptersStatus(targetPath, options = {}) {
124
152
  const includeUserLocal = options.includeUserLocal ?? false;
125
- const integrationRootPath = resolveIntegrationRootPath(targetPath, options.integrationRootPath);
126
- const guidance = await getGuidanceStatus(targetPath);
127
- const pinnedVersion = await resolvePinnedVersionOrNull(targetPath);
128
- const projectMcp = await inspectProjectMcpSupport(targetPath, {
153
+ const context = await resolveAdapterTargetContext(targetPath, options.integrationRootPath);
154
+ const guidance = await getGuidanceStatus(context.targetPath);
155
+ const pinnedVersion = await resolvePinnedVersionOrNull(context.targetPath);
156
+ const projectMcp = await inspectProjectMcpSupport(context.targetPath, {
129
157
  includeUserLocal,
130
- integrationRootPath,
158
+ integrationRootPath: context.integrationRootPath,
131
159
  pinnedVersion,
132
160
  });
133
161
  const [codex, claude, cursor] = await Promise.all([
134
- inspectCodexAdapter(targetPath, guidance, projectMcp, pinnedVersion, {
162
+ inspectCodexAdapter(context.targetPath, guidance, projectMcp, pinnedVersion, {
135
163
  includeUserLocal,
136
164
  }),
137
- inspectClaudeAdapter(targetPath, guidance, projectMcp, pinnedVersion),
138
- inspectCursorAdapter(targetPath, guidance, projectMcp, pinnedVersion),
165
+ inspectClaudeAdapter(context.targetPath, guidance, projectMcp, pinnedVersion),
166
+ inspectCursorAdapter(context.targetPath, guidance, projectMcp, pinnedVersion),
139
167
  ]);
140
168
  return {
141
- targetPath,
169
+ targetPath: context.targetPath,
142
170
  guidance,
143
171
  adapters: [codex, claude, cursor],
144
172
  };
@@ -146,14 +174,14 @@ export async function getSupportedAgentAdaptersStatus(targetPath, options = {})
146
174
  export async function installSupportedAgentAdapters(targetPath, options = {}) {
147
175
  const dryRun = options.dryRun ?? false;
148
176
  const requestedAgents = normalizeRequestedAgents(options.agents);
149
- const integrationRootPath = resolveIntegrationRootPath(targetPath, options.integrationRootPath);
150
- const status = await getSupportedAgentAdaptersStatus(targetPath, {
151
- integrationRootPath,
177
+ const context = await resolveAdapterTargetContext(targetPath, options.integrationRootPath);
178
+ const status = await getSupportedAgentAdaptersStatus(context.targetPath, {
179
+ integrationRootPath: context.integrationRootPath,
152
180
  });
153
- const pinnedVersion = await resolvePinnedVersion(targetPath);
181
+ const pinnedVersion = await resolvePinnedVersion(context.targetPath);
154
182
  if (!hasReadyVisibilityChain(status.guidance) && options.allowBootstrap !== true) {
155
183
  return {
156
- targetPath,
184
+ targetPath: context.targetPath,
157
185
  dryRun,
158
186
  state: "blocked",
159
187
  summary: "The canonical GDH visibility chain is not ready, so agent reinforcement cannot be installed safely.",
@@ -163,10 +191,10 @@ export async function installSupportedAgentAdapters(targetPath, options = {}) {
163
191
  actions: [],
164
192
  };
165
193
  }
166
- const actions = await planInstallActions(targetPath, status.adapters, requestedAgents, {
194
+ const actions = await planInstallActions(context.targetPath, status.adapters, requestedAgents, {
167
195
  user: options.user ?? false,
168
196
  devRepoPath: options.devRepoPath ?? null,
169
- integrationRootPath,
197
+ integrationRootPath: context.integrationRootPath,
170
198
  pinnedVersion,
171
199
  });
172
200
  let appliedActions = actions;
@@ -207,7 +235,7 @@ export async function installSupportedAgentAdapters(targetPath, options = {}) {
207
235
  appliedActions = newActions;
208
236
  }
209
237
  return {
210
- targetPath,
238
+ targetPath: context.targetPath,
211
239
  dryRun,
212
240
  state: dryRun ? "planned" : "applied",
213
241
  summary: actions.length === 0 || actions.every((action) => action.state === "unchanged")
@@ -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
  }
@@ -1170,6 +1239,31 @@ function normalizeRequestedAgents(agents) {
1170
1239
  function resolveIntegrationRootPath(targetPath, integrationRootPath) {
1171
1240
  return path.resolve(integrationRootPath ?? targetPath);
1172
1241
  }
1242
+ async function resolveAdapterTargetContext(targetPath, integrationRootPath) {
1243
+ const rawTargetPath = path.resolve(targetPath);
1244
+ const projectRoot = await resolveProjectRoot(rawTargetPath);
1245
+ const resolvedIntegrationRootPath = path.resolve(integrationRootPath ?? projectRoot ?? rawTargetPath);
1246
+ const guidance = await getGuidanceStatus(rawTargetPath).catch(() => null);
1247
+ if (guidance?.state === "ready") {
1248
+ return {
1249
+ targetPath: path.resolve(guidance.guidanceRootPath),
1250
+ integrationRootPath: path.resolve(guidance.integrationRootPath),
1251
+ };
1252
+ }
1253
+ const projectConfig = await readProjectConfig(resolvedIntegrationRootPath);
1254
+ if (projectConfig === null || projectConfig.primaryGodotProjectPath === ".") {
1255
+ return {
1256
+ targetPath: projectRoot !== null && integrationRootPath === undefined
1257
+ ? path.resolve(projectRoot)
1258
+ : rawTargetPath,
1259
+ integrationRootPath: resolvedIntegrationRootPath,
1260
+ };
1261
+ }
1262
+ return {
1263
+ targetPath: path.resolve(resolvedIntegrationRootPath, projectConfig.primaryGodotProjectPath),
1264
+ integrationRootPath: resolvedIntegrationRootPath,
1265
+ };
1266
+ }
1173
1267
  async function planInstallActions(targetPath, adapters, requestedAgents, options) {
1174
1268
  const projectMcp = await inspectProjectMcpSupport(targetPath, {
1175
1269
  includeUserLocal: options.user,
@@ -1198,10 +1292,15 @@ async function planInstallActions(targetPath, adapters, requestedAgents, options
1198
1292
  continue;
1199
1293
  }
1200
1294
  if (adapter.agent === "cursor") {
1201
- actions.push(...planCursorInstallActions(targetPath, adapter, options.pinnedVersion));
1295
+ actions.push(...planCursorInstallActions(targetPath, adapter, options.pinnedVersion, options.integrationRootPath));
1202
1296
  }
1203
1297
  }
1204
1298
  actions.push(...planRetiredManagedSurfaceCleanupActions(targetPath));
1299
+ if (path.resolve(options.integrationRootPath) !== path.resolve(targetPath)) {
1300
+ actions.push(...planRetiredManagedSurfaceCleanupActions(options.integrationRootPath));
1301
+ actions.push(...planLegacyCodexSkillCleanupActions(options.integrationRootPath, options.pinnedVersion));
1302
+ actions.push(...planLegacyCursorSkillCleanupActions(options.integrationRootPath));
1303
+ }
1205
1304
  return dedupeInstallActions(actions);
1206
1305
  }
1207
1306
  function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDevRepoPath, integrationRootPath, pinnedVersion) {
@@ -1209,6 +1308,8 @@ function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDe
1209
1308
  return [];
1210
1309
  }
1211
1310
  const actions = [];
1311
+ const managedMcpEntry = buildManagedMcpServerEntry({ pinnedVersion });
1312
+ const nestedTarget = path.resolve(targetPath) !== path.resolve(integrationRootPath);
1212
1313
  if (projectMcp.projectFile.state !== "ready") {
1213
1314
  actions.push(createInstallAction({
1214
1315
  agent,
@@ -1221,7 +1322,7 @@ function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDe
1221
1322
  summary: projectMcp.projectFile.present
1222
1323
  ? "Replace the managed `gdh` MCP entry in .mcp.json while preserving any non-GDH servers."
1223
1324
  : "Create .mcp.json with the managed `gdh` MCP entry.",
1224
- content: renderManagedMcpConfig(path.join(integrationRootPath, PROJECT_MCP_RELATIVE_PATH), buildManagedMcpServerEntry({ pinnedVersion })),
1325
+ content: renderManagedMcpConfig(path.join(integrationRootPath, PROJECT_MCP_RELATIVE_PATH), managedMcpEntry),
1225
1326
  }));
1226
1327
  }
1227
1328
  if (projectMcp.cursorFile.state !== "ready") {
@@ -1236,7 +1337,37 @@ function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDe
1236
1337
  summary: projectMcp.cursorFile.present
1237
1338
  ? "Replace the managed `gdh` MCP entry in .cursor/mcp.json while preserving any non-GDH servers."
1238
1339
  : "Create .cursor/mcp.json with the managed `gdh` MCP entry.",
1239
- content: renderManagedMcpConfig(path.join(integrationRootPath, CURSOR_MCP_RELATIVE_PATH), buildManagedMcpServerEntry({ pinnedVersion })),
1340
+ content: renderManagedMcpConfig(path.join(integrationRootPath, CURSOR_MCP_RELATIVE_PATH), managedMcpEntry),
1341
+ }));
1342
+ }
1343
+ if (nestedTarget && projectMcp.targetProjectFile.state !== "ready") {
1344
+ actions.push(createInstallAction({
1345
+ agent,
1346
+ kind: "write_file",
1347
+ scope: "repo",
1348
+ targetPath,
1349
+ relativePath: PROJECT_MCP_RELATIVE_PATH,
1350
+ state: "planned",
1351
+ mode: projectMcp.targetProjectFile.present ? "replace" : "create",
1352
+ summary: projectMcp.targetProjectFile.present
1353
+ ? "Replace the managed `gdh` MCP entry in target-local .mcp.json while preserving any non-GDH servers."
1354
+ : "Create target-local .mcp.json so Claude can discover GDH when launched from the Godot target.",
1355
+ content: renderManagedMcpConfig(path.join(targetPath, PROJECT_MCP_RELATIVE_PATH), managedMcpEntry),
1356
+ }));
1357
+ }
1358
+ if (nestedTarget && projectMcp.targetCursorFile.state !== "ready") {
1359
+ actions.push(createInstallAction({
1360
+ agent,
1361
+ kind: "write_file",
1362
+ scope: "repo",
1363
+ targetPath,
1364
+ relativePath: CURSOR_MCP_RELATIVE_PATH,
1365
+ state: "planned",
1366
+ mode: projectMcp.targetCursorFile.present ? "replace" : "create",
1367
+ summary: projectMcp.targetCursorFile.present
1368
+ ? "Replace the managed `gdh` MCP entry in target-local .cursor/mcp.json while preserving any non-GDH servers."
1369
+ : "Create target-local .cursor/mcp.json so Cursor can discover GDH when opened at the Godot target.",
1370
+ content: renderManagedMcpConfig(path.join(targetPath, CURSOR_MCP_RELATIVE_PATH), managedMcpEntry),
1240
1371
  }));
1241
1372
  }
1242
1373
  if (projectMcp.localPathHints.gdhDevRepoPath !== effectiveDevRepoPath) {
@@ -1296,6 +1427,35 @@ function planSkillInstallAction(agent, targetPath, adapter, relativePath, render
1296
1427
  content: renderFn(pinnedVersion),
1297
1428
  });
1298
1429
  }
1430
+ function planCommandInstallAction(agent, targetPath, adapter, relativePath, renderFn, commandName, pinnedVersion) {
1431
+ const surface = adapter.surfaces.find((s) => s.relativePath === relativePath);
1432
+ if (!surface || surface.state === "ready") {
1433
+ return createInstallAction({
1434
+ agent,
1435
+ kind: "write_file",
1436
+ scope: "repo",
1437
+ targetPath,
1438
+ relativePath,
1439
+ state: "unchanged",
1440
+ mode: "unchanged",
1441
+ summary: `The managed ${agentLabel(agent)} \`/${commandName}\` command already matches the expected GDH content.`,
1442
+ content: renderFn(pinnedVersion),
1443
+ });
1444
+ }
1445
+ return createInstallAction({
1446
+ agent,
1447
+ kind: "write_file",
1448
+ scope: "repo",
1449
+ targetPath,
1450
+ relativePath,
1451
+ state: "planned",
1452
+ mode: surface.present ? "replace" : "create",
1453
+ summary: surface.present
1454
+ ? `Replace the existing ${agentLabel(agent)} \`/${commandName}\` command with the managed GDH command.`
1455
+ : `Create the managed ${agentLabel(agent)} \`/${commandName}\` command.`,
1456
+ content: renderFn(pinnedVersion),
1457
+ });
1458
+ }
1299
1459
  function planHookInstallAction(agent, targetPath, adapter, relativePath, renderFn, hookName, pinnedVersion) {
1300
1460
  const surface = adapter.surfaces.find((entry) => entry.relativePath === relativePath);
1301
1461
  if (!surface || surface.state === "ready") {
@@ -1369,6 +1529,15 @@ function planCodexRepoInstallActions(targetPath, adapter, pinnedVersion, project
1369
1529
  content: renderManagedCodexProjectConfig(existingContent, pinnedVersion),
1370
1530
  }));
1371
1531
  }
1532
+ if (path.resolve(integrationRootPath) !== path.resolve(targetPath)) {
1533
+ actions.push(...planRootSymlinkActions({
1534
+ agent: "codex",
1535
+ integrationRootPath,
1536
+ targetPath,
1537
+ relativePaths: CODEX_SKILL_RELATIVE_PATHS,
1538
+ noun: "Codex skill",
1539
+ }));
1540
+ }
1372
1541
  actions.push(...planLegacyCodexSkillCleanupActions(targetPath, pinnedVersion));
1373
1542
  return actions;
1374
1543
  }
@@ -1387,6 +1556,10 @@ function isManagedLegacyCodexSkillContent(content) {
1387
1556
  return (content.includes(GDH_MANAGED_AGENT_SKILL_MARKER) ||
1388
1557
  content.includes(LEGACY_CODEX_MANAGED_SKILL_MARKER));
1389
1558
  }
1559
+ function isManagedLegacyCursorSkillContent(content) {
1560
+ return (content.includes(GDH_MANAGED_AGENT_SKILL_MARKER) ||
1561
+ content.includes(LEGACY_CURSOR_MANAGED_SKILL_MARKER));
1562
+ }
1390
1563
  function isManagedLegacyClaudeCommandContent(content, skillName) {
1391
1564
  const slashName = skillName.replace("gdh-", "gdh:");
1392
1565
  return (content.includes(`name: ${slashName}`) &&
@@ -1471,6 +1644,7 @@ function planRetiredManagedSurfaceCleanupActions(targetPath) {
1471
1644
  summary: `Remove retired managed \`${entry.skillName}\` surface. ${entry.replacementSummary}`,
1472
1645
  }));
1473
1646
  }
1647
+ actions.push(...planLegacyCursorSkillCleanupActions(targetPath));
1474
1648
  return actions;
1475
1649
  }
1476
1650
  function planLegacyCodexSkillCleanupActions(targetPath, _pinnedVersion) {
@@ -1502,6 +1676,29 @@ function planLegacyCodexSkillCleanupActions(targetPath, _pinnedVersion) {
1502
1676
  }
1503
1677
  return actions;
1504
1678
  }
1679
+ function planLegacyCursorSkillCleanupActions(targetPath) {
1680
+ const actions = [];
1681
+ for (const [relativePath, skillName] of LEGACY_CURSOR_SKILL_RELATIVE_PATHS) {
1682
+ const absolutePath = path.join(targetPath, relativePath);
1683
+ const content = fsSync.existsSync(absolutePath)
1684
+ ? fsSync.readFileSync(absolutePath, "utf8")
1685
+ : null;
1686
+ if (content === null || !isManagedLegacyCursorSkillContent(content)) {
1687
+ continue;
1688
+ }
1689
+ actions.push(createInstallAction({
1690
+ agent: "cursor",
1691
+ kind: "remove_file",
1692
+ scope: "repo",
1693
+ targetPath,
1694
+ relativePath,
1695
+ state: "planned",
1696
+ mode: "delete",
1697
+ summary: `Remove legacy managed Cursor \`/${skillName}\` skill from .cursor/skills after installing the documented .cursor/commands location.`,
1698
+ }));
1699
+ }
1700
+ return actions;
1701
+ }
1505
1702
  function planLegacyClaudeCommandCleanupActions(targetPath) {
1506
1703
  const actions = [];
1507
1704
  for (const [relativePath, skillName] of LEGACY_CLAUDE_SKILL_COMMAND_RELATIVE_PATHS) {
@@ -1705,6 +1902,22 @@ function planDirectSymlinkAction(input) {
1705
1902
  expectedTarget: input.expectedTarget,
1706
1903
  });
1707
1904
  }
1905
+ function planRootSymlinkActions(input) {
1906
+ return input.relativePaths.map((relativePath) => {
1907
+ const rootLinkDirectory = path.dirname(path.join(input.integrationRootPath, relativePath));
1908
+ const targetFilePath = path.join(input.targetPath, relativePath);
1909
+ const expectedTarget = normalizePortableRelativePath(path.relative(rootLinkDirectory, targetFilePath));
1910
+ return planDirectSymlinkAction({
1911
+ agent: input.agent,
1912
+ targetPath: input.integrationRootPath,
1913
+ relativePath,
1914
+ expectedTarget,
1915
+ readySummary: `Root-launched ${input.noun} ${relativePath} already points to the target-local managed surface.`,
1916
+ createSummary: `Create root-launched ${input.noun} symlink ${relativePath} to the target-local managed surface.`,
1917
+ replaceSummary: `Replace root-launched ${input.noun} ${relativePath} with a symlink to the target-local managed surface.`,
1918
+ });
1919
+ });
1920
+ }
1708
1921
  function planDirectWriteFileAction(input) {
1709
1922
  const absolutePath = path.resolve(input.targetPath, input.relativePath);
1710
1923
  let existingContent = null;
@@ -1758,9 +1971,10 @@ function planRootClaudeAuthoringSettingsAction(integrationRootPath) {
1758
1971
  content: patchedSettings,
1759
1972
  });
1760
1973
  }
1761
- function planCursorInstallActions(targetPath, adapter, pinnedVersion) {
1974
+ function planCursorInstallActions(targetPath, adapter, pinnedVersion, integrationRootPath) {
1762
1975
  const actions = [];
1763
1976
  const cursorRuleSurface = adapter.surfaces.find((surface) => surface.relativePath === CURSOR_RULE_RELATIVE_PATH);
1977
+ const targetRelativePath = path.relative(integrationRootPath, targetPath) || ".";
1764
1978
  if (!cursorRuleSurface || cursorRuleSurface.state === "ready") {
1765
1979
  actions.push(createInstallAction({
1766
1980
  agent: "cursor",
@@ -1791,7 +2005,24 @@ function planCursorInstallActions(targetPath, adapter, pinnedVersion) {
1791
2005
  content: renderCursorRule(),
1792
2006
  }));
1793
2007
  }
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));
2008
+ 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));
2009
+ if (path.resolve(integrationRootPath) !== path.resolve(targetPath)) {
2010
+ actions.push(planDirectWriteFileAction({
2011
+ agent: "cursor",
2012
+ targetPath: integrationRootPath,
2013
+ relativePath: CURSOR_RULE_RELATIVE_PATH,
2014
+ content: renderCursorRule({ targetRelativePath }),
2015
+ readySummary: "Root-launched Cursor rule already routes to the managed Godot target.",
2016
+ createSummary: "Create root-launched Cursor rule that routes to the managed Godot target.",
2017
+ replaceSummary: "Replace root-launched Cursor rule with target-aware links to the managed Godot target.",
2018
+ }), ...planRootSymlinkActions({
2019
+ agent: "cursor",
2020
+ integrationRootPath,
2021
+ targetPath,
2022
+ relativePaths: CURSOR_COMMAND_RELATIVE_PATHS,
2023
+ noun: "Cursor command",
2024
+ }));
2025
+ }
1795
2026
  return actions;
1796
2027
  }
1797
2028
  function dedupeInstallActions(actions) {
@@ -1837,7 +2068,8 @@ async function applySymlinkAction(action) {
1837
2068
  if (action.absolutePath === null) {
1838
2069
  throw new Error("Symlink install action is missing an absolute path.");
1839
2070
  }
1840
- await fs.rm(action.absolutePath, { force: true });
2071
+ await fs.mkdir(path.dirname(action.absolutePath), { recursive: true });
2072
+ await fs.rm(action.absolutePath, { recursive: true, force: true });
1841
2073
  await fs.symlink(action.expectedTarget ?? "AGENTS.md", action.absolutePath);
1842
2074
  }
1843
2075
  async function applyWriteFileAction(action) {