@skillcap/gdh 0.4.1 → 0.6.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 (105) hide show
  1. package/INSTALL-BUNDLE.json +1 -1
  2. package/README.md +54 -92
  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 +34 -18
  20. package/node_modules/@gdh/adapters/dist/index.d.ts.map +1 -1
  21. package/node_modules/@gdh/adapters/dist/index.js +596 -145
  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 +2 -1
  29. package/node_modules/@gdh/authoring/dist/index.d.ts.map +1 -1
  30. package/node_modules/@gdh/authoring/dist/index.js +2 -1
  31. package/node_modules/@gdh/authoring/dist/index.js.map +1 -1
  32. package/node_modules/@gdh/authoring/dist/project.d.ts +24 -0
  33. package/node_modules/@gdh/authoring/dist/project.d.ts.map +1 -1
  34. package/node_modules/@gdh/authoring/dist/project.js +51 -1
  35. package/node_modules/@gdh/authoring/dist/project.js.map +1 -1
  36. package/node_modules/@gdh/authoring/dist/writePinnedVersion.d.ts +17 -0
  37. package/node_modules/@gdh/authoring/dist/writePinnedVersion.d.ts.map +1 -0
  38. package/node_modules/@gdh/authoring/dist/writePinnedVersion.js +50 -0
  39. package/node_modules/@gdh/authoring/dist/writePinnedVersion.js.map +1 -0
  40. package/node_modules/@gdh/authoring/package.json +5 -2
  41. package/node_modules/@gdh/cli/dist/index.d.ts +15 -0
  42. package/node_modules/@gdh/cli/dist/index.d.ts.map +1 -1
  43. package/node_modules/@gdh/cli/dist/index.js +292 -40
  44. package/node_modules/@gdh/cli/dist/index.js.map +1 -1
  45. package/node_modules/@gdh/cli/dist/migrate.d.ts +1 -0
  46. package/node_modules/@gdh/cli/dist/migrate.d.ts.map +1 -1
  47. package/node_modules/@gdh/cli/dist/migrate.js +180 -72
  48. package/node_modules/@gdh/cli/dist/migrate.js.map +1 -1
  49. package/node_modules/@gdh/cli/dist/self-update.d.ts +3 -0
  50. package/node_modules/@gdh/cli/dist/self-update.d.ts.map +1 -0
  51. package/node_modules/@gdh/cli/dist/self-update.js +235 -0
  52. package/node_modules/@gdh/cli/dist/self-update.js.map +1 -0
  53. package/node_modules/@gdh/cli/dist/setup.d.ts.map +1 -1
  54. package/node_modules/@gdh/cli/dist/setup.js +49 -1
  55. package/node_modules/@gdh/cli/dist/setup.js.map +1 -1
  56. package/node_modules/@gdh/cli/dist/update-banner.d.ts +42 -0
  57. package/node_modules/@gdh/cli/dist/update-banner.d.ts.map +1 -0
  58. package/node_modules/@gdh/cli/dist/update-banner.js +49 -0
  59. package/node_modules/@gdh/cli/dist/update-banner.js.map +1 -0
  60. package/node_modules/@gdh/cli/package.json +10 -10
  61. package/node_modules/@gdh/core/dist/dev-mode.d.ts +13 -0
  62. package/node_modules/@gdh/core/dist/dev-mode.d.ts.map +1 -0
  63. package/node_modules/@gdh/core/dist/dev-mode.js +21 -0
  64. package/node_modules/@gdh/core/dist/dev-mode.js.map +1 -0
  65. package/node_modules/@gdh/core/dist/index.d.ts +12 -5
  66. package/node_modules/@gdh/core/dist/index.d.ts.map +1 -1
  67. package/node_modules/@gdh/core/dist/index.js +10 -2
  68. package/node_modules/@gdh/core/dist/index.js.map +1 -1
  69. package/node_modules/@gdh/core/dist/update-cache.d.ts +46 -0
  70. package/node_modules/@gdh/core/dist/update-cache.d.ts.map +1 -0
  71. package/node_modules/@gdh/core/dist/update-cache.js +90 -0
  72. package/node_modules/@gdh/core/dist/update-cache.js.map +1 -0
  73. package/node_modules/@gdh/core/dist/update-probe.d.ts +102 -0
  74. package/node_modules/@gdh/core/dist/update-probe.d.ts.map +1 -0
  75. package/node_modules/@gdh/core/dist/update-probe.js +195 -0
  76. package/node_modules/@gdh/core/dist/update-probe.js.map +1 -0
  77. package/node_modules/@gdh/core/package.json +1 -1
  78. package/node_modules/@gdh/docs/dist/guidance.d.ts.map +1 -1
  79. package/node_modules/@gdh/docs/dist/guidance.js +47 -0
  80. package/node_modules/@gdh/docs/dist/guidance.js.map +1 -1
  81. package/node_modules/@gdh/docs/package.json +2 -2
  82. package/node_modules/@gdh/mcp/dist/index.d.ts +20 -0
  83. package/node_modules/@gdh/mcp/dist/index.d.ts.map +1 -1
  84. package/node_modules/@gdh/mcp/dist/index.js +45 -4
  85. package/node_modules/@gdh/mcp/dist/index.js.map +1 -1
  86. package/node_modules/@gdh/mcp/package.json +8 -8
  87. package/node_modules/@gdh/observability/dist/guidance-audit.js +3 -1
  88. package/node_modules/@gdh/observability/dist/guidance-audit.js.map +1 -1
  89. package/node_modules/@gdh/observability/package.json +2 -2
  90. package/node_modules/@gdh/runtime/package.json +2 -2
  91. package/node_modules/@gdh/scan/dist/index.d.ts +2 -0
  92. package/node_modules/@gdh/scan/dist/index.d.ts.map +1 -1
  93. package/node_modules/@gdh/scan/dist/index.js +1 -0
  94. package/node_modules/@gdh/scan/dist/index.js.map +1 -1
  95. package/node_modules/@gdh/scan/dist/inventory-cache.d.ts +15 -0
  96. package/node_modules/@gdh/scan/dist/inventory-cache.d.ts.map +1 -0
  97. package/node_modules/@gdh/scan/dist/inventory-cache.js +53 -0
  98. package/node_modules/@gdh/scan/dist/inventory-cache.js.map +1 -0
  99. package/node_modules/@gdh/scan/dist/onboard.d.ts +7 -0
  100. package/node_modules/@gdh/scan/dist/onboard.d.ts.map +1 -1
  101. package/node_modules/@gdh/scan/dist/onboard.js +7 -1
  102. package/node_modules/@gdh/scan/dist/onboard.js.map +1 -1
  103. package/node_modules/@gdh/scan/package.json +3 -3
  104. package/node_modules/@gdh/verify/package.json +7 -7
  105. package/package.json +11 -11
@@ -1,18 +1,20 @@
1
1
  import fs from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import { buildGdhStatusResult, createGsdSnapshot, getSupportedAgentAdaptersStatus, installSupportedAgentAdapters, } from "@gdh/adapters";
5
- import { getManagedLspStatus, hasCompleteOnboardingSurface, inspectCacheState, pruneCacheState, readProjectConfig, resolveProjectRoot, readWorktreeState, resolveAuthoringStatus, resolveTargetGodotDocsVersion, runAuthoringCheck, runImportRefresh, runTargetPrepare, runWarmup, } from "@gdh/authoring";
4
+ import { buildGdhStatusResult, CLAUDE_CHECK_COMMAND_RELATIVE_PATH, CLAUDE_CHECK_UPDATE_HOOK_RELATIVE_PATH, CLAUDE_MIGRATE_COMMAND_RELATIVE_PATH, CLAUDE_ONBOARD_COMMAND_RELATIVE_PATH, CLAUDE_PREPARE_COMMAND_RELATIVE_PATH, CLAUDE_STATUS_COMMAND_RELATIVE_PATH, CLAUDE_VERIFY_COMMAND_RELATIVE_PATH, CODEX_CHECK_SKILL_RELATIVE_PATH, CODEX_MIGRATE_SKILL_RELATIVE_PATH, CODEX_ONBOARD_SKILL_RELATIVE_PATH, CODEX_PREPARE_SKILL_RELATIVE_PATH, CODEX_STATUS_SKILL_RELATIVE_PATH, CODEX_VERIFY_SKILL_RELATIVE_PATH, createGsdSnapshot, CURSOR_CHECK_SKILL_RELATIVE_PATH, CURSOR_MIGRATE_SKILL_RELATIVE_PATH, CURSOR_ONBOARD_SKILL_RELATIVE_PATH, CURSOR_PREPARE_SKILL_RELATIVE_PATH, CURSOR_STATUS_SKILL_RELATIVE_PATH, CURSOR_VERIFY_SKILL_RELATIVE_PATH, getSupportedAgentAdaptersStatus, installSupportedAgentAdapters, MCP_LAUNCHER_RELATIVE_PATH, } from "@gdh/adapters";
5
+ import { getManagedLspStatus, hasCompleteOnboardingSurface, inspectCacheState, pruneCacheState, readProjectConfig, resolvePinnedVersion, resolveProjectRoot, readWorktreeState, resolveAuthoringStatus, resolveTargetGodotDocsVersion, runAuthoringCheck, runImportRefresh, runTargetPrepare, runWarmup, } from "@gdh/authoring";
6
6
  import { definePackageBoundary, GDH_AUTHORING_DOGFOOD_VERSION, GDH_AUTHORING_SLICE_REPORT_VERSION, GDH_PRODUCT_NAME, resolveCurrentGdhInstall, resolveGdhProductMetadata, } from "@gdh/core";
7
7
  import { fetchOfficialGodotDoc, getGuidanceStatus, resolveGuidanceQuery, searchOfficialGodotDocs, } from "@gdh/docs";
8
8
  import { createMcpManifest, invokeMcpTool, serveMcpOverStdio } from "@gdh/mcp";
9
9
  import { inspectAuthoringEffectiveness, inspectAuthoringSessions, inspectGuidanceAudit, recordAuthoringSessionEvent, } from "@gdh/observability";
10
10
  import { checkRuntimeRecipe, inspectRuntimeBridgeSurface, installRuntimeBridgeSurface, listRuntimeRecipes, removeRuntimeBridgeSurface, repairRuntimeBridgeSurface, runRuntimeRecipe, showRuntimeRecipe, } from "@gdh/runtime";
11
- import { applyRepairableOnboardingWrites, onboardGodotProject, scanGodotProjectInventory, } from "@gdh/scan";
11
+ import { applyRepairableOnboardingWrites, onboardGodotProject, persistInventoryForTarget, readInventoryCacheOrScan, scanGodotProjectInventory, } from "@gdh/scan";
12
12
  import { evaluateDonePolicy, exerciseRuntimeCorpusEntry, inspectRuntimeCorpusStatus, inspectRuntimeVerificationBundleState, inspectRuntimeVerificationReadiness, listRuntimeScenarios, materializeRuntimeCorpusEntry, recommendValidationForChange, recordRuntimeCorpusValidation, runRuntimeVerificationScenario, showRuntimeScenario, } from "@gdh/verify";
13
13
  import { migrateProjectLifecycleSurface } from "./migrate.js";
14
14
  import { presentPublicRuntimeTerms } from "./public-terms.js";
15
+ import { runSelfUpdateCommand } from "./self-update.js";
15
16
  import { executeSetupCommand, isSetupCanceledError, renderSetupIntro, renderSetupOutro, renderSetupSummary, } from "./setup.js";
17
+ import { emitUpdateBannerIfStale } from "./update-banner.js";
16
18
  export const cliPackage = definePackageBoundary({
17
19
  name: "@gdh/cli",
18
20
  layer: "surface",
@@ -102,6 +104,9 @@ export async function runCli(args, io = { stdout: process.stdout, stderr: proces
102
104
  if (command === "migrate") {
103
105
  return runMigrateCommand(rest, io);
104
106
  }
107
+ if (command === "self-update") {
108
+ return runSelfUpdateCommand(rest, io);
109
+ }
105
110
  if (command === "cache") {
106
111
  return runCacheCommand(rest, io);
107
112
  }
@@ -111,7 +116,7 @@ export async function runCli(args, io = { stdout: process.stdout, stderr: proces
111
116
  io.stderr.write(`Unknown command: ${command}\n\n${renderHelp()}`);
112
117
  return 1;
113
118
  }
114
- function writeJsonResult(io, value, options = {}) {
119
+ export function writeJsonResult(io, value, options = {}) {
115
120
  const output = options.presentRuntimeTerms ? presentPublicRuntimeTerms(value) : value;
116
121
  io.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
117
122
  }
@@ -134,12 +139,51 @@ async function runScanCommand(args, io) {
134
139
  return 1;
135
140
  }
136
141
  const targetPath = parsedTarget.targetPath;
142
+ const commandStartedAtMs = Date.now();
143
+ let onboardedRoot = null;
137
144
  try {
138
- const inventory = await scanGodotProjectInventory(targetPath);
139
- io.stdout.write(`${JSON.stringify(inventory, null, 2)}\n`);
145
+ onboardedRoot = await resolveProjectRoot(targetPath);
146
+ // Use readInventoryCacheOrScan so that a re-scan of an already-persisted, unchanged
147
+ // filesystem returns the cached inventory byte-for-byte identical to what is on disk.
148
+ // This lets persistInventoryForTarget detect mode: "unchanged" on repeat invocations,
149
+ // keeping the semantics idempotent for unmodified projects (REFR-01 TBD-04).
150
+ // For non-onboarded targets (onboardedRoot === null), fall back to a plain live scan.
151
+ const inventoryRoot = onboardedRoot ?? targetPath;
152
+ const inventoryResult = await readInventoryCacheOrScan(inventoryRoot);
153
+ const inventory = inventoryResult.inventory;
154
+ if (onboardedRoot !== null) {
155
+ const persisted = await persistInventoryForTarget(inventory, onboardedRoot);
156
+ await recordSessionEvent(onboardedRoot, {
157
+ commandStartedAtMs,
158
+ kind: "scan",
159
+ command: "gdh scan",
160
+ state: "succeeded",
161
+ summary: `Scanned target and persisted inventory to ${persisted.relativePath} (${persisted.mode}).`,
162
+ producedArtifacts: persisted.mode !== "unchanged" ? [persisted.relativePath] : [],
163
+ });
164
+ io.stdout.write(`${JSON.stringify({ inventory, persisted }, null, 2)}\n`);
165
+ }
166
+ else {
167
+ io.stdout.write(`${JSON.stringify({ inventory, persisted: null }, null, 2)}\n`);
168
+ }
140
169
  return 0;
141
170
  }
142
171
  catch (error) {
172
+ // Best-effort: if walk-up hasn't run yet, try it once in the catch path so we can still
173
+ // emit the session event when an onboarded root exists but the live scan threw.
174
+ if (onboardedRoot === null) {
175
+ onboardedRoot = await resolveProjectRoot(targetPath).catch(() => null);
176
+ }
177
+ if (onboardedRoot !== null) {
178
+ await recordSessionEvent(onboardedRoot, {
179
+ commandStartedAtMs,
180
+ kind: "scan",
181
+ command: "gdh scan",
182
+ state: "failed",
183
+ summary: "gdh scan failed before completing inventory write.",
184
+ errorMessage: formatCliError(error),
185
+ });
186
+ }
143
187
  io.stderr.write(`Failed to scan target "${targetPath}": ${formatCliError(error)}\n`);
144
188
  return 1;
145
189
  }
@@ -347,6 +391,11 @@ async function runStatusCommand(args, io) {
347
391
  }
348
392
  const targetPath = parsedTarget.targetPath;
349
393
  const commandStartedAtMs = Date.now();
394
+ // UPD-04: emit the update banner to stderr BEFORE primary work so the
395
+ // stderr prelude precedes any stdout JSON payload. D-13 scopes the banner
396
+ // to `gdh status` and `gdh verify` only. Silent when cache is null /
397
+ // current / offline / dev-mode (D-06).
398
+ await emitUpdateBannerIfStale(targetPath, io);
350
399
  try {
351
400
  const status = await buildGdhStatusResult(targetPath);
352
401
  await recordSessionEvent(targetPath, {
@@ -1209,6 +1258,8 @@ async function runVerifyCommand(args, io) {
1209
1258
  " Summarize runtime verification readiness, stale evidence, and recurring runtime feedback for one target.",
1210
1259
  " corpus <status|record>",
1211
1260
  " Inspect or record runtime corpus release-readiness evidence as JSON.",
1261
+ " drift [target]",
1262
+ " Check whether baked `@skillcap/gdh@<version>` literals (launcher + skills + commands) match .gdh/project.yaml gdh_version.",
1212
1263
  " recommend [target] --files <path> [--files <path>...]",
1213
1264
  " Recommend authoring-first validation requirements for the supplied change set.",
1214
1265
  " done [target] --files <path> [--files <path>...] [--performed <kind>]",
@@ -1216,6 +1267,16 @@ async function runVerifyCommand(args, io) {
1216
1267
  ].join("\n") + "\n");
1217
1268
  return 0;
1218
1269
  }
1270
+ // UPD-04: emit the update banner to stderr BEFORE dispatching to verify
1271
+ // subcommands so the stderr prelude precedes any stdout JSON payload. D-13
1272
+ // scopes the banner to `gdh status` and `gdh verify` only. Target for the
1273
+ // banner's pre-onboard re-check is the first non-flag positional arg
1274
+ // after the subcommand (falling back to "."). The cache itself is per-user,
1275
+ // not per-target, so any resolvable path suffices for the dev-mode check.
1276
+ {
1277
+ const verifyTargetPath = rest.find((arg) => !arg.startsWith("--")) ?? ".";
1278
+ await emitUpdateBannerIfStale(verifyTargetPath, io);
1279
+ }
1219
1280
  if (subcommand === "recommend") {
1220
1281
  return runVerifyRecommendCommand(rest, io);
1221
1282
  }
@@ -1234,6 +1295,9 @@ async function runVerifyCommand(args, io) {
1234
1295
  if (subcommand === "corpus") {
1235
1296
  return runVerifyCorpusCommand(rest, io);
1236
1297
  }
1298
+ if (subcommand === "drift") {
1299
+ return runVerifyDriftCommand(rest, io);
1300
+ }
1237
1301
  io.stderr.write([
1238
1302
  `Unknown verify command: ${subcommand}`,
1239
1303
  "",
@@ -1244,6 +1308,7 @@ async function runVerifyCommand(args, io) {
1244
1308
  "Usage: gdh verify corpus materialize --entry <id> [--manifest <path>] [--workspace-root <path>]",
1245
1309
  "Usage: gdh verify corpus exercise --entry <id> [--manifest <path>] [--workspace-root <path>] [--no-prepare]",
1246
1310
  "Usage: gdh verify corpus record --entry <id> --target <path> [--manifest <path>] [--workspace-root <path>]",
1311
+ "Usage: gdh verify drift [target]",
1247
1312
  "Usage: gdh verify recommend [target] --files <path> [--files <path>...]",
1248
1313
  "Usage: gdh verify done [target] --files <path> [--files <path>...] [--performed <kind>]",
1249
1314
  ].join("\n") + "\n");
@@ -1583,14 +1648,15 @@ async function runRecipeListCommand(args, io) {
1583
1648
  return 1;
1584
1649
  }
1585
1650
  const targetPath = positionalArgs[0] ?? ".";
1651
+ const effectiveTargetPath = await resolveEffectiveTargetPath(targetPath);
1586
1652
  const commandStartedAtMs = Date.now();
1587
1653
  try {
1588
- const projectConfig = await readProjectConfig(targetPath);
1654
+ const projectConfig = await readProjectConfig(effectiveTargetPath);
1589
1655
  const result = await listRuntimeRecipes({
1590
- targetPath,
1656
+ targetPath: effectiveTargetPath,
1591
1657
  projectConfig,
1592
1658
  });
1593
- await recordSessionEvent(targetPath, {
1659
+ await recordSessionEvent(effectiveTargetPath, {
1594
1660
  commandStartedAtMs,
1595
1661
  kind: "recipe_list",
1596
1662
  command: "gdh run-config list",
@@ -1601,7 +1667,7 @@ async function runRecipeListCommand(args, io) {
1601
1667
  return 0;
1602
1668
  }
1603
1669
  catch (error) {
1604
- await recordSessionEvent(targetPath, {
1670
+ await recordSessionEvent(effectiveTargetPath, {
1605
1671
  commandStartedAtMs,
1606
1672
  kind: "recipe_list",
1607
1673
  command: "gdh run-config list",
@@ -1636,15 +1702,16 @@ async function runRecipeShowCommand(args, io) {
1636
1702
  }
1637
1703
  const targetPath = positionalArgs.length === 1 ? "." : positionalArgs[0];
1638
1704
  const recipeId = positionalArgs.length === 1 ? positionalArgs[0] : positionalArgs[1];
1705
+ const effectiveTargetPath = await resolveEffectiveTargetPath(targetPath);
1639
1706
  const commandStartedAtMs = Date.now();
1640
1707
  try {
1641
- const projectConfig = await readProjectConfig(targetPath);
1708
+ const projectConfig = await readProjectConfig(effectiveTargetPath);
1642
1709
  const result = await showRuntimeRecipe({
1643
- targetPath,
1710
+ targetPath: effectiveTargetPath,
1644
1711
  projectConfig,
1645
1712
  recipeId,
1646
1713
  });
1647
- await recordSessionEvent(targetPath, {
1714
+ await recordSessionEvent(effectiveTargetPath, {
1648
1715
  commandStartedAtMs,
1649
1716
  kind: "recipe_show",
1650
1717
  command: "gdh run-config show",
@@ -1655,7 +1722,7 @@ async function runRecipeShowCommand(args, io) {
1655
1722
  return result.state === "ready" ? 0 : 1;
1656
1723
  }
1657
1724
  catch (error) {
1658
- await recordSessionEvent(targetPath, {
1725
+ await recordSessionEvent(effectiveTargetPath, {
1659
1726
  commandStartedAtMs,
1660
1727
  kind: "recipe_show",
1661
1728
  command: "gdh run-config show",
@@ -1691,11 +1758,12 @@ async function runRecipeCheckCommand(args, io) {
1691
1758
  }
1692
1759
  const targetPath = positionalArgs.length === 1 ? "." : positionalArgs[0];
1693
1760
  const recipeId = positionalArgs.length === 1 ? positionalArgs[0] : positionalArgs[1];
1761
+ const effectiveTargetPath = await resolveEffectiveTargetPath(targetPath);
1694
1762
  const commandStartedAtMs = Date.now();
1695
1763
  try {
1696
- const projectConfig = await readProjectConfig(targetPath);
1764
+ const projectConfig = await readProjectConfig(effectiveTargetPath);
1697
1765
  const result = await checkRuntimeRecipe({
1698
- targetPath,
1766
+ targetPath: effectiveTargetPath,
1699
1767
  projectConfig,
1700
1768
  recipeId,
1701
1769
  provider: readSingleOptionValue(args, "--provider"),
@@ -1705,7 +1773,7 @@ async function runRecipeCheckCommand(args, io) {
1705
1773
  environment: collectAssignmentOptionValues(args, "--env"),
1706
1774
  workspaceMode: "isolated_copy",
1707
1775
  });
1708
- await recordSessionEvent(targetPath, {
1776
+ await recordSessionEvent(effectiveTargetPath, {
1709
1777
  commandStartedAtMs,
1710
1778
  kind: "recipe_check",
1711
1779
  command: "gdh run-config check",
@@ -1716,7 +1784,7 @@ async function runRecipeCheckCommand(args, io) {
1716
1784
  return result.state === "runnable" ? 0 : 1;
1717
1785
  }
1718
1786
  catch (error) {
1719
- await recordSessionEvent(targetPath, {
1787
+ await recordSessionEvent(effectiveTargetPath, {
1720
1788
  commandStartedAtMs,
1721
1789
  kind: "recipe_check",
1722
1790
  command: "gdh run-config check",
@@ -1753,11 +1821,12 @@ async function runRecipeRunCommand(args, io) {
1753
1821
  }
1754
1822
  const targetPath = positionalArgs.length === 1 ? "." : positionalArgs[0];
1755
1823
  const recipeId = positionalArgs.length === 1 ? positionalArgs[0] : positionalArgs[1];
1824
+ const effectiveTargetPath = await resolveEffectiveTargetPath(targetPath);
1756
1825
  const commandStartedAtMs = Date.now();
1757
1826
  try {
1758
- const projectConfig = await readProjectConfig(targetPath);
1827
+ const projectConfig = await readProjectConfig(effectiveTargetPath);
1759
1828
  const result = await runRuntimeRecipe({
1760
- targetPath,
1829
+ targetPath: effectiveTargetPath,
1761
1830
  projectConfig,
1762
1831
  recipeId,
1763
1832
  provider: readSingleOptionValue(args, "--provider"),
@@ -1767,7 +1836,7 @@ async function runRecipeRunCommand(args, io) {
1767
1836
  environment: collectAssignmentOptionValues(args, "--env"),
1768
1837
  workspaceMode: args.includes("--live-workspace") ? "live_workspace" : "isolated_copy",
1769
1838
  });
1770
- await recordSessionEvent(targetPath, {
1839
+ await recordSessionEvent(effectiveTargetPath, {
1771
1840
  commandStartedAtMs,
1772
1841
  kind: "recipe_run",
1773
1842
  command: "gdh run-config run",
@@ -1779,7 +1848,7 @@ async function runRecipeRunCommand(args, io) {
1779
1848
  return result.state === "passed" ? 0 : 1;
1780
1849
  }
1781
1850
  catch (error) {
1782
- await recordSessionEvent(targetPath, {
1851
+ await recordSessionEvent(effectiveTargetPath, {
1783
1852
  commandStartedAtMs,
1784
1853
  kind: "recipe_run",
1785
1854
  command: "gdh run-config run",
@@ -1813,10 +1882,11 @@ async function runScenarioListCommand(args, io) {
1813
1882
  return 1;
1814
1883
  }
1815
1884
  const targetPath = positionalArgs[0] ?? ".";
1885
+ const effectiveTargetPath = await resolveEffectiveTargetPath(targetPath);
1816
1886
  const commandStartedAtMs = Date.now();
1817
1887
  try {
1818
- const result = await listRuntimeScenarios({ targetPath });
1819
- await recordSessionEvent(targetPath, {
1888
+ const result = await listRuntimeScenarios({ targetPath: effectiveTargetPath });
1889
+ await recordSessionEvent(effectiveTargetPath, {
1820
1890
  commandStartedAtMs,
1821
1891
  kind: "scenario_list",
1822
1892
  command: "gdh verification-scenario list",
@@ -1827,7 +1897,7 @@ async function runScenarioListCommand(args, io) {
1827
1897
  return result.state === "ready" ? 0 : 1;
1828
1898
  }
1829
1899
  catch (error) {
1830
- await recordSessionEvent(targetPath, {
1900
+ await recordSessionEvent(effectiveTargetPath, {
1831
1901
  commandStartedAtMs,
1832
1902
  kind: "scenario_list",
1833
1903
  command: "gdh verification-scenario list",
@@ -1862,13 +1932,14 @@ async function runScenarioShowCommand(args, io) {
1862
1932
  }
1863
1933
  const targetPath = positionalArgs.length === 1 ? "." : positionalArgs[0];
1864
1934
  const scenarioId = positionalArgs.length === 1 ? positionalArgs[0] : positionalArgs[1];
1935
+ const effectiveTargetPath = await resolveEffectiveTargetPath(targetPath);
1865
1936
  const commandStartedAtMs = Date.now();
1866
1937
  try {
1867
1938
  const result = await showRuntimeScenario({
1868
- targetPath,
1939
+ targetPath: effectiveTargetPath,
1869
1940
  scenarioId,
1870
1941
  });
1871
- await recordSessionEvent(targetPath, {
1942
+ await recordSessionEvent(effectiveTargetPath, {
1872
1943
  commandStartedAtMs,
1873
1944
  kind: "scenario_show",
1874
1945
  command: "gdh verification-scenario show",
@@ -1879,7 +1950,7 @@ async function runScenarioShowCommand(args, io) {
1879
1950
  return result.state === "ready" ? 0 : 1;
1880
1951
  }
1881
1952
  catch (error) {
1882
- await recordSessionEvent(targetPath, {
1953
+ await recordSessionEvent(effectiveTargetPath, {
1883
1954
  commandStartedAtMs,
1884
1955
  kind: "scenario_show",
1885
1956
  command: "gdh verification-scenario show",
@@ -2106,10 +2177,17 @@ async function runMcpInvokeCommand(args, io) {
2106
2177
  return 1;
2107
2178
  }
2108
2179
  }
2109
- async function buildAuthoringContext(targetPath) {
2180
+ async function resolveEffectiveTargetPath(targetPath) {
2110
2181
  const resolvedRoot = await resolveProjectRoot(targetPath);
2111
- const effectiveTargetPath = resolvedRoot ?? targetPath;
2112
- const inventory = await scanGodotProjectInventory(effectiveTargetPath);
2182
+ return resolvedRoot ?? targetPath;
2183
+ }
2184
+ async function buildAuthoringContext(targetPath, io) {
2185
+ const effectiveTargetPath = await resolveEffectiveTargetPath(targetPath);
2186
+ const inventoryResult = await readInventoryCacheOrScan(effectiveTargetPath);
2187
+ if (inventoryResult.degraded && io !== undefined) {
2188
+ io.stderr.write(`Warning: .gdh-state/inventory.json is corrupt or unreadable; fell back to live scan.\n`);
2189
+ }
2190
+ const inventory = inventoryResult.inventory;
2113
2191
  const projectConfig = await readProjectConfig(effectiveTargetPath);
2114
2192
  const worktreeState = await readWorktreeState(effectiveTargetPath);
2115
2193
  const status = resolveAuthoringStatus({
@@ -2311,11 +2389,12 @@ async function runVerifyRunCommand(args, io) {
2311
2389
  }
2312
2390
  const targetPath = positionalArgs.length === 1 ? "." : positionalArgs[0];
2313
2391
  const recipeId = positionalArgs.length === 1 ? positionalArgs[0] : positionalArgs[1];
2392
+ const effectiveTargetPath = await resolveEffectiveTargetPath(targetPath);
2314
2393
  const commandStartedAtMs = Date.now();
2315
2394
  try {
2316
- const projectConfig = await readProjectConfig(targetPath);
2395
+ const projectConfig = await readProjectConfig(effectiveTargetPath);
2317
2396
  const result = await runRuntimeVerificationScenario({
2318
- targetPath,
2397
+ targetPath: effectiveTargetPath,
2319
2398
  projectConfig,
2320
2399
  recipeId,
2321
2400
  scenarioId,
@@ -2326,7 +2405,7 @@ async function runVerifyRunCommand(args, io) {
2326
2405
  environment: collectAssignmentOptionValues(args, "--env"),
2327
2406
  workspaceMode: args.includes("--live-workspace") ? "live_workspace" : "isolated_copy",
2328
2407
  });
2329
- await recordSessionEvent(targetPath, {
2408
+ await recordSessionEvent(effectiveTargetPath, {
2330
2409
  commandStartedAtMs,
2331
2410
  kind: "verify_run",
2332
2411
  command: "gdh verify run",
@@ -2342,7 +2421,7 @@ async function runVerifyRunCommand(args, io) {
2342
2421
  return result.outcome === "passed" || result.outcome === "flaky" ? 0 : 1;
2343
2422
  }
2344
2423
  catch (error) {
2345
- await recordSessionEvent(targetPath, {
2424
+ await recordSessionEvent(effectiveTargetPath, {
2346
2425
  commandStartedAtMs,
2347
2426
  kind: "verify_run",
2348
2427
  command: "gdh verify run",
@@ -2462,6 +2541,179 @@ async function runVerifyReadinessCommand(args, io) {
2462
2541
  return 1;
2463
2542
  }
2464
2543
  }
2544
+ const VERIFY_DRIFT_SCANNED_FILES = [
2545
+ { relativePath: MCP_LAUNCHER_RELATIVE_PATH, description: "managed MCP launcher" },
2546
+ { relativePath: CODEX_ONBOARD_SKILL_RELATIVE_PATH, description: "Codex gdh-onboard skill" },
2547
+ { relativePath: CODEX_STATUS_SKILL_RELATIVE_PATH, description: "Codex gdh-status skill" },
2548
+ { relativePath: CODEX_MIGRATE_SKILL_RELATIVE_PATH, description: "Codex gdh-migrate skill" },
2549
+ { relativePath: CODEX_CHECK_SKILL_RELATIVE_PATH, description: "Codex gdh-check skill" },
2550
+ { relativePath: CODEX_PREPARE_SKILL_RELATIVE_PATH, description: "Codex gdh-prepare skill" },
2551
+ { relativePath: CODEX_VERIFY_SKILL_RELATIVE_PATH, description: "Codex gdh-verify skill" },
2552
+ { relativePath: CURSOR_ONBOARD_SKILL_RELATIVE_PATH, description: "Cursor gdh-onboard skill" },
2553
+ { relativePath: CURSOR_STATUS_SKILL_RELATIVE_PATH, description: "Cursor gdh-status skill" },
2554
+ { relativePath: CURSOR_MIGRATE_SKILL_RELATIVE_PATH, description: "Cursor gdh-migrate skill" },
2555
+ { relativePath: CURSOR_CHECK_SKILL_RELATIVE_PATH, description: "Cursor gdh-check skill" },
2556
+ { relativePath: CURSOR_PREPARE_SKILL_RELATIVE_PATH, description: "Cursor gdh-prepare skill" },
2557
+ { relativePath: CURSOR_VERIFY_SKILL_RELATIVE_PATH, description: "Cursor gdh-verify skill" },
2558
+ { relativePath: CLAUDE_ONBOARD_COMMAND_RELATIVE_PATH, description: "Claude gdh/onboard command" },
2559
+ { relativePath: CLAUDE_STATUS_COMMAND_RELATIVE_PATH, description: "Claude gdh/status command" },
2560
+ { relativePath: CLAUDE_MIGRATE_COMMAND_RELATIVE_PATH, description: "Claude gdh/migrate command" },
2561
+ { relativePath: CLAUDE_CHECK_COMMAND_RELATIVE_PATH, description: "Claude gdh/check command" },
2562
+ { relativePath: CLAUDE_PREPARE_COMMAND_RELATIVE_PATH, description: "Claude gdh/prepare command" },
2563
+ { relativePath: CLAUDE_VERIFY_COMMAND_RELATIVE_PATH, description: "Claude gdh/verify command" },
2564
+ // Phase 12 extension — close the Phase 11 coverage gap (research §Baked Version Surface Inventory rows 26-28).
2565
+ // These files are re-baked at pin time and must be audited by MIG-02's post-condition invariant.
2566
+ //
2567
+ // SessionStart hook: bakes `GDH_PINNED_VERSION: "<semver>"` (not an npx literal). Uses a
2568
+ // dedicated versionRegex to extract the pin from that env-injection pattern.
2569
+ {
2570
+ relativePath: CLAUDE_CHECK_UPDATE_HOOK_RELATIVE_PATH,
2571
+ description: "Claude SessionStart update hook",
2572
+ // The hook body sets GDH_PINNED_VERSION: "<semver>" in the spawned worker's env.
2573
+ // VERIFY_DRIFT_BAKED_VERSION_REGEX matches `@skillcap/gdh@<v>` npx literals; this
2574
+ // supplementary regex extracts the pin from the env-injection literal instead.
2575
+ versionRegex: /GDH_PINNED_VERSION:\s*"(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)"/,
2576
+ },
2577
+ // Intentionally excluded: the Claude background worker (gdh-check-update-worker.js) — the
2578
+ // worker script body reads GDH_PINNED_VERSION from process.env at runtime and does NOT contain
2579
+ // an `@skillcap/gdh@<v>` literal or any other baked-version pattern. Adding it would cause
2580
+ // VERIFY_DRIFT_BAKED_VERSION_REGEX to report `no_baked_version` → false positive. Worker drift
2581
+ // is audited via the `// gdh-hook-version: N` marker through the renderer-layer contract, not
2582
+ // via this version-literal scanner. Covered in integration tests at
2583
+ // packages/adapters/src/claude-update-install-integration.test.ts.
2584
+ //
2585
+ // Intentionally excluded: the Claude statusline (gdh-statusline.js) — the statusline renderer
2586
+ // produces a version-agnostic script (reads the update cache at runtime; no semver literal baked
2587
+ // in the output). Adding it would permanently report `no_baked_version` → false positive. The
2588
+ // statusline IS re-baked on every `gdh adapters install` via the renderer pipeline, which ensures
2589
+ // it stays current with the hook version; pin-literal drift detection is not applicable here.
2590
+ //
2591
+ // Intentionally excluded: the Claude /gdh-update command (.claude/commands/gdh/update.md) —
2592
+ // the rendered command shells out to `npx -y @skillcap/gdh@latest self-update` (LITERAL @latest,
2593
+ // not @<pinnedVersion>). The shellout MUST bake @latest so the NEW CLI performs the update,
2594
+ // not the OLD (potentially buggy) pinned one. Because @latest is not a semver literal, the
2595
+ // VERIFY_DRIFT_BAKED_VERSION_REGEX finds no pinned-version match and would permanently report
2596
+ // `no_baked_version` → false positive. Phase 13 D-13 locks this exclusion; Check 44 in
2597
+ // scripts/validate-docs.mjs enforces both the @latest invariant and the exclusion.
2598
+ //
2599
+ // Intentionally excluded: the Codex gdh-update skill (.codex/skills/gdh-update/SKILL.md) —
2600
+ // same rationale: renders @skillcap/gdh@latest, not @<pinnedVersion>. Phase 13 D-13.
2601
+ //
2602
+ // Intentionally excluded: the Cursor gdh-update skill (.cursor/skills/gdh-update/SKILL.md) —
2603
+ // same rationale: renders @skillcap/gdh@latest, not @<pinnedVersion>. Phase 13 D-13.
2604
+ ];
2605
+ // Mirrors EXACT_SEMVER_PATTERN in @gdh/authoring so only well-formed SemVer
2606
+ // values match — drift check does not pick up unrelated doc prose.
2607
+ const VERIFY_DRIFT_BAKED_VERSION_REGEX = /@skillcap\/gdh@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)/;
2608
+ async function runVerifyDriftCommand(args, io) {
2609
+ if (args.includes("--help") || args.includes("help")) {
2610
+ io.stdout.write([
2611
+ "Usage: gdh verify drift [target]",
2612
+ "",
2613
+ "Compares every baked `@skillcap/gdh@<version>` literal across the managed",
2614
+ "surfaces (.gdh/bin/gdh-mcp.mjs launcher + regenerated Codex/Cursor/Claude",
2615
+ "skill + command files) against .gdh/project.yaml gdh_version. Exits 1 and",
2616
+ "emits a structured JSON result when ANY file's baked version diverges from",
2617
+ "the pinned value. Covers ROADMAP Success Criterion #3 (one source of truth).",
2618
+ "",
2619
+ ].join("\n"));
2620
+ return 0;
2621
+ }
2622
+ const unsupportedOptionsError = findUnsupportedOptionsError(args, {
2623
+ usage: "Usage: gdh verify drift [target]\n",
2624
+ });
2625
+ if (unsupportedOptionsError !== null) {
2626
+ io.stderr.write(unsupportedOptionsError);
2627
+ return 1;
2628
+ }
2629
+ const positionalArgs = extractPositionalArgs(args, new Set());
2630
+ if (positionalArgs.length > 1) {
2631
+ io.stderr.write("Usage error: gdh verify drift accepts at most one positional target path.\n");
2632
+ return 1;
2633
+ }
2634
+ const targetPath = positionalArgs[0] ?? ".";
2635
+ const commandStartedAtMs = Date.now();
2636
+ let pinnedVersion;
2637
+ try {
2638
+ pinnedVersion = await resolvePinnedVersion(targetPath);
2639
+ }
2640
+ catch (error) {
2641
+ await recordSessionEvent(targetPath, {
2642
+ commandStartedAtMs,
2643
+ kind: "verify_drift",
2644
+ command: "gdh verify drift",
2645
+ state: "failed",
2646
+ summary: "Drift check could not resolve the pinned GDH version for this target.",
2647
+ errorMessage: formatCliError(error),
2648
+ });
2649
+ // D-07: surface the concrete `gdh migrate --apply` guidance that the
2650
+ // resolvePinnedVersion throw already names — write the full error
2651
+ // message to stdout so agents parsing JSON get the fix command and
2652
+ // stderr so operators get an unmistakable failure line.
2653
+ const message = formatCliError(error);
2654
+ io.stdout.write(`${message}\n`);
2655
+ io.stderr.write(`Failed to check managed-surface drift for "${targetPath}": ${message}\n`);
2656
+ return 1;
2657
+ }
2658
+ const fileResults = await Promise.all(VERIFY_DRIFT_SCANNED_FILES.map(async (file) => {
2659
+ const absolutePath = path.join(targetPath, file.relativePath);
2660
+ const content = await fs.readFile(absolutePath, "utf8").catch(() => null);
2661
+ if (content === null) {
2662
+ return {
2663
+ relativePath: file.relativePath,
2664
+ description: file.description,
2665
+ present: false,
2666
+ baked: null,
2667
+ drift: true,
2668
+ reason: "file_missing",
2669
+ };
2670
+ }
2671
+ const match = content.match(file.versionRegex ?? VERIFY_DRIFT_BAKED_VERSION_REGEX);
2672
+ const baked = match?.[1] ?? null;
2673
+ const reason = baked === null
2674
+ ? "no_baked_version"
2675
+ : baked !== pinnedVersion
2676
+ ? "version_mismatch"
2677
+ : "ok";
2678
+ return {
2679
+ relativePath: file.relativePath,
2680
+ description: file.description,
2681
+ present: true,
2682
+ baked,
2683
+ drift: reason !== "ok",
2684
+ reason,
2685
+ };
2686
+ }));
2687
+ const driftFiles = fileResults.filter((file) => file.drift);
2688
+ const overallDrift = driftFiles.length > 0;
2689
+ const result = {
2690
+ ok: !overallDrift,
2691
+ drift: overallDrift,
2692
+ pinned: pinnedVersion,
2693
+ scannedFileCount: fileResults.length,
2694
+ driftFileCount: driftFiles.length,
2695
+ files: fileResults,
2696
+ action: overallDrift
2697
+ ? {
2698
+ kind: "run_repair",
2699
+ summary: `${driftFiles.length} managed-surface file(s) have a baked version that does not match .gdh/project.yaml gdh_version. Run \`gdh adapters install\` to regenerate them.`,
2700
+ command: ["gdh", "adapters", "install", targetPath],
2701
+ autoApplicable: true,
2702
+ }
2703
+ : null,
2704
+ };
2705
+ await recordSessionEvent(targetPath, {
2706
+ commandStartedAtMs,
2707
+ kind: "verify_drift",
2708
+ command: "gdh verify drift",
2709
+ state: overallDrift ? "failed" : "succeeded",
2710
+ summary: overallDrift
2711
+ ? `Detected ${driftFiles.length} managed-surface file(s) with a baked version drifted from gdh_version.`
2712
+ : `All ${fileResults.length} managed-surface file(s) match .gdh/project.yaml gdh_version.`,
2713
+ });
2714
+ writeJsonResult(io, result);
2715
+ return overallDrift ? 1 : 0;
2716
+ }
2465
2717
  async function runVerifyCorpusStatusCommand(args, io) {
2466
2718
  if (args.includes("--help")) {
2467
2719
  io.stdout.write([
@@ -3541,7 +3793,7 @@ function renderHelp() {
3541
3793
  "Run `gdh <command> --help` for command-specific usage.",
3542
3794
  ].join("\n") + "\n");
3543
3795
  }
3544
- function formatCliError(error) {
3796
+ export function formatCliError(error) {
3545
3797
  return error instanceof Error ? error.message : String(error);
3546
3798
  }
3547
3799
  function resolveCachePruneScope(args) {
@@ -3599,7 +3851,7 @@ function collectAssignmentOptionValues(args, optionName) {
3599
3851
  }
3600
3852
  return assignments;
3601
3853
  }
3602
- function readSingleOptionValue(args, optionName) {
3854
+ export function readSingleOptionValue(args, optionName) {
3603
3855
  const index = args.indexOf(optionName);
3604
3856
  if (index === -1) {
3605
3857
  return null;
@@ -3610,7 +3862,7 @@ function readSingleOptionValue(args, optionName) {
3610
3862
  }
3611
3863
  return value;
3612
3864
  }
3613
- function findUnsupportedOptionsError(args, options) {
3865
+ export function findUnsupportedOptionsError(args, options) {
3614
3866
  const supportedOptions = new Set([
3615
3867
  ...(options.optionsWithValues ?? []),
3616
3868
  ...(options.booleanOptions ?? []),
@@ -3672,7 +3924,7 @@ function buildGuidanceSnapshot(result) {
3672
3924
  recommendedUnitIds: result.recommendedUnits.map((unit) => unit.id),
3673
3925
  };
3674
3926
  }
3675
- async function recordSessionEvent(targetPath, input) {
3927
+ export async function recordSessionEvent(targetPath, input) {
3676
3928
  try {
3677
3929
  const { commandStartedAtMs, ...eventInput } = input;
3678
3930
  const startedAt = typeof commandStartedAtMs === "number" && Number.isFinite(commandStartedAtMs)
@@ -3747,7 +3999,7 @@ function extractGuidanceResolvePositionalArgs(args) {
3747
3999
  function extractRecipePositionalArgs(args) {
3748
4000
  return extractPositionalArgs(args, new Set(["--provider", "--param", "--feature", "--no-feature", "--env"]));
3749
4001
  }
3750
- function extractPositionalArgs(args, optionsWithValues) {
4002
+ export function extractPositionalArgs(args, optionsWithValues) {
3751
4003
  const positionalArgs = [];
3752
4004
  for (let index = 0; index < args.length; index += 1) {
3753
4005
  const arg = args[index];