@opengsd/gsd-core 1.16.0 → 1.18.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 (211) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -2
  3. package/.opencode/plugins/gsd-core.js +251 -1
  4. package/.opencode/plugins/gsd-statusline/tui.tsx +55 -0
  5. package/GEMINI.md +1 -1
  6. package/agents/gsd-advisor-researcher.compact.md +1 -1
  7. package/agents/gsd-advisor-researcher.md +1 -1
  8. package/agents/gsd-assumptions-analyzer.compact.md +1 -1
  9. package/agents/gsd-assumptions-analyzer.md +1 -1
  10. package/agents/gsd-code-fixer.compact.md +1 -1
  11. package/agents/gsd-code-fixer.md +1 -1
  12. package/agents/gsd-code-reviewer.compact.md +1 -1
  13. package/agents/gsd-code-reviewer.md +1 -1
  14. package/agents/gsd-codebase-mapper.compact.md +1 -1
  15. package/agents/gsd-codebase-mapper.md +1 -1
  16. package/agents/gsd-debug-session-manager.compact.md +6 -0
  17. package/agents/gsd-debug-session-manager.md +6 -0
  18. package/agents/gsd-debugger.md +1 -1
  19. package/agents/gsd-doc-verifier.compact.md +4 -4
  20. package/agents/gsd-doc-verifier.md +4 -3
  21. package/agents/gsd-doc-writer.compact.md +23 -5
  22. package/agents/gsd-doc-writer.md +19 -6
  23. package/agents/gsd-eval-auditor.compact.md +1 -1
  24. package/agents/gsd-eval-auditor.md +1 -1
  25. package/agents/gsd-executor.md +7 -3
  26. package/agents/gsd-integration-checker.compact.md +1 -1
  27. package/agents/gsd-integration-checker.md +1 -1
  28. package/agents/gsd-nyquist-auditor.compact.md +1 -1
  29. package/agents/gsd-nyquist-auditor.md +1 -1
  30. package/agents/gsd-phase-researcher.md +1 -1
  31. package/agents/gsd-plan-checker.md +2 -1
  32. package/agents/gsd-planner.md +5 -4
  33. package/agents/gsd-project-researcher.compact.md +1 -1
  34. package/agents/gsd-project-researcher.md +1 -1
  35. package/agents/gsd-research-synthesizer.compact.md +5 -5
  36. package/agents/gsd-research-synthesizer.md +5 -5
  37. package/agents/gsd-roadmapper.compact.md +1 -1
  38. package/agents/gsd-roadmapper.md +1 -1
  39. package/agents/gsd-security-auditor.compact.md +1 -1
  40. package/agents/gsd-security-auditor.md +1 -1
  41. package/agents/gsd-ui-auditor.compact.md +1 -1
  42. package/agents/gsd-ui-auditor.md +1 -1
  43. package/agents/gsd-ui-checker.compact.md +1 -1
  44. package/agents/gsd-ui-checker.md +1 -1
  45. package/agents/gsd-ui-researcher.compact.md +13 -1
  46. package/agents/gsd-ui-researcher.md +28 -1
  47. package/agents/gsd-verifier.md +1 -1
  48. package/bin/gsd-mcp-server.js +3 -1
  49. package/bin/install.js +398 -95
  50. package/commands/gsd/code-review.md +8 -0
  51. package/commands/gsd/pause-work.md +1 -1
  52. package/gsd-core/bin/gsd-tools.cjs +34 -12
  53. package/gsd-core/bin/lib/adr-parser.cjs +37 -5
  54. package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
  55. package/gsd-core/bin/lib/capability-lock.cjs +29 -7
  56. package/gsd-core/bin/lib/capability-registry.cjs +138 -80
  57. package/gsd-core/bin/lib/capability-source.cjs +3 -1
  58. package/gsd-core/bin/lib/capability-state.cjs +6 -3
  59. package/gsd-core/bin/lib/capability-validator.cjs +8 -5
  60. package/gsd-core/bin/lib/capability-writer.cjs +2 -1
  61. package/gsd-core/bin/lib/commands.cjs +33 -9
  62. package/gsd-core/bin/lib/config-loader.cjs +3 -3
  63. package/gsd-core/bin/lib/decision-coverage-support.cjs +19 -0
  64. package/gsd-core/bin/lib/gate-context-drift.cjs +5 -4
  65. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +15 -0
  66. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +13 -0
  67. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
  68. package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
  69. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +60 -5
  70. package/gsd-core/bin/lib/gate-ui-safety.cjs +2 -1
  71. package/gsd-core/bin/lib/graphify.cjs +3 -2
  72. package/gsd-core/bin/lib/init.cjs +43 -16
  73. package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
  74. package/gsd-core/bin/lib/install-engine.cjs +11 -0
  75. package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
  76. package/gsd-core/bin/lib/install-profiles.cjs +5 -3
  77. package/gsd-core/bin/lib/install-shadow-report.cjs +12 -0
  78. package/gsd-core/bin/lib/installer-migrations/011-opencode-mcp-no-prompts.cjs +99 -0
  79. package/gsd-core/bin/lib/installer-migrations.cjs +54 -1
  80. package/gsd-core/bin/lib/jsonc-edit.cjs +418 -0
  81. package/gsd-core/bin/lib/loop-host-contract.cjs +2 -1
  82. package/gsd-core/bin/lib/mcp-server.cjs +5 -1
  83. package/gsd-core/bin/lib/opencode-mcp-entry.cjs +39 -0
  84. package/gsd-core/bin/lib/opencode-version.cjs +486 -0
  85. package/gsd-core/bin/lib/phase.cjs +141 -11
  86. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +2 -1
  87. package/gsd-core/bin/lib/report-parser.cjs +57 -1
  88. package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
  89. package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
  90. package/gsd-core/bin/lib/roadmap.cjs +56 -5
  91. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +142 -6
  92. package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
  93. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +58 -3
  94. package/gsd-core/bin/lib/runtime-slash.cjs +13 -0
  95. package/gsd-core/bin/lib/shell-command-projection.cjs +2 -0
  96. package/gsd-core/bin/lib/smart-entry.cjs +100 -53
  97. package/gsd-core/bin/lib/state-document.cjs +37 -3
  98. package/gsd-core/bin/lib/state-transition.cjs +98 -19
  99. package/gsd-core/bin/lib/state.cjs +544 -126
  100. package/gsd-core/bin/lib/task-command-router.cjs +2 -3
  101. package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
  102. package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
  103. package/gsd-core/bin/lib/update-context.cjs +18 -1
  104. package/gsd-core/bin/lib/verification.cjs +107 -17
  105. package/gsd-core/bin/lib/verify.cjs +36 -1
  106. package/gsd-core/references/agent-contracts.md +1 -1
  107. package/gsd-core/references/agent-skills-bootstrap.md +26 -7
  108. package/gsd-core/references/git-integration.md +3 -3
  109. package/gsd-core/references/planner-reversibility.md +18 -4
  110. package/gsd-core/references/planner-verify-command-grounding.md +18 -0
  111. package/gsd-core/references/tdd.md +4 -3
  112. package/gsd-core/references/verifier-phase-gates.md +8 -1
  113. package/gsd-core/workflows/add-backlog.md +10 -2
  114. package/gsd-core/workflows/add-phase.md +5 -1
  115. package/gsd-core/workflows/add-tests.md +5 -1
  116. package/gsd-core/workflows/add-todo.md +5 -1
  117. package/gsd-core/workflows/ai-integration-phase.md +5 -1
  118. package/gsd-core/workflows/audit-milestone.md +21 -9
  119. package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
  120. package/gsd-core/workflows/autonomous.md +52 -64
  121. package/gsd-core/workflows/check-todos.md +5 -1
  122. package/gsd-core/workflows/code-review-fix.md +5 -1
  123. package/gsd-core/workflows/code-review.md +21 -2
  124. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
  125. package/gsd-core/workflows/complete-milestone.md +25 -5
  126. package/gsd-core/workflows/debug.md +5 -1
  127. package/gsd-core/workflows/diagnose-issues.md +5 -1
  128. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
  129. package/gsd-core/workflows/discuss-phase-assumptions.md +11 -3
  130. package/gsd-core/workflows/discuss-phase.md +5 -1
  131. package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
  132. package/gsd-core/workflows/docs-update.md +27 -5
  133. package/gsd-core/workflows/edit-phase.md +5 -1
  134. package/gsd-core/workflows/eval-review.md +11 -3
  135. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
  136. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +1 -1
  137. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +12 -6
  138. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +19 -2
  139. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
  140. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +5 -1
  141. package/gsd-core/workflows/execute-phase.md +40 -7
  142. package/gsd-core/workflows/execute-plan.md +8 -4
  143. package/gsd-core/workflows/extract-learnings.md +6 -2
  144. package/gsd-core/workflows/forensics.md +5 -1
  145. package/gsd-core/workflows/import.md +5 -1
  146. package/gsd-core/workflows/insert-phase.md +5 -1
  147. package/gsd-core/workflows/manager.md +10 -2
  148. package/gsd-core/workflows/map-codebase.md +5 -1
  149. package/gsd-core/workflows/milestone-summary.md +10 -2
  150. package/gsd-core/workflows/new-milestone.md +13 -5
  151. package/gsd-core/workflows/new-project.md +9 -5
  152. package/gsd-core/workflows/next.md +8 -4
  153. package/gsd-core/workflows/pause-work.md +3 -3
  154. package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
  155. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +5 -1
  156. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
  157. package/gsd-core/workflows/plan-phase.md +41 -73
  158. package/gsd-core/workflows/plan-review-convergence.md +17 -5
  159. package/gsd-core/workflows/plant-seed.md +8 -4
  160. package/gsd-core/workflows/progress.md +6 -2
  161. package/gsd-core/workflows/quick-batch.md +5 -1
  162. package/gsd-core/workflows/quick.md +22 -7
  163. package/gsd-core/workflows/remove-phase.md +5 -1
  164. package/gsd-core/workflows/resume-project.md +19 -11
  165. package/gsd-core/workflows/review.md +6 -2
  166. package/gsd-core/workflows/scan.md +5 -1
  167. package/gsd-core/workflows/secure-phase.md +6 -2
  168. package/gsd-core/workflows/settings.md +3 -3
  169. package/gsd-core/workflows/ship.md +13 -10
  170. package/gsd-core/workflows/thread.md +10 -2
  171. package/gsd-core/workflows/transition.md +5 -1
  172. package/gsd-core/workflows/ui-phase.md +5 -1
  173. package/gsd-core/workflows/ui-review.md +6 -2
  174. package/gsd-core/workflows/ultraplan-phase.md +5 -1
  175. package/gsd-core/workflows/update.md +5 -1
  176. package/gsd-core/workflows/validate-phase.md +6 -2
  177. package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
  178. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
  179. package/gsd-core/workflows/verify-work.md +11 -3
  180. package/hooks/dist/gsd-agent-isolation-guard.js +3 -1
  181. package/hooks/dist/gsd-check-update.js +26 -20
  182. package/hooks/dist/gsd-context-monitor.js +14 -5
  183. package/hooks/dist/gsd-cursor-subagent-start.js +2 -1
  184. package/hooks/dist/gsd-secret-read-guard.js +189 -15
  185. package/hooks/dist/gsd-statusline.js +321 -22
  186. package/hooks/dist/gsd-validate-commit.sh +27 -5
  187. package/hooks/gsd-agent-isolation-guard.js +3 -1
  188. package/hooks/gsd-check-update.js +26 -20
  189. package/hooks/gsd-context-monitor.js +14 -5
  190. package/hooks/gsd-cursor-subagent-start.js +2 -1
  191. package/hooks/gsd-secret-read-guard.js +189 -15
  192. package/hooks/gsd-statusline.js +321 -22
  193. package/hooks/gsd-validate-commit.sh +27 -5
  194. package/package.json +1 -1
  195. package/scripts/audit-workflow-script-paths.cjs +1 -1
  196. package/scripts/diagnose-validate-commit-in-tier.cjs +270 -0
  197. package/scripts/diagnose-validate-commit-phases.cjs +348 -0
  198. package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
  199. package/scripts/docs-guard-registry.cjs +2 -0
  200. package/scripts/gen-capability-registry.cjs +7 -1
  201. package/scripts/gen-loop-host-contract.cjs +4 -1
  202. package/scripts/lib/macos-conformance-tier.generated.cjs +13 -0
  203. package/scripts/lib/platform-conformance-tier.generated.cjs +26 -0
  204. package/scripts/lint-phase-arg-assignment.cjs +2 -2
  205. package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
  206. package/scripts/mutation-matrix.cjs +3 -1
  207. package/scripts/release-tarball-smoke.cjs +15 -7
  208. package/scripts/xtrace-phase-timing.cjs +205 -0
  209. package/skills/gsd-code-review/SKILL.md +8 -0
  210. package/skills/gsd-pause-work/SKILL.md +1 -1
  211. package/vscode/package.json +1 -1
@@ -295,6 +295,61 @@ function buildKiloAgentPermissionBlock(claudeTools) {
295
295
  }
296
296
  return lines;
297
297
  }
298
+ // OpenCode agent tool restrictions (#5338, ADR-5331 D4). Claude's `tools:` allowlist and
299
+ // `disallowedTools:` denylist become OpenCode `permission:` entries. The V1 `permission` form is the one
300
+ // written because OpenCode 2.x translates it automatically ("legacy agent frontmatter … is translated"),
301
+ // so one form serves both generations — verified: a `permission: {edit: deny, bash: deny}` agent drops the
302
+ // edit/write/shell tools from the model's tool list on 1.18.29 and 2.0.27. Only denials are emitted: an
303
+ // allowed tool keeps the user's own configured default instead of being forced to `allow`, and MCP tool
304
+ // entries are not restricted.
305
+ const claudeToOpencodePermission = {
306
+ Read: 'read',
307
+ Write: 'edit',
308
+ Edit: 'edit',
309
+ MultiEdit: 'edit',
310
+ NotebookEdit: 'edit',
311
+ Bash: 'bash',
312
+ Grep: 'grep',
313
+ Glob: 'glob',
314
+ WebFetch: 'webfetch',
315
+ WebSearch: 'websearch',
316
+ Task: 'task',
317
+ Agent: 'task',
318
+ Skill: 'skill',
319
+ SlashCommand: 'skill',
320
+ TodoWrite: 'todowrite',
321
+ };
322
+ const opencodeAgentPermissionOrder = ['read', 'edit', 'bash', 'grep', 'glob', 'webfetch', 'websearch', 'task', 'skill', 'todowrite'];
323
+ /**
324
+ * `permission:` frontmatter lines for an OpenCode agent, or `[]` when the agent declares no tool
325
+ * restriction (an agent with neither `tools` nor `disallowedTools` inherits everything, as in Claude).
326
+ */
327
+ function buildOpencodeAgentPermissionBlock(allowedTools, disallowedTools) {
328
+ const allowed = new Set();
329
+ for (const tool of allowedTools) {
330
+ const mapped = claudeToOpencodePermission[tool];
331
+ if (mapped)
332
+ allowed.add(mapped);
333
+ }
334
+ const denied = new Set();
335
+ if (allowedTools.length > 0) {
336
+ for (const permission of opencodeAgentPermissionOrder) {
337
+ if (!allowed.has(permission))
338
+ denied.add(permission);
339
+ }
340
+ }
341
+ for (const tool of disallowedTools) {
342
+ const mapped = claudeToOpencodePermission[tool];
343
+ if (mapped)
344
+ denied.add(mapped);
345
+ }
346
+ const lines = [];
347
+ for (const permission of opencodeAgentPermissionOrder) {
348
+ if (denied.has(permission))
349
+ lines.push(` ${permission}: deny`);
350
+ }
351
+ return lines.length > 0 ? ['permission:', ...lines] : [];
352
+ }
298
353
  function replaceRelativePathReference(content, fromPath, toPath) {
299
354
  const escapedPath = (0, pattern_cjs_1.escapeRegex)(fromPath);
300
355
  return content.replace(new RegExp(`(^|[^A-Za-z0-9_./-])${escapedPath}`, 'g'), (_, prefix) => `${prefix}${toPath}`);
@@ -1953,7 +2008,11 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
1953
2008
  const newLines = [];
1954
2009
  let inAllowedTools = false;
1955
2010
  let inSkippedArray = false;
2011
+ let inAgentTools = false;
2012
+ let inDisallowedTools = false;
1956
2013
  const allowedTools = [];
2014
+ const agentTools = [];
2015
+ const agentDisallowed = [];
1957
2016
  for (const line of lines) {
1958
2017
  const trimmed = line.trim();
1959
2018
  // For agents: skip commented-out lines (e.g. hooks blocks)
@@ -1965,11 +2024,31 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
1965
2024
  inAllowedTools = true;
1966
2025
  continue;
1967
2026
  }
2027
+ // Agents: collect the `tools:` allowlist and `disallowedTools:` denylist (block-list continuation
2028
+ // lines) so they can be rendered as `permission:` entries below.
2029
+ if (isAgent && (inAgentTools || inDisallowedTools)) {
2030
+ if (trimmed.startsWith('- ')) {
2031
+ const tool = decodeToolScalar(trimmed.substring(2));
2032
+ if (tool !== null)
2033
+ (inAgentTools ? agentTools : agentDisallowed).push(tool);
2034
+ continue;
2035
+ }
2036
+ if (trimmed && !trimmed.startsWith('-')) {
2037
+ inAgentTools = false;
2038
+ inDisallowedTools = false;
2039
+ }
2040
+ }
1968
2041
  // Detect inline tools: field (comma-separated string)
1969
2042
  if (trimmed.startsWith('tools:')) {
1970
2043
  if (isAgent) {
1971
- // Agents: strip tools entirely (not supported in OpenCode agent frontmatter)
1972
- inSkippedArray = true;
2044
+ const toolsValue = trimmed.substring(6).trim();
2045
+ // A comment-only value (`tools: # note`) is not inline content: read the block list instead.
2046
+ if (toolsValue && !toolsValue.startsWith('#')) {
2047
+ agentTools.push(...splitToolScalars(toolsValue).map(decodeToolScalar).filter((tool) => tool !== null));
2048
+ }
2049
+ else {
2050
+ inAgentTools = true;
2051
+ }
1973
2052
  continue;
1974
2053
  }
1975
2054
  const toolsValue = trimmed.substring(6).trim();
@@ -1980,8 +2059,18 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
1980
2059
  }
1981
2060
  continue;
1982
2061
  }
1983
- // For agents: strip skills:, color:, memory:, maxTurns:, permissionMode:, disallowedTools:
1984
- if (isAgent && /^(skills|color|memory|maxTurns|permissionMode|disallowedTools):/.test(trimmed)) {
2062
+ if (isAgent && trimmed.startsWith('disallowedTools:')) {
2063
+ const value = trimmed.substring(16).trim();
2064
+ if (value && !value.startsWith('#')) {
2065
+ agentDisallowed.push(...splitToolScalars(value).map(decodeToolScalar).filter((tool) => tool !== null));
2066
+ }
2067
+ else {
2068
+ inDisallowedTools = true;
2069
+ }
2070
+ continue;
2071
+ }
2072
+ // For agents: strip skills:, color:, memory:, maxTurns:, permissionMode:
2073
+ if (isAgent && /^(skills|color|memory|maxTurns|permissionMode):/.test(trimmed)) {
1985
2074
  inSkippedArray = true;
1986
2075
  continue;
1987
2076
  }
@@ -2065,6 +2154,7 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
2065
2154
  if (variant) {
2066
2155
  newLines.push(frontmatterScalar('variant:', variant));
2067
2156
  }
2157
+ newLines.push(...buildOpencodeAgentPermissionBlock(agentTools, agentDisallowed));
2068
2158
  }
2069
2159
  // For commands: add tools object if we had allowed-tools or tools
2070
2160
  if (!isAgent && allowedTools.length > 0) {
@@ -2833,8 +2923,13 @@ function computePathPrefix({ isGlobal, isOpencode, isWindowsHost: _isWindowsHost
2833
2923
  // See DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT in CONTEXT.md.
2834
2924
  const posixTarget = (0, shell_command_projection_cjs_1.posixNormalize)(String(resolvedTarget));
2835
2925
  const posixHome = homeDir ? (0, shell_command_projection_cjs_1.posixNormalize)(String(homeDir)) : homeDir;
2836
- if (isGlobal && posixTarget.startsWith(posixHome) && !isOpencode) {
2837
- return '$HOME' + posixTarget.slice(posixHome.length) + '/';
2926
+ // #5123: `startsWith` alone treats a SIBLING directory whose name begins with
2927
+ // the home path (`/home/u2/cfg` under `/home/u`) as under home and yields the
2928
+ // broken `$HOMEu2/cfg/`. Require a path boundary.
2929
+ const underHome = typeof posixHome === 'string' && posixHome.length > 0
2930
+ && (posixTarget === posixHome || posixTarget.startsWith(posixHome.endsWith('/') ? posixHome : `${posixHome}/`));
2931
+ if (isGlobal && underHome && !isOpencode) {
2932
+ return '$HOME' + posixTarget.slice(posixHome.length).replace(/^(?!\/)(?=.)/, '/') + '/';
2838
2933
  }
2839
2934
  if (!isGlobal && projectRelative) {
2840
2935
  // An explicit local config dir need not be the descriptor's conventional
@@ -2848,6 +2943,44 @@ function computePathPrefix({ isGlobal, isOpencode, isWindowsHost: _isWindowsHost
2848
2943
  }
2849
2944
  return `${posixTarget}/`;
2850
2945
  }
2946
+ /**
2947
+ * #5123: retarget an installed artifact's `@` includes to the REAL install root.
2948
+ * Driven by the runtime descriptor's hostBehaviors, never by a runtime name:
2949
+ *
2950
+ * - `defaultHomeIncludeRoot` (Copilot, Antigravity): their converters hard-code
2951
+ * the DEFAULT home into includes, so with `--config-dir` the payload lives
2952
+ * elsewhere and `@~/<root>/gsd-core/…` points at nothing. `@~/<root>/` and
2953
+ * `@$HOME/<root>/` become `@<pathPrefix>`. A default install (pathPrefix is
2954
+ * already `$HOME/<root>/`) is returned unchanged, so it stays byte-identical.
2955
+ * - `retargetsGsdCoreProsePath` (Kimi): Kimi has no `@` include, so its prompt
2956
+ * converter turns `~/.claude/gsd-core` into the prose "GSD core", leaving
2957
+ * `@GSD core/x.md` pointing nowhere. A `GSD core/<path>` reference followed
2958
+ * by a path segment becomes the real `<pathPrefix>gsd-core/<path>`, with the
2959
+ * unsupported `@` dropped. Bare prose "GSD core" is not followed by `/`.
2960
+ *
2961
+ * `pathPrefix` is the install root as computePathPrefix derives it
2962
+ * (`$HOME/…/` under HOME, otherwise absolute), with a trailing slash. Pure and
2963
+ * idempotent: a second pass finds none of the literals it rewrites.
2964
+ */
2965
+ function retargetInstalledIncludes(content, behaviors, pathPrefix) {
2966
+ if (typeof content !== 'string' || content.length === 0)
2967
+ return content;
2968
+ if (typeof pathPrefix !== 'string' || !pathPrefix.endsWith('/'))
2969
+ return content;
2970
+ if (!behaviors)
2971
+ return content;
2972
+ let result = content;
2973
+ if (behaviors.retargetsGsdCoreProsePath === true) {
2974
+ result = result.replace(/@?GSD core\/(?=[a-z])/g, `${pathPrefix}gsd-core/`);
2975
+ }
2976
+ const home = behaviors.defaultHomeIncludeRoot;
2977
+ if (typeof home === 'string' && home.length > 0 && pathPrefix !== `$HOME/${home}/`) {
2978
+ result = result
2979
+ .split(`@~/${home}/`).join(`@${pathPrefix}`)
2980
+ .split(`@$HOME/${home}/`).join(`@${pathPrefix}`);
2981
+ }
2982
+ return result;
2983
+ }
2851
2984
  /** Return a safe project-relative prefix for a resolved install target. */
2852
2985
  function projectRelativePrefixFromProjectRoot(projectRoot, resolvedTarget) {
2853
2986
  if (typeof projectRoot !== 'string' || typeof resolvedTarget !== 'string')
@@ -3816,6 +3949,7 @@ module.exports = {
3816
3949
  convertClaudeToOpencodeFrontmatter,
3817
3950
  convertClaudeToKiloFrontmatter,
3818
3951
  _decodeToolScalar: decodeToolScalar,
3952
+ _buildOpencodeAgentPermissionBlock: buildOpencodeAgentPermissionBlock,
3819
3953
  _splitToolScalars: splitToolScalars,
3820
3954
  readGsdCommandNames,
3821
3955
  transformContentToHyphen,
@@ -3883,4 +4017,6 @@ module.exports = {
3883
4017
  _negotiatedDispatchIsolation,
3884
4018
  // #1521: canonical non-Claude runtime list for test files and tooling
3885
4019
  NON_CLAUDE_RUNTIMES,
4020
+ // #5123: post-install include retarget (config-dir aware; Kimi prose path)
4021
+ retargetInstalledIncludes,
3886
4022
  };
@@ -40,6 +40,7 @@ exports.resolveConfigHomeFromDescriptor = resolveConfigHomeFromDescriptor;
40
40
  exports.resolveAntigravityGlobalDir = resolveAntigravityGlobalDir;
41
41
  exports.detectAntigravityDirAmbiguity = detectAntigravityDirAmbiguity;
42
42
  exports.resolveKimiGlobalDir = resolveKimiGlobalDir;
43
+ exports.resolveGsdHome = resolveGsdHome;
43
44
  exports.resolveKimiHooksTomlDir = resolveKimiHooksTomlDir;
44
45
  exports.getGlobalConfigDir = getGlobalConfigDir;
45
46
  exports.resolveSkillsBaseFromDescriptor = resolveSkillsBaseFromDescriptor;
@@ -403,6 +404,23 @@ exports.NON_REGISTRY_CONFIG_HOME_DESCRIPTORS = [
403
404
  * through resolveConfigHomeFromDescriptor and have no `kind`/`name` shape.
404
405
  */
405
406
  exports.GSD_LOCATION_ENV_KEYS = ['GSD_HOME', 'GSD_AGENTS_DIR'];
407
+ /**
408
+ * #5017: the ONE rule for the root of the GSD-owned store (`$GSD_HOME/.gsd/`:
409
+ * defaults.json, consent.json, capability overlays) — `GSD_HOME` when set and
410
+ * non-blank, otherwise the home directory. The config loader, the install-time
411
+ * resolvers, the installer's defaults writer and capability staging all call
412
+ * this, so one configuration gives one answer at install time, at dispatch time
413
+ * and in the staged capabilities. The hooks (cold-tree, no lib import) and the
414
+ * settings / new-project workflows (shell) carry the same rule inline; a test
415
+ * pins each of them to it.
416
+ *
417
+ * `fallbackHome` lets a caller that already holds an injected home directory
418
+ * (the install-time resolvers' test seam) keep using it when GSD_HOME is unset.
419
+ * A blank `GSD_HOME` is "unset": `''` must never become a relative store path.
420
+ */
421
+ function resolveGsdHome(fallbackHome = node_os_1.default.homedir()) {
422
+ return process.env['GSD_HOME'] || fallbackHome;
423
+ }
406
424
  /**
407
425
  * Resolve the directory holding the Kimi product's OWN native config.toml —
408
426
  * the file that product itself reads for providers/models/hooks/etc, and the
@@ -992,7 +992,16 @@ function buildCodexHookWindowsShimIR(scriptAbsPath, absoluteRunnerToken) {
992
992
  const scriptQuoted = JSON.stringify(targetAbs);
993
993
  const cmdPath = scriptAbsPath.replace(/\.js$/, '.cmd');
994
994
  const hookCommand = JSON.stringify(shellCmdProjection.posixNormalize(cmdPath));
995
- const runnerQuoted = JSON.stringify(interpreter);
995
+ // #4838: node is resolved when the hook fires. The install-time path dies when a
996
+ // version manager (WinGet) removes the versioned directory on upgrade, and a
997
+ // shim that only knew that path failed silently until the installer re-ran.
998
+ // Order: explicit GSD_NODE_PATH; the install-time path while it still exists
999
+ // (keeps the #3662 minimal-PATH guarantee); node.exe on PATH. `where $PATH:`
1000
+ // searches PATH only, never the current directory, so repo content cannot
1001
+ // become the runner. `%` is doubled so cmd.exe cannot expand a variable out
1002
+ // of an install path.
1003
+ const bakedBat = JSON.stringify(interpreter).slice(1, -1).replace(/%/g, '%%');
1004
+ const scriptBat = scriptQuoted.replace(/%/g, '%%');
996
1005
  return {
997
1006
  invocation: { interpreter, target: scriptAbsPath },
998
1007
  cmdPath,
@@ -1000,7 +1009,16 @@ function buildCodexHookWindowsShimIR(scriptAbsPath, absoluteRunnerToken) {
1000
1009
  eol: { cmd: '\r\n' },
1001
1010
  passthroughArgs: true,
1002
1011
  render: {
1003
- cmd: () => `@ECHO OFF\r\n@SETLOCAL\r\n@${runnerQuoted} ${scriptQuoted} %*\r\n`,
1012
+ cmd: () => [
1013
+ '@ECHO OFF',
1014
+ '@SETLOCAL',
1015
+ '@SET "GSD_NODE="',
1016
+ '@IF DEFINED GSD_NODE_PATH IF EXIST "%GSD_NODE_PATH%" SET "GSD_NODE=%GSD_NODE_PATH%"',
1017
+ `@IF NOT DEFINED GSD_NODE IF EXIST "${bakedBat}" SET "GSD_NODE=${bakedBat}"`,
1018
+ '@IF NOT DEFINED GSD_NODE FOR /F "delims=" %%N IN (\'where $PATH:node.exe 2^>NUL\') DO @IF NOT DEFINED GSD_NODE SET "GSD_NODE=%%N"',
1019
+ `@"%GSD_NODE%" ${scriptBat} %*`,
1020
+ '',
1021
+ ].join('\r\n'),
1004
1022
  },
1005
1023
  };
1006
1024
  }
@@ -2279,6 +2297,35 @@ function pushHookGroup(entries, matcher, command, timeout) {
2279
2297
  hook['timeout'] = timeout;
2280
2298
  entries.push(matcher === undefined ? { hooks: [hook] } : { matcher, hooks: [hook] });
2281
2299
  }
2300
+ /** #4840: the hook that reads the statusline's metrics bridge file. */
2301
+ const CONTEXT_MONITOR_HOOK = 'gsd-context-monitor.js';
2302
+ /**
2303
+ * #4840: remove GSD-managed registrations of `stem` from one event's groups,
2304
+ * leaving user-authored hooks (even in the same group) untouched. Returns
2305
+ * whether anything was removed. A group left with no hooks is dropped.
2306
+ */
2307
+ function stripManagedHookRegistrations(entries, stem) {
2308
+ let removed = false;
2309
+ for (let i = entries.length - 1; i >= 0; i--) {
2310
+ const entry = entries[i];
2311
+ if (!entry || !Array.isArray(entry.hooks))
2312
+ continue;
2313
+ const kept = entry.hooks.filter((h) => {
2314
+ const managed = referencesHook(h, stem)
2315
+ && isManagedHookCommand(h.command, { surface: 'settings-json', includeLegacyAliases: true });
2316
+ if (managed)
2317
+ removed = true;
2318
+ return !managed;
2319
+ });
2320
+ if (kept.length === entry.hooks.length)
2321
+ continue;
2322
+ if (kept.length === 0)
2323
+ entries.splice(i, 1);
2324
+ else
2325
+ entry.hooks = kept;
2326
+ }
2327
+ return removed;
2328
+ }
2282
2329
  function applySettingsJsonHookTables(settings, opts, tables) {
2283
2330
  const { runtime, postToolEvent, hookEvents, extendedHookEvents, hooksSurface, targetDir, hookOpts } = opts;
2284
2331
  // ADR-857 phase 5f-3: extended hook events are driven by the registry
@@ -2326,6 +2373,12 @@ function applySettingsJsonHookTables(settings, opts, tables) {
2326
2373
  settings.hooks[key] = [];
2327
2374
  }
2328
2375
  const entries = settings.hooks[key];
2376
+ if (row.file === CONTEXT_MONITOR_HOOK && opts.contextWarnings === false) {
2377
+ if (stripManagedHookRegistrations(entries, hookStem(row.file))) {
2378
+ console.log(` ${green}✓${reset} Removed stale context monitor hook (${_capabilityTitle(runtime)} has no context metrics writer, #4840)`);
2379
+ }
2380
+ continue;
2381
+ }
2329
2382
  const command = resolveRowCommand(row, opts);
2330
2383
  if ('build' in row.command && row.command.build === 'sh')
2331
2384
  shellCommands[row.file] = command;
@@ -2468,7 +2521,8 @@ function buildKimiHooksTomlBlock(targetDir, opts) {
2468
2521
  { event: 'PreToolUse', command: cmd('gsd-workflow-guard.js'), matcher: 'Shell|WriteFile|StrReplaceFile', timeout: kimiBlockingGuardTimeout('gsd-workflow-guard.js') },
2469
2522
  { event: 'PreToolUse', command: cmd('gsd-validate-commit.sh'), matcher: 'Shell', timeout: kimiBlockingGuardTimeout('gsd-validate-commit.sh') },
2470
2523
  // PostToolUse
2471
- { event: 'PostToolUse', command: cmd('gsd-context-monitor.js'), timeout: 10 },
2524
+ // #4840: no metrics writer on kimi, so the warning registration is skipped (a block from an earlier install is replaced wholesale).
2525
+ { event: 'PostToolUse', command: opts.contextWarnings === false ? null : cmd('gsd-context-monitor.js'), timeout: 10 },
2472
2526
  { event: 'PostToolUse', command: cmd('gsd-phase-boundary.sh'), matcher: 'WriteFile|StrReplaceFile', timeout: 5 },
2473
2527
  { event: 'PostToolUse', command: cmd('gsd-read-injection-scanner.js'), matcher: 'ReadFile', timeout: 5 },
2474
2528
  { event: 'PostToolUse', command: cmd('gsd-graphify-update.sh'), matcher: 'Shell', timeout: 5 },
@@ -2541,6 +2595,7 @@ function stripKimiHooksTomlBlock(content) {
2541
2595
  function writeKimiHooksToml(configPath, targetDir, opts) {
2542
2596
  const configuredEntrypoints = [];
2543
2597
  const trackedOpts = {
2598
+ contextWarnings: opts.contextWarnings,
2544
2599
  hookOpts: {
2545
2600
  ...opts.hookOpts,
2546
2601
  configPath,
@@ -36,6 +36,7 @@ exports.resolveExplicitRuntime = resolveExplicitRuntime;
36
36
  exports.resolveRuntime = resolveRuntime;
37
37
  exports.resolveActiveRuntime = resolveActiveRuntime;
38
38
  exports.formatGsdSlashFor = formatGsdSlashFor;
39
+ exports.runtimeCommand = runtimeCommand;
39
40
  const node_fs_1 = __importDefault(require("node:fs"));
40
41
  const node_path_1 = __importDefault(require("node:path"));
41
42
  const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
@@ -227,3 +228,15 @@ function resolveActiveRuntime(config, env = process.env) {
227
228
  function formatGsdSlashFor(projectDir, commandName) {
228
229
  return formatGsdSlash(commandName, resolveRuntime(projectDir));
229
230
  }
231
+ /**
232
+ * #4859: the command as the project's runtime registers it, typed as a string —
233
+ * for runtime code that builds user-facing guidance. String literals in compiled
234
+ * runtime code are printed verbatim (the install-time conversion only rewrites
235
+ * markdown artifact bodies), so a hard-coded `/gsd:<cmd>` names a command no
236
+ * install route registers. Route every such reference through here:
237
+ * `runtimeCommand(cwd, 'plan-phase 3 --research')` → `/gsd-plan-phase 3 --research`
238
+ * (`$gsd-plan-phase …` on Codex). `commandName` is the bare name plus any argument tail.
239
+ */
240
+ function runtimeCommand(projectDir, commandName) {
241
+ return String(formatGsdSlashFor(projectDir, commandName));
242
+ }
@@ -1131,6 +1131,8 @@ function _normalizeMd(content) {
1131
1131
  }
1132
1132
  function normalizeContent(filePath, content, opts = {}) {
1133
1133
  const encoding = opts.encoding ?? 'utf-8';
1134
+ if (opts.verbatim === true)
1135
+ return { content: content ?? '', encoding };
1134
1136
  const isMd = node_path_1.default.extname(filePath).toLowerCase() === '.md';
1135
1137
  let normalized;
1136
1138
  if (isMd) {