@opengsd/gsd-core 1.5.0-rc.3 → 1.5.0-rc.4

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 (85) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/gsd-advisor-researcher.md +1 -1
  3. package/agents/gsd-assumptions-analyzer.md +1 -1
  4. package/agents/gsd-code-fixer.md +1 -1
  5. package/agents/gsd-code-reviewer.md +1 -1
  6. package/agents/gsd-codebase-mapper.md +1 -1
  7. package/agents/gsd-debugger.md +1 -1
  8. package/agents/gsd-doc-writer.md +1 -1
  9. package/agents/gsd-eval-auditor.md +1 -1
  10. package/agents/gsd-executor.md +1 -1
  11. package/agents/gsd-integration-checker.md +1 -1
  12. package/agents/gsd-nyquist-auditor.md +1 -0
  13. package/agents/gsd-phase-researcher.md +1 -1
  14. package/agents/gsd-plan-checker.md +1 -1
  15. package/agents/gsd-planner.md +1 -1
  16. package/agents/gsd-project-researcher.md +1 -1
  17. package/agents/gsd-research-synthesizer.md +1 -1
  18. package/agents/gsd-roadmapper.md +55 -2
  19. package/agents/gsd-security-auditor.md +1 -0
  20. package/agents/gsd-ui-auditor.md +1 -1
  21. package/agents/gsd-ui-checker.md +1 -1
  22. package/agents/gsd-ui-researcher.md +1 -1
  23. package/agents/gsd-verifier.md +13 -2
  24. package/bin/install.js +36 -57
  25. package/commands/gsd/progress.md +2 -1
  26. package/gemini-extension.json +1 -1
  27. package/gsd-core/bin/gsd-tools.cjs +167 -3
  28. package/gsd-core/bin/lib/active-workstream-store.cjs +6 -0
  29. package/gsd-core/bin/lib/capability-state.cjs +97 -3
  30. package/gsd-core/bin/lib/capability-writer.cjs +354 -0
  31. package/gsd-core/bin/lib/config.cjs +80 -24
  32. package/gsd-core/bin/lib/edge-probe.cjs +25 -2
  33. package/gsd-core/bin/lib/frontmatter.cjs +53 -1
  34. package/gsd-core/bin/lib/git-base-branch.cjs +194 -0
  35. package/gsd-core/bin/lib/init.cjs +28 -6
  36. package/gsd-core/bin/lib/install-profiles.cjs +55 -0
  37. package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
  38. package/gsd-core/bin/lib/phase.cjs +28 -12
  39. package/gsd-core/bin/lib/plan-drift-guard.cjs +117 -0
  40. package/gsd-core/bin/lib/probe-core.cjs +117 -1
  41. package/gsd-core/bin/lib/roadmap-parser.cjs +13 -3
  42. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +246 -0
  43. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +34 -2
  44. package/gsd-core/bin/lib/state.cjs +240 -59
  45. package/gsd-core/bin/lib/verify.cjs +73 -4
  46. package/gsd-core/bin/lib/worktree-safety.cjs +2 -1
  47. package/gsd-core/references/edge-probe.md +11 -0
  48. package/gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json +14 -0
  49. package/gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json +4 -0
  50. package/gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json +32 -0
  51. package/gsd-core/references/prohibition-probe.md +248 -0
  52. package/gsd-core/templates/spec.md +14 -0
  53. package/gsd-core/workflows/complete-milestone.md +1 -5
  54. package/gsd-core/workflows/execute-phase.md +4 -3
  55. package/gsd-core/workflows/execute-plan.md +21 -6
  56. package/gsd-core/workflows/help/modes/full.md +4 -0
  57. package/gsd-core/workflows/next.md +50 -2
  58. package/gsd-core/workflows/pause-work.md +7 -1
  59. package/gsd-core/workflows/plan-phase.md +2 -0
  60. package/gsd-core/workflows/plan-review-convergence.md +14 -4
  61. package/gsd-core/workflows/pr-branch.md +4 -2
  62. package/gsd-core/workflows/quick.md +3 -2
  63. package/gsd-core/workflows/resume-project.md +17 -1
  64. package/gsd-core/workflows/settings.md +27 -1
  65. package/gsd-core/workflows/ship.md +1 -5
  66. package/gsd-core/workflows/spec-phase.md +75 -0
  67. package/gsd-core/workflows/verify-phase.md +14 -4
  68. package/hooks/dist/gsd-ensure-canonical-path.js +305 -0
  69. package/hooks/dist/gsd-statusline.js +1 -1
  70. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  71. package/hooks/gsd-ensure-canonical-path.js +305 -0
  72. package/hooks/gsd-statusline.js +1 -1
  73. package/hooks/hooks.json +1 -0
  74. package/hooks/managed-hooks-registry.cjs +1 -0
  75. package/package.json +3 -3
  76. package/scripts/build-hooks.js +7 -0
  77. package/scripts/changeset/new.cjs +17 -3
  78. package/scripts/fix-slash-commands.cjs +15 -3
  79. package/scripts/gen-capability-registry.cjs +14 -1
  80. package/scripts/lint-allow-test-rule-refs.allowlist.json +2 -1
  81. package/scripts/lint-test-file-count.allowlist.json +6 -0
  82. package/scripts/mutation-matrix.cjs +108 -7
  83. package/scripts/pr-target-policy.cjs +63 -0
  84. package/scripts/research-profiles.cjs +5 -5
  85. package/scripts/run-tests.cjs +107 -6
@@ -319,8 +319,17 @@ function stripFencedLines(text) {
319
319
  /**
320
320
  * Returns a filter function that checks whether a phase directory belongs
321
321
  * to the current milestone based on ROADMAP.md phase headings.
322
+ *
323
+ * @param cwd - Project working directory.
324
+ * @param versionOverride - Optional version string to scope the phase filter
325
+ * to a specific milestone (e.g. 'v1.2').
326
+ * @param phaseIdConvention - The resolved `phase_id_convention` config value.
327
+ * When `'milestone-prefixed'`, a deprecation warning is emitted for
328
+ * free-form ROADMAPs that lack versioned milestone headings. When absent or
329
+ * any other value, the warning is suppressed — legacy/default projects must
330
+ * never see spurious warnings.
322
331
  */
323
- function getMilestonePhaseFilter(cwd, versionOverride) {
332
+ function getMilestonePhaseFilter(cwd, versionOverride, phaseIdConvention) {
324
333
  const milestonePhaseNums = new Set();
325
334
  let missingExplicitVersion = false;
326
335
  try {
@@ -331,9 +340,10 @@ function getMilestonePhaseFilter(cwd, versionOverride) {
331
340
  let roadmap = extractCurrentMilestone(roadmapContent, cwd);
332
341
  const hasVersionedMilestonesGlobal = /^#{1,3}\s+.*v\d+\.\d+/mi.test(roadmapContent);
333
342
  const hasPhaseHeadings = /#{2,4}\s*(?:\[[^\]]+\]\s*)?Phase\s+[\w]/i.test(roadmapContent);
334
- if (!hasVersionedMilestonesGlobal && hasPhaseHeadings) {
343
+ if (!hasVersionedMilestonesGlobal && hasPhaseHeadings && phaseIdConvention === 'milestone-prefixed') {
335
344
  console.warn('[gsd] Deprecated: free-form ROADMAP.md detected (no versioned milestone headings). ' +
336
- 'Set phase_id_convention in config.json to suppress this warning.');
345
+ 'The project has phase_id_convention set to "milestone-prefixed" in config.json but the ' +
346
+ 'ROADMAP does not use versioned milestone headings. Run `gsd-tools roadmap upgrade --convention milestone-prefixed` to migrate (dry-run by default).');
337
347
  }
338
348
  if (versionOverride) {
339
349
  const escapedVersion = escapeRegex(versionOverride);
@@ -1607,6 +1607,238 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false } = {}) {
1607
1607
  const newFrontmatter = newLines.join('\n').trim();
1608
1608
  return `---\n${newFrontmatter}\n---${body}`;
1609
1609
  }
1610
+ // ── Agent converters — #1182 extraction ─────────────────────────────────────
1611
+ // These were previously only in bin/install.js. Extracted here so the module
1612
+ // is self-contained and #1173 descriptor-driven dispatch can call them without
1613
+ // reaching through the Installer Module.
1614
+ // NOTE: Do NOT remove the inline copies from bin/install.js in this PR —
1615
+ // that is #1175. This PR only adds them to the module's export surface.
1616
+ // Copilot tool name mapping — Claude Code tools to GitHub Copilot tools
1617
+ // Tool mapping applies ONLY to agents, NOT to skills (per CONTEXT.md decision)
1618
+ const claudeToCopilotTools = {
1619
+ Read: 'read',
1620
+ Write: 'edit',
1621
+ Edit: 'edit',
1622
+ Bash: 'execute',
1623
+ Grep: 'search',
1624
+ Glob: 'search',
1625
+ Task: 'agent',
1626
+ WebSearch: 'web',
1627
+ WebFetch: 'web',
1628
+ TodoWrite: 'todo',
1629
+ AskUserQuestion: 'ask_user',
1630
+ SlashCommand: 'skill',
1631
+ };
1632
+ // Tool name mapping from Claude Code to Gemini CLI
1633
+ // Gemini CLI uses snake_case built-in tool names
1634
+ const claudeToGeminiTools = {
1635
+ Read: 'read_file',
1636
+ Write: 'write_file',
1637
+ Edit: 'replace',
1638
+ Bash: 'run_shell_command',
1639
+ Glob: 'glob',
1640
+ Grep: 'search_file_content',
1641
+ WebSearch: 'google_web_search',
1642
+ WebFetch: 'web_fetch',
1643
+ TodoWrite: 'write_todos',
1644
+ };
1645
+ /**
1646
+ * Convert a Claude Code tool name to Gemini CLI format
1647
+ * - Applies Claude→Gemini mapping (Read→read_file, Bash→run_shell_command, etc.)
1648
+ * - Filters out MCP tools (mcp__*) — they are auto-discovered at runtime in Gemini
1649
+ * - Filters out Task/Agent — agents are auto-registered as tools in Gemini
1650
+ * @returns {string|null} Gemini tool name, or null if tool should be excluded
1651
+ */
1652
+ function convertGeminiToolName(claudeTool) {
1653
+ // MCP tools: exclude — auto-discovered from mcpServers config at runtime
1654
+ if (claudeTool.startsWith('mcp__')) {
1655
+ return null;
1656
+ }
1657
+ // Task/Agent: exclude — agents are auto-registered as callable tools.
1658
+ // AskUserQuestion: exclude — Gemini CLI does not expose an ask_user tool;
1659
+ // emitting it causes frontmatter validation errors (#3362).
1660
+ if (claudeTool === 'Task' ||
1661
+ claudeTool === 'Agent' ||
1662
+ claudeTool === 'AskUserQuestion' ||
1663
+ claudeTool === 'ask_user') {
1664
+ return null;
1665
+ }
1666
+ // Check for explicit mapping
1667
+ if (claudeToGeminiTools[claudeTool]) {
1668
+ return claudeToGeminiTools[claudeTool];
1669
+ }
1670
+ // Default: lowercase
1671
+ return claudeTool.toLowerCase();
1672
+ }
1673
+ /**
1674
+ * Convert a Claude Code tool name to GitHub Copilot format.
1675
+ * - Applies explicit mapping from claudeToCopilotTools
1676
+ * - Handles mcp__context7__* prefix → io.github.upstash/context7/*
1677
+ * - Falls back to lowercase for unknown tools
1678
+ */
1679
+ function convertCopilotToolName(claudeTool) {
1680
+ // mcp__context7__* wildcard → io.github.upstash/context7/*
1681
+ if (claudeTool.startsWith('mcp__context7__')) {
1682
+ return 'io.github.upstash/context7/' + claudeTool.slice('mcp__context7__'.length);
1683
+ }
1684
+ // Check explicit mapping
1685
+ if (claudeToCopilotTools[claudeTool]) {
1686
+ return claudeToCopilotTools[claudeTool];
1687
+ }
1688
+ // mcp__{tavily,ref,jina,exa,firecrawl}__* use the generic MCP passthrough like exa/firecrawl;
1689
+ // add explicit Copilot registry mappings when the io.github ids are confirmed (#657 follow-up)
1690
+ // Default: lowercase
1691
+ return claudeTool.toLowerCase();
1692
+ }
1693
+ /**
1694
+ * Convert a Claude agent (.md) to a GitHub Copilot agent.
1695
+ * CONV-04: JSON array format. CONV-05: Tool name mapping.
1696
+ */
1697
+ function convertClaudeAgentToCopilotAgent(content, isGlobal = false) {
1698
+ const converted = convertClaudeToCopilotContent(content, isGlobal);
1699
+ const { frontmatter, body } = extractFrontmatterAndBody(converted);
1700
+ if (!frontmatter)
1701
+ return converted;
1702
+ const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
1703
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
1704
+ const color = extractFrontmatterField(frontmatter, 'color');
1705
+ const toolsRaw = extractFrontmatterField(frontmatter, 'tools') || '';
1706
+ // CONV-04 + CONV-05: Map tools, deduplicate, format as JSON array
1707
+ const claudeTools = toolsRaw.split(',').map(t => t.trim()).filter(Boolean);
1708
+ const mappedTools = claudeTools.map(t => convertCopilotToolName(t));
1709
+ const uniqueTools = [...new Set(mappedTools)];
1710
+ const toolsArray = uniqueTools.length > 0
1711
+ ? "['" + uniqueTools.join("', '") + "']"
1712
+ : '[]';
1713
+ // Reconstruct frontmatter in Copilot format. Quote description (#2876)
1714
+ // so a leading YAML flow indicator (`[BETA] …`, `{ … }`, etc.) doesn't
1715
+ // crash the Copilot frontmatter loader.
1716
+ let fm = `---\nname: ${name}\ndescription: ${yamlQuote(description)}\ntools: ${toolsArray}\n`;
1717
+ if (color)
1718
+ fm += `color: ${color}\n`;
1719
+ fm += '---';
1720
+ return `${fm}\n${body}`;
1721
+ }
1722
+ /**
1723
+ * Convert a Claude agent (.md) to an Antigravity agent.
1724
+ * Uses Gemini tool names since Antigravity runs on Gemini 3 backend.
1725
+ */
1726
+ function convertClaudeAgentToAntigravityAgent(content, isGlobal = false) {
1727
+ const converted = convertClaudeToAntigravityContent(content, isGlobal);
1728
+ const { frontmatter, body } = extractFrontmatterAndBody(converted);
1729
+ if (!frontmatter)
1730
+ return converted;
1731
+ const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
1732
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
1733
+ const color = extractFrontmatterField(frontmatter, 'color');
1734
+ const toolsRaw = extractFrontmatterField(frontmatter, 'tools') || '';
1735
+ // Map tools to Gemini equivalents (reuse existing convertGeminiToolName)
1736
+ const claudeTools = toolsRaw.split(',').map(t => t.trim()).filter(Boolean);
1737
+ const mappedTools = claudeTools.map(t => convertGeminiToolName(t)).filter(Boolean);
1738
+ // #2876: quote description for the same reason as the skill variant.
1739
+ let fm = `---\nname: ${name}\ndescription: ${yamlQuote(description)}\ntools: ${mappedTools.join(', ')}\n`;
1740
+ if (color)
1741
+ fm += `color: ${color}\n`;
1742
+ fm += '---';
1743
+ return `${fm}\n${body}`;
1744
+ }
1745
+ /**
1746
+ * Convert Claude Code agent markdown to Cursor agent format.
1747
+ * Strips frontmatter fields Cursor doesn't support (color, skills),
1748
+ * converts tool references, and adds a role context header.
1749
+ */
1750
+ function convertClaudeAgentToCursorAgent(content) {
1751
+ const converted = convertClaudeToCursorMarkdown(content);
1752
+ const { frontmatter, body } = extractFrontmatterAndBody(converted);
1753
+ if (!frontmatter)
1754
+ return converted;
1755
+ const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
1756
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
1757
+ const cleanFrontmatter = `---\nname: ${yamlIdentifier(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
1758
+ return `${cleanFrontmatter}\n${body}`;
1759
+ }
1760
+ /**
1761
+ * Convert Claude Code agent markdown to Windsurf agent format.
1762
+ * Strips frontmatter fields Windsurf doesn't support (color, skills),
1763
+ * converts tool references, and adds a role context header.
1764
+ */
1765
+ function convertClaudeAgentToWindsurfAgent(content) {
1766
+ const converted = convertClaudeToWindsurfMarkdown(content);
1767
+ const { frontmatter, body } = extractFrontmatterAndBody(converted);
1768
+ if (!frontmatter)
1769
+ return converted;
1770
+ const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
1771
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
1772
+ const cleanFrontmatter = `---\nname: ${yamlIdentifier(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
1773
+ return `${cleanFrontmatter}\n${body}`;
1774
+ }
1775
+ /**
1776
+ * Convert Claude Code agent markdown to Augment agent format.
1777
+ * Strips frontmatter fields Augment doesn't support (color, skills),
1778
+ * converts tool references, and cleans up for Augment agents.
1779
+ */
1780
+ function convertClaudeAgentToAugmentAgent(content) {
1781
+ const converted = convertClaudeToAugmentMarkdown(content);
1782
+ const { frontmatter, body } = extractFrontmatterAndBody(converted);
1783
+ if (!frontmatter)
1784
+ return converted;
1785
+ const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
1786
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
1787
+ const cleanFrontmatter = `---\nname: ${yamlIdentifier(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
1788
+ return `${cleanFrontmatter}\n${body}`;
1789
+ }
1790
+ function convertClaudeAgentToTraeAgent(content) {
1791
+ const converted = convertClaudeToTraeMarkdown(content);
1792
+ const { frontmatter, body } = extractFrontmatterAndBody(converted);
1793
+ if (!frontmatter)
1794
+ return converted;
1795
+ const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
1796
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
1797
+ const cleanFrontmatter = `---\nname: ${yamlIdentifier(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
1798
+ return `${cleanFrontmatter}\n${body}`;
1799
+ }
1800
+ function convertClaudeAgentToCodebuddyAgent(content) {
1801
+ const converted = convertClaudeToCodebuddyMarkdown(content);
1802
+ const { frontmatter, body } = extractFrontmatterAndBody(converted);
1803
+ if (!frontmatter)
1804
+ return converted;
1805
+ const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
1806
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
1807
+ const cleanFrontmatter = `---\nname: ${yamlIdentifier(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
1808
+ return `${cleanFrontmatter}\n${body}`;
1809
+ }
1810
+ function convertClaudeAgentToClineAgent(content) {
1811
+ const converted = convertClaudeToCliineMarkdown(content);
1812
+ const { frontmatter, body } = extractFrontmatterAndBody(converted);
1813
+ if (!frontmatter)
1814
+ return converted;
1815
+ const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
1816
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
1817
+ const cleanFrontmatter = `---\nname: ${yamlIdentifier(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
1818
+ return `${cleanFrontmatter}\n${body}`;
1819
+ }
1820
+ /**
1821
+ * Convert Claude Code agent markdown to Codex agent format.
1822
+ * Applies base markdown conversions, then adds a <codex_agent_role> header
1823
+ * and cleans up frontmatter (removes tools/color fields).
1824
+ */
1825
+ function convertClaudeAgentToCodexAgent(content) {
1826
+ const converted = convertClaudeToCodexMarkdown(content);
1827
+ const { frontmatter, body } = extractFrontmatterAndBody(converted);
1828
+ if (!frontmatter)
1829
+ return converted;
1830
+ const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
1831
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
1832
+ const tools = extractFrontmatterField(frontmatter, 'tools') || '';
1833
+ const roleHeader = `<codex_agent_role>
1834
+ role: ${name}
1835
+ tools: ${tools}
1836
+ purpose: ${toSingleLine(description)}
1837
+ </codex_agent_role>`;
1838
+ const cleanFrontmatter = `---\nname: ${yamlQuote(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
1839
+ return `${cleanFrontmatter}\n\n${roleHeader}\n${body}`;
1840
+ }
1841
+ // ── End agent converters #1182 ───────────────────────────────────────────────
1610
1842
  /**
1611
1843
  * Shared SKILL.md writer for the OpenCode-family runtimes (OpenCode + Kilo),
1612
1844
  * which share a config schema (Kilo derives from OpenCode). OpenCode discovers
@@ -1697,4 +1929,18 @@ module.exports = {
1697
1929
  convertClaudeCommandToKiloSkill,
1698
1930
  readGsdCommandNames,
1699
1931
  transformContentToHyphen,
1932
+ // #1182: agent converters + tool-name table dependency closure
1933
+ claudeToCopilotTools,
1934
+ convertCopilotToolName,
1935
+ claudeToGeminiTools,
1936
+ convertGeminiToolName,
1937
+ convertClaudeAgentToCopilotAgent,
1938
+ convertClaudeAgentToAntigravityAgent,
1939
+ convertClaudeAgentToCursorAgent,
1940
+ convertClaudeAgentToWindsurfAgent,
1941
+ convertClaudeAgentToAugmentAgent,
1942
+ convertClaudeAgentToTraeAgent,
1943
+ convertClaudeAgentToCodebuddyAgent,
1944
+ convertClaudeAgentToClineAgent,
1945
+ convertClaudeAgentToCodexAgent,
1700
1946
  };
@@ -19,7 +19,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
19
19
  const node_os_1 = __importDefault(require("node:os"));
20
20
  // eslint-disable-next-line @typescript-eslint/no-require-imports
21
21
  const installProfiles = require("./install-profiles.cjs");
22
- const { stageSkillsForProfile, stageAgentsForProfile, stageSkillsForRuntimeAsSkills, stageCommandsForRuntimeFlat, } = installProfiles;
22
+ const { stageSkillsForProfile, stageAgentsForProfile, stageAgentsForRuntimeWithConverter, stageSkillsForRuntimeAsSkills, stageCommandsForRuntimeFlat, } = installProfiles;
23
23
  // eslint-disable-next-line @typescript-eslint/no-require-imports
24
24
  const runtimeArtifactConversion = require("./runtime-artifact-conversion.cjs");
25
25
  const conversionExports = runtimeArtifactConversion;
@@ -146,6 +146,35 @@ function agentsKind(destSubpath, prefix, configDir) {
146
146
  stage: (resolved) => stageAgentsForProfile(findAgentsSourceRoot(configDir), resolved),
147
147
  };
148
148
  }
149
+ /**
150
+ * Build a converted-agents kind descriptor for runtimes whose agent `.md` files
151
+ * need runtime-specific frontmatter/body conversion (e.g. Copilot, Cursor, Codex).
152
+ *
153
+ * Unlike `agentsKind` (which raw-copies source files), this kind applies
154
+ * `converterName` from Runtime Artifact Conversion exports to each agent file
155
+ * during staging, writing flat `${name}.md` files to the staged directory.
156
+ *
157
+ * Agent filenames are preserved verbatim (the prefix is already embedded in the
158
+ * agent stem — e.g. `gsd-planner.md`).
159
+ *
160
+ * Mirrors the `convertedCommandsKind` pattern (#785).
161
+ *
162
+ * @param destSubpath destination subpath within configDir (e.g. 'agents')
163
+ * @param prefix filename prefix (informational; not applied here)
164
+ * @param converterName name of converter function in Runtime Artifact Conversion exports
165
+ * @param configDir runtime config dir (for .gsd-source marker resolution)
166
+ */
167
+ function convertedAgentsKind(destSubpath, prefix, converterName, configDir) {
168
+ return {
169
+ kind: 'agents',
170
+ destSubpath,
171
+ prefix,
172
+ stage: (resolved) => {
173
+ const converter = conversionExports[converterName];
174
+ return stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, converter);
175
+ },
176
+ };
177
+ }
149
178
  function kimiAgentsKind(destSubpath, prefix, configDir) {
150
179
  return {
151
180
  kind: 'kimi-agents',
@@ -262,7 +291,10 @@ function dispatchKindEntry(entry, runtime, configDir, scope) {
262
291
  }
263
292
  return convertedCommandsKind(destSubpath, prefix, converter, configDir);
264
293
  case 'agents':
265
- return agentsKind(destSubpath, prefix, configDir);
294
+ if (converter == null) {
295
+ return agentsKind(destSubpath, prefix, configDir);
296
+ }
297
+ return convertedAgentsKind(destSubpath, prefix, converter, configDir);
266
298
  case 'skills':
267
299
  if (converter == null) {
268
300
  throw new TypeError(`resolveRuntimeArtifactLayout: skills entry for '${runtime}' has converter=null (converter is required for skills)`);