@skillcap/gdh 0.5.0 → 0.7.0

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 (83) hide show
  1. package/INSTALL-BUNDLE.json +1 -1
  2. package/README.md +66 -85
  3. package/node_modules/@gdh/adapters/dist/claude-settings-patch.d.ts +74 -0
  4. package/node_modules/@gdh/adapters/dist/claude-settings-patch.d.ts.map +1 -0
  5. package/node_modules/@gdh/adapters/dist/claude-settings-patch.js +158 -0
  6. package/node_modules/@gdh/adapters/dist/claude-settings-patch.js.map +1 -0
  7. package/node_modules/@gdh/adapters/dist/claude-statusline-render.d.ts +51 -0
  8. package/node_modules/@gdh/adapters/dist/claude-statusline-render.d.ts.map +1 -0
  9. package/node_modules/@gdh/adapters/dist/claude-statusline-render.js +80 -0
  10. package/node_modules/@gdh/adapters/dist/claude-statusline-render.js.map +1 -0
  11. package/node_modules/@gdh/adapters/dist/claude-update-hook-render.d.ts +35 -0
  12. package/node_modules/@gdh/adapters/dist/claude-update-hook-render.d.ts.map +1 -0
  13. package/node_modules/@gdh/adapters/dist/claude-update-hook-render.js +76 -0
  14. package/node_modules/@gdh/adapters/dist/claude-update-hook-render.js.map +1 -0
  15. package/node_modules/@gdh/adapters/dist/claude-update-worker-render.d.ts +28 -0
  16. package/node_modules/@gdh/adapters/dist/claude-update-worker-render.d.ts.map +1 -0
  17. package/node_modules/@gdh/adapters/dist/claude-update-worker-render.js +99 -0
  18. package/node_modules/@gdh/adapters/dist/claude-update-worker-render.js.map +1 -0
  19. package/node_modules/@gdh/adapters/dist/index.d.ts +12 -2
  20. package/node_modules/@gdh/adapters/dist/index.d.ts.map +1 -1
  21. package/node_modules/@gdh/adapters/dist/index.js +382 -244
  22. package/node_modules/@gdh/adapters/dist/index.js.map +1 -1
  23. package/node_modules/@gdh/adapters/dist/self-update-mechanics.d.ts +51 -0
  24. package/node_modules/@gdh/adapters/dist/self-update-mechanics.d.ts.map +1 -0
  25. package/node_modules/@gdh/adapters/dist/self-update-mechanics.js +155 -0
  26. package/node_modules/@gdh/adapters/dist/self-update-mechanics.js.map +1 -0
  27. package/node_modules/@gdh/adapters/package.json +8 -8
  28. package/node_modules/@gdh/authoring/dist/index.d.ts +1 -0
  29. package/node_modules/@gdh/authoring/dist/index.d.ts.map +1 -1
  30. package/node_modules/@gdh/authoring/dist/index.js +1 -0
  31. package/node_modules/@gdh/authoring/dist/index.js.map +1 -1
  32. package/node_modules/@gdh/authoring/dist/writePinnedVersion.d.ts +17 -0
  33. package/node_modules/@gdh/authoring/dist/writePinnedVersion.d.ts.map +1 -0
  34. package/node_modules/@gdh/authoring/dist/writePinnedVersion.js +50 -0
  35. package/node_modules/@gdh/authoring/dist/writePinnedVersion.js.map +1 -0
  36. package/node_modules/@gdh/authoring/package.json +5 -2
  37. package/node_modules/@gdh/cli/dist/index.d.ts +15 -0
  38. package/node_modules/@gdh/cli/dist/index.d.ts.map +1 -1
  39. package/node_modules/@gdh/cli/dist/index.js +119 -20
  40. package/node_modules/@gdh/cli/dist/index.js.map +1 -1
  41. package/node_modules/@gdh/cli/dist/migrate.d.ts +1 -1
  42. package/node_modules/@gdh/cli/dist/migrate.d.ts.map +1 -1
  43. package/node_modules/@gdh/cli/dist/migrate.js +44 -3
  44. package/node_modules/@gdh/cli/dist/migrate.js.map +1 -1
  45. package/node_modules/@gdh/cli/dist/self-update.d.ts +3 -0
  46. package/node_modules/@gdh/cli/dist/self-update.d.ts.map +1 -0
  47. package/node_modules/@gdh/cli/dist/self-update.js +235 -0
  48. package/node_modules/@gdh/cli/dist/self-update.js.map +1 -0
  49. package/node_modules/@gdh/cli/dist/update-banner.d.ts +42 -0
  50. package/node_modules/@gdh/cli/dist/update-banner.d.ts.map +1 -0
  51. package/node_modules/@gdh/cli/dist/update-banner.js +49 -0
  52. package/node_modules/@gdh/cli/dist/update-banner.js.map +1 -0
  53. package/node_modules/@gdh/cli/package.json +10 -10
  54. package/node_modules/@gdh/core/dist/dev-mode.d.ts +13 -0
  55. package/node_modules/@gdh/core/dist/dev-mode.d.ts.map +1 -0
  56. package/node_modules/@gdh/core/dist/dev-mode.js +21 -0
  57. package/node_modules/@gdh/core/dist/dev-mode.js.map +1 -0
  58. package/node_modules/@gdh/core/dist/index.d.ts +9 -4
  59. package/node_modules/@gdh/core/dist/index.d.ts.map +1 -1
  60. package/node_modules/@gdh/core/dist/index.js +8 -5
  61. package/node_modules/@gdh/core/dist/index.js.map +1 -1
  62. package/node_modules/@gdh/core/dist/update-cache.d.ts +46 -0
  63. package/node_modules/@gdh/core/dist/update-cache.d.ts.map +1 -0
  64. package/node_modules/@gdh/core/dist/update-cache.js +90 -0
  65. package/node_modules/@gdh/core/dist/update-cache.js.map +1 -0
  66. package/node_modules/@gdh/core/dist/update-probe.d.ts +102 -0
  67. package/node_modules/@gdh/core/dist/update-probe.d.ts.map +1 -0
  68. package/node_modules/@gdh/core/dist/update-probe.js +195 -0
  69. package/node_modules/@gdh/core/dist/update-probe.js.map +1 -0
  70. package/node_modules/@gdh/core/package.json +1 -1
  71. package/node_modules/@gdh/docs/package.json +2 -2
  72. package/node_modules/@gdh/mcp/dist/index.d.ts +20 -0
  73. package/node_modules/@gdh/mcp/dist/index.d.ts.map +1 -1
  74. package/node_modules/@gdh/mcp/dist/index.js +39 -2
  75. package/node_modules/@gdh/mcp/dist/index.js.map +1 -1
  76. package/node_modules/@gdh/mcp/package.json +8 -8
  77. package/node_modules/@gdh/observability/dist/guidance-audit.js +2 -1
  78. package/node_modules/@gdh/observability/dist/guidance-audit.js.map +1 -1
  79. package/node_modules/@gdh/observability/package.json +2 -2
  80. package/node_modules/@gdh/runtime/package.json +2 -2
  81. package/node_modules/@gdh/scan/package.json +3 -3
  82. package/node_modules/@gdh/verify/package.json +7 -7
  83. package/package.json +11 -11
@@ -5,7 +5,11 @@ import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { promisify } from "node:util";
7
7
  import { readProjectConfig, resolvePinnedVersion, resolvePinnedVersionOrNull, resolveProjectRoot, readWorktreeState, resolveAuthoringStatus, } from "@gdh/authoring";
8
- import { GDH_AGENT_CONTRACT_VERSION, GDH_CURSOR_RULE_VERSION, GDH_GUIDANCE_INDEX_VERSION, GDH_GUIDANCE_UNIT_VERSION, GDH_MCP_LAUNCHER_VERSION, GDH_PROJECT_CONFIG_VERSION, GDH_RECIPE_SCHEMA_VERSION, GDH_RULES_SCHEMA_VERSION, GDH_SCENARIO_SCHEMA_VERSION, definePackageBoundary, resolveCurrentGdhInstall, resolveGdhProductMetadata, } from "@gdh/core";
8
+ import { CLAUDE_CHECK_UPDATE_HOOK_RELATIVE_PATH, CLAUDE_CHECK_UPDATE_WORKER_RELATIVE_PATH, renderClaudeCheckUpdateHook, } from "./claude-update-hook-render.js";
9
+ import { renderClaudeCheckUpdateWorker } from "./claude-update-worker-render.js";
10
+ import { CLAUDE_STATUSLINE_RELATIVE_PATH, renderClaudeUpdateStatusline, } from "./claude-statusline-render.js";
11
+ import { CLAUDE_SETTINGS_RELATIVE_PATH, patchClaudeSettingsForGdhSessionStart, patchClaudeSettingsForGdhStatusline, } from "./claude-settings-patch.js";
12
+ import { GDH_AGENT_CONTRACT_VERSION, GDH_CURSOR_RULE_VERSION, GDH_GUIDANCE_INDEX_VERSION, GDH_GUIDANCE_UNIT_VERSION, GDH_PROJECT_CONFIG_VERSION, GDH_RECIPE_SCHEMA_VERSION, GDH_RULES_SCHEMA_VERSION, GDH_SCENARIO_SCHEMA_VERSION, definePackageBoundary, resolveCurrentGdhInstall, resolveGdhProductMetadata, } from "@gdh/core";
9
13
  import { createDefaultGuidanceUnits, getGuidanceStatus, resolveGuidanceQuery, } from "@gdh/docs";
10
14
  import { inspectGuidanceAudit } from "@gdh/observability";
11
15
  import { inspectRuntimeBridgeSurface } from "@gdh/runtime";
@@ -48,11 +52,20 @@ export const CURSOR_MIGRATE_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-migrate/SK
48
52
  export const CURSOR_CHECK_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-check/SKILL.md";
49
53
  export const CURSOR_PREPARE_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-prepare/SKILL.md";
50
54
  export const CURSOR_VERIFY_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-verify/SKILL.md";
55
+ // Phase 13 SELF-01: /gdh-update skill surface path constants. The rendered
56
+ // bodies shell out to `npx -y @skillcap/gdh@latest self-update` (LITERAL
57
+ // @latest, not the pinned version — D-10) so the NEW CLI performs the update,
58
+ // not the (potentially pre-Phase-12) OLD pinned one. These constants are
59
+ // INTENTIONALLY excluded from VERIFY_DRIFT_SCANNED_FILES (D-13) because the
60
+ // rendered bodies are version-agnostic by design.
61
+ export const CLAUDE_UPDATE_COMMAND_RELATIVE_PATH = ".claude/commands/gdh/update.md";
62
+ export const CODEX_UPDATE_SKILL_RELATIVE_PATH = ".codex/skills/gdh-update/SKILL.md";
63
+ export const CURSOR_UPDATE_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-update/SKILL.md";
51
64
  export const CLAUDE_SCAN_COMMAND_RELATIVE_PATH = ".claude/commands/gdh/scan.md";
52
65
  export const CODEX_SCAN_SKILL_RELATIVE_PATH = ".codex/skills/gdh-scan/SKILL.md";
53
66
  export const CURSOR_SCAN_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-scan/SKILL.md";
54
67
  export const LOCAL_PATH_HINTS_RELATIVE_PATH = ".gdh-state/local-paths.json";
55
- export const MCP_LAUNCHER_RELATIVE_PATH = ".gdh/bin/gdh-mcp.mjs";
68
+ export const CODEX_PROJECT_CONFIG_RELATIVE_PATH = ".codex/config.toml";
56
69
  export const GDH_MCP_SERVER_NAME = "gdh";
57
70
  const execFile = promisify(execFileCallback);
58
71
  export async function getSupportedAgentAdaptersStatus(targetPath, options = {}) {
@@ -203,7 +216,6 @@ export async function inspectProjectLifecycleCompatibility(targetPath) {
203
216
  await inspectGuidanceUnitLifecycleSurface(resolvedTargetPath, projectConfig),
204
217
  inspectMcpManifestLifecycleSurface(resolvedTargetPath, projectConfig.mcp.enabled, adapterStatus),
205
218
  inspectCursorRuleLifecycleSurface(resolvedTargetPath, adapterStatus),
206
- inspectMcpLauncherLifecycleSurface(resolvedTargetPath, projectConfig.mcp.enabled, adapterStatus),
207
219
  inspectRuntimeBridgeLifecycleSurface(resolvedTargetPath, bridgeStatus),
208
220
  ]);
209
221
  }
@@ -774,6 +786,133 @@ export function renderCursorMigrateSkill(pinnedVersion) {
774
786
  "",
775
787
  ].join("\n");
776
788
  }
789
+ // --- gdh-update skill renders (Phase 13 SELF-01) ---
790
+ //
791
+ // D-10 invariant: every rendered body shells out to the LITERAL string
792
+ // `@skillcap/gdh@latest` in every npx line (dry-run, apply, verify drift).
793
+ // The `pinnedVersion` parameter is accepted for planSkillInstallAction
794
+ // signature symmetry with every other renderer, but MUST NOT be interpolated
795
+ // into the shellout — running the OLD pinned CLI (potentially pre-Phase-12
796
+ // and lacking bumpAndRebakePin entirely) to perform its own update is the
797
+ // failure mode Phase 13 exists to close. Check 44 in scripts/validate-docs.mjs
798
+ // enforces both the @latest presence AND the @${pinnedVersion} absence.
799
+ //
800
+ // D-11: preview-then-apply flow WITHOUT a confirmation gate — the human's
801
+ // original intent ("update GDH") IS the approval. No AskUserQuestion for
802
+ // Claude; no `## User questions` H2 for Codex/Cursor. Differs from /gdh-migrate
803
+ // which DOES gate on explicit approval.
804
+ //
805
+ // D-12: accept an optional positional version forwarded verbatim to the CLI.
806
+ // `/gdh-update` = latest; `/gdh-update 0.6.0` = pin that version.
807
+ //
808
+ // D-13: the three rendered skill files are EXCLUDED from VERIFY_DRIFT_SCANNED_FILES
809
+ // because @latest is not a semver literal — the baked-version scanner would
810
+ // permanently report no_baked_version. See rationale comments adjacent to the
811
+ // VERIFY_DRIFT_SCANNED_FILES declaration in packages/cli/src/index.ts.
812
+ export function renderClaudeUpdateCommand(_pinnedVersion) {
813
+ return [
814
+ "---",
815
+ "name: gdh:update",
816
+ "description: Update GDH to npm latest (bump pinned version + re-bake managed surfaces)",
817
+ "allowed-tools:",
818
+ " - Read",
819
+ " - Bash",
820
+ "---",
821
+ "<objective>",
822
+ "Update this project's GDH pinning to npm latest and re-bake every managed surface at the new pin.",
823
+ "Treat any positional argument (e.g., `/gdh-update 0.6.0`) as an explicit version; otherwise default to npm latest.",
824
+ "</objective>",
825
+ "",
826
+ "<process>",
827
+ "Follow this order:",
828
+ "",
829
+ "1. Preview: run `npx -y @skillcap/gdh@latest self-update [version] --dry-run` (omit `[version]` to preview against npm latest).",
830
+ "2. Surface the planned version delta and re-bake action count to the human conversationally.",
831
+ "3. Apply: run `npx -y @skillcap/gdh@latest self-update [version]` (omit `[version]` to apply against npm latest; forward whatever positional the user passed to `/gdh-update`).",
832
+ "4. Verify: run `npx -y @skillcap/gdh@latest verify drift` to confirm every baked surface matches the new pin.",
833
+ "</process>",
834
+ "",
835
+ "<rules>",
836
+ "- Do NOT ask the user to confirm apply — their original intent (\"update GDH\") IS the approval.",
837
+ "- Bake literal `@latest` in the shellout so the new CLI performs the update, not the old one.",
838
+ "- If `self-update` returns `rolled_back` or `blocked`, surface the failure reason and stop (do not retry).",
839
+ "- If `self-update` returns `skipped_dev_mode`, explain that dev-from-source mode bypasses pinning; no action needed.",
840
+ "</rules>",
841
+ "",
842
+ ].join("\n");
843
+ }
844
+ export function renderCodexUpdateSkill(_pinnedVersion) {
845
+ return [
846
+ "---",
847
+ 'name: "gdh-update"',
848
+ 'description: "Update GDH to npm latest (bump pinned version + re-bake managed surfaces)"',
849
+ "metadata:",
850
+ ' short-description: "Update GDH to npm latest"',
851
+ "---",
852
+ "",
853
+ "<codex_skill_adapter>",
854
+ "## Invocation",
855
+ "- This skill is invoked when the user says `/gdh-update` or mentions `$gdh-update`.",
856
+ "- Treat any positional argument (e.g., `/gdh-update 0.6.0`) as an explicit version; otherwise default to npm latest.",
857
+ "</codex_skill_adapter>",
858
+ "",
859
+ "<objective>",
860
+ "Update this project's GDH pinning to npm latest and re-bake every managed surface at the new pin.",
861
+ "</objective>",
862
+ "",
863
+ "<process>",
864
+ "Follow this order:",
865
+ "",
866
+ "1. Preview: run `npx -y @skillcap/gdh@latest self-update [version] --dry-run` (omit `[version]` to preview against npm latest).",
867
+ "2. Surface the planned version delta and re-bake action count to the human conversationally.",
868
+ "3. Apply: run `npx -y @skillcap/gdh@latest self-update [version]` (omit `[version]` to apply against npm latest; forward whatever positional the user passed to `/gdh-update`).",
869
+ "4. Verify: run `npx -y @skillcap/gdh@latest verify drift` to confirm every baked surface matches the new pin.",
870
+ "</process>",
871
+ "",
872
+ "<rules>",
873
+ "- Do NOT ask the user to confirm apply — their original intent (\"update GDH\") IS the approval.",
874
+ "- Bake literal `@latest` in the shellout so the new CLI performs the update, not the old one.",
875
+ "- If `self-update` returns `rolled_back` or `blocked`, surface the failure reason and stop (do not retry).",
876
+ "- If `self-update` returns `skipped_dev_mode`, explain that dev-from-source mode bypasses pinning; no action needed.",
877
+ "</rules>",
878
+ "",
879
+ ].join("\n");
880
+ }
881
+ export function renderCursorUpdateSkill(_pinnedVersion) {
882
+ return [
883
+ "---",
884
+ "name: gdh-update",
885
+ 'description: "Update GDH to npm latest (bump pinned version + re-bake managed surfaces)"',
886
+ "---",
887
+ "",
888
+ "<cursor_skill_adapter>",
889
+ "## Invocation",
890
+ "- This skill is invoked when the user says `/gdh-update` or mentions `gdh-update`.",
891
+ "- Treat any positional argument (e.g., `gdh-update 0.6.0`) as an explicit version; otherwise default to npm latest.",
892
+ "</cursor_skill_adapter>",
893
+ "",
894
+ "<objective>",
895
+ "Update this project's GDH pinning to npm latest and re-bake every managed surface at the new pin.",
896
+ "</objective>",
897
+ "",
898
+ "<process>",
899
+ "Follow this order:",
900
+ "",
901
+ "1. Preview: run `npx -y @skillcap/gdh@latest self-update [version] --dry-run` (omit `[version]` to preview against npm latest).",
902
+ "2. Surface the planned version delta and re-bake action count to the human conversationally.",
903
+ "3. Apply: run `npx -y @skillcap/gdh@latest self-update [version]` (omit `[version]` to apply against npm latest; forward whatever positional the user passed to `/gdh-update`).",
904
+ "4. Verify: run `npx -y @skillcap/gdh@latest verify drift` to confirm every baked surface matches the new pin.",
905
+ "</process>",
906
+ "",
907
+ "<rules>",
908
+ "- Do NOT ask the user to confirm apply — their original intent (\"update GDH\") IS the approval.",
909
+ "- Bake literal `@latest` in the shellout so the new CLI performs the update, not the old one.",
910
+ "- If `self-update` returns `rolled_back` or `blocked`, surface the failure reason and stop (do not retry).",
911
+ "- If `self-update` returns `skipped_dev_mode`, explain that dev-from-source mode bypasses pinning; no action needed.",
912
+ "</rules>",
913
+ "",
914
+ ].join("\n");
915
+ }
777
916
  // --- gdh-check skill renders ---
778
917
  export function renderClaudeCheckCommand(pinnedVersion) {
779
918
  return [
@@ -1117,34 +1256,26 @@ export function renderCursorVerifySkill(pinnedVersion) {
1117
1256
  async function inspectProjectMcpSupport(targetPath, options) {
1118
1257
  const projectConfig = await readProjectConfig(targetPath);
1119
1258
  const enabled = resolveProjectMcpEnabled(projectConfig);
1120
- const launcherContent = options.pinnedVersion === null
1121
- ? null
1122
- : renderManagedMcpLauncher(options.pinnedVersion);
1123
1259
  const managedMcpEntry = buildManagedMcpServerEntry({
1124
- targetPath,
1125
- integrationRootPath: options.integrationRootPath,
1126
- launcherPathForConfig: path.resolve(options.integrationRootPath, MCP_LAUNCHER_RELATIVE_PATH),
1260
+ pinnedVersion: options.pinnedVersion ?? "latest",
1127
1261
  });
1128
- const [projectFile, cursorFile, launcherSource, localPathHints] = await Promise.all([
1262
+ const [projectFile, cursorFile, codexProjectContent, localPathHints] = await Promise.all([
1129
1263
  inspectJsonFile(path.join(options.integrationRootPath, PROJECT_MCP_RELATIVE_PATH)),
1130
1264
  inspectJsonFile(path.join(options.integrationRootPath, CURSOR_MCP_RELATIVE_PATH)),
1131
- fs.readFile(path.join(options.integrationRootPath, MCP_LAUNCHER_RELATIVE_PATH), "utf8").catch(() => null),
1265
+ fs.readFile(path.join(options.integrationRootPath, CODEX_PROJECT_CONFIG_RELATIVE_PATH), "utf8").catch(() => null),
1132
1266
  readLocalPathHints(options.integrationRootPath),
1133
1267
  ]);
1134
- const launcherBootstrap = await inspectLauncherBootstrap(localPathHints);
1135
1268
  const codexConfigPath = path.join(os.homedir(), ".codex/config.toml");
1136
1269
  const codexServerName = projectConfig === null ? null : createCodexServerName(projectConfig.projectKeySeed);
1137
1270
  return {
1138
1271
  enabled,
1139
1272
  integrationRootPath: options.integrationRootPath,
1140
- launcherContent,
1141
1273
  localPathHints,
1142
- launcherBootstrap,
1143
- launcherFile: inspectLauncherFile(launcherSource, launcherContent, enabled, launcherBootstrap),
1144
1274
  projectFile: inspectManagedMcpFile(projectFile, enabled, PROJECT_MCP_RELATIVE_PATH, "Claude project MCP config", managedMcpEntry),
1145
1275
  cursorFile: inspectManagedMcpFile(cursorFile, enabled, CURSOR_MCP_RELATIVE_PATH, "Cursor project MCP config", managedMcpEntry),
1276
+ codexProjectFile: inspectManagedCodexProjectFile(codexProjectContent, enabled, options.pinnedVersion),
1146
1277
  codexRegistration: options.includeUserLocal
1147
- ? await inspectCodexRegistration(targetPath, enabled, codexServerName, codexConfigPath, options.integrationRootPath)
1278
+ ? await inspectCodexRegistration(targetPath, enabled, codexServerName, codexConfigPath, options.integrationRootPath, options.pinnedVersion)
1148
1279
  : {
1149
1280
  present: false,
1150
1281
  state: "ready",
@@ -1221,71 +1352,52 @@ function inspectManagedMcpFile(file, enabled, relativePath, displayName, expecte
1221
1352
  summary: `${displayName} includes the managed \`${GDH_MCP_SERVER_NAME}\` MCP server entry.`,
1222
1353
  };
1223
1354
  }
1224
- function inspectLauncherFile(launcherSource, expectedContent, enabled, bootstrap) {
1355
+ function inspectManagedCodexProjectFile(content, enabled, pinnedVersion) {
1356
+ const relativePath = CODEX_PROJECT_CONFIG_RELATIVE_PATH;
1225
1357
  if (!enabled) {
1226
1358
  return {
1227
- present: launcherSource !== null,
1359
+ present: content !== null,
1228
1360
  state: "ready",
1229
- summary: "The managed GDH MCP launcher is disabled for this target.",
1361
+ summary: "Codex project MCP config is disabled for this target.",
1230
1362
  };
1231
1363
  }
1232
- if (expectedContent === null) {
1364
+ if (pinnedVersion === null) {
1233
1365
  return {
1234
- present: launcherSource !== null,
1366
+ present: content !== null,
1235
1367
  state: "missing",
1236
- summary: "The managed GDH MCP launcher is pending project onboard: no `gdh_version` is pinned yet, so there is no expected launcher content to compare against.",
1368
+ summary: `Codex project MCP config cannot be validated yet: no \`gdh_version\` is pinned (run \`gdh setup\` or \`gdh migrate --apply\`).`,
1237
1369
  };
1238
1370
  }
1239
- if (launcherSource === null) {
1371
+ if (content === null) {
1240
1372
  return {
1241
1373
  present: false,
1242
1374
  state: "missing",
1243
- summary: `The managed GDH MCP launcher is missing and should live at ${MCP_LAUNCHER_RELATIVE_PATH}.`,
1375
+ summary: `Codex project MCP config is missing and should define the managed \`[mcp_servers.${GDH_MCP_SERVER_NAME}]\` entry at ${relativePath}.`,
1244
1376
  };
1245
1377
  }
1246
- if (launcherSource !== expectedContent) {
1378
+ const existingSection = extractManagedCodexSection(content);
1379
+ const expectedSection = renderManagedCodexProjectSection(pinnedVersion);
1380
+ if (existingSection === null) {
1247
1381
  return {
1248
1382
  present: true,
1249
1383
  state: "misconfigured",
1250
- summary: "The managed GDH MCP launcher exists but no longer matches the expected launcher content.",
1384
+ summary: `${relativePath} exists but does not contain the managed \`[mcp_servers.${GDH_MCP_SERVER_NAME}]\` section.`,
1251
1385
  };
1252
1386
  }
1253
- return {
1254
- present: true,
1255
- state: bootstrap.state,
1256
- summary: bootstrap.state === "ready"
1257
- ? "The managed GDH MCP launcher is present and can resolve the current GDH bootstrap path."
1258
- : bootstrap.summary,
1259
- };
1260
- }
1261
- async function inspectLauncherBootstrap(localPathHints) {
1262
- const envDevRepoPath = process.env["GDH_DEV_REPO"]?.trim() || null;
1263
- const effectiveDevRepoPath = envDevRepoPath ?? localPathHints.gdhDevRepoPath;
1264
- if (effectiveDevRepoPath !== null) {
1265
- const cliEntryPath = path.join(effectiveDevRepoPath, "packages/cli/dist/cli.js");
1266
- if (await fileExists(cliEntryPath)) {
1267
- return {
1268
- state: "ready",
1269
- summary: `The managed GDH MCP launcher will bootstrap from the current GDH checkout at ${effectiveDevRepoPath}.`,
1270
- };
1271
- }
1387
+ if (existingSection !== expectedSection) {
1272
1388
  return {
1389
+ present: true,
1273
1390
  state: "misconfigured",
1274
- summary: `The configured GDH checkout at ${effectiveDevRepoPath} does not contain packages/cli/dist/cli.js yet. Run \`corepack yarn build\` in the GDH checkout or update the local path hints.`,
1275
- };
1276
- }
1277
- if (await executableExistsOnPath("gdh")) {
1278
- return {
1279
- state: "ready",
1280
- summary: "The managed GDH MCP launcher can resolve `gdh` from PATH.",
1391
+ summary: `${relativePath} managed \`[mcp_servers.${GDH_MCP_SERVER_NAME}]\` section does not match the expected GDH shape.`,
1281
1392
  };
1282
1393
  }
1283
1394
  return {
1284
- state: "missing",
1285
- summary: "The managed GDH MCP launcher cannot find `gdh` on PATH and no local GDH checkout hint is recorded. Run `gdh adapters install <target> --dev-repo <path-to-gdh>` or install `gdh` on PATH.",
1395
+ present: true,
1396
+ state: "ready",
1397
+ summary: `Codex project MCP config includes the managed \`[mcp_servers.${GDH_MCP_SERVER_NAME}]\` entry.`,
1286
1398
  };
1287
1399
  }
1288
- async function inspectCodexRegistration(targetPath, enabled, codexServerName, codexConfigPath, integrationRootPath) {
1400
+ async function inspectCodexRegistration(targetPath, enabled, codexServerName, codexConfigPath, integrationRootPath, pinnedVersion) {
1289
1401
  if (!enabled || codexServerName === null) {
1290
1402
  return {
1291
1403
  present: false,
@@ -1309,16 +1421,22 @@ async function inspectCodexRegistration(targetPath, enabled, codexServerName, co
1309
1421
  summary: `Codex does not yet have the expected user-local MCP registration \`${codexServerName}\` in ${codexConfigPath}.`,
1310
1422
  };
1311
1423
  }
1312
- const expectedLauncherPath = path.join(integrationRootPath, MCP_LAUNCHER_RELATIVE_PATH);
1424
+ if (pinnedVersion === null) {
1425
+ return {
1426
+ present: true,
1427
+ state: "misconfigured",
1428
+ summary: `Codex MCP registration \`${codexServerName}\` exists but no \`gdh_version\` is pinned; cannot validate the expected npx invocation.`,
1429
+ };
1430
+ }
1313
1431
  if (!isMatchingCodexRegistration(registration, {
1314
1432
  targetPath,
1315
1433
  integrationRootPath,
1316
- launcherPath: expectedLauncherPath,
1434
+ pinnedVersion,
1317
1435
  })) {
1318
1436
  return {
1319
1437
  present: true,
1320
1438
  state: "misconfigured",
1321
- summary: `Codex MCP registration \`${codexServerName}\` exists but does not point at the managed GDH launcher ${expectedLauncherPath}.`,
1439
+ summary: `Codex MCP registration \`${codexServerName}\` exists but does not match the expected \`npx -y @skillcap/gdh@${pinnedVersion} mcp serve\` invocation.`,
1322
1440
  };
1323
1441
  }
1324
1442
  return {
@@ -1440,6 +1558,11 @@ async function inspectCodexAdapter(targetPath, guidance, projectMcp, pinnedVersi
1440
1558
  const codexSkillContent = await fs.readFile(codexSkillPath, "utf8").catch(() => null);
1441
1559
  const codexStatusContent = await fs.readFile(path.join(targetPath, CODEX_STATUS_SKILL_RELATIVE_PATH), "utf8").catch(() => null);
1442
1560
  const codexMigrateContent = await fs.readFile(path.join(targetPath, CODEX_MIGRATE_SKILL_RELATIVE_PATH), "utf8").catch(() => null);
1561
+ // Phase 13 Plan 13-03 deliverable — /gdh-update skill for Codex. Inspection
1562
+ // wiring is required so planSkillInstallAction can see the surface state;
1563
+ // otherwise the planner returns `unchanged` for a missing file and install
1564
+ // becomes a no-op (Plan 13-05 integration-test Rule 2 fix).
1565
+ const codexUpdateContent = await fs.readFile(path.join(targetPath, CODEX_UPDATE_SKILL_RELATIVE_PATH), "utf8").catch(() => null);
1443
1566
  const codexCheckContent = await fs.readFile(path.join(targetPath, CODEX_CHECK_SKILL_RELATIVE_PATH), "utf8").catch(() => null);
1444
1567
  const codexPrepareContent = await fs.readFile(path.join(targetPath, CODEX_PREPARE_SKILL_RELATIVE_PATH), "utf8").catch(() => null);
1445
1568
  const codexVerifyContent = await fs.readFile(path.join(targetPath, CODEX_VERIFY_SKILL_RELATIVE_PATH), "utf8").catch(() => null);
@@ -1447,6 +1570,7 @@ async function inspectCodexAdapter(targetPath, guidance, projectMcp, pinnedVersi
1447
1570
  const expectedCodexOnboardSkill = pinnedVersion === null ? null : renderCodexOnboardSkill(pinnedVersion);
1448
1571
  const expectedCodexStatusSkill = pinnedVersion === null ? null : renderCodexStatusSkill(pinnedVersion);
1449
1572
  const expectedCodexMigrateSkill = pinnedVersion === null ? null : renderCodexMigrateSkill(pinnedVersion);
1573
+ const expectedCodexUpdateSkill = pinnedVersion === null ? null : renderCodexUpdateSkill(pinnedVersion);
1450
1574
  const expectedCodexCheckSkill = pinnedVersion === null ? null : renderCodexCheckSkill(pinnedVersion);
1451
1575
  const expectedCodexPrepareSkill = pinnedVersion === null ? null : renderCodexPrepareSkill(pinnedVersion);
1452
1576
  const expectedCodexVerifySkill = pinnedVersion === null ? null : renderCodexVerifySkill(pinnedVersion);
@@ -1494,6 +1618,7 @@ async function inspectCodexAdapter(targetPath, guidance, projectMcp, pinnedVersi
1494
1618
  }),
1495
1619
  ...inspectCodexSkillSurface(targetPath, CODEX_STATUS_SKILL_RELATIVE_PATH, codexStatusContent, expectedCodexStatusSkill, "gdh-status"),
1496
1620
  ...inspectCodexSkillSurface(targetPath, CODEX_MIGRATE_SKILL_RELATIVE_PATH, codexMigrateContent, expectedCodexMigrateSkill, "gdh-migrate"),
1621
+ ...inspectCodexSkillSurface(targetPath, CODEX_UPDATE_SKILL_RELATIVE_PATH, codexUpdateContent, expectedCodexUpdateSkill, "gdh-update"),
1497
1622
  ...inspectCodexSkillSurface(targetPath, CODEX_CHECK_SKILL_RELATIVE_PATH, codexCheckContent, expectedCodexCheckSkill, "gdh-check"),
1498
1623
  ...inspectCodexSkillSurface(targetPath, CODEX_PREPARE_SKILL_RELATIVE_PATH, codexPrepareContent, expectedCodexPrepareSkill, "gdh-prepare"),
1499
1624
  ...inspectCodexSkillSurface(targetPath, CODEX_VERIFY_SKILL_RELATIVE_PATH, codexVerifyContent, expectedCodexVerifySkill, "gdh-verify"),
@@ -1501,14 +1626,14 @@ async function inspectCodexAdapter(targetPath, guidance, projectMcp, pinnedVersi
1501
1626
  ];
1502
1627
  if (projectMcp.enabled) {
1503
1628
  surfaces.push(createSurfaceStatus({
1504
- kind: "launcher_file",
1629
+ kind: "mcp_file",
1505
1630
  scope: "repo",
1506
1631
  targetPath: projectMcp.integrationRootPath,
1507
- relativePath: MCP_LAUNCHER_RELATIVE_PATH,
1508
- present: projectMcp.launcherFile.present,
1509
- state: projectMcp.launcherFile.state,
1510
- summary: projectMcp.launcherFile.summary,
1511
- version: GDH_MCP_LAUNCHER_VERSION,
1632
+ relativePath: CODEX_PROJECT_CONFIG_RELATIVE_PATH,
1633
+ present: projectMcp.codexProjectFile.present,
1634
+ state: projectMcp.codexProjectFile.state,
1635
+ summary: projectMcp.codexProjectFile.summary,
1636
+ version: null,
1512
1637
  }));
1513
1638
  if (options.includeUserLocal) {
1514
1639
  surfaces.push(createSurfaceStatus({
@@ -1533,17 +1658,29 @@ async function inspectClaudeAdapter(targetPath, guidance, projectMcp, pinnedVers
1533
1658
  const onboardCommandContent = await fs.readFile(onboardCommandPath, "utf8").catch(() => null);
1534
1659
  const claudeStatusContent = await fs.readFile(path.join(targetPath, CLAUDE_STATUS_COMMAND_RELATIVE_PATH), "utf8").catch(() => null);
1535
1660
  const claudeMigrateContent = await fs.readFile(path.join(targetPath, CLAUDE_MIGRATE_COMMAND_RELATIVE_PATH), "utf8").catch(() => null);
1661
+ // Phase 13 Plan 13-03 deliverable — /gdh-update slash command for Claude.
1662
+ // Inspection wiring is required so planSkillInstallAction can see the surface
1663
+ // state; otherwise the planner returns `unchanged` for a missing file and
1664
+ // install becomes a no-op (Plan 13-05 integration-test Rule 2 fix).
1665
+ const claudeUpdateContent = await fs.readFile(path.join(targetPath, CLAUDE_UPDATE_COMMAND_RELATIVE_PATH), "utf8").catch(() => null);
1536
1666
  const claudeCheckContent = await fs.readFile(path.join(targetPath, CLAUDE_CHECK_COMMAND_RELATIVE_PATH), "utf8").catch(() => null);
1537
1667
  const claudePrepareContent = await fs.readFile(path.join(targetPath, CLAUDE_PREPARE_COMMAND_RELATIVE_PATH), "utf8").catch(() => null);
1538
1668
  const claudeVerifyContent = await fs.readFile(path.join(targetPath, CLAUDE_VERIFY_COMMAND_RELATIVE_PATH), "utf8").catch(() => null);
1539
1669
  const claudeScanContent = await fs.readFile(path.join(targetPath, CLAUDE_SCAN_COMMAND_RELATIVE_PATH), "utf8").catch(() => null);
1670
+ const claudeCheckUpdateHookContent = await fs.readFile(path.join(targetPath, CLAUDE_CHECK_UPDATE_HOOK_RELATIVE_PATH), "utf8").catch(() => null);
1671
+ const claudeCheckUpdateWorkerContent = await fs.readFile(path.join(targetPath, CLAUDE_CHECK_UPDATE_WORKER_RELATIVE_PATH), "utf8").catch(() => null);
1672
+ const claudeStatuslineContent = await fs.readFile(path.join(targetPath, CLAUDE_STATUSLINE_RELATIVE_PATH), "utf8").catch(() => null);
1540
1673
  let detectedTarget = null;
1541
1674
  if (lstat?.isSymbolicLink()) {
1542
1675
  detectedTarget = await fs.readlink(absolutePath).catch(() => null);
1543
1676
  }
1544
1677
  const expectedClaudeOnboardCommand = pinnedVersion === null ? null : renderClaudeOnboardCommand(pinnedVersion);
1678
+ const expectedClaudeCheckUpdateHook = pinnedVersion === null ? null : renderClaudeCheckUpdateHook(pinnedVersion);
1679
+ const expectedClaudeCheckUpdateWorker = pinnedVersion === null ? null : renderClaudeCheckUpdateWorker(pinnedVersion);
1680
+ const expectedClaudeStatusline = pinnedVersion === null ? null : renderClaudeUpdateStatusline(pinnedVersion);
1545
1681
  const expectedClaudeStatusCommand = pinnedVersion === null ? null : renderClaudeStatusCommand(pinnedVersion);
1546
1682
  const expectedClaudeMigrateCommand = pinnedVersion === null ? null : renderClaudeMigrateCommand(pinnedVersion);
1683
+ const expectedClaudeUpdateCommand = pinnedVersion === null ? null : renderClaudeUpdateCommand(pinnedVersion);
1547
1684
  const expectedClaudeCheckCommand = pinnedVersion === null ? null : renderClaudeCheckCommand(pinnedVersion);
1548
1685
  const expectedClaudePrepareCommand = pinnedVersion === null ? null : renderClaudePrepareCommand(pinnedVersion);
1549
1686
  const expectedClaudeVerifyCommand = pinnedVersion === null ? null : renderClaudeVerifyCommand(pinnedVersion);
@@ -1593,22 +1730,17 @@ async function inspectClaudeAdapter(targetPath, guidance, projectMcp, pinnedVers
1593
1730
  }),
1594
1731
  ...inspectClaudeCommandSurface(targetPath, CLAUDE_STATUS_COMMAND_RELATIVE_PATH, claudeStatusContent, expectedClaudeStatusCommand, "gdh-status"),
1595
1732
  ...inspectClaudeCommandSurface(targetPath, CLAUDE_MIGRATE_COMMAND_RELATIVE_PATH, claudeMigrateContent, expectedClaudeMigrateCommand, "gdh-migrate"),
1733
+ ...inspectClaudeCommandSurface(targetPath, CLAUDE_UPDATE_COMMAND_RELATIVE_PATH, claudeUpdateContent, expectedClaudeUpdateCommand, "gdh-update"),
1596
1734
  ...inspectClaudeCommandSurface(targetPath, CLAUDE_CHECK_COMMAND_RELATIVE_PATH, claudeCheckContent, expectedClaudeCheckCommand, "gdh-check"),
1597
1735
  ...inspectClaudeCommandSurface(targetPath, CLAUDE_PREPARE_COMMAND_RELATIVE_PATH, claudePrepareContent, expectedClaudePrepareCommand, "gdh-prepare"),
1598
1736
  ...inspectClaudeCommandSurface(targetPath, CLAUDE_VERIFY_COMMAND_RELATIVE_PATH, claudeVerifyContent, expectedClaudeVerifyCommand, "gdh-verify"),
1599
1737
  ...inspectClaudeCommandSurface(targetPath, CLAUDE_SCAN_COMMAND_RELATIVE_PATH, claudeScanContent, expectedClaudeScanCommand, "gdh-scan"),
1738
+ ...inspectClaudeCommandSurface(targetPath, CLAUDE_CHECK_UPDATE_HOOK_RELATIVE_PATH, claudeCheckUpdateHookContent, expectedClaudeCheckUpdateHook, "gdh-check-update-hook"),
1739
+ ...inspectClaudeCommandSurface(targetPath, CLAUDE_CHECK_UPDATE_WORKER_RELATIVE_PATH, claudeCheckUpdateWorkerContent, expectedClaudeCheckUpdateWorker, "gdh-check-update-worker"),
1740
+ ...inspectClaudeCommandSurface(targetPath, CLAUDE_STATUSLINE_RELATIVE_PATH, claudeStatuslineContent, expectedClaudeStatusline, "gdh-statusline"),
1600
1741
  ];
1601
1742
  if (projectMcp.enabled) {
1602
1743
  surfaces.push(createSurfaceStatus({
1603
- kind: "launcher_file",
1604
- scope: "repo",
1605
- targetPath: projectMcp.integrationRootPath,
1606
- relativePath: MCP_LAUNCHER_RELATIVE_PATH,
1607
- present: projectMcp.launcherFile.present,
1608
- state: projectMcp.launcherFile.state,
1609
- summary: projectMcp.launcherFile.summary,
1610
- version: GDH_MCP_LAUNCHER_VERSION,
1611
- }), createSurfaceStatus({
1612
1744
  kind: "mcp_file",
1613
1745
  scope: "repo",
1614
1746
  targetPath: projectMcp.integrationRootPath,
@@ -1630,6 +1762,11 @@ async function inspectCursorAdapter(targetPath, guidance, projectMcp, pinnedVers
1630
1762
  const onboardSkillContent = await fs.readFile(onboardSkillPath, "utf8").catch(() => null);
1631
1763
  const cursorStatusContent = await fs.readFile(path.join(targetPath, CURSOR_STATUS_SKILL_RELATIVE_PATH), "utf8").catch(() => null);
1632
1764
  const cursorMigrateContent = await fs.readFile(path.join(targetPath, CURSOR_MIGRATE_SKILL_RELATIVE_PATH), "utf8").catch(() => null);
1765
+ // Phase 13 Plan 13-03 deliverable — /gdh-update skill for Cursor. Inspection
1766
+ // wiring required so planSkillInstallAction sees the surface state; otherwise
1767
+ // the planner returns `unchanged` for a missing file and install becomes a
1768
+ // no-op (Plan 13-05 integration-test Rule 2 fix).
1769
+ const cursorUpdateContent = await fs.readFile(path.join(targetPath, CURSOR_UPDATE_SKILL_RELATIVE_PATH), "utf8").catch(() => null);
1633
1770
  const cursorCheckContent = await fs.readFile(path.join(targetPath, CURSOR_CHECK_SKILL_RELATIVE_PATH), "utf8").catch(() => null);
1634
1771
  const cursorPrepareContent = await fs.readFile(path.join(targetPath, CURSOR_PREPARE_SKILL_RELATIVE_PATH), "utf8").catch(() => null);
1635
1772
  const cursorVerifyContent = await fs.readFile(path.join(targetPath, CURSOR_VERIFY_SKILL_RELATIVE_PATH), "utf8").catch(() => null);
@@ -1639,6 +1776,7 @@ async function inspectCursorAdapter(targetPath, guidance, projectMcp, pinnedVers
1639
1776
  const expectedCursorOnboardSkill = pinnedVersion === null ? null : renderCursorOnboardSkill(pinnedVersion);
1640
1777
  const expectedCursorStatusSkill = pinnedVersion === null ? null : renderCursorStatusSkill(pinnedVersion);
1641
1778
  const expectedCursorMigrateSkill = pinnedVersion === null ? null : renderCursorMigrateSkill(pinnedVersion);
1779
+ const expectedCursorUpdateSkill = pinnedVersion === null ? null : renderCursorUpdateSkill(pinnedVersion);
1642
1780
  const expectedCursorCheckSkill = pinnedVersion === null ? null : renderCursorCheckSkill(pinnedVersion);
1643
1781
  const expectedCursorPrepareSkill = pinnedVersion === null ? null : renderCursorPrepareSkill(pinnedVersion);
1644
1782
  const expectedCursorVerifySkill = pinnedVersion === null ? null : renderCursorVerifySkill(pinnedVersion);
@@ -1686,6 +1824,7 @@ async function inspectCursorAdapter(targetPath, guidance, projectMcp, pinnedVers
1686
1824
  }),
1687
1825
  ...inspectCursorSkillSurface(targetPath, CURSOR_STATUS_SKILL_RELATIVE_PATH, cursorStatusContent, expectedCursorStatusSkill, "gdh-status"),
1688
1826
  ...inspectCursorSkillSurface(targetPath, CURSOR_MIGRATE_SKILL_RELATIVE_PATH, cursorMigrateContent, expectedCursorMigrateSkill, "gdh-migrate"),
1827
+ ...inspectCursorSkillSurface(targetPath, CURSOR_UPDATE_SKILL_RELATIVE_PATH, cursorUpdateContent, expectedCursorUpdateSkill, "gdh-update"),
1689
1828
  ...inspectCursorSkillSurface(targetPath, CURSOR_CHECK_SKILL_RELATIVE_PATH, cursorCheckContent, expectedCursorCheckSkill, "gdh-check"),
1690
1829
  ...inspectCursorSkillSurface(targetPath, CURSOR_PREPARE_SKILL_RELATIVE_PATH, cursorPrepareContent, expectedCursorPrepareSkill, "gdh-prepare"),
1691
1830
  ...inspectCursorSkillSurface(targetPath, CURSOR_VERIFY_SKILL_RELATIVE_PATH, cursorVerifyContent, expectedCursorVerifySkill, "gdh-verify"),
@@ -1693,15 +1832,6 @@ async function inspectCursorAdapter(targetPath, guidance, projectMcp, pinnedVers
1693
1832
  ];
1694
1833
  if (projectMcp.enabled) {
1695
1834
  surfaces.push(createSurfaceStatus({
1696
- kind: "launcher_file",
1697
- scope: "repo",
1698
- targetPath: projectMcp.integrationRootPath,
1699
- relativePath: MCP_LAUNCHER_RELATIVE_PATH,
1700
- present: projectMcp.launcherFile.present,
1701
- state: projectMcp.launcherFile.state,
1702
- summary: projectMcp.launcherFile.summary,
1703
- version: GDH_MCP_LAUNCHER_VERSION,
1704
- }), createSurfaceStatus({
1705
1835
  kind: "mcp_file",
1706
1836
  scope: "repo",
1707
1837
  targetPath: projectMcp.integrationRootPath,
@@ -1785,17 +1915,17 @@ async function planInstallActions(targetPath, adapters, requestedAgents, options
1785
1915
  const effectiveDevRepoPath = options.devRepoPath ?? resolveCurrentGdhInstall(import.meta.url).defaultDevRepoPath;
1786
1916
  if (options.user) {
1787
1917
  if (requestedAgents.includes("codex")) {
1788
- actions.push(...planCodexUserInstallActions(targetPath, projectMcp, options.integrationRootPath));
1918
+ actions.push(...planCodexUserInstallActions(targetPath, projectMcp, options.integrationRootPath, options.pinnedVersion));
1789
1919
  }
1790
1920
  return dedupeInstallActions(actions);
1791
1921
  }
1792
- actions.push(...planSharedRepoInstallActions(targetPath, projectMcp, requestedAgents[0] ?? "claude", effectiveDevRepoPath, options.integrationRootPath));
1922
+ actions.push(...planSharedRepoInstallActions(targetPath, projectMcp, requestedAgents[0] ?? "claude", effectiveDevRepoPath, options.integrationRootPath, options.pinnedVersion));
1793
1923
  for (const adapter of adapters) {
1794
1924
  if (!requestedAgents.includes(adapter.agent)) {
1795
1925
  continue;
1796
1926
  }
1797
1927
  if (adapter.agent === "codex") {
1798
- actions.push(...planCodexRepoInstallActions(targetPath, adapter, options.pinnedVersion));
1928
+ actions.push(...planCodexRepoInstallActions(targetPath, adapter, options.pinnedVersion, projectMcp, options.integrationRootPath));
1799
1929
  continue;
1800
1930
  }
1801
1931
  if (adapter.agent === "claude") {
@@ -1808,27 +1938,11 @@ async function planInstallActions(targetPath, adapters, requestedAgents, options
1808
1938
  }
1809
1939
  return dedupeInstallActions(actions);
1810
1940
  }
1811
- function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDevRepoPath, integrationRootPath) {
1941
+ function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDevRepoPath, integrationRootPath, pinnedVersion) {
1812
1942
  if (!projectMcp.enabled) {
1813
1943
  return [];
1814
1944
  }
1815
1945
  const actions = [];
1816
- if (projectMcp.launcherFile.state !== "ready") {
1817
- actions.push(createInstallAction({
1818
- agent,
1819
- kind: "write_file",
1820
- scope: "repo",
1821
- targetPath: integrationRootPath,
1822
- relativePath: MCP_LAUNCHER_RELATIVE_PATH,
1823
- state: "planned",
1824
- mode: projectMcp.launcherFile.present ? "replace" : "create",
1825
- summary: projectMcp.launcherFile.present
1826
- ? "Replace the managed GDH MCP launcher with the current launcher content."
1827
- : "Create the managed GDH MCP launcher under .gdh/bin/.",
1828
- version: GDH_MCP_LAUNCHER_VERSION,
1829
- content: projectMcp.launcherContent,
1830
- }));
1831
- }
1832
1946
  if (projectMcp.projectFile.state !== "ready") {
1833
1947
  actions.push(createInstallAction({
1834
1948
  agent,
@@ -1841,11 +1955,7 @@ function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDe
1841
1955
  summary: projectMcp.projectFile.present
1842
1956
  ? "Replace the managed `gdh` MCP entry in .mcp.json while preserving any non-GDH servers."
1843
1957
  : "Create .mcp.json with the managed `gdh` MCP entry.",
1844
- content: renderManagedMcpConfig(path.join(integrationRootPath, PROJECT_MCP_RELATIVE_PATH), buildManagedMcpServerEntry({
1845
- targetPath,
1846
- integrationRootPath,
1847
- launcherPathForConfig: path.resolve(integrationRootPath, MCP_LAUNCHER_RELATIVE_PATH),
1848
- })),
1958
+ content: renderManagedMcpConfig(path.join(integrationRootPath, PROJECT_MCP_RELATIVE_PATH), buildManagedMcpServerEntry({ pinnedVersion })),
1849
1959
  }));
1850
1960
  }
1851
1961
  if (projectMcp.cursorFile.state !== "ready") {
@@ -1860,11 +1970,7 @@ function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDe
1860
1970
  summary: projectMcp.cursorFile.present
1861
1971
  ? "Replace the managed `gdh` MCP entry in .cursor/mcp.json while preserving any non-GDH servers."
1862
1972
  : "Create .cursor/mcp.json with the managed `gdh` MCP entry.",
1863
- content: renderManagedMcpConfig(path.join(integrationRootPath, CURSOR_MCP_RELATIVE_PATH), buildManagedMcpServerEntry({
1864
- targetPath,
1865
- integrationRootPath,
1866
- launcherPathForConfig: path.resolve(integrationRootPath, MCP_LAUNCHER_RELATIVE_PATH),
1867
- })),
1973
+ content: renderManagedMcpConfig(path.join(integrationRootPath, CURSOR_MCP_RELATIVE_PATH), buildManagedMcpServerEntry({ pinnedVersion })),
1868
1974
  }));
1869
1975
  }
1870
1976
  if (projectMcp.localPathHints.gdhDevRepoPath !== effectiveDevRepoPath) {
@@ -1934,28 +2040,53 @@ function agentLabel(agent) {
1934
2040
  return "Cursor";
1935
2041
  }
1936
2042
  }
1937
- function planCodexRepoInstallActions(targetPath, adapter, pinnedVersion) {
1938
- return [
2043
+ function planCodexRepoInstallActions(targetPath, adapter, pinnedVersion, projectMcp, integrationRootPath) {
2044
+ const actions = [
1939
2045
  planSkillInstallAction("codex", targetPath, adapter, CODEX_ONBOARD_SKILL_RELATIVE_PATH, renderCodexOnboardSkill, "gdh-onboard", pinnedVersion),
1940
2046
  planSkillInstallAction("codex", targetPath, adapter, CODEX_STATUS_SKILL_RELATIVE_PATH, renderCodexStatusSkill, "gdh-status", pinnedVersion),
1941
2047
  planSkillInstallAction("codex", targetPath, adapter, CODEX_MIGRATE_SKILL_RELATIVE_PATH, renderCodexMigrateSkill, "gdh-migrate", pinnedVersion),
2048
+ planSkillInstallAction("codex", targetPath, adapter, CODEX_UPDATE_SKILL_RELATIVE_PATH, renderCodexUpdateSkill, "gdh-update", pinnedVersion),
1942
2049
  planSkillInstallAction("codex", targetPath, adapter, CODEX_CHECK_SKILL_RELATIVE_PATH, renderCodexCheckSkill, "gdh-check", pinnedVersion),
1943
2050
  planSkillInstallAction("codex", targetPath, adapter, CODEX_PREPARE_SKILL_RELATIVE_PATH, renderCodexPrepareSkill, "gdh-prepare", pinnedVersion),
1944
2051
  planSkillInstallAction("codex", targetPath, adapter, CODEX_VERIFY_SKILL_RELATIVE_PATH, renderCodexVerifySkill, "gdh-verify", pinnedVersion),
1945
2052
  planSkillInstallAction("codex", targetPath, adapter, CODEX_SCAN_SKILL_RELATIVE_PATH, renderCodexScanSkill, "gdh-scan", pinnedVersion),
1946
2053
  ];
2054
+ if (projectMcp.enabled && projectMcp.codexProjectFile.state !== "ready") {
2055
+ const absolutePath = path.join(integrationRootPath, CODEX_PROJECT_CONFIG_RELATIVE_PATH);
2056
+ const existingContent = fsSync.existsSync(absolutePath)
2057
+ ? fsSync.readFileSync(absolutePath, "utf8")
2058
+ : null;
2059
+ actions.push(createInstallAction({
2060
+ agent: "codex",
2061
+ kind: "write_file",
2062
+ scope: "repo",
2063
+ targetPath: integrationRootPath,
2064
+ relativePath: CODEX_PROJECT_CONFIG_RELATIVE_PATH,
2065
+ state: "planned",
2066
+ mode: projectMcp.codexProjectFile.present ? "replace" : "create",
2067
+ summary: projectMcp.codexProjectFile.present
2068
+ ? `Replace the managed \`[mcp_servers.${GDH_MCP_SERVER_NAME}]\` section in .codex/config.toml while preserving any non-GDH content.`
2069
+ : `Create .codex/config.toml with the managed \`[mcp_servers.${GDH_MCP_SERVER_NAME}]\` entry so Codex can discover the project MCP server without a user-global registration.`,
2070
+ content: renderManagedCodexProjectConfig(existingContent, pinnedVersion),
2071
+ }));
2072
+ }
2073
+ return actions;
1947
2074
  }
1948
- function planCodexUserInstallActions(targetPath, projectMcp, integrationRootPath) {
2075
+ function planCodexUserInstallActions(targetPath, projectMcp, integrationRootPath, pinnedVersion) {
1949
2076
  if (!projectMcp.enabled || projectMcp.codexServerName === null) {
1950
2077
  return [];
1951
2078
  }
1952
- const expectedLauncherPath = path.join(integrationRootPath, MCP_LAUNCHER_RELATIVE_PATH);
1953
- const codexArgs = buildManagedLauncherInvocationArgs({
1954
- targetPath,
1955
- integrationRootPath,
1956
- launcherPath: expectedLauncherPath,
1957
- useAbsoluteTargetPath: true,
1958
- });
2079
+ const resolvedTargetPath = path.resolve(targetPath);
2080
+ const resolvedIntegrationRootPath = path.resolve(integrationRootPath);
2081
+ const codexCommandArgs = [
2082
+ "-y",
2083
+ `@skillcap/gdh@${pinnedVersion}`,
2084
+ "mcp",
2085
+ "serve",
2086
+ ];
2087
+ if (resolvedTargetPath !== resolvedIntegrationRootPath) {
2088
+ codexCommandArgs.push("--target", resolvedTargetPath);
2089
+ }
1959
2090
  const actions = [];
1960
2091
  if (projectMcp.codexRegistration.present) {
1961
2092
  actions.push(createInstallAction({
@@ -1967,7 +2098,7 @@ function planCodexUserInstallActions(targetPath, projectMcp, integrationRootPath
1967
2098
  absolutePath: projectMcp.codexConfigPath,
1968
2099
  state: "planned",
1969
2100
  mode: "replace",
1970
- summary: `Remove the existing Codex MCP registration \`${projectMcp.codexServerName}\` before re-registering it with the managed GDH launcher.`,
2101
+ summary: `Remove the existing Codex MCP registration \`${projectMcp.codexServerName}\` before re-registering it with the managed GDH npx invocation.`,
1971
2102
  command: ["codex", "mcp", "remove", projectMcp.codexServerName],
1972
2103
  }));
1973
2104
  }
@@ -1980,15 +2111,15 @@ function planCodexUserInstallActions(targetPath, projectMcp, integrationRootPath
1980
2111
  absolutePath: projectMcp.codexConfigPath,
1981
2112
  state: "planned",
1982
2113
  mode: projectMcp.codexRegistration.present ? "replace" : "create",
1983
- summary: `Register the managed GDH launcher with Codex as \`${projectMcp.codexServerName}\`.`,
2114
+ summary: `Register the managed GDH MCP server with Codex as \`${projectMcp.codexServerName}\` via pinned npx invocation.`,
1984
2115
  command: [
1985
2116
  "codex",
1986
2117
  "mcp",
1987
2118
  "add",
1988
2119
  projectMcp.codexServerName,
1989
2120
  "--",
1990
- "node",
1991
- ...codexArgs,
2121
+ "npx",
2122
+ ...codexCommandArgs,
1992
2123
  ],
1993
2124
  }));
1994
2125
  return actions;
@@ -2024,7 +2155,55 @@ function planClaudeInstallActions(targetPath, adapter, pinnedVersion) {
2024
2155
  expectedTarget: "AGENTS.md",
2025
2156
  }));
2026
2157
  }
2027
- actions.push(planSkillInstallAction("claude", targetPath, adapter, CLAUDE_ONBOARD_COMMAND_RELATIVE_PATH, renderClaudeOnboardCommand, "gdh-onboard", pinnedVersion), planSkillInstallAction("claude", targetPath, adapter, CLAUDE_STATUS_COMMAND_RELATIVE_PATH, renderClaudeStatusCommand, "gdh-status", pinnedVersion), planSkillInstallAction("claude", targetPath, adapter, CLAUDE_MIGRATE_COMMAND_RELATIVE_PATH, renderClaudeMigrateCommand, "gdh-migrate", pinnedVersion), planSkillInstallAction("claude", targetPath, adapter, CLAUDE_CHECK_COMMAND_RELATIVE_PATH, renderClaudeCheckCommand, "gdh-check", pinnedVersion), planSkillInstallAction("claude", targetPath, adapter, CLAUDE_PREPARE_COMMAND_RELATIVE_PATH, renderClaudePrepareCommand, "gdh-prepare", pinnedVersion), planSkillInstallAction("claude", targetPath, adapter, CLAUDE_VERIFY_COMMAND_RELATIVE_PATH, renderClaudeVerifyCommand, "gdh-verify", pinnedVersion), planSkillInstallAction("claude", targetPath, adapter, CLAUDE_SCAN_COMMAND_RELATIVE_PATH, renderClaudeScanCommand, "gdh-scan", pinnedVersion));
2158
+ actions.push(planSkillInstallAction("claude", targetPath, adapter, CLAUDE_ONBOARD_COMMAND_RELATIVE_PATH, renderClaudeOnboardCommand, "gdh-onboard", pinnedVersion), planSkillInstallAction("claude", targetPath, adapter, CLAUDE_STATUS_COMMAND_RELATIVE_PATH, renderClaudeStatusCommand, "gdh-status", pinnedVersion), planSkillInstallAction("claude", targetPath, adapter, CLAUDE_MIGRATE_COMMAND_RELATIVE_PATH, renderClaudeMigrateCommand, "gdh-migrate", pinnedVersion), planSkillInstallAction("claude", targetPath, adapter, CLAUDE_UPDATE_COMMAND_RELATIVE_PATH, renderClaudeUpdateCommand, "gdh-update", pinnedVersion), planSkillInstallAction("claude", targetPath, adapter, CLAUDE_CHECK_COMMAND_RELATIVE_PATH, renderClaudeCheckCommand, "gdh-check", pinnedVersion), planSkillInstallAction("claude", targetPath, adapter, CLAUDE_PREPARE_COMMAND_RELATIVE_PATH, renderClaudePrepareCommand, "gdh-prepare", pinnedVersion), planSkillInstallAction("claude", targetPath, adapter, CLAUDE_VERIFY_COMMAND_RELATIVE_PATH, renderClaudeVerifyCommand, "gdh-verify", pinnedVersion), planSkillInstallAction("claude", targetPath, adapter, CLAUDE_SCAN_COMMAND_RELATIVE_PATH, renderClaudeScanCommand, "gdh-scan", pinnedVersion),
2159
+ // UPD-01 managed hook surfaces baked at the pinned version.
2160
+ planSkillInstallAction("claude", targetPath, adapter, CLAUDE_CHECK_UPDATE_HOOK_RELATIVE_PATH, renderClaudeCheckUpdateHook, "gdh-check-update-hook", pinnedVersion), planSkillInstallAction("claude", targetPath, adapter, CLAUDE_CHECK_UPDATE_WORKER_RELATIVE_PATH, renderClaudeCheckUpdateWorker, "gdh-check-update-worker", pinnedVersion),
2161
+ // UPD-02 managed statusline surface baked at the pinned version. The .js
2162
+ // file is ALWAYS baked, even when settings.json statusLine is not owned
2163
+ // by GDH (write-if-absent below) — users can manually point their config
2164
+ // at gdh-statusline.js later; the baked file is a no-op until wired.
2165
+ planSkillInstallAction("claude", targetPath, adapter, CLAUDE_STATUSLINE_RELATIVE_PATH, renderClaudeUpdateStatusline, "gdh-statusline", pinnedVersion));
2166
+ // UPD-01 + UPD-02 .claude/settings.json composite patch action.
2167
+ // - Plan 02 (Strategy A, planner-lock #1): patch-merge the SessionStart
2168
+ // hook entry by exact command-literal match, preserving siblings.
2169
+ // - Plan 03 (write-if-absent, planner-lock #2 / D-18): add the statusLine
2170
+ // entry ONLY when no existing statusLine is configured (e.g. GSD-owned
2171
+ // or user-owned). Silent no-op for UPD-02 on targets where another
2172
+ // tool already owns the slot; the universal surface is MCP _meta.
2173
+ //
2174
+ // Synchronous read via fsSync is intentional: planClaudeInstallActions is
2175
+ // called without `await`, and making it async would ripple to every sibling
2176
+ // planner (Codex, Cursor). The composed patches are idempotent and
2177
+ // commutative (proven in Task 1 Test J) so application order is irrelevant.
2178
+ let existingSettingsContent = "";
2179
+ try {
2180
+ existingSettingsContent = fsSync.readFileSync(path.join(targetPath, CLAUDE_SETTINGS_RELATIVE_PATH), "utf8");
2181
+ }
2182
+ catch {
2183
+ existingSettingsContent = "";
2184
+ }
2185
+ const patchedSettings = patchClaudeSettingsForGdhStatusline(patchClaudeSettingsForGdhSessionStart(existingSettingsContent));
2186
+ const settingsIsUnchanged = existingSettingsContent === patchedSettings;
2187
+ const settingsExistedOnDisk = existingSettingsContent.length > 0;
2188
+ actions.push(createInstallAction({
2189
+ agent: "claude",
2190
+ kind: "write_file",
2191
+ scope: "repo",
2192
+ targetPath,
2193
+ relativePath: CLAUDE_SETTINGS_RELATIVE_PATH,
2194
+ state: settingsIsUnchanged ? "unchanged" : "planned",
2195
+ mode: settingsIsUnchanged
2196
+ ? "unchanged"
2197
+ : settingsExistedOnDisk
2198
+ ? "replace"
2199
+ : "create",
2200
+ summary: settingsIsUnchanged
2201
+ ? "GDH SessionStart hook + statusline already registered in .claude/settings.json."
2202
+ : settingsExistedOnDisk
2203
+ ? "Register the GDH SessionStart hook and statusline (write-if-absent) in .claude/settings.json while preserving sibling content (patch-merge)."
2204
+ : "Create .claude/settings.json with the GDH SessionStart hook + statusline registration.",
2205
+ content: patchedSettings,
2206
+ }));
2028
2207
  return actions;
2029
2208
  }
2030
2209
  function planCursorInstallActions(targetPath, adapter, pinnedVersion) {
@@ -2060,7 +2239,7 @@ function planCursorInstallActions(targetPath, adapter, pinnedVersion) {
2060
2239
  content: renderCursorRule(),
2061
2240
  }));
2062
2241
  }
2063
- 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_CHECK_SKILL_RELATIVE_PATH, renderCursorCheckSkill, "gdh-check", pinnedVersion), planSkillInstallAction("cursor", targetPath, adapter, CURSOR_PREPARE_SKILL_RELATIVE_PATH, renderCursorPrepareSkill, "gdh-prepare", pinnedVersion), planSkillInstallAction("cursor", targetPath, adapter, CURSOR_VERIFY_SKILL_RELATIVE_PATH, renderCursorVerifySkill, "gdh-verify", pinnedVersion), planSkillInstallAction("cursor", targetPath, adapter, CURSOR_SCAN_SKILL_RELATIVE_PATH, renderCursorScanSkill, "gdh-scan", pinnedVersion));
2242
+ 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_CHECK_SKILL_RELATIVE_PATH, renderCursorCheckSkill, "gdh-check", pinnedVersion), planSkillInstallAction("cursor", targetPath, adapter, CURSOR_PREPARE_SKILL_RELATIVE_PATH, renderCursorPrepareSkill, "gdh-prepare", pinnedVersion), planSkillInstallAction("cursor", targetPath, adapter, CURSOR_VERIFY_SKILL_RELATIVE_PATH, renderCursorVerifySkill, "gdh-verify", pinnedVersion), planSkillInstallAction("cursor", targetPath, adapter, CURSOR_SCAN_SKILL_RELATIVE_PATH, renderCursorScanSkill, "gdh-scan", pinnedVersion));
2064
2243
  return actions;
2065
2244
  }
2066
2245
  function dedupeInstallActions(actions) {
@@ -2158,64 +2337,11 @@ function resolveProjectMcpEnabled(projectConfig) {
2158
2337
  function createCodexServerName(projectKeySeed) {
2159
2338
  return `gdh-${projectKeySeed}`;
2160
2339
  }
2161
- export function renderManagedMcpLauncher(pinnedVersion) {
2162
- return [
2163
- "#!/usr/bin/env node",
2164
- `// GDH MCP launcher v${GDH_MCP_LAUNCHER_VERSION}`,
2165
- 'import fs from "node:fs";',
2166
- 'import path from "node:path";',
2167
- 'import { spawnSync } from "node:child_process";',
2168
- 'import { fileURLToPath } from "node:url";',
2169
- "",
2170
- "const launcherPath = fileURLToPath(import.meta.url);",
2171
- 'const integrationRootPath = path.resolve(path.dirname(launcherPath), "../..");',
2172
- 'const targetOptionIndex = process.argv.indexOf("--target");',
2173
- "const configuredTargetPath =",
2174
- ' targetOptionIndex >= 0 && typeof process.argv[targetOptionIndex + 1] === "string"',
2175
- " ? process.argv[targetOptionIndex + 1]",
2176
- " : null;",
2177
- "const targetPath = configuredTargetPath",
2178
- " ? path.resolve(integrationRootPath, configuredTargetPath)",
2179
- " : integrationRootPath;",
2180
- 'const hintsPath = path.join(integrationRootPath, ".gdh-state", "local-paths.json");',
2181
- "",
2182
- "let gdhDevRepoPath = null;",
2183
- "try {",
2184
- ' const raw = fs.readFileSync(hintsPath, "utf8");',
2185
- " const parsed = JSON.parse(raw);",
2186
- ' if (typeof parsed.gdhDevRepoPath === "string" && parsed.gdhDevRepoPath.length > 0) {',
2187
- " gdhDevRepoPath = parsed.gdhDevRepoPath;",
2188
- " }",
2189
- "} catch {}",
2190
- "",
2191
- "const effectiveDevRepoPath = process.env.GDH_DEV_REPO?.trim() || gdhDevRepoPath;",
2192
- "if (effectiveDevRepoPath) {",
2193
- ' const devCliEntryPath = path.join(effectiveDevRepoPath, "packages/cli/dist/cli.js");',
2194
- " if (fs.existsSync(devCliEntryPath)) {",
2195
- ' const result = spawnSync(process.execPath, [devCliEntryPath, "mcp", "serve", "--target", targetPath], { stdio: "inherit" });',
2196
- " process.exit(result.status ?? 1);",
2197
- " }",
2198
- "}",
2199
- "",
2200
- `const result = spawnSync("npx", ["-y", "@skillcap/gdh@${pinnedVersion}", "mcp", "serve", "--target", targetPath], { stdio: "inherit", cwd: targetPath });`,
2201
- 'if (result.error && result.error.code === "ENOENT") {',
2202
- ` console.error("GDH MCP launcher could not launch npx for @skillcap/gdh@${pinnedVersion}. Ensure Node.js 20+ is installed (npx ships with Node), or configure the contributor dev escape hatch via the GDH_DEV_REPO env var or .gdh-state/local-paths.json gdhDevRepoPath.");`,
2203
- " process.exit(1);",
2204
- "}",
2205
- "process.exit(result.status ?? 1);",
2206
- "",
2207
- ].join("\n");
2208
- }
2209
2340
  function buildManagedMcpServerEntry(input) {
2210
2341
  return {
2211
2342
  type: "stdio",
2212
- command: "node",
2213
- args: buildManagedLauncherInvocationArgs({
2214
- targetPath: input.targetPath,
2215
- integrationRootPath: input.integrationRootPath,
2216
- launcherPath: input.launcherPathForConfig,
2217
- useAbsoluteTargetPath: true,
2218
- }),
2343
+ command: "npx",
2344
+ args: ["-y", `@skillcap/gdh@${input.pinnedVersion}`, "mcp", "serve"],
2219
2345
  };
2220
2346
  }
2221
2347
  function getManagedMcpServerEntry(jsonObject) {
@@ -2256,6 +2382,64 @@ function readExistingMcpConfig(absolutePath) {
2256
2382
  return {};
2257
2383
  }
2258
2384
  }
2385
+ // Renders the managed [mcp_servers.gdh] section for Codex project-local
2386
+ // config. The section body is line-based (no TOML parser) — preserves any
2387
+ // other content the user has in the file via regex replacement of the
2388
+ // bracketed header span. Exotic forms (inline-table `mcp_servers.gdh = {...}`
2389
+ // or dotted-key `mcp_servers.gdh.command = "..."`) are not detected; users
2390
+ // with those forms may end up with a duplicate and must migrate manually.
2391
+ export function renderManagedCodexProjectSection(pinnedVersion) {
2392
+ return [
2393
+ "[mcp_servers.gdh]",
2394
+ 'command = "npx"',
2395
+ `args = ["-y", "@skillcap/gdh@${pinnedVersion}", "mcp", "serve"]`,
2396
+ ].join("\n");
2397
+ }
2398
+ export function renderManagedCodexProjectConfig(existingContent, pinnedVersion) {
2399
+ const section = renderManagedCodexProjectSection(pinnedVersion);
2400
+ if (existingContent === null || existingContent.trim() === "") {
2401
+ return `${section}\n`;
2402
+ }
2403
+ const lines = existingContent.split("\n");
2404
+ const sectionStart = lines.findIndex((line) => /^\[mcp_servers\.gdh\]\s*$/.test(line));
2405
+ if (sectionStart === -1) {
2406
+ const trailingNewline = existingContent.endsWith("\n") ? "" : "\n";
2407
+ const separator = existingContent.length > 0 ? "\n" : "";
2408
+ return `${existingContent}${trailingNewline}${separator}${section}\n`;
2409
+ }
2410
+ let sectionEnd = lines.length;
2411
+ for (let i = sectionStart + 1; i < lines.length; i++) {
2412
+ if (/^\[/.test(lines[i] ?? "")) {
2413
+ sectionEnd = i;
2414
+ break;
2415
+ }
2416
+ }
2417
+ const before = lines.slice(0, sectionStart).join("\n");
2418
+ const afterLines = lines.slice(sectionEnd);
2419
+ const after = afterLines.join("\n");
2420
+ const beforeJoin = before.length > 0 ? `${before}\n` : "";
2421
+ const afterNonEmpty = after.replace(/^\s+/, "");
2422
+ const afterJoin = afterNonEmpty.length > 0 ? `\n\n${afterNonEmpty}` : "\n";
2423
+ return `${beforeJoin}${section}${afterJoin}`;
2424
+ }
2425
+ function extractManagedCodexSection(content) {
2426
+ const lines = content.split("\n");
2427
+ const start = lines.findIndex((line) => /^\[mcp_servers\.gdh\]\s*$/.test(line));
2428
+ if (start === -1) {
2429
+ return null;
2430
+ }
2431
+ let end = lines.length;
2432
+ for (let i = start + 1; i < lines.length; i++) {
2433
+ if (/^\[/.test(lines[i] ?? "")) {
2434
+ end = i;
2435
+ break;
2436
+ }
2437
+ }
2438
+ return lines
2439
+ .slice(start, end)
2440
+ .join("\n")
2441
+ .replace(/\s+$/, "");
2442
+ }
2259
2443
  function normalizeJson(value) {
2260
2444
  if (Array.isArray(value)) {
2261
2445
  return value.map((entry) => normalizeJson(entry));
@@ -2395,14 +2579,19 @@ async function listCodexMcpServers() {
2395
2579
  }
2396
2580
  }
2397
2581
  function isMatchingCodexRegistration(registration, input) {
2398
- const expectedArgs = buildManagedLauncherInvocationArgs({
2399
- targetPath: input.targetPath,
2400
- integrationRootPath: input.integrationRootPath,
2401
- launcherPath: input.launcherPath,
2402
- useAbsoluteTargetPath: true,
2403
- });
2582
+ const resolvedTargetPath = path.resolve(input.targetPath);
2583
+ const resolvedIntegrationRootPath = path.resolve(input.integrationRootPath);
2584
+ const expectedArgs = [
2585
+ "-y",
2586
+ `@skillcap/gdh@${input.pinnedVersion}`,
2587
+ "mcp",
2588
+ "serve",
2589
+ ];
2590
+ if (resolvedTargetPath !== resolvedIntegrationRootPath) {
2591
+ expectedArgs.push("--target", resolvedTargetPath);
2592
+ }
2404
2593
  return (registration.transport?.type === "stdio" &&
2405
- registration.transport.command === "node" &&
2594
+ registration.transport.command === "npx" &&
2406
2595
  JSON.stringify(registration.transport.args ?? []) === JSON.stringify(expectedArgs));
2407
2596
  }
2408
2597
  async function inspectProjectConfigLifecycleSurface(targetPath) {
@@ -2835,60 +3024,6 @@ function inspectCursorRuleLifecycleSurface(targetPath, adapterStatus) {
2835
3024
  },
2836
3025
  });
2837
3026
  }
2838
- function inspectMcpLauncherLifecycleSurface(targetPath, mcpEnabled, adapterStatus) {
2839
- if (!mcpEnabled) {
2840
- return createLifecycleSurfaceStatus({
2841
- surface: "mcp_launcher",
2842
- management: "managed",
2843
- state: "compatible",
2844
- summary: "Project-scoped MCP launcher is not required while MCP is disabled.",
2845
- reasons: [],
2846
- probes: [],
2847
- action: null,
2848
- });
2849
- }
2850
- const launcherSurface = findAdapterSurface(adapterStatus, "claude", "launcher_file") ??
2851
- findAdapterSurface(adapterStatus, "cursor", "launcher_file") ??
2852
- findAdapterSurface(adapterStatus, "codex", "launcher_file");
2853
- const probes = launcherSurface === null
2854
- ? []
2855
- : [
2856
- createVersionProbe({
2857
- targetPath,
2858
- relativePath: launcherSurface.relativePath ?? MCP_LAUNCHER_RELATIVE_PATH,
2859
- present: launcherSurface.present,
2860
- expectedVersion: GDH_MCP_LAUNCHER_VERSION,
2861
- detectedVersion: launcherSurface.present ? launcherSurface.version : null,
2862
- }),
2863
- ];
2864
- if (launcherSurface?.state === "ready") {
2865
- return createLifecycleSurfaceStatus({
2866
- surface: "mcp_launcher",
2867
- management: "managed",
2868
- state: "compatible",
2869
- summary: "Managed MCP launcher file matches the current GDH version.",
2870
- reasons: [],
2871
- probes,
2872
- action: null,
2873
- });
2874
- }
2875
- return createLifecycleSurfaceStatus({
2876
- surface: "mcp_launcher",
2877
- management: "managed",
2878
- state: launcherSurface?.present ? "migration_available" : "migration_needed",
2879
- summary: "Managed MCP launcher file needs to be created or refreshed through the adapter install flow.",
2880
- reasons: launcherSurface?.present
2881
- ? ["mcp_launcher_misconfigured"]
2882
- : ["mcp_launcher_missing"],
2883
- probes,
2884
- action: {
2885
- kind: "run_repair",
2886
- summary: "Run GDH migrate or adapters install to refresh the managed MCP launcher file.",
2887
- command: ["gdh", "adapters", "install", targetPath],
2888
- autoApplicable: true,
2889
- },
2890
- });
2891
- }
2892
3027
  function inspectRuntimeBridgeLifecycleSurface(targetPath, bridgeStatus) {
2893
3028
  const probes = bridgeStatus.managedArtifacts.map((artifact) => createVersionProbe({
2894
3029
  targetPath,
@@ -3279,4 +3414,7 @@ function deriveRepoState(context) {
3279
3414
  function normalizeChangedFiles(files) {
3280
3415
  return [...new Set(files.map((file) => file.trim()).filter((file) => file.length > 0))];
3281
3416
  }
3417
+ export { bumpAndRebakePin, } from "./self-update-mechanics.js";
3418
+ export { CLAUDE_CHECK_UPDATE_HOOK_RELATIVE_PATH } from "./claude-update-hook-render.js";
3419
+ export { CLAUDE_STATUSLINE_RELATIVE_PATH } from "./claude-statusline-render.js";
3282
3420
  //# sourceMappingURL=index.js.map