@skillcap/gdh 0.6.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.
@@ -9,7 +9,7 @@ import { CLAUDE_CHECK_UPDATE_HOOK_RELATIVE_PATH, CLAUDE_CHECK_UPDATE_WORKER_RELA
9
9
  import { renderClaudeCheckUpdateWorker } from "./claude-update-worker-render.js";
10
10
  import { CLAUDE_STATUSLINE_RELATIVE_PATH, renderClaudeUpdateStatusline, } from "./claude-statusline-render.js";
11
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_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";
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";
13
13
  import { createDefaultGuidanceUnits, getGuidanceStatus, resolveGuidanceQuery, } from "@gdh/docs";
14
14
  import { inspectGuidanceAudit } from "@gdh/observability";
15
15
  import { inspectRuntimeBridgeSurface } from "@gdh/runtime";
@@ -65,7 +65,7 @@ export const CLAUDE_SCAN_COMMAND_RELATIVE_PATH = ".claude/commands/gdh/scan.md";
65
65
  export const CODEX_SCAN_SKILL_RELATIVE_PATH = ".codex/skills/gdh-scan/SKILL.md";
66
66
  export const CURSOR_SCAN_SKILL_RELATIVE_PATH = ".cursor/skills/gdh-scan/SKILL.md";
67
67
  export const LOCAL_PATH_HINTS_RELATIVE_PATH = ".gdh-state/local-paths.json";
68
- export const MCP_LAUNCHER_RELATIVE_PATH = ".gdh/bin/gdh-mcp.mjs";
68
+ export const CODEX_PROJECT_CONFIG_RELATIVE_PATH = ".codex/config.toml";
69
69
  export const GDH_MCP_SERVER_NAME = "gdh";
70
70
  const execFile = promisify(execFileCallback);
71
71
  export async function getSupportedAgentAdaptersStatus(targetPath, options = {}) {
@@ -216,7 +216,6 @@ export async function inspectProjectLifecycleCompatibility(targetPath) {
216
216
  await inspectGuidanceUnitLifecycleSurface(resolvedTargetPath, projectConfig),
217
217
  inspectMcpManifestLifecycleSurface(resolvedTargetPath, projectConfig.mcp.enabled, adapterStatus),
218
218
  inspectCursorRuleLifecycleSurface(resolvedTargetPath, adapterStatus),
219
- inspectMcpLauncherLifecycleSurface(resolvedTargetPath, projectConfig.mcp.enabled, adapterStatus),
220
219
  inspectRuntimeBridgeLifecycleSurface(resolvedTargetPath, bridgeStatus),
221
220
  ]);
222
221
  }
@@ -1257,34 +1256,26 @@ export function renderCursorVerifySkill(pinnedVersion) {
1257
1256
  async function inspectProjectMcpSupport(targetPath, options) {
1258
1257
  const projectConfig = await readProjectConfig(targetPath);
1259
1258
  const enabled = resolveProjectMcpEnabled(projectConfig);
1260
- const launcherContent = options.pinnedVersion === null
1261
- ? null
1262
- : renderManagedMcpLauncher(options.pinnedVersion);
1263
1259
  const managedMcpEntry = buildManagedMcpServerEntry({
1264
- targetPath,
1265
- integrationRootPath: options.integrationRootPath,
1266
- launcherPathForConfig: path.resolve(options.integrationRootPath, MCP_LAUNCHER_RELATIVE_PATH),
1260
+ pinnedVersion: options.pinnedVersion ?? "latest",
1267
1261
  });
1268
- const [projectFile, cursorFile, launcherSource, localPathHints] = await Promise.all([
1262
+ const [projectFile, cursorFile, codexProjectContent, localPathHints] = await Promise.all([
1269
1263
  inspectJsonFile(path.join(options.integrationRootPath, PROJECT_MCP_RELATIVE_PATH)),
1270
1264
  inspectJsonFile(path.join(options.integrationRootPath, CURSOR_MCP_RELATIVE_PATH)),
1271
- 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),
1272
1266
  readLocalPathHints(options.integrationRootPath),
1273
1267
  ]);
1274
- const launcherBootstrap = await inspectLauncherBootstrap(localPathHints);
1275
1268
  const codexConfigPath = path.join(os.homedir(), ".codex/config.toml");
1276
1269
  const codexServerName = projectConfig === null ? null : createCodexServerName(projectConfig.projectKeySeed);
1277
1270
  return {
1278
1271
  enabled,
1279
1272
  integrationRootPath: options.integrationRootPath,
1280
- launcherContent,
1281
1273
  localPathHints,
1282
- launcherBootstrap,
1283
- launcherFile: inspectLauncherFile(launcherSource, launcherContent, enabled, launcherBootstrap),
1284
1274
  projectFile: inspectManagedMcpFile(projectFile, enabled, PROJECT_MCP_RELATIVE_PATH, "Claude project MCP config", managedMcpEntry),
1285
1275
  cursorFile: inspectManagedMcpFile(cursorFile, enabled, CURSOR_MCP_RELATIVE_PATH, "Cursor project MCP config", managedMcpEntry),
1276
+ codexProjectFile: inspectManagedCodexProjectFile(codexProjectContent, enabled, options.pinnedVersion),
1286
1277
  codexRegistration: options.includeUserLocal
1287
- ? await inspectCodexRegistration(targetPath, enabled, codexServerName, codexConfigPath, options.integrationRootPath)
1278
+ ? await inspectCodexRegistration(targetPath, enabled, codexServerName, codexConfigPath, options.integrationRootPath, options.pinnedVersion)
1288
1279
  : {
1289
1280
  present: false,
1290
1281
  state: "ready",
@@ -1361,71 +1352,52 @@ function inspectManagedMcpFile(file, enabled, relativePath, displayName, expecte
1361
1352
  summary: `${displayName} includes the managed \`${GDH_MCP_SERVER_NAME}\` MCP server entry.`,
1362
1353
  };
1363
1354
  }
1364
- function inspectLauncherFile(launcherSource, expectedContent, enabled, bootstrap) {
1355
+ function inspectManagedCodexProjectFile(content, enabled, pinnedVersion) {
1356
+ const relativePath = CODEX_PROJECT_CONFIG_RELATIVE_PATH;
1365
1357
  if (!enabled) {
1366
1358
  return {
1367
- present: launcherSource !== null,
1359
+ present: content !== null,
1368
1360
  state: "ready",
1369
- summary: "The managed GDH MCP launcher is disabled for this target.",
1361
+ summary: "Codex project MCP config is disabled for this target.",
1370
1362
  };
1371
1363
  }
1372
- if (expectedContent === null) {
1364
+ if (pinnedVersion === null) {
1373
1365
  return {
1374
- present: launcherSource !== null,
1366
+ present: content !== null,
1375
1367
  state: "missing",
1376
- 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\`).`,
1377
1369
  };
1378
1370
  }
1379
- if (launcherSource === null) {
1371
+ if (content === null) {
1380
1372
  return {
1381
1373
  present: false,
1382
1374
  state: "missing",
1383
- 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}.`,
1384
1376
  };
1385
1377
  }
1386
- if (launcherSource !== expectedContent) {
1378
+ const existingSection = extractManagedCodexSection(content);
1379
+ const expectedSection = renderManagedCodexProjectSection(pinnedVersion);
1380
+ if (existingSection === null) {
1387
1381
  return {
1388
1382
  present: true,
1389
1383
  state: "misconfigured",
1390
- 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.`,
1391
1385
  };
1392
1386
  }
1393
- return {
1394
- present: true,
1395
- state: bootstrap.state,
1396
- summary: bootstrap.state === "ready"
1397
- ? "The managed GDH MCP launcher is present and can resolve the current GDH bootstrap path."
1398
- : bootstrap.summary,
1399
- };
1400
- }
1401
- async function inspectLauncherBootstrap(localPathHints) {
1402
- const envDevRepoPath = process.env["GDH_DEV_REPO"]?.trim() || null;
1403
- const effectiveDevRepoPath = envDevRepoPath ?? localPathHints.gdhDevRepoPath;
1404
- if (effectiveDevRepoPath !== null) {
1405
- const cliEntryPath = path.join(effectiveDevRepoPath, "packages/cli/dist/cli.js");
1406
- if (await fileExists(cliEntryPath)) {
1407
- return {
1408
- state: "ready",
1409
- summary: `The managed GDH MCP launcher will bootstrap from the current GDH checkout at ${effectiveDevRepoPath}.`,
1410
- };
1411
- }
1387
+ if (existingSection !== expectedSection) {
1412
1388
  return {
1389
+ present: true,
1413
1390
  state: "misconfigured",
1414
- 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.`,
1415
- };
1416
- }
1417
- if (await executableExistsOnPath("gdh")) {
1418
- return {
1419
- state: "ready",
1420
- 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.`,
1421
1392
  };
1422
1393
  }
1423
1394
  return {
1424
- state: "missing",
1425
- 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.`,
1426
1398
  };
1427
1399
  }
1428
- async function inspectCodexRegistration(targetPath, enabled, codexServerName, codexConfigPath, integrationRootPath) {
1400
+ async function inspectCodexRegistration(targetPath, enabled, codexServerName, codexConfigPath, integrationRootPath, pinnedVersion) {
1429
1401
  if (!enabled || codexServerName === null) {
1430
1402
  return {
1431
1403
  present: false,
@@ -1449,16 +1421,22 @@ async function inspectCodexRegistration(targetPath, enabled, codexServerName, co
1449
1421
  summary: `Codex does not yet have the expected user-local MCP registration \`${codexServerName}\` in ${codexConfigPath}.`,
1450
1422
  };
1451
1423
  }
1452
- 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
+ }
1453
1431
  if (!isMatchingCodexRegistration(registration, {
1454
1432
  targetPath,
1455
1433
  integrationRootPath,
1456
- launcherPath: expectedLauncherPath,
1434
+ pinnedVersion,
1457
1435
  })) {
1458
1436
  return {
1459
1437
  present: true,
1460
1438
  state: "misconfigured",
1461
- 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.`,
1462
1440
  };
1463
1441
  }
1464
1442
  return {
@@ -1648,14 +1626,14 @@ async function inspectCodexAdapter(targetPath, guidance, projectMcp, pinnedVersi
1648
1626
  ];
1649
1627
  if (projectMcp.enabled) {
1650
1628
  surfaces.push(createSurfaceStatus({
1651
- kind: "launcher_file",
1629
+ kind: "mcp_file",
1652
1630
  scope: "repo",
1653
1631
  targetPath: projectMcp.integrationRootPath,
1654
- relativePath: MCP_LAUNCHER_RELATIVE_PATH,
1655
- present: projectMcp.launcherFile.present,
1656
- state: projectMcp.launcherFile.state,
1657
- summary: projectMcp.launcherFile.summary,
1658
- 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,
1659
1637
  }));
1660
1638
  if (options.includeUserLocal) {
1661
1639
  surfaces.push(createSurfaceStatus({
@@ -1763,15 +1741,6 @@ async function inspectClaudeAdapter(targetPath, guidance, projectMcp, pinnedVers
1763
1741
  ];
1764
1742
  if (projectMcp.enabled) {
1765
1743
  surfaces.push(createSurfaceStatus({
1766
- kind: "launcher_file",
1767
- scope: "repo",
1768
- targetPath: projectMcp.integrationRootPath,
1769
- relativePath: MCP_LAUNCHER_RELATIVE_PATH,
1770
- present: projectMcp.launcherFile.present,
1771
- state: projectMcp.launcherFile.state,
1772
- summary: projectMcp.launcherFile.summary,
1773
- version: GDH_MCP_LAUNCHER_VERSION,
1774
- }), createSurfaceStatus({
1775
1744
  kind: "mcp_file",
1776
1745
  scope: "repo",
1777
1746
  targetPath: projectMcp.integrationRootPath,
@@ -1863,15 +1832,6 @@ async function inspectCursorAdapter(targetPath, guidance, projectMcp, pinnedVers
1863
1832
  ];
1864
1833
  if (projectMcp.enabled) {
1865
1834
  surfaces.push(createSurfaceStatus({
1866
- kind: "launcher_file",
1867
- scope: "repo",
1868
- targetPath: projectMcp.integrationRootPath,
1869
- relativePath: MCP_LAUNCHER_RELATIVE_PATH,
1870
- present: projectMcp.launcherFile.present,
1871
- state: projectMcp.launcherFile.state,
1872
- summary: projectMcp.launcherFile.summary,
1873
- version: GDH_MCP_LAUNCHER_VERSION,
1874
- }), createSurfaceStatus({
1875
1835
  kind: "mcp_file",
1876
1836
  scope: "repo",
1877
1837
  targetPath: projectMcp.integrationRootPath,
@@ -1955,17 +1915,17 @@ async function planInstallActions(targetPath, adapters, requestedAgents, options
1955
1915
  const effectiveDevRepoPath = options.devRepoPath ?? resolveCurrentGdhInstall(import.meta.url).defaultDevRepoPath;
1956
1916
  if (options.user) {
1957
1917
  if (requestedAgents.includes("codex")) {
1958
- actions.push(...planCodexUserInstallActions(targetPath, projectMcp, options.integrationRootPath));
1918
+ actions.push(...planCodexUserInstallActions(targetPath, projectMcp, options.integrationRootPath, options.pinnedVersion));
1959
1919
  }
1960
1920
  return dedupeInstallActions(actions);
1961
1921
  }
1962
- 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));
1963
1923
  for (const adapter of adapters) {
1964
1924
  if (!requestedAgents.includes(adapter.agent)) {
1965
1925
  continue;
1966
1926
  }
1967
1927
  if (adapter.agent === "codex") {
1968
- actions.push(...planCodexRepoInstallActions(targetPath, adapter, options.pinnedVersion));
1928
+ actions.push(...planCodexRepoInstallActions(targetPath, adapter, options.pinnedVersion, projectMcp, options.integrationRootPath));
1969
1929
  continue;
1970
1930
  }
1971
1931
  if (adapter.agent === "claude") {
@@ -1978,27 +1938,11 @@ async function planInstallActions(targetPath, adapters, requestedAgents, options
1978
1938
  }
1979
1939
  return dedupeInstallActions(actions);
1980
1940
  }
1981
- function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDevRepoPath, integrationRootPath) {
1941
+ function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDevRepoPath, integrationRootPath, pinnedVersion) {
1982
1942
  if (!projectMcp.enabled) {
1983
1943
  return [];
1984
1944
  }
1985
1945
  const actions = [];
1986
- if (projectMcp.launcherFile.state !== "ready") {
1987
- actions.push(createInstallAction({
1988
- agent,
1989
- kind: "write_file",
1990
- scope: "repo",
1991
- targetPath: integrationRootPath,
1992
- relativePath: MCP_LAUNCHER_RELATIVE_PATH,
1993
- state: "planned",
1994
- mode: projectMcp.launcherFile.present ? "replace" : "create",
1995
- summary: projectMcp.launcherFile.present
1996
- ? "Replace the managed GDH MCP launcher with the current launcher content."
1997
- : "Create the managed GDH MCP launcher under .gdh/bin/.",
1998
- version: GDH_MCP_LAUNCHER_VERSION,
1999
- content: projectMcp.launcherContent,
2000
- }));
2001
- }
2002
1946
  if (projectMcp.projectFile.state !== "ready") {
2003
1947
  actions.push(createInstallAction({
2004
1948
  agent,
@@ -2011,11 +1955,7 @@ function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDe
2011
1955
  summary: projectMcp.projectFile.present
2012
1956
  ? "Replace the managed `gdh` MCP entry in .mcp.json while preserving any non-GDH servers."
2013
1957
  : "Create .mcp.json with the managed `gdh` MCP entry.",
2014
- content: renderManagedMcpConfig(path.join(integrationRootPath, PROJECT_MCP_RELATIVE_PATH), buildManagedMcpServerEntry({
2015
- targetPath,
2016
- integrationRootPath,
2017
- launcherPathForConfig: path.resolve(integrationRootPath, MCP_LAUNCHER_RELATIVE_PATH),
2018
- })),
1958
+ content: renderManagedMcpConfig(path.join(integrationRootPath, PROJECT_MCP_RELATIVE_PATH), buildManagedMcpServerEntry({ pinnedVersion })),
2019
1959
  }));
2020
1960
  }
2021
1961
  if (projectMcp.cursorFile.state !== "ready") {
@@ -2030,11 +1970,7 @@ function planSharedRepoInstallActions(targetPath, projectMcp, agent, effectiveDe
2030
1970
  summary: projectMcp.cursorFile.present
2031
1971
  ? "Replace the managed `gdh` MCP entry in .cursor/mcp.json while preserving any non-GDH servers."
2032
1972
  : "Create .cursor/mcp.json with the managed `gdh` MCP entry.",
2033
- content: renderManagedMcpConfig(path.join(integrationRootPath, CURSOR_MCP_RELATIVE_PATH), buildManagedMcpServerEntry({
2034
- targetPath,
2035
- integrationRootPath,
2036
- launcherPathForConfig: path.resolve(integrationRootPath, MCP_LAUNCHER_RELATIVE_PATH),
2037
- })),
1973
+ content: renderManagedMcpConfig(path.join(integrationRootPath, CURSOR_MCP_RELATIVE_PATH), buildManagedMcpServerEntry({ pinnedVersion })),
2038
1974
  }));
2039
1975
  }
2040
1976
  if (projectMcp.localPathHints.gdhDevRepoPath !== effectiveDevRepoPath) {
@@ -2104,8 +2040,8 @@ function agentLabel(agent) {
2104
2040
  return "Cursor";
2105
2041
  }
2106
2042
  }
2107
- function planCodexRepoInstallActions(targetPath, adapter, pinnedVersion) {
2108
- return [
2043
+ function planCodexRepoInstallActions(targetPath, adapter, pinnedVersion, projectMcp, integrationRootPath) {
2044
+ const actions = [
2109
2045
  planSkillInstallAction("codex", targetPath, adapter, CODEX_ONBOARD_SKILL_RELATIVE_PATH, renderCodexOnboardSkill, "gdh-onboard", pinnedVersion),
2110
2046
  planSkillInstallAction("codex", targetPath, adapter, CODEX_STATUS_SKILL_RELATIVE_PATH, renderCodexStatusSkill, "gdh-status", pinnedVersion),
2111
2047
  planSkillInstallAction("codex", targetPath, adapter, CODEX_MIGRATE_SKILL_RELATIVE_PATH, renderCodexMigrateSkill, "gdh-migrate", pinnedVersion),
@@ -2115,18 +2051,42 @@ function planCodexRepoInstallActions(targetPath, adapter, pinnedVersion) {
2115
2051
  planSkillInstallAction("codex", targetPath, adapter, CODEX_VERIFY_SKILL_RELATIVE_PATH, renderCodexVerifySkill, "gdh-verify", pinnedVersion),
2116
2052
  planSkillInstallAction("codex", targetPath, adapter, CODEX_SCAN_SKILL_RELATIVE_PATH, renderCodexScanSkill, "gdh-scan", pinnedVersion),
2117
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;
2118
2074
  }
2119
- function planCodexUserInstallActions(targetPath, projectMcp, integrationRootPath) {
2075
+ function planCodexUserInstallActions(targetPath, projectMcp, integrationRootPath, pinnedVersion) {
2120
2076
  if (!projectMcp.enabled || projectMcp.codexServerName === null) {
2121
2077
  return [];
2122
2078
  }
2123
- const expectedLauncherPath = path.join(integrationRootPath, MCP_LAUNCHER_RELATIVE_PATH);
2124
- const codexArgs = buildManagedLauncherInvocationArgs({
2125
- targetPath,
2126
- integrationRootPath,
2127
- launcherPath: expectedLauncherPath,
2128
- useAbsoluteTargetPath: true,
2129
- });
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
+ }
2130
2090
  const actions = [];
2131
2091
  if (projectMcp.codexRegistration.present) {
2132
2092
  actions.push(createInstallAction({
@@ -2138,7 +2098,7 @@ function planCodexUserInstallActions(targetPath, projectMcp, integrationRootPath
2138
2098
  absolutePath: projectMcp.codexConfigPath,
2139
2099
  state: "planned",
2140
2100
  mode: "replace",
2141
- 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.`,
2142
2102
  command: ["codex", "mcp", "remove", projectMcp.codexServerName],
2143
2103
  }));
2144
2104
  }
@@ -2151,15 +2111,15 @@ function planCodexUserInstallActions(targetPath, projectMcp, integrationRootPath
2151
2111
  absolutePath: projectMcp.codexConfigPath,
2152
2112
  state: "planned",
2153
2113
  mode: projectMcp.codexRegistration.present ? "replace" : "create",
2154
- 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.`,
2155
2115
  command: [
2156
2116
  "codex",
2157
2117
  "mcp",
2158
2118
  "add",
2159
2119
  projectMcp.codexServerName,
2160
2120
  "--",
2161
- "node",
2162
- ...codexArgs,
2121
+ "npx",
2122
+ ...codexCommandArgs,
2163
2123
  ],
2164
2124
  }));
2165
2125
  return actions;
@@ -2377,64 +2337,11 @@ function resolveProjectMcpEnabled(projectConfig) {
2377
2337
  function createCodexServerName(projectKeySeed) {
2378
2338
  return `gdh-${projectKeySeed}`;
2379
2339
  }
2380
- export function renderManagedMcpLauncher(pinnedVersion) {
2381
- return [
2382
- "#!/usr/bin/env node",
2383
- `// GDH MCP launcher v${GDH_MCP_LAUNCHER_VERSION}`,
2384
- 'import fs from "node:fs";',
2385
- 'import path from "node:path";',
2386
- 'import { spawnSync } from "node:child_process";',
2387
- 'import { fileURLToPath } from "node:url";',
2388
- "",
2389
- "const launcherPath = fileURLToPath(import.meta.url);",
2390
- 'const integrationRootPath = path.resolve(path.dirname(launcherPath), "../..");',
2391
- 'const targetOptionIndex = process.argv.indexOf("--target");',
2392
- "const configuredTargetPath =",
2393
- ' targetOptionIndex >= 0 && typeof process.argv[targetOptionIndex + 1] === "string"',
2394
- " ? process.argv[targetOptionIndex + 1]",
2395
- " : null;",
2396
- "const targetPath = configuredTargetPath",
2397
- " ? path.resolve(integrationRootPath, configuredTargetPath)",
2398
- " : integrationRootPath;",
2399
- 'const hintsPath = path.join(integrationRootPath, ".gdh-state", "local-paths.json");',
2400
- "",
2401
- "let gdhDevRepoPath = null;",
2402
- "try {",
2403
- ' const raw = fs.readFileSync(hintsPath, "utf8");',
2404
- " const parsed = JSON.parse(raw);",
2405
- ' if (typeof parsed.gdhDevRepoPath === "string" && parsed.gdhDevRepoPath.length > 0) {',
2406
- " gdhDevRepoPath = parsed.gdhDevRepoPath;",
2407
- " }",
2408
- "} catch {}",
2409
- "",
2410
- "const effectiveDevRepoPath = process.env.GDH_DEV_REPO?.trim() || gdhDevRepoPath;",
2411
- "if (effectiveDevRepoPath) {",
2412
- ' const devCliEntryPath = path.join(effectiveDevRepoPath, "packages/cli/dist/cli.js");',
2413
- " if (fs.existsSync(devCliEntryPath)) {",
2414
- ' const result = spawnSync(process.execPath, [devCliEntryPath, "mcp", "serve", "--target", targetPath], { stdio: "inherit" });',
2415
- " process.exit(result.status ?? 1);",
2416
- " }",
2417
- "}",
2418
- "",
2419
- `const result = spawnSync("npx", ["-y", "@skillcap/gdh@${pinnedVersion}", "mcp", "serve", "--target", targetPath], { stdio: "inherit", cwd: targetPath });`,
2420
- 'if (result.error && result.error.code === "ENOENT") {',
2421
- ` 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.");`,
2422
- " process.exit(1);",
2423
- "}",
2424
- "process.exit(result.status ?? 1);",
2425
- "",
2426
- ].join("\n");
2427
- }
2428
2340
  function buildManagedMcpServerEntry(input) {
2429
2341
  return {
2430
2342
  type: "stdio",
2431
- command: "node",
2432
- args: buildManagedLauncherInvocationArgs({
2433
- targetPath: input.targetPath,
2434
- integrationRootPath: input.integrationRootPath,
2435
- launcherPath: input.launcherPathForConfig,
2436
- useAbsoluteTargetPath: true,
2437
- }),
2343
+ command: "npx",
2344
+ args: ["-y", `@skillcap/gdh@${input.pinnedVersion}`, "mcp", "serve"],
2438
2345
  };
2439
2346
  }
2440
2347
  function getManagedMcpServerEntry(jsonObject) {
@@ -2475,6 +2382,64 @@ function readExistingMcpConfig(absolutePath) {
2475
2382
  return {};
2476
2383
  }
2477
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
+ }
2478
2443
  function normalizeJson(value) {
2479
2444
  if (Array.isArray(value)) {
2480
2445
  return value.map((entry) => normalizeJson(entry));
@@ -2614,14 +2579,19 @@ async function listCodexMcpServers() {
2614
2579
  }
2615
2580
  }
2616
2581
  function isMatchingCodexRegistration(registration, input) {
2617
- const expectedArgs = buildManagedLauncherInvocationArgs({
2618
- targetPath: input.targetPath,
2619
- integrationRootPath: input.integrationRootPath,
2620
- launcherPath: input.launcherPath,
2621
- useAbsoluteTargetPath: true,
2622
- });
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
+ }
2623
2593
  return (registration.transport?.type === "stdio" &&
2624
- registration.transport.command === "node" &&
2594
+ registration.transport.command === "npx" &&
2625
2595
  JSON.stringify(registration.transport.args ?? []) === JSON.stringify(expectedArgs));
2626
2596
  }
2627
2597
  async function inspectProjectConfigLifecycleSurface(targetPath) {
@@ -3054,60 +3024,6 @@ function inspectCursorRuleLifecycleSurface(targetPath, adapterStatus) {
3054
3024
  },
3055
3025
  });
3056
3026
  }
3057
- function inspectMcpLauncherLifecycleSurface(targetPath, mcpEnabled, adapterStatus) {
3058
- if (!mcpEnabled) {
3059
- return createLifecycleSurfaceStatus({
3060
- surface: "mcp_launcher",
3061
- management: "managed",
3062
- state: "compatible",
3063
- summary: "Project-scoped MCP launcher is not required while MCP is disabled.",
3064
- reasons: [],
3065
- probes: [],
3066
- action: null,
3067
- });
3068
- }
3069
- const launcherSurface = findAdapterSurface(adapterStatus, "claude", "launcher_file") ??
3070
- findAdapterSurface(adapterStatus, "cursor", "launcher_file") ??
3071
- findAdapterSurface(adapterStatus, "codex", "launcher_file");
3072
- const probes = launcherSurface === null
3073
- ? []
3074
- : [
3075
- createVersionProbe({
3076
- targetPath,
3077
- relativePath: launcherSurface.relativePath ?? MCP_LAUNCHER_RELATIVE_PATH,
3078
- present: launcherSurface.present,
3079
- expectedVersion: GDH_MCP_LAUNCHER_VERSION,
3080
- detectedVersion: launcherSurface.present ? launcherSurface.version : null,
3081
- }),
3082
- ];
3083
- if (launcherSurface?.state === "ready") {
3084
- return createLifecycleSurfaceStatus({
3085
- surface: "mcp_launcher",
3086
- management: "managed",
3087
- state: "compatible",
3088
- summary: "Managed MCP launcher file matches the current GDH version.",
3089
- reasons: [],
3090
- probes,
3091
- action: null,
3092
- });
3093
- }
3094
- return createLifecycleSurfaceStatus({
3095
- surface: "mcp_launcher",
3096
- management: "managed",
3097
- state: launcherSurface?.present ? "migration_available" : "migration_needed",
3098
- summary: "Managed MCP launcher file needs to be created or refreshed through the adapter install flow.",
3099
- reasons: launcherSurface?.present
3100
- ? ["mcp_launcher_misconfigured"]
3101
- : ["mcp_launcher_missing"],
3102
- probes,
3103
- action: {
3104
- kind: "run_repair",
3105
- summary: "Run GDH migrate or adapters install to refresh the managed MCP launcher file.",
3106
- command: ["gdh", "adapters", "install", targetPath],
3107
- autoApplicable: true,
3108
- },
3109
- });
3110
- }
3111
3027
  function inspectRuntimeBridgeLifecycleSurface(targetPath, bridgeStatus) {
3112
3028
  const probes = bridgeStatus.managedArtifacts.map((artifact) => createVersionProbe({
3113
3029
  targetPath,