hatch3r 1.0.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 (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +437 -0
  3. package/agents/hatch3r-a11y-auditor.md +126 -0
  4. package/agents/hatch3r-architect.md +160 -0
  5. package/agents/hatch3r-ci-watcher.md +123 -0
  6. package/agents/hatch3r-context-rules.md +97 -0
  7. package/agents/hatch3r-dependency-auditor.md +164 -0
  8. package/agents/hatch3r-devops.md +138 -0
  9. package/agents/hatch3r-docs-writer.md +97 -0
  10. package/agents/hatch3r-implementer.md +162 -0
  11. package/agents/hatch3r-learnings-loader.md +108 -0
  12. package/agents/hatch3r-lint-fixer.md +104 -0
  13. package/agents/hatch3r-perf-profiler.md +123 -0
  14. package/agents/hatch3r-researcher.md +642 -0
  15. package/agents/hatch3r-reviewer.md +81 -0
  16. package/agents/hatch3r-security-auditor.md +119 -0
  17. package/agents/hatch3r-test-writer.md +134 -0
  18. package/commands/hatch3r-agent-customize.md +146 -0
  19. package/commands/hatch3r-api-spec.md +49 -0
  20. package/commands/hatch3r-benchmark.md +50 -0
  21. package/commands/hatch3r-board-fill.md +504 -0
  22. package/commands/hatch3r-board-init.md +315 -0
  23. package/commands/hatch3r-board-pickup.md +672 -0
  24. package/commands/hatch3r-board-refresh.md +198 -0
  25. package/commands/hatch3r-board-shared.md +369 -0
  26. package/commands/hatch3r-bug-plan.md +410 -0
  27. package/commands/hatch3r-codebase-map.md +1182 -0
  28. package/commands/hatch3r-command-customize.md +94 -0
  29. package/commands/hatch3r-context-health.md +112 -0
  30. package/commands/hatch3r-cost-tracking.md +139 -0
  31. package/commands/hatch3r-dep-audit.md +171 -0
  32. package/commands/hatch3r-feature-plan.md +379 -0
  33. package/commands/hatch3r-healthcheck.md +307 -0
  34. package/commands/hatch3r-hooks.md +282 -0
  35. package/commands/hatch3r-learn.md +217 -0
  36. package/commands/hatch3r-migration-plan.md +51 -0
  37. package/commands/hatch3r-onboard.md +56 -0
  38. package/commands/hatch3r-project-spec.md +1153 -0
  39. package/commands/hatch3r-recipe.md +179 -0
  40. package/commands/hatch3r-refactor-plan.md +426 -0
  41. package/commands/hatch3r-release.md +328 -0
  42. package/commands/hatch3r-roadmap.md +556 -0
  43. package/commands/hatch3r-rule-customize.md +114 -0
  44. package/commands/hatch3r-security-audit.md +370 -0
  45. package/commands/hatch3r-skill-customize.md +93 -0
  46. package/commands/hatch3r-workflow.md +377 -0
  47. package/dist/cli/hooks-ZOTFDEA3.js +59 -0
  48. package/dist/cli/index.d.ts +2 -0
  49. package/dist/cli/index.js +3584 -0
  50. package/github-agents/hatch3r-docs-agent.md +46 -0
  51. package/github-agents/hatch3r-lint-agent.md +41 -0
  52. package/github-agents/hatch3r-security-agent.md +54 -0
  53. package/github-agents/hatch3r-test-agent.md +66 -0
  54. package/hooks/hatch3r-ci-failure.md +10 -0
  55. package/hooks/hatch3r-file-save.md +11 -0
  56. package/hooks/hatch3r-post-merge.md +10 -0
  57. package/hooks/hatch3r-pre-commit.md +11 -0
  58. package/hooks/hatch3r-pre-push.md +10 -0
  59. package/hooks/hatch3r-session-start.md +10 -0
  60. package/mcp/mcp.json +62 -0
  61. package/package.json +84 -0
  62. package/prompts/hatch3r-bug-triage.md +155 -0
  63. package/prompts/hatch3r-code-review.md +131 -0
  64. package/prompts/hatch3r-pr-description.md +173 -0
  65. package/rules/hatch3r-accessibility-standards.md +77 -0
  66. package/rules/hatch3r-accessibility-standards.mdc +75 -0
  67. package/rules/hatch3r-agent-orchestration.md +160 -0
  68. package/rules/hatch3r-api-design.md +176 -0
  69. package/rules/hatch3r-api-design.mdc +176 -0
  70. package/rules/hatch3r-browser-verification.md +73 -0
  71. package/rules/hatch3r-browser-verification.mdc +73 -0
  72. package/rules/hatch3r-ci-cd.md +70 -0
  73. package/rules/hatch3r-ci-cd.mdc +68 -0
  74. package/rules/hatch3r-code-standards.md +102 -0
  75. package/rules/hatch3r-code-standards.mdc +100 -0
  76. package/rules/hatch3r-component-conventions.md +102 -0
  77. package/rules/hatch3r-component-conventions.mdc +102 -0
  78. package/rules/hatch3r-data-classification.md +85 -0
  79. package/rules/hatch3r-data-classification.mdc +83 -0
  80. package/rules/hatch3r-dependency-management.md +17 -0
  81. package/rules/hatch3r-dependency-management.mdc +15 -0
  82. package/rules/hatch3r-error-handling.md +17 -0
  83. package/rules/hatch3r-error-handling.mdc +15 -0
  84. package/rules/hatch3r-feature-flags.md +112 -0
  85. package/rules/hatch3r-feature-flags.mdc +112 -0
  86. package/rules/hatch3r-git-conventions.md +47 -0
  87. package/rules/hatch3r-git-conventions.mdc +45 -0
  88. package/rules/hatch3r-i18n.md +90 -0
  89. package/rules/hatch3r-i18n.mdc +90 -0
  90. package/rules/hatch3r-learning-consult.md +29 -0
  91. package/rules/hatch3r-learning-consult.mdc +27 -0
  92. package/rules/hatch3r-migrations.md +17 -0
  93. package/rules/hatch3r-migrations.mdc +15 -0
  94. package/rules/hatch3r-observability.md +165 -0
  95. package/rules/hatch3r-observability.mdc +165 -0
  96. package/rules/hatch3r-performance-budgets.md +109 -0
  97. package/rules/hatch3r-performance-budgets.mdc +109 -0
  98. package/rules/hatch3r-secrets-management.md +76 -0
  99. package/rules/hatch3r-secrets-management.mdc +74 -0
  100. package/rules/hatch3r-security-patterns.md +211 -0
  101. package/rules/hatch3r-security-patterns.mdc +211 -0
  102. package/rules/hatch3r-testing.md +89 -0
  103. package/rules/hatch3r-testing.mdc +87 -0
  104. package/rules/hatch3r-theming.md +51 -0
  105. package/rules/hatch3r-theming.mdc +51 -0
  106. package/rules/hatch3r-tooling-hierarchy.md +92 -0
  107. package/rules/hatch3r-tooling-hierarchy.mdc +79 -0
  108. package/skills/hatch3r-a11y-audit/SKILL.md +131 -0
  109. package/skills/hatch3r-agent-customize/SKILL.md +75 -0
  110. package/skills/hatch3r-api-spec/SKILL.md +66 -0
  111. package/skills/hatch3r-architecture-review/SKILL.md +96 -0
  112. package/skills/hatch3r-bug-fix/SKILL.md +129 -0
  113. package/skills/hatch3r-ci-pipeline/SKILL.md +76 -0
  114. package/skills/hatch3r-command-customize/SKILL.md +67 -0
  115. package/skills/hatch3r-context-health/SKILL.md +76 -0
  116. package/skills/hatch3r-cost-tracking/SKILL.md +65 -0
  117. package/skills/hatch3r-dep-audit/SKILL.md +82 -0
  118. package/skills/hatch3r-feature/SKILL.md +129 -0
  119. package/skills/hatch3r-gh-agentic-workflows/SKILL.md +150 -0
  120. package/skills/hatch3r-incident-response/SKILL.md +86 -0
  121. package/skills/hatch3r-issue-workflow/SKILL.md +139 -0
  122. package/skills/hatch3r-logical-refactor/SKILL.md +73 -0
  123. package/skills/hatch3r-migration/SKILL.md +76 -0
  124. package/skills/hatch3r-perf-audit/SKILL.md +114 -0
  125. package/skills/hatch3r-pr-creation/SKILL.md +85 -0
  126. package/skills/hatch3r-qa-validation/SKILL.md +86 -0
  127. package/skills/hatch3r-recipe/SKILL.md +67 -0
  128. package/skills/hatch3r-refactor/SKILL.md +86 -0
  129. package/skills/hatch3r-release/SKILL.md +93 -0
  130. package/skills/hatch3r-rule-customize/SKILL.md +70 -0
  131. package/skills/hatch3r-skill-customize/SKILL.md +67 -0
  132. package/skills/hatch3r-visual-refactor/SKILL.md +89 -0
@@ -0,0 +1,3584 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/cli/commands/add.ts
7
+ import chalk2 from "chalk";
8
+
9
+ // src/cli/shared/ui.ts
10
+ import chalk from "chalk";
11
+ import ora from "ora";
12
+ import boxen from "boxen";
13
+
14
+ // src/version.ts
15
+ var HATCH3R_VERSION = "1.0.0";
16
+
17
+ // src/cli/shared/ui.ts
18
+ var CYAN = chalk.hex("#06b6d4");
19
+ var DIM_CYAN = chalk.hex("#67e8f9");
20
+ var SHADOW_CHARS = new Set("\u2554\u2550\u2557\u255A\u255D\u2551");
21
+ function gradient(text, from, to) {
22
+ const chars = [...text];
23
+ const len = chars.filter((c) => c !== " ").length;
24
+ let idx = 0;
25
+ return chars.map((c) => {
26
+ if (c === " ") return c;
27
+ const t = len > 1 ? idx / (len - 1) : 0;
28
+ idx++;
29
+ const r = Math.round(from[0] + (to[0] - from[0]) * t);
30
+ const g = Math.round(from[1] + (to[1] - from[1]) * t);
31
+ const b = Math.round(from[2] + (to[2] - from[2]) * t);
32
+ if (SHADOW_CHARS.has(c)) {
33
+ const DIM = 0.55;
34
+ return chalk.rgb(
35
+ Math.round(r * DIM),
36
+ Math.round(g * DIM),
37
+ Math.round(b * DIM)
38
+ )(c);
39
+ }
40
+ return chalk.rgb(r, g, b).bold(c);
41
+ }).join("");
42
+ }
43
+ var LOGO = [
44
+ "\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
45
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
46
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
47
+ "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
48
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551",
49
+ "\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D"
50
+ ].map((row) => gradient(row, [6, 182, 212], [20, 184, 166]));
51
+ function buildBanner() {
52
+ const lines = [""];
53
+ for (const row of LOGO) {
54
+ lines.push(` ${row}`);
55
+ }
56
+ lines.push(` ${DIM_CYAN("Crack the egg. Hatch better agents.")}`);
57
+ lines.push(` ${chalk.dim(`v${HATCH3R_VERSION}`)}`);
58
+ lines.push("");
59
+ return lines;
60
+ }
61
+ var BANNER_LINES = buildBanner();
62
+ function printBanner(compact = false) {
63
+ if (compact) {
64
+ console.log(
65
+ `
66
+ ${CYAN.bold("hatch3r")} ${chalk.dim(`v${HATCH3R_VERSION}`)}
67
+ `
68
+ );
69
+ return;
70
+ }
71
+ for (const line of BANNER_LINES) {
72
+ console.log(line);
73
+ }
74
+ }
75
+ function createSpinner(text) {
76
+ return ora({
77
+ text,
78
+ color: "cyan",
79
+ spinner: "dots",
80
+ indent: 2
81
+ });
82
+ }
83
+ function printBox(title, lines, style = "info") {
84
+ const colors = {
85
+ success: "#10b981",
86
+ info: "#06b6d4",
87
+ error: "#ef4444"
88
+ };
89
+ const content = lines.join("\n");
90
+ console.log(
91
+ boxen(content, {
92
+ title,
93
+ titleAlignment: "left",
94
+ padding: { top: 0, bottom: 0, left: 1, right: 1 },
95
+ margin: { top: 0, bottom: 1, left: 1, right: 0 },
96
+ borderColor: colors[style],
97
+ borderStyle: "round",
98
+ dimBorder: style === "info"
99
+ })
100
+ );
101
+ }
102
+ function error(msg) {
103
+ console.log(` ${chalk.red("\u2716")} ${msg}`);
104
+ }
105
+ function warn(msg) {
106
+ console.log(` ${chalk.yellow("\u26A0")} ${msg}`);
107
+ }
108
+ function info(msg) {
109
+ console.log(` ${CYAN("\u2139")} ${msg}`);
110
+ }
111
+ function step(n, total, msg) {
112
+ return `${chalk.dim(`[${n}/${total}]`)} ${msg}`;
113
+ }
114
+ function label(name, value) {
115
+ return `${chalk.dim(name.padEnd(12))} ${value}`;
116
+ }
117
+
118
+ // src/cli/commands/add.ts
119
+ async function addCommand() {
120
+ printBanner(true);
121
+ console.log();
122
+ console.log(chalk2.yellow(" Coming soon!"));
123
+ console.log(chalk2.dim(" The `add` command will allow installing community packs."));
124
+ console.log(chalk2.dim(" Follow https://github.com/hatch3r for updates."));
125
+ console.log();
126
+ }
127
+
128
+ // src/cli/commands/init.ts
129
+ import { access as access3, cp, mkdir as mkdir2, readFile as readFile17, writeFile as writeFile4 } from "fs/promises";
130
+ import { fileURLToPath } from "url";
131
+ import { dirname as dirname16, join as join18 } from "path";
132
+ import { execFileSync } from "child_process";
133
+ import chalk15 from "chalk";
134
+ import inquirer from "inquirer";
135
+
136
+ // src/adapters/aider.ts
137
+ import { dirname } from "path";
138
+
139
+ // src/types.ts
140
+ var MANAGED_BLOCK_START = "<!-- HATCH3R:BEGIN -->";
141
+ var MANAGED_BLOCK_END = "<!-- HATCH3R:END -->";
142
+ var HATCH3R_PREFIX = "hatch3r-";
143
+ var AGENTS_DIR = ".agents";
144
+ function toPrefixedId(id, prefix = HATCH3R_PREFIX) {
145
+ const base = id.replace(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`), "");
146
+ return `${prefix}${base}`;
147
+ }
148
+ var MANIFEST_FILE = "hatch.json";
149
+ var DEFAULT_FEATURES = {
150
+ agents: true,
151
+ skills: true,
152
+ rules: true,
153
+ prompts: true,
154
+ commands: true,
155
+ mcp: true,
156
+ guardrails: false,
157
+ githubAgents: true,
158
+ hooks: true
159
+ };
160
+ var ENV_VAR_HELP = {
161
+ GITHUB_PAT: {
162
+ comment: "GitHub MCP server (classic PAT: repo, read:org \u2014 or fine-grained: Contents/Issues/PRs)",
163
+ url: "https://github.com/settings/tokens/new"
164
+ },
165
+ BRAVE_API_KEY: {
166
+ comment: "Brave Search (free: 2,000 queries/month)",
167
+ url: "https://brave.com/search/api/"
168
+ },
169
+ SENTRY_AUTH_TOKEN: {
170
+ comment: "Sentry error tracking",
171
+ url: "https://sentry.io/settings/account/api/auth-tokens/"
172
+ },
173
+ POSTGRES_URL: {
174
+ comment: "PostgreSQL connection string (e.g. postgresql://user:pass@host:5432/db)",
175
+ url: ""
176
+ },
177
+ LINEAR_API_KEY: {
178
+ comment: "Linear issue tracking",
179
+ url: "https://linear.app/settings/api"
180
+ }
181
+ };
182
+ var AVAILABLE_MCP_SERVERS = {
183
+ github: {
184
+ description: "GitHub repository management, code review, issues, PRs, and project boards",
185
+ requiresEnv: ["GITHUB_PAT"]
186
+ },
187
+ context7: {
188
+ description: "Up-to-date, version-specific library documentation for LLMs"
189
+ },
190
+ filesystem: {
191
+ description: "File management and code editing operations"
192
+ },
193
+ playwright: {
194
+ description: "Browser automation, web testing, and UI interaction"
195
+ },
196
+ "brave-search": {
197
+ description: "Web research, fact-checking, and current information retrieval",
198
+ requiresEnv: ["BRAVE_API_KEY"]
199
+ },
200
+ sentry: {
201
+ description: "Error tracking and performance monitoring (enable and configure with your Sentry auth token)",
202
+ requiresEnv: ["SENTRY_AUTH_TOKEN"]
203
+ },
204
+ postgres: {
205
+ description: "PostgreSQL database queries and schema inspection (enable and configure with your connection string)",
206
+ requiresEnv: ["POSTGRES_URL"]
207
+ },
208
+ linear: {
209
+ description: "Linear issue tracking and project management (enable and configure with your Linear API key)",
210
+ requiresEnv: ["LINEAR_API_KEY"]
211
+ }
212
+ };
213
+
214
+ // src/merge/managedBlocks.ts
215
+ function insertManagedBlock(existingContent, managedContent) {
216
+ const startIdx = existingContent.indexOf(MANAGED_BLOCK_START);
217
+ const endIdx = existingContent.indexOf(MANAGED_BLOCK_END);
218
+ const block = `${MANAGED_BLOCK_START}
219
+ ${managedContent}
220
+ ${MANAGED_BLOCK_END}`;
221
+ if (startIdx === -1 || endIdx === -1) {
222
+ throw new Error("Content must contain managed block markers");
223
+ }
224
+ if (startIdx >= endIdx) {
225
+ throw new Error("Corrupted managed block: start marker must appear before end marker");
226
+ }
227
+ const before = existingContent.substring(0, startIdx);
228
+ const after = existingContent.substring(endIdx + MANAGED_BLOCK_END.length);
229
+ return `${before}${block}${after}`;
230
+ }
231
+ function extractManagedBlock(content) {
232
+ const startIdx = content.indexOf(MANAGED_BLOCK_START);
233
+ const endIdx = content.indexOf(MANAGED_BLOCK_END);
234
+ if (startIdx === -1 || endIdx === -1) {
235
+ return null;
236
+ }
237
+ return content.substring(startIdx + MANAGED_BLOCK_START.length, endIdx).trim();
238
+ }
239
+ function wrapInManagedBlock(content) {
240
+ return `${MANAGED_BLOCK_START}
241
+ ${content}
242
+ ${MANAGED_BLOCK_END}`;
243
+ }
244
+ function hasManagedBlock(content) {
245
+ return content.includes(MANAGED_BLOCK_START) && content.includes(MANAGED_BLOCK_END);
246
+ }
247
+
248
+ // src/cli/shared/agentsContent.ts
249
+ var BRIDGE_ORCHESTRATION = `## Universal Sub-Agent Pipeline
250
+
251
+ Every task \u2014 board-pickup, workflow command, plain chat, single-task, or multi-task \u2014 MUST use this three-phase sub-agent pipeline. There are NO exceptions. Never implement code inline; always delegate to sub-agents.
252
+
253
+ **Phase 1 \u2014 Research:** Spawn \`hatch3r-researcher\` for context gathering. Skip only for trivial single-line edits (typos, comment fixes, single-value config changes).
254
+
255
+ **Phase 2 \u2014 Implement:** Spawn \`hatch3r-implementer\` for ALL code changes. One dedicated implementer per task. Never implement inline \u2014 always delegate.
256
+
257
+ **Phase 3 \u2014 Quality:** Spawn ALL applicable specialists in parallel after implementation:
258
+ - \`hatch3r-reviewer\` \u2014 ALWAYS (mandatory for every task)
259
+ - \`hatch3r-test-writer\` \u2014 ALWAYS for code changes (mandatory, not just bugs)
260
+ - \`hatch3r-security-auditor\` \u2014 ALWAYS for code changes (mandatory, not just area:security)
261
+ - \`hatch3r-docs-writer\` \u2014 ALWAYS evaluate; spawn when changes affect APIs, architecture, or user-facing behavior
262
+ - \`hatch3r-lint-fixer\` \u2014 when lint errors present after implementation
263
+ - \`hatch3r-a11y-auditor\` \u2014 when UI/accessibility changes
264
+ - \`hatch3r-perf-profiler\` \u2014 when performance-sensitive changes
265
+ - \`hatch3r-dependency-auditor\` \u2014 when dependencies change
266
+
267
+ For plain chat tasks without issue context: classify the task (bug/feature/refactor/QA), create synthetic issue context (title, acceptance criteria, type), then run the full pipeline above.
268
+
269
+ ## Mandatory Behaviors
270
+
271
+ 1. **Load the matching skill** before implementing any task. Read \`/.agents/skills/\` for the skill matching the task type (bug-fix, feature, refactor, qa-validation, etc.).
272
+ 2. **Use the Task tool** (\`subagent_type: "generalPurpose"\`) for all agent delegations. Launch as many independent subagents in parallel as the platform supports \u2014 no artificial concurrency limit.
273
+ 3. **Propagate rules to subagents**: include all \`scope: always\` rule directives in subagent prompts \u2014 subagents do not inherit the parent's rule context automatically.
274
+ 4. **Consult learnings**: check \`/.agents/learnings/\` for relevant pitfalls and patterns before implementation.
275
+
276
+ ## Agent Quick Reference
277
+
278
+ | Agent | When to Use |
279
+ |-------|-------------|
280
+ | \`hatch3r-researcher\` | ALWAYS before implementation (skip only for trivial single-line edits) |
281
+ | \`hatch3r-implementer\` | ALWAYS. One dedicated implementer per task \u2014 standalone, epic sub-issue, batch, or plain chat |
282
+ | \`hatch3r-reviewer\` | ALWAYS after implementation, before PR |
283
+ | \`hatch3r-test-writer\` | ALWAYS for code changes |
284
+ | \`hatch3r-security-auditor\` | ALWAYS for code changes |
285
+ | \`hatch3r-docs-writer\` | ALWAYS evaluate; spawn when documentation impact exists |
286
+ | \`hatch3r-lint-fixer\` | When lint/type errors present after implementation |
287
+ | \`hatch3r-a11y-auditor\` | When UI/accessibility changes |
288
+ | \`hatch3r-perf-profiler\` | When performance-sensitive changes |
289
+ | \`hatch3r-dependency-auditor\` | When dependencies change |
290
+ | \`hatch3r-ci-watcher\` | When CI fails |
291
+
292
+ See the \`hatch3r-agent-orchestration\` rule in \`/.agents/rules/\` for the full orchestration protocol.
293
+
294
+ ## Canonical Structure
295
+
296
+ - Rules: \`/.agents/rules/\` (source of truth for all tool-specific rules)
297
+ - Agents: \`/.agents/agents/\` (agent definitions)
298
+ - Skills: \`/.agents/skills/\` (skill workflows)
299
+ - Commands: \`/.agents/commands/\` (executable commands)
300
+ - MCP: \`/.agents/mcp/mcp.json\` (MCP server configuration)
301
+ - Policy: \`/.agents/policy/\` (guardrails and deny lists)
302
+
303
+ Do not manually edit files with the \`hatch3r-\` prefix -- they are managed by hatch3r
304
+ and will be overwritten on update. Create non-prefixed files for customizations.`;
305
+ var AGENTS_MD_INNER = [
306
+ "# Project Agent Instructions",
307
+ "",
308
+ "This project uses hatch3r for agentic coding setup.",
309
+ "Full canonical instructions are at `/.agents/AGENTS.md`.",
310
+ "",
311
+ "## Quick Reference",
312
+ "",
313
+ "- Rules: `/.agents/rules/`",
314
+ "- Agents: `/.agents/agents/`",
315
+ "- Skills: `/.agents/skills/`",
316
+ "- Commands: `/.agents/commands/`"
317
+ ].join("\n");
318
+ var AGENTS_MD_FULL = `${wrapInManagedBlock(AGENTS_MD_INNER)}
319
+ `;
320
+ var CANONICAL_AGENTS_MD = `# hatch3r \u2014 Canonical Agent Instructions
321
+
322
+ This file is the canonical reference for all agent orchestration in this project. It is auto-generated by hatch3r and should not be manually edited.
323
+
324
+ ## Universal Sub-Agent Pipeline
325
+
326
+ Every task \u2014 board-pickup, workflow command, plain chat, single-task, or multi-task \u2014 MUST use this three-phase sub-agent pipeline. There are NO exceptions. Never implement code inline; always delegate to sub-agents.
327
+
328
+ **Phase 1 \u2014 Research:** Spawn \`hatch3r-researcher\` for context gathering before implementation. Skip only for trivial single-line edits (typos, comment fixes, single-value config changes). Select research modes by task type.
329
+
330
+ **Phase 2 \u2014 Implement:** Spawn \`hatch3r-implementer\` for ALL code changes. One dedicated implementer per task. Never implement inline \u2014 always delegate via the Task tool.
331
+
332
+ **Phase 3 \u2014 Quality:** Spawn ALL applicable specialists in parallel after implementation completes:
333
+ - \`hatch3r-reviewer\` \u2014 ALWAYS (mandatory for every task)
334
+ - \`hatch3r-test-writer\` \u2014 ALWAYS for code changes (mandatory, not just bugs)
335
+ - \`hatch3r-security-auditor\` \u2014 ALWAYS for code changes (mandatory, not just area:security)
336
+ - \`hatch3r-docs-writer\` \u2014 ALWAYS evaluate; spawn when changes affect APIs, architecture, or user-facing behavior
337
+ - \`hatch3r-lint-fixer\` \u2014 when lint errors present after implementation
338
+ - \`hatch3r-a11y-auditor\` \u2014 when UI/accessibility changes
339
+ - \`hatch3r-perf-profiler\` \u2014 when performance-sensitive changes
340
+ - \`hatch3r-dependency-auditor\` \u2014 when dependencies change
341
+ - \`hatch3r-ci-watcher\` \u2014 when CI fails
342
+
343
+ This pipeline applies regardless of how the task was initiated. For plain chat tasks without issue context: classify the task (bug/feature/refactor/QA), create synthetic issue context (title, acceptance criteria, type), then run the full pipeline.
344
+
345
+ ## Orchestration Protocol
346
+
347
+ Every task MUST follow this protocol:
348
+
349
+ 1. **Load the matching skill** from \`/.agents/skills/\` based on task type before implementation.
350
+ 2. **Spawn a researcher subagent** (\`hatch3r-researcher\`) for context gathering before implementation.
351
+ 3. **Spawn an implementer subagent** (\`hatch3r-implementer\`) for code changes. Never implement inline.
352
+ 4. **Spawn quality subagents** after implementation: reviewer, test-writer, and security-auditor (always for code changes), plus docs-writer, a11y-auditor, perf-profiler, lint-fixer as applicable.
353
+ 5. **Propagate rules** to all subagent prompts \u2014 subagents do not inherit rules automatically.
354
+ 6. **Consult learnings** from \`/.agents/learnings/\` before implementation.
355
+
356
+ See the \`hatch3r-agent-orchestration\` rule in \`/.agents/rules/\` for the full mandatory protocol.
357
+
358
+ ## Agent Roster
359
+
360
+ | Agent | Purpose | Invoke When |
361
+ |-------|---------|-------------|
362
+ | \`hatch3r-researcher\` | Context gathering across 12 research modes | ALWAYS before implementation (skip only for trivial single-line edits) |
363
+ | \`hatch3r-implementer\` | Focused single-task implementation | ALWAYS. One dedicated implementer per task \u2014 standalone, epic sub-issue, batch, or plain chat. |
364
+ | \`hatch3r-reviewer\` | Code review | ALWAYS after implementation, before PR creation |
365
+ | \`hatch3r-test-writer\` | Regression and coverage tests | ALWAYS for code changes |
366
+ | \`hatch3r-security-auditor\` | Security audit | ALWAYS for code changes |
367
+ | \`hatch3r-docs-writer\` | Documentation maintenance | ALWAYS evaluate; spawn when documentation impact exists |
368
+ | \`hatch3r-lint-fixer\` | Style, formatting, type cleanup | When lint errors present after implementation |
369
+ | \`hatch3r-a11y-auditor\` | WCAG AA compliance | When UI/accessibility changes |
370
+ | \`hatch3r-perf-profiler\` | Performance profiling | When performance-sensitive changes |
371
+ | \`hatch3r-dependency-auditor\` | Supply chain security | When dependencies change |
372
+ | \`hatch3r-ci-watcher\` | CI/CD failure diagnosis | When CI fails |
373
+
374
+ ## Skill Dispatch Table
375
+
376
+ | Task Type | Skill |
377
+ |-----------|-------|
378
+ | \`type:bug\` | \`hatch3r-bug-fix\` |
379
+ | \`type:feature\` | \`hatch3r-feature\` |
380
+ | \`type:refactor\` + \`area:ui\` | \`hatch3r-visual-refactor\` |
381
+ | \`type:refactor\` + behavior change | \`hatch3r-logical-refactor\` |
382
+ | \`type:refactor\` (other) | \`hatch3r-refactor\` |
383
+ | \`type:qa\` | \`hatch3r-qa-validation\` |
384
+
385
+ ## Researcher Mode Selection
386
+
387
+ | Task Type | Recommended Modes |
388
+ |-----------|-------------------|
389
+ | \`type:bug\` | \`symptom-trace\`, \`root-cause\`, \`codebase-impact\` |
390
+ | \`type:feature\` | \`codebase-impact\`, \`feature-design\`, \`architecture\` |
391
+ | \`type:refactor\` | \`current-state\`, \`refactoring-strategy\`, \`migration-path\` |
392
+ | \`type:qa\` | \`codebase-impact\` |
393
+
394
+ ## Subagent Spawning
395
+
396
+ Use the Task tool with \`subagent_type: "generalPurpose"\` for all agent delegations. Launch as many independent subagents in parallel as the platform supports \u2014 no artificial concurrency limit. Every subagent prompt MUST include:
397
+
398
+ - The agent protocol to follow
399
+ - All \`scope: always\` rules from \`/.agents/rules/\`
400
+ - The project's tooling hierarchy
401
+ - Relevant learnings from \`/.agents/learnings/\`
402
+
403
+ ## Multi-Issue Parallelism
404
+
405
+ When multiple issues or tasks are identified (board pickup batch, multiple issue references, or multi-task chat instructions), the framework MUST parallelize them:
406
+
407
+ 1. **Parse** into individual discrete tasks.
408
+ 2. **Build dependency graph**, group into parallel levels (independent = same level).
409
+ 3. **Phase 1 \u2014 Researchers:** Spawn one \`hatch3r-researcher\` per task in parallel. Skip for trivial single-line edits only.
410
+ 4. **Phase 2 \u2014 Implementers:** Spawn one \`hatch3r-implementer\` per task per dependency level. Each implementer receives its researcher output.
411
+ 5. **Phase 3 \u2014 Specialists:** Spawn \`hatch3r-reviewer\`, \`hatch3r-test-writer\`, and \`hatch3r-security-auditor\` (always for code changes), plus \`hatch3r-docs-writer\`, auditors, etc. as applicable across the batch.
412
+ 6. **Shared branch, combined PR** closing all issues.
413
+
414
+ This pattern applies to ALL contexts: board-pickup, workflow command, and plain chat. Every task gets its own implementer subagent \u2014 never implement multiple tasks inline.
415
+
416
+ ## Single-Task and Plain Chat Protocol
417
+
418
+ When the user provides a single task in plain chat (no command invoked, no issue reference), the full sub-agent pipeline still applies:
419
+
420
+ 1. **Classify** the task by type (bug/feature/refactor/QA/other) based on context.
421
+ 2. **Create synthetic issue context** (title, acceptance criteria, type) from the instruction.
422
+ 3. **Run the Universal Sub-Agent Pipeline**: Research \u2192 Implement \u2192 Quality (all three phases, all mandatory specialists).
423
+
424
+ This ensures consistent quality regardless of how the task was initiated.
425
+
426
+ ## Directory Structure
427
+
428
+ - \`/.agents/rules/\` \u2014 Rules (source of truth for all tool-specific rules)
429
+ - \`/.agents/agents/\` \u2014 Agent definitions
430
+ - \`/.agents/skills/\` \u2014 Skill workflows
431
+ - \`/.agents/commands/\` \u2014 Executable commands
432
+ - \`/.agents/mcp/\` \u2014 MCP server configuration
433
+ - \`/.agents/policy/\` \u2014 Guardrails and deny lists
434
+ - \`/.agents/learnings/\` \u2014 Project learnings (pitfalls, patterns, decisions)
435
+ `;
436
+
437
+ // src/adapters/base.ts
438
+ function output(path, content, managedContent) {
439
+ return { path, content, managedContent, action: "create" };
440
+ }
441
+
442
+ // src/adapters/canonical.ts
443
+ import { readFile, readdir } from "fs/promises";
444
+ import { join } from "path";
445
+ import chalk3 from "chalk";
446
+ import { glob } from "glob";
447
+ import { parse as parseYaml } from "yaml";
448
+ var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?$/;
449
+ function parseFrontmatter(rawContent) {
450
+ const match = rawContent.match(FRONTMATTER_REGEX);
451
+ if (!match) {
452
+ return {
453
+ metadata: { id: "", type: "rule", description: "" },
454
+ content: rawContent
455
+ };
456
+ }
457
+ const [, frontmatterStr, content = ""] = match;
458
+ const parsed = parseYaml(frontmatterStr ?? "");
459
+ const metadata = {
460
+ id: "",
461
+ type: "rule",
462
+ description: ""
463
+ };
464
+ if (parsed && typeof parsed === "object") {
465
+ for (const [key, value] of Object.entries(parsed)) {
466
+ if (typeof value === "string") {
467
+ metadata[key] = value;
468
+ }
469
+ }
470
+ }
471
+ if (!metadata.id && metadata.name) {
472
+ metadata.id = metadata.name;
473
+ }
474
+ metadata.type = metadata.type ?? "rule";
475
+ metadata.description = metadata.description ?? "";
476
+ return { metadata, content: content ?? "" };
477
+ }
478
+ async function readCanonicalFiles(agentsDir, type) {
479
+ const results = [];
480
+ switch (type) {
481
+ case "rules": {
482
+ let files = [];
483
+ try {
484
+ files = await glob("**/*.md", {
485
+ cwd: join(agentsDir, "rules"),
486
+ nodir: true
487
+ });
488
+ } catch {
489
+ break;
490
+ }
491
+ const ruleEntries = await Promise.all(
492
+ files.map(async (relPath) => {
493
+ const fullPath = join(agentsDir, "rules", relPath);
494
+ const rawContent = await readFile(fullPath, "utf-8");
495
+ const { metadata, content } = parseFrontmatter(rawContent);
496
+ const id = metadata.id || relPath.replace(/\.md$/, "").replace(/\//g, "-");
497
+ return {
498
+ id,
499
+ type: "rule",
500
+ description: metadata.description ?? "",
501
+ scope: metadata.scope,
502
+ content,
503
+ rawContent,
504
+ sourcePath: fullPath
505
+ };
506
+ })
507
+ );
508
+ results.push(...ruleEntries);
509
+ break;
510
+ }
511
+ case "agents": {
512
+ let files = [];
513
+ try {
514
+ files = await glob("**/*.md", {
515
+ cwd: join(agentsDir, "agents"),
516
+ nodir: true
517
+ });
518
+ } catch {
519
+ break;
520
+ }
521
+ const agentEntries = await Promise.all(
522
+ files.map(async (relPath) => {
523
+ const fullPath = join(agentsDir, "agents", relPath);
524
+ const rawContent = await readFile(fullPath, "utf-8");
525
+ const { metadata, content } = parseFrontmatter(rawContent);
526
+ const id = metadata.id ?? metadata.name ?? relPath.replace(/\.md$/, "");
527
+ return {
528
+ id,
529
+ type: "agent",
530
+ description: metadata.description ?? "",
531
+ model: metadata.model,
532
+ content,
533
+ rawContent,
534
+ sourcePath: fullPath
535
+ };
536
+ })
537
+ );
538
+ results.push(...agentEntries);
539
+ break;
540
+ }
541
+ case "skills": {
542
+ let skillDirs = [];
543
+ try {
544
+ skillDirs = await readdir(join(agentsDir, "skills"), {
545
+ withFileTypes: true
546
+ });
547
+ } catch {
548
+ break;
549
+ }
550
+ const dirs = skillDirs.filter((d) => d.isDirectory());
551
+ const skillEntries = await Promise.all(
552
+ dirs.map(async (dir) => {
553
+ const skillPath = join(agentsDir, "skills", dir.name, "SKILL.md");
554
+ try {
555
+ const rawContent = await readFile(skillPath, "utf-8");
556
+ const { metadata, content } = parseFrontmatter(rawContent);
557
+ const id = metadata.name ?? metadata.id ?? dir.name;
558
+ return {
559
+ id,
560
+ type: "skill",
561
+ description: metadata.description ?? "",
562
+ content,
563
+ rawContent,
564
+ sourcePath: skillPath
565
+ };
566
+ } catch (err) {
567
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
568
+ if (code !== "ENOENT") {
569
+ console.warn(chalk3.yellow(` Warning: Could not read SKILL.md in ${dir.name}: ${err instanceof Error ? err.message : String(err)}`));
570
+ }
571
+ return null;
572
+ }
573
+ })
574
+ );
575
+ results.push(...skillEntries.filter((e) => e !== null));
576
+ break;
577
+ }
578
+ case "commands": {
579
+ let files = [];
580
+ try {
581
+ files = await glob("**/*.md", {
582
+ cwd: join(agentsDir, "commands"),
583
+ nodir: true
584
+ });
585
+ } catch {
586
+ break;
587
+ }
588
+ const cmdEntries = await Promise.all(
589
+ files.map(async (relPath) => {
590
+ const fullPath = join(agentsDir, "commands", relPath);
591
+ const rawContent = await readFile(fullPath, "utf-8");
592
+ const { metadata, content } = parseFrontmatter(rawContent);
593
+ const id = metadata.id || relPath.replace(/\.md$/, "");
594
+ return {
595
+ id,
596
+ type: "command",
597
+ description: metadata.description ?? "",
598
+ content,
599
+ rawContent,
600
+ sourcePath: fullPath
601
+ };
602
+ })
603
+ );
604
+ results.push(...cmdEntries);
605
+ break;
606
+ }
607
+ case "prompts": {
608
+ const dir = join(agentsDir, "prompts");
609
+ try {
610
+ const files = await glob("**/*.md", { cwd: dir, nodir: true });
611
+ const promptEntries = await Promise.all(
612
+ files.map(async (relPath) => {
613
+ const fullPath = join(dir, relPath);
614
+ const rawContent = await readFile(fullPath, "utf-8");
615
+ const { metadata, content } = parseFrontmatter(rawContent);
616
+ const id = metadata.id ?? metadata.name ?? relPath.replace(/\.md$/, "");
617
+ return {
618
+ id,
619
+ type: "prompt",
620
+ description: metadata.description ?? "",
621
+ content,
622
+ rawContent,
623
+ sourcePath: fullPath
624
+ };
625
+ })
626
+ );
627
+ results.push(...promptEntries);
628
+ } catch (err) {
629
+ console.warn(chalk3.yellow(` Warning: Could not read prompts: ${err instanceof Error ? err.message : String(err)}`));
630
+ }
631
+ break;
632
+ }
633
+ case "github-agents": {
634
+ const dir = join(agentsDir, "github-agents");
635
+ try {
636
+ const files = await glob("**/*.md", { cwd: dir, nodir: true });
637
+ const ghEntries = await Promise.all(
638
+ files.map(async (relPath) => {
639
+ const fullPath = join(dir, relPath);
640
+ const rawContent = await readFile(fullPath, "utf-8");
641
+ const { metadata, content } = parseFrontmatter(rawContent);
642
+ const id = metadata.id ?? metadata.name ?? relPath.replace(/\.md$/, "");
643
+ return {
644
+ id,
645
+ type: "github-agent",
646
+ description: metadata.description ?? "",
647
+ content,
648
+ rawContent,
649
+ sourcePath: fullPath
650
+ };
651
+ })
652
+ );
653
+ results.push(...ghEntries);
654
+ } catch (err) {
655
+ console.warn(chalk3.yellow(` Warning: Could not read github-agents: ${err instanceof Error ? err.message : String(err)}`));
656
+ }
657
+ break;
658
+ }
659
+ }
660
+ return results;
661
+ }
662
+
663
+ // src/models/aliases.ts
664
+ var MODEL_ALIASES = {
665
+ "opus": "claude-opus-4-6",
666
+ "sonnet": "claude-sonnet-4-6",
667
+ "haiku": "claude-haiku-4-5",
668
+ "codex": "gpt-5.3-codex",
669
+ "codex-prev": "gpt-5.2-codex",
670
+ "codex-mini": "gpt-5.1-codex-mini",
671
+ "codex-spark": "gpt-5.3-codex-spark",
672
+ "gemini-pro": "gemini-3.1-pro",
673
+ "gemini-flash": "gemini-3-flash",
674
+ "gemini-stable": "gemini-2.5-pro"
675
+ };
676
+ function resolveModelAlias(input) {
677
+ return MODEL_ALIASES[input] ?? input;
678
+ }
679
+
680
+ // src/models/resolve.ts
681
+ function resolveAgentModel(agentId, agent, manifest, customize) {
682
+ const raw = customize?.model ?? manifest.models?.agents?.[agentId] ?? agent.model ?? manifest.models?.default;
683
+ return raw ? resolveModelAlias(raw) : void 0;
684
+ }
685
+ var PROVIDER_PREFIXES = [
686
+ [/^claude-/, "anthropic"],
687
+ [/^gpt-|^codex-/, "openai"],
688
+ [/^gemini-/, "google"]
689
+ ];
690
+ function withProviderPrefix(modelId) {
691
+ for (const [pattern, provider] of PROVIDER_PREFIXES) {
692
+ if (pattern.test(modelId)) return `${provider}/${modelId}`;
693
+ }
694
+ return modelId;
695
+ }
696
+
697
+ // src/models/customize.ts
698
+ import { readFile as readFile2 } from "fs/promises";
699
+ import { join as join2 } from "path";
700
+ import { parse as parseYaml2 } from "yaml";
701
+ async function readCustomization(projectRoot, type, id) {
702
+ const path = join2(projectRoot, ".hatch3r", type, `${id}.customize.yaml`);
703
+ try {
704
+ const raw = await readFile2(path, "utf-8");
705
+ const parsed = parseYaml2(raw);
706
+ if (!parsed || typeof parsed !== "object") return void 0;
707
+ const result = {};
708
+ let hasValue = false;
709
+ if (typeof parsed.model === "string" && parsed.model.length > 0) {
710
+ result.model = parsed.model;
711
+ hasValue = true;
712
+ }
713
+ if (typeof parsed.scope === "string" && parsed.scope.length > 0) {
714
+ result.scope = parsed.scope;
715
+ hasValue = true;
716
+ }
717
+ if (typeof parsed.description === "string" && parsed.description.length > 0) {
718
+ result.description = parsed.description;
719
+ hasValue = true;
720
+ }
721
+ if (typeof parsed.enabled === "boolean") {
722
+ result.enabled = parsed.enabled;
723
+ hasValue = true;
724
+ }
725
+ return hasValue ? result : void 0;
726
+ } catch {
727
+ return void 0;
728
+ }
729
+ }
730
+ async function readCustomizationMarkdown(projectRoot, type, id) {
731
+ const path = join2(projectRoot, ".hatch3r", type, `${id}.customize.md`);
732
+ try {
733
+ const content = await readFile2(path, "utf-8");
734
+ const trimmed = content.trim();
735
+ return trimmed.length > 0 ? trimmed : void 0;
736
+ } catch {
737
+ return void 0;
738
+ }
739
+ }
740
+
741
+ // src/adapters/customization.ts
742
+ var TYPE_TO_DIR = {
743
+ agent: "agents",
744
+ skill: "skills",
745
+ command: "commands",
746
+ rule: "rules"
747
+ };
748
+ async function applyCustomization(projectRoot, file) {
749
+ const dir = TYPE_TO_DIR[file.type];
750
+ if (!dir) {
751
+ return { content: file.content, skip: false, overrides: {} };
752
+ }
753
+ const [yaml, md] = await Promise.all([
754
+ readCustomization(projectRoot, dir, file.id),
755
+ readCustomizationMarkdown(projectRoot, dir, file.id)
756
+ ]);
757
+ const overrides = yaml ?? {};
758
+ if (overrides.enabled === false) {
759
+ return { content: file.content, skip: true, overrides };
760
+ }
761
+ let content = file.content;
762
+ if (md) {
763
+ content = `${content}
764
+
765
+ ---
766
+
767
+ ## Project Customizations
768
+
769
+ ${md}`;
770
+ }
771
+ return { content, skip: false, overrides };
772
+ }
773
+ async function applyCustomizationRaw(projectRoot, file) {
774
+ const dir = TYPE_TO_DIR[file.type];
775
+ if (!dir) {
776
+ return { content: file.rawContent, skip: false, overrides: {} };
777
+ }
778
+ const [yaml, md] = await Promise.all([
779
+ readCustomization(projectRoot, dir, file.id),
780
+ readCustomizationMarkdown(projectRoot, dir, file.id)
781
+ ]);
782
+ const overrides = yaml ?? {};
783
+ if (overrides.enabled === false) {
784
+ return { content: file.rawContent, skip: true, overrides };
785
+ }
786
+ let content = file.rawContent;
787
+ if (md) {
788
+ content = `${content}
789
+
790
+ ---
791
+
792
+ ## Project Customizations
793
+
794
+ ${md}`;
795
+ }
796
+ return { content, skip: false, overrides };
797
+ }
798
+
799
+ // src/adapters/aider.ts
800
+ var AiderAdapter = class {
801
+ name = "aider";
802
+ async generate(agentsDir, manifest) {
803
+ const results = [];
804
+ const { features } = manifest;
805
+ const projectRoot = dirname(agentsDir);
806
+ const conventionLines = [
807
+ "",
808
+ "# Hatch3r Agent Instructions",
809
+ "",
810
+ "Full canonical agent instructions are at `/.agents/AGENTS.md`.",
811
+ "",
812
+ BRIDGE_ORCHESTRATION,
813
+ ""
814
+ ];
815
+ if (features.rules) {
816
+ const rules = await readCanonicalFiles(agentsDir, "rules");
817
+ for (const rule of rules) {
818
+ const { content, skip, overrides } = await applyCustomization(projectRoot, rule);
819
+ if (skip) continue;
820
+ const desc = overrides.description ?? rule.description;
821
+ conventionLines.push(`## ${rule.id}`);
822
+ conventionLines.push("");
823
+ conventionLines.push(desc);
824
+ conventionLines.push("");
825
+ conventionLines.push(content);
826
+ conventionLines.push("");
827
+ }
828
+ }
829
+ if (features.agents) {
830
+ const agents = await readCanonicalFiles(agentsDir, "agents");
831
+ for (const agent of agents) {
832
+ const { content, skip, overrides } = await applyCustomization(projectRoot, agent);
833
+ if (skip) continue;
834
+ const model = resolveAgentModel(agent.id, agent, manifest, overrides);
835
+ const desc = overrides.description ?? agent.description;
836
+ conventionLines.push(`## Agent: ${agent.id}`);
837
+ if (model) conventionLines.push(`**Recommended model:** \`${model}\``);
838
+ conventionLines.push("");
839
+ conventionLines.push(desc);
840
+ conventionLines.push("");
841
+ conventionLines.push(content);
842
+ conventionLines.push("");
843
+ }
844
+ }
845
+ const conventionInner = conventionLines.join("\n");
846
+ results.push(output("CONVENTIONS.md", wrapInManagedBlock(conventionInner), conventionInner));
847
+ if (features.skills) {
848
+ const skills = await readCanonicalFiles(agentsDir, "skills");
849
+ for (const skill of skills) {
850
+ const { content, skip } = await applyCustomizationRaw(projectRoot, skill);
851
+ if (skip) continue;
852
+ results.push(
853
+ output(
854
+ `.aider/skills/${toPrefixedId(skill.id)}/SKILL.md`,
855
+ wrapInManagedBlock(content),
856
+ content
857
+ )
858
+ );
859
+ }
860
+ }
861
+ const configYaml = [
862
+ "# Managed by hatch3r \u2014 do not edit manually",
863
+ "read:",
864
+ " - CONVENTIONS.md",
865
+ " - .agents/AGENTS.md",
866
+ "auto-lint: true",
867
+ ""
868
+ ].join("\n");
869
+ results.push(output(".aider.conf.yml", configYaml));
870
+ return results;
871
+ }
872
+ };
873
+
874
+ // src/adapters/amp.ts
875
+ import { readFile as readFile3 } from "fs/promises";
876
+ import { dirname as dirname2, join as join3 } from "path";
877
+ import chalk4 from "chalk";
878
+ var AmpAdapter = class {
879
+ name = "amp";
880
+ async generate(agentsDir, manifest) {
881
+ const results = [];
882
+ const { features } = manifest;
883
+ const projectRoot = dirname2(agentsDir);
884
+ const bridgeLines = [];
885
+ if (features.rules) {
886
+ const rules = await readCanonicalFiles(agentsDir, "rules");
887
+ for (const rule of rules) {
888
+ const { content, skip, overrides } = await applyCustomization(projectRoot, rule);
889
+ if (skip) continue;
890
+ const desc = overrides.description ?? rule.description;
891
+ bridgeLines.push(`## ${rule.id}`);
892
+ bridgeLines.push("");
893
+ bridgeLines.push(desc);
894
+ bridgeLines.push("");
895
+ bridgeLines.push(content);
896
+ bridgeLines.push("");
897
+ }
898
+ }
899
+ if (features.agents) {
900
+ const agents = await readCanonicalFiles(agentsDir, "agents");
901
+ for (const agent of agents) {
902
+ const { content, skip, overrides } = await applyCustomization(projectRoot, agent);
903
+ if (skip) continue;
904
+ const model = resolveAgentModel(agent.id, agent, manifest, overrides);
905
+ const desc = overrides.description ?? agent.description;
906
+ bridgeLines.push(`## Agent: ${agent.id}`);
907
+ if (model) bridgeLines.push(`**Recommended model:** \`${model}\`. Use Smart mode for Opus, Rush for Haiku, Deep for Codex.`);
908
+ bridgeLines.push("");
909
+ bridgeLines.push(desc);
910
+ bridgeLines.push("");
911
+ bridgeLines.push(content);
912
+ bridgeLines.push("");
913
+ }
914
+ }
915
+ const innerContent = [
916
+ "",
917
+ "# Hatch3r Agent Instructions",
918
+ "",
919
+ "Full canonical agent instructions are at `/.agents/AGENTS.md`.",
920
+ "",
921
+ BRIDGE_ORCHESTRATION,
922
+ "",
923
+ ...bridgeLines
924
+ ].join("\n");
925
+ results.push(output(".amp/AGENTS.md", wrapInManagedBlock(innerContent), innerContent));
926
+ if (features.skills) {
927
+ const skills = await readCanonicalFiles(agentsDir, "skills");
928
+ for (const skill of skills) {
929
+ const { content, skip } = await applyCustomizationRaw(projectRoot, skill);
930
+ if (skip) continue;
931
+ results.push(
932
+ output(
933
+ `.amp/skills/${toPrefixedId(skill.id)}/SKILL.md`,
934
+ wrapInManagedBlock(content),
935
+ content
936
+ )
937
+ );
938
+ }
939
+ }
940
+ if (features.mcp && manifest.mcp.servers.length > 0) {
941
+ const mcpPath = join3(agentsDir, "mcp", "mcp.json");
942
+ try {
943
+ const mcpRaw = await readFile3(mcpPath, "utf-8");
944
+ const mcpParsed = JSON.parse(mcpRaw);
945
+ if (mcpParsed.mcpServers) {
946
+ const ampMcp = {};
947
+ for (const [name, server] of Object.entries(mcpParsed.mcpServers)) {
948
+ if (server._disabled) continue;
949
+ if (server.command) {
950
+ ampMcp[name] = {
951
+ command: server.command,
952
+ args: server.args || [],
953
+ ...server.env && Object.keys(server.env).length > 0 ? { env: server.env } : {}
954
+ };
955
+ } else if (server.url) {
956
+ ampMcp[name] = { url: server.url };
957
+ }
958
+ }
959
+ if (Object.keys(ampMcp).length > 0) {
960
+ const ampSettings = {
961
+ "amp.mcpServers": ampMcp
962
+ };
963
+ results.push(
964
+ output(".amp/settings.json", JSON.stringify(ampSettings, null, 2))
965
+ );
966
+ }
967
+ }
968
+ } catch (err) {
969
+ console.warn(chalk4.yellow(` Warning: Could not read MCP config: ${err instanceof Error ? err.message : String(err)}`));
970
+ }
971
+ }
972
+ return results;
973
+ }
974
+ };
975
+
976
+ // src/adapters/claude.ts
977
+ import { readFile as readFile4 } from "fs/promises";
978
+ import { dirname as dirname3, join as join4 } from "path";
979
+ import chalk5 from "chalk";
980
+ function mapToClaudeEvent(event) {
981
+ const mapping = {
982
+ "pre-commit": "PreToolUse",
983
+ "post-merge": "PostToolUse",
984
+ "ci-failure": "SubagentStart",
985
+ "file-save": "PostToolUse",
986
+ "session-start": "SessionStart",
987
+ "pre-push": "PreToolUse"
988
+ };
989
+ return mapping[event] || event;
990
+ }
991
+ function getClaudeToolMatcher(hook) {
992
+ const eventToolMap = {
993
+ "pre-commit": "Bash",
994
+ "post-merge": "Bash",
995
+ "file-save": "Write",
996
+ "session-start": ".*",
997
+ "pre-push": "Bash",
998
+ "ci-failure": "Bash"
999
+ };
1000
+ return eventToolMap[hook.event] || ".*";
1001
+ }
1002
+ var ClaudeAdapter = class {
1003
+ name = "claude";
1004
+ async generate(agentsDir, manifest) {
1005
+ const results = [];
1006
+ const { features } = manifest;
1007
+ const projectRoot = dirname3(agentsDir);
1008
+ const innerContent = [
1009
+ "",
1010
+ "# Hatch3r Project Instructions",
1011
+ "",
1012
+ "Full canonical agent instructions are at `.agents/AGENTS.md`.",
1013
+ "Rules are managed in `.claude/rules/` and agents in `.claude/agents/`.",
1014
+ "",
1015
+ BRIDGE_ORCHESTRATION,
1016
+ "",
1017
+ "## Agent Teams (Experimental)",
1018
+ "",
1019
+ "This project's sub-agentic architecture (implementer agent + issue workflow skill) is",
1020
+ "compatible with Claude Code Agent Teams. When using Agent Teams:",
1021
+ "",
1022
+ "- Give each teammate explicit file boundaries in spawn prompts",
1023
+ "- The hatch3r-implementer agent pattern maps to teammate spawn prompts",
1024
+ "- Use `/.agents/skills/hatch3r-issue-workflow/SKILL.md` for orchestration",
1025
+ "- Teammates do not inherit conversation history; include task-specific context",
1026
+ "",
1027
+ "## Personal Settings",
1028
+ "",
1029
+ "Create `CLAUDE.local.md` for personal settings (not committed to git).",
1030
+ "Claude Code reads this file for user-specific preferences.",
1031
+ ""
1032
+ ].join("\n");
1033
+ results.push(output("CLAUDE.md", wrapInManagedBlock(innerContent), innerContent));
1034
+ if (features.rules) {
1035
+ const rules = await readCanonicalFiles(agentsDir, "rules");
1036
+ for (const rule of rules) {
1037
+ const { content, skip, overrides } = await applyCustomization(projectRoot, rule);
1038
+ if (skip) continue;
1039
+ const desc = overrides.description ?? rule.description;
1040
+ const body = `# ${rule.id}
1041
+
1042
+ ${desc}
1043
+
1044
+ ${content}`;
1045
+ results.push(output(`.claude/rules/${toPrefixedId(rule.id)}.md`, wrapInManagedBlock(body), body));
1046
+ }
1047
+ }
1048
+ if (features.agents) {
1049
+ const agents = await readCanonicalFiles(agentsDir, "agents");
1050
+ for (const agent of agents) {
1051
+ const { content, skip, overrides } = await applyCustomization(projectRoot, agent);
1052
+ if (skip) continue;
1053
+ const agentId = toPrefixedId(agent.id);
1054
+ const model = resolveAgentModel(agent.id, agent, manifest, overrides);
1055
+ const modelGuidance = model ? `
1056
+
1057
+ ## Recommended Model
1058
+
1059
+ Preferred: \`${model}\`. Set via \`/model ${model}\` or env \`CLAUDE_CODE_SUBAGENT_MODEL=${model}\`.` : "";
1060
+ const desc = overrides.description ?? agent.description;
1061
+ const fm = `---
1062
+ description: ${desc}
1063
+ ---`;
1064
+ const body = `${content}${modelGuidance}`;
1065
+ results.push(output(`.claude/agents/${agentId}.md`, `${fm}
1066
+
1067
+ ${wrapInManagedBlock(body)}`, body));
1068
+ }
1069
+ }
1070
+ const settingsObj = {
1071
+ permissions: {
1072
+ allow: [
1073
+ "Read",
1074
+ "Edit",
1075
+ "MultiEdit",
1076
+ "Write",
1077
+ "Grep",
1078
+ "Glob",
1079
+ "LS",
1080
+ "TodoRead",
1081
+ "TodoWrite"
1082
+ ],
1083
+ deny: []
1084
+ },
1085
+ teammateMode: "tool-using"
1086
+ };
1087
+ if (features.hooks) {
1088
+ const { readHookDefinitions } = await import("./hooks-ZOTFDEA3.js");
1089
+ const hooks = await readHookDefinitions(agentsDir);
1090
+ if (hooks.length > 0) {
1091
+ const hooksConfig = {};
1092
+ for (const hook of hooks) {
1093
+ const claudeEvent = mapToClaudeEvent(hook.event);
1094
+ if (!hooksConfig[claudeEvent]) {
1095
+ hooksConfig[claudeEvent] = [];
1096
+ }
1097
+ const matcher = getClaudeToolMatcher(hook);
1098
+ hooksConfig[claudeEvent].push({
1099
+ matcher,
1100
+ hooks: [
1101
+ {
1102
+ type: "command",
1103
+ command: `echo "hatch3r hook: ${hook.id} \u2014 activate ${hook.agent} agent"`
1104
+ }
1105
+ ]
1106
+ });
1107
+ }
1108
+ settingsObj.hooks = hooksConfig;
1109
+ }
1110
+ }
1111
+ results.push(
1112
+ output(".claude/settings.json", JSON.stringify(settingsObj, null, 2))
1113
+ );
1114
+ if (features.skills) {
1115
+ const skills = await readCanonicalFiles(agentsDir, "skills");
1116
+ for (const skill of skills) {
1117
+ const { content, skip } = await applyCustomizationRaw(projectRoot, skill);
1118
+ if (skip) continue;
1119
+ results.push(
1120
+ output(
1121
+ `.claude/skills/${toPrefixedId(skill.id)}/SKILL.md`,
1122
+ wrapInManagedBlock(content),
1123
+ content
1124
+ )
1125
+ );
1126
+ }
1127
+ }
1128
+ if (features.commands) {
1129
+ const commands = await readCanonicalFiles(agentsDir, "commands");
1130
+ for (const cmd of commands) {
1131
+ const { content, skip } = await applyCustomizationRaw(projectRoot, cmd);
1132
+ if (skip) continue;
1133
+ results.push(
1134
+ output(
1135
+ `.claude/commands/${toPrefixedId(cmd.id)}.md`,
1136
+ wrapInManagedBlock(content),
1137
+ content
1138
+ )
1139
+ );
1140
+ }
1141
+ }
1142
+ if (features.mcp && manifest.mcp.servers.length > 0) {
1143
+ const mcpPath = join4(agentsDir, "mcp", "mcp.json");
1144
+ try {
1145
+ const mcpContent = await readFile4(mcpPath, "utf-8");
1146
+ results.push(output(".mcp.json", mcpContent));
1147
+ } catch (err) {
1148
+ console.warn(chalk5.yellow(` Warning: Could not read MCP config: ${err instanceof Error ? err.message : String(err)}`));
1149
+ }
1150
+ }
1151
+ return results;
1152
+ }
1153
+ };
1154
+
1155
+ // src/adapters/cline.ts
1156
+ import { readFile as readFile5 } from "fs/promises";
1157
+ import { dirname as dirname4, join as join5 } from "path";
1158
+ import chalk6 from "chalk";
1159
+ var ClineAdapter = class {
1160
+ name = "cline";
1161
+ async generate(agentsDir, manifest) {
1162
+ const results = [];
1163
+ const { features } = manifest;
1164
+ const projectRoot = dirname4(agentsDir);
1165
+ const customModes = [];
1166
+ if (features.agents) {
1167
+ const agents = await readCanonicalFiles(agentsDir, "agents");
1168
+ for (const agent of agents) {
1169
+ const { content, skip, overrides } = await applyCustomization(projectRoot, agent);
1170
+ if (skip) continue;
1171
+ const slug = toPrefixedId(agent.id);
1172
+ const model = resolveAgentModel(agent.id, agent, manifest, overrides);
1173
+ const modelGuidance = model ? `
1174
+
1175
+ Recommended model: ${model}. Select this model in the Roo Code model dropdown when using this mode.` : "";
1176
+ customModes.push({
1177
+ slug,
1178
+ name: agent.id,
1179
+ roleDefinition: content + modelGuidance,
1180
+ groups: ["read", "edit", "browser", "command", "mcp"],
1181
+ whenToUse: overrides.description ?? agent.description
1182
+ });
1183
+ }
1184
+ }
1185
+ if (customModes.length > 0) {
1186
+ results.push(
1187
+ output(".roomodes", JSON.stringify({ customModes }, null, 2))
1188
+ );
1189
+ }
1190
+ if (features.skills) {
1191
+ const skills = await readCanonicalFiles(agentsDir, "skills");
1192
+ for (const skill of skills) {
1193
+ const { content, skip } = await applyCustomizationRaw(projectRoot, skill);
1194
+ if (skip) continue;
1195
+ results.push(
1196
+ output(
1197
+ `.cline/skills/${toPrefixedId(skill.id)}/SKILL.md`,
1198
+ wrapInManagedBlock(content),
1199
+ content
1200
+ )
1201
+ );
1202
+ }
1203
+ }
1204
+ if (features.rules) {
1205
+ const rules = await readCanonicalFiles(agentsDir, "rules");
1206
+ for (const rule of rules) {
1207
+ const { content, skip, overrides } = await applyCustomization(projectRoot, rule);
1208
+ if (skip) continue;
1209
+ const ruleId = toPrefixedId(rule.id);
1210
+ const desc = overrides.description ?? rule.description;
1211
+ const body = `# ${rule.id}
1212
+
1213
+ ${desc}
1214
+
1215
+ ${content}`;
1216
+ results.push(output(`.roo/rules/${ruleId}.md`, wrapInManagedBlock(body), body));
1217
+ }
1218
+ }
1219
+ if (features.hooks) {
1220
+ const { readHookDefinitions } = await import("./hooks-ZOTFDEA3.js");
1221
+ const hooks = await readHookDefinitions(agentsDir);
1222
+ for (const hook of hooks) {
1223
+ const globs = hook.condition?.globs || [];
1224
+ const body = [
1225
+ `# Hook: ${hook.id}`,
1226
+ "",
1227
+ `**Event:** ${hook.event}`,
1228
+ `**Agent:** ${hook.agent}`,
1229
+ "",
1230
+ hook.description,
1231
+ "",
1232
+ `When this hook's event (${hook.event}) is triggered${globs.length > 0 ? ` for files matching ${globs.join(", ")}` : ""}, activate the ${hook.agent} agent.`
1233
+ ].join("\n");
1234
+ const hookId = toPrefixedId(`hook-${hook.id}`);
1235
+ results.push(output(`.roo/rules/${hookId}.md`, wrapInManagedBlock(body), body));
1236
+ }
1237
+ }
1238
+ if (features.commands) {
1239
+ const commands = await readCanonicalFiles(agentsDir, "commands");
1240
+ for (const cmd of commands) {
1241
+ const { content, skip } = await applyCustomizationRaw(projectRoot, cmd);
1242
+ if (skip) continue;
1243
+ results.push(
1244
+ output(
1245
+ `.clinerules/workflows/${toPrefixedId(cmd.id)}.md`,
1246
+ wrapInManagedBlock(content),
1247
+ content
1248
+ )
1249
+ );
1250
+ }
1251
+ }
1252
+ if (features.mcp && manifest.mcp.servers.length > 0) {
1253
+ const mcpPath = join5(agentsDir, "mcp", "mcp.json");
1254
+ try {
1255
+ const mcpRaw = await readFile5(mcpPath, "utf-8");
1256
+ const mcpParsed = JSON.parse(mcpRaw);
1257
+ if (mcpParsed.mcpServers) {
1258
+ const rooMcp = {};
1259
+ for (const [name, server] of Object.entries(mcpParsed.mcpServers)) {
1260
+ if (server._disabled) continue;
1261
+ if (server.command) {
1262
+ rooMcp[name] = {
1263
+ command: server.command,
1264
+ args: server.args || [],
1265
+ ...server.env && Object.keys(server.env).length > 0 ? { env: server.env } : {}
1266
+ };
1267
+ } else if (server.url) {
1268
+ rooMcp[name] = {
1269
+ url: server.url,
1270
+ transport: "streamable-http"
1271
+ };
1272
+ }
1273
+ }
1274
+ if (Object.keys(rooMcp).length > 0) {
1275
+ results.push(
1276
+ output(".roo/mcp.json", JSON.stringify({ mcpServers: rooMcp }, null, 2))
1277
+ );
1278
+ }
1279
+ }
1280
+ } catch (err) {
1281
+ console.warn(chalk6.yellow(` Warning: Could not read MCP config: ${err instanceof Error ? err.message : String(err)}`));
1282
+ }
1283
+ }
1284
+ return results;
1285
+ }
1286
+ };
1287
+
1288
+ // src/adapters/codex.ts
1289
+ import { readFile as readFile6 } from "fs/promises";
1290
+ import { dirname as dirname5, join as join6 } from "path";
1291
+ import chalk7 from "chalk";
1292
+ var CodexAdapter = class {
1293
+ name = "codex";
1294
+ async generate(agentsDir, manifest) {
1295
+ const results = [];
1296
+ const projectRoot = dirname5(agentsDir);
1297
+ const configLines = [
1298
+ "# Codex project configuration (managed by hatch3r)",
1299
+ "#",
1300
+ "# Do not manually edit \u2014 run `npx hatch3r sync` to regenerate.",
1301
+ "",
1302
+ 'model_instructions_file = ".agents/AGENTS.md"',
1303
+ ""
1304
+ ];
1305
+ if (manifest.features.rules) {
1306
+ const rules = await readCanonicalFiles(agentsDir, "rules");
1307
+ const enabledRules = [];
1308
+ for (const rule of rules) {
1309
+ const { skip, overrides } = await applyCustomization(projectRoot, rule);
1310
+ if (skip) continue;
1311
+ const desc = overrides.description ?? rule.description;
1312
+ enabledRules.push({ ...rule, description: desc });
1313
+ }
1314
+ if (enabledRules.length > 0) {
1315
+ configLines.push("# Additional instruction files (rules)");
1316
+ for (const rule of enabledRules) {
1317
+ configLines.push(`# rule: ${rule.id} \u2014 ${rule.description}`);
1318
+ }
1319
+ configLines.push("");
1320
+ }
1321
+ }
1322
+ if (manifest.features.agents) {
1323
+ const agents = await readCanonicalFiles(agentsDir, "agents");
1324
+ for (const agent of agents) {
1325
+ const { skip, overrides } = await applyCustomization(projectRoot, agent);
1326
+ if (skip) continue;
1327
+ const agentId = toPrefixedId(agent.id);
1328
+ const model = resolveAgentModel(agent.id, agent, manifest, overrides);
1329
+ configLines.push(`[agents.${agentId}]`);
1330
+ configLines.push(`model_instructions_file = ".agents/agents/${agent.id}.md"`);
1331
+ if (model) configLines.push(`model = "${model}"`);
1332
+ configLines.push("");
1333
+ }
1334
+ }
1335
+ if (manifest.features.mcp && manifest.mcp.servers.length > 0) {
1336
+ const mcpPath = join6(agentsDir, "mcp", "mcp.json");
1337
+ try {
1338
+ const mcpRaw = await readFile6(mcpPath, "utf-8");
1339
+ const mcpParsed = JSON.parse(mcpRaw);
1340
+ if (mcpParsed.mcpServers) {
1341
+ for (const [name, server] of Object.entries(mcpParsed.mcpServers)) {
1342
+ if (server._disabled) continue;
1343
+ configLines.push(`[mcp_servers.${name}]`);
1344
+ if (server.command) {
1345
+ configLines.push(`command = "${server.command}"`);
1346
+ if (server.args && server.args.length > 0) {
1347
+ const argsStr = server.args.map((a) => `"${a}"`).join(", ");
1348
+ configLines.push(`args = [${argsStr}]`);
1349
+ }
1350
+ } else if (server.url) {
1351
+ configLines.push(`url = "${server.url}"`);
1352
+ }
1353
+ if (server.env) {
1354
+ for (const [k, v] of Object.entries(server.env)) {
1355
+ configLines.push(`env.${k} = "${v}"`);
1356
+ }
1357
+ }
1358
+ configLines.push("");
1359
+ }
1360
+ }
1361
+ } catch (err) {
1362
+ console.warn(chalk7.yellow(` Warning: Could not read MCP config: ${err instanceof Error ? err.message : String(err)}`));
1363
+ }
1364
+ }
1365
+ results.push(output(".codex/config.toml", configLines.join("\n")));
1366
+ if (manifest.features.skills) {
1367
+ const skills = await readCanonicalFiles(agentsDir, "skills");
1368
+ for (const skill of skills) {
1369
+ const { content, skip } = await applyCustomizationRaw(projectRoot, skill);
1370
+ if (skip) continue;
1371
+ results.push(
1372
+ output(
1373
+ `.codex/skills/${toPrefixedId(skill.id)}/SKILL.md`,
1374
+ wrapInManagedBlock(content),
1375
+ content
1376
+ )
1377
+ );
1378
+ }
1379
+ }
1380
+ return results;
1381
+ }
1382
+ };
1383
+
1384
+ // src/adapters/copilot.ts
1385
+ import { existsSync } from "fs";
1386
+ import { readFile as readFile7 } from "fs/promises";
1387
+ import { dirname as dirname6, join as join7 } from "path";
1388
+ import chalk8 from "chalk";
1389
+ function detectInstallCommand(projectRoot) {
1390
+ if (existsSync(join7(projectRoot, "bun.lockb"))) return { install: "bun install", build: "bun run build" };
1391
+ if (existsSync(join7(projectRoot, "pnpm-lock.yaml"))) return { install: "pnpm install --frozen-lockfile", build: "pnpm run build" };
1392
+ if (existsSync(join7(projectRoot, "yarn.lock"))) return { install: "yarn install --frozen-lockfile", build: "yarn build" };
1393
+ return { install: "npm ci", build: "npm run build" };
1394
+ }
1395
+ var CopilotAdapter = class {
1396
+ name = "copilot";
1397
+ async generate(agentsDir, manifest) {
1398
+ const results = [];
1399
+ const { features } = manifest;
1400
+ const projectRoot = dirname6(agentsDir);
1401
+ const alwaysRules = [];
1402
+ const scopedRules = [];
1403
+ if (features.rules) {
1404
+ const rules = await readCanonicalFiles(agentsDir, "rules");
1405
+ for (const rule of rules) {
1406
+ const { content, skip, overrides } = await applyCustomization(projectRoot, rule);
1407
+ if (skip) continue;
1408
+ const scope = overrides.scope ?? rule.scope;
1409
+ if (scope && scope !== "always") {
1410
+ scopedRules.push({ rule: { ...rule, description: overrides.description ?? rule.description }, content, scope });
1411
+ } else {
1412
+ alwaysRules.push({ rule: { ...rule, description: overrides.description ?? rule.description }, content });
1413
+ }
1414
+ }
1415
+ }
1416
+ const innerContent = [
1417
+ "",
1418
+ "# Hatch3r Project Instructions",
1419
+ "",
1420
+ "Full canonical agent instructions are at `/.agents/AGENTS.md`.",
1421
+ "",
1422
+ BRIDGE_ORCHESTRATION,
1423
+ "",
1424
+ "## Hatch3r Rules",
1425
+ "",
1426
+ ...alwaysRules.map(
1427
+ (r) => `### ${r.rule.id}
1428
+
1429
+ ${r.rule.description}
1430
+
1431
+ ${r.content}`
1432
+ ),
1433
+ ""
1434
+ ].join("\n");
1435
+ results.push(
1436
+ output(".github/copilot-instructions.md", wrapInManagedBlock(innerContent), innerContent)
1437
+ );
1438
+ const { install, build } = detectInstallCommand(projectRoot);
1439
+ const copilotSetupSteps = `name: "Copilot Setup Steps"
1440
+ on: [push]
1441
+ jobs:
1442
+ setup:
1443
+ runs-on: ubuntu-latest
1444
+ steps:
1445
+ - uses: actions/checkout@v4
1446
+ - name: Install dependencies
1447
+ run: ${install}
1448
+ - name: Build
1449
+ run: ${build}
1450
+ `;
1451
+ results.push(output(".github/workflows/copilot-setup-steps.yml", copilotSetupSteps));
1452
+ for (const { rule, content, scope } of scopedRules) {
1453
+ const globs = scope.includes(",") ? scope.split(",").map((g) => g.trim()) : [scope];
1454
+ const applyTo = globs.join(", ");
1455
+ const fm = `---
1456
+ applyTo: "${applyTo}"
1457
+ ---`;
1458
+ const body = `# ${rule.id}
1459
+
1460
+ ${rule.description}
1461
+
1462
+ ${content}`;
1463
+ results.push(
1464
+ output(
1465
+ `.github/instructions/${toPrefixedId(rule.id)}.instructions.md`,
1466
+ `${fm}
1467
+
1468
+ ${wrapInManagedBlock(body)}`,
1469
+ body
1470
+ )
1471
+ );
1472
+ }
1473
+ if (features.agents) {
1474
+ const agents = await readCanonicalFiles(agentsDir, "agents");
1475
+ for (const agent of agents) {
1476
+ const { content, skip, overrides } = await applyCustomization(projectRoot, agent);
1477
+ if (skip) continue;
1478
+ const model = resolveAgentModel(agent.id, agent, manifest, overrides);
1479
+ const desc = overrides.description ?? agent.description;
1480
+ const lines = [
1481
+ `name: ${agent.id}`,
1482
+ `description: ${desc}`
1483
+ ];
1484
+ if (model) lines.push(`model: ${model}`);
1485
+ const fm = `---
1486
+ ${lines.join("\n")}
1487
+ ---`;
1488
+ results.push(
1489
+ output(
1490
+ `.github/agents/${toPrefixedId(agent.id)}.md`,
1491
+ `${fm}
1492
+
1493
+ ${wrapInManagedBlock(content)}`,
1494
+ content
1495
+ )
1496
+ );
1497
+ }
1498
+ }
1499
+ if (features.prompts) {
1500
+ const prompts = await readCanonicalFiles(agentsDir, "prompts");
1501
+ for (const prompt of prompts) {
1502
+ const body = prompt.rawContent;
1503
+ results.push(
1504
+ output(
1505
+ `.github/prompts/${toPrefixedId(prompt.id)}.prompt.md`,
1506
+ wrapInManagedBlock(body),
1507
+ body
1508
+ )
1509
+ );
1510
+ }
1511
+ }
1512
+ if (features.commands) {
1513
+ const commands = await readCanonicalFiles(agentsDir, "commands");
1514
+ for (const cmd of commands) {
1515
+ const { content, skip } = await applyCustomizationRaw(projectRoot, cmd);
1516
+ if (skip) continue;
1517
+ results.push(
1518
+ output(
1519
+ `.github/copilot/commands/${toPrefixedId(cmd.id)}.prompt.md`,
1520
+ wrapInManagedBlock(content),
1521
+ content
1522
+ )
1523
+ );
1524
+ }
1525
+ }
1526
+ if (features.githubAgents) {
1527
+ const ghAgents = await readCanonicalFiles(agentsDir, "github-agents");
1528
+ for (const agent of ghAgents) {
1529
+ const body = agent.rawContent;
1530
+ results.push(
1531
+ output(
1532
+ `.github/copilot/agents/${toPrefixedId(agent.id)}.md`,
1533
+ wrapInManagedBlock(body),
1534
+ body
1535
+ )
1536
+ );
1537
+ }
1538
+ }
1539
+ if (features.skills) {
1540
+ const skills = await readCanonicalFiles(agentsDir, "skills");
1541
+ for (const skill of skills) {
1542
+ const { content, skip, overrides } = await applyCustomization(projectRoot, skill);
1543
+ if (skip) continue;
1544
+ const desc = overrides.description ?? skill.description;
1545
+ const fm = `---
1546
+ name: ${skill.id}
1547
+ description: ${desc}
1548
+ ---`;
1549
+ results.push(
1550
+ output(
1551
+ `.github/skills/${toPrefixedId(skill.id)}/SKILL.md`,
1552
+ `${fm}
1553
+
1554
+ ${wrapInManagedBlock(content)}`,
1555
+ content
1556
+ )
1557
+ );
1558
+ }
1559
+ }
1560
+ if (features.mcp && manifest.mcp.servers.length > 0) {
1561
+ const mcpPath = join7(agentsDir, "mcp", "mcp.json");
1562
+ try {
1563
+ const mcpRaw = await readFile7(mcpPath, "utf-8");
1564
+ const mcpParsed = JSON.parse(mcpRaw);
1565
+ if (mcpParsed.mcpServers) {
1566
+ for (const server of Object.values(mcpParsed.mcpServers)) {
1567
+ if (server.command) {
1568
+ server.envFile = "${workspaceFolder}/.env.mcp";
1569
+ }
1570
+ }
1571
+ }
1572
+ results.push(
1573
+ output(
1574
+ ".vscode/mcp.json",
1575
+ JSON.stringify(mcpParsed, null, 2) + "\n"
1576
+ )
1577
+ );
1578
+ } catch (err) {
1579
+ console.warn(chalk8.yellow(` Warning: Could not read MCP config: ${err instanceof Error ? err.message : String(err)}`));
1580
+ }
1581
+ }
1582
+ return results;
1583
+ }
1584
+ };
1585
+
1586
+ // src/adapters/cursor.ts
1587
+ import { readFile as readFile8 } from "fs/promises";
1588
+ import { dirname as dirname7, join as join8 } from "path";
1589
+ import chalk9 from "chalk";
1590
+ function cursorRuleFrontmatter(rule, scopeOverride) {
1591
+ const scope = scopeOverride ?? rule.scope;
1592
+ const lines = [`description: ${rule.description}`];
1593
+ if (scope === "always") {
1594
+ lines.push("alwaysApply: true");
1595
+ } else if (scope) {
1596
+ const globs = scope.includes(",") ? scope.split(",").map((g) => g.trim()) : [scope];
1597
+ lines.push(`globs: [${globs.map((g) => `"${g}"`).join(", ")}]`);
1598
+ } else {
1599
+ lines.push("alwaysApply: false");
1600
+ }
1601
+ return `---
1602
+ ${lines.join("\n")}
1603
+ ---`;
1604
+ }
1605
+ function mdcOutput(path, frontmatter, body) {
1606
+ return output(path, `${frontmatter}
1607
+
1608
+ ${wrapInManagedBlock(body)}`, body);
1609
+ }
1610
+ var CursorAdapter = class {
1611
+ name = "cursor";
1612
+ async generate(agentsDir, manifest) {
1613
+ const results = [];
1614
+ const { features } = manifest;
1615
+ const projectRoot = dirname7(agentsDir);
1616
+ if (features.rules) {
1617
+ const rules = await readCanonicalFiles(agentsDir, "rules");
1618
+ for (const rule of rules) {
1619
+ const { content, skip, overrides } = await applyCustomization(projectRoot, rule);
1620
+ if (skip) continue;
1621
+ const desc = overrides.description ?? rule.description;
1622
+ const ruleWithDesc = { ...rule, description: desc };
1623
+ const baseName = `${toPrefixedId(rule.id)}.mdc`;
1624
+ results.push(
1625
+ mdcOutput(`.cursor/rules/${baseName}`, cursorRuleFrontmatter(ruleWithDesc, overrides.scope), content)
1626
+ );
1627
+ }
1628
+ }
1629
+ if (features.agents) {
1630
+ const agents = await readCanonicalFiles(agentsDir, "agents");
1631
+ for (const agent of agents) {
1632
+ const { content, skip, overrides } = await applyCustomization(projectRoot, agent);
1633
+ if (skip) continue;
1634
+ const model = resolveAgentModel(agent.id, agent, manifest, overrides);
1635
+ const desc = overrides.description ?? agent.description;
1636
+ const lines = [
1637
+ `name: ${agent.id}`,
1638
+ `description: ${desc}`
1639
+ ];
1640
+ if (model) lines.push(`model: ${model}`);
1641
+ const fm = `---
1642
+ ${lines.join("\n")}
1643
+ ---`;
1644
+ results.push(
1645
+ mdcOutput(`.cursor/agents/${toPrefixedId(agent.id)}.md`, fm, content)
1646
+ );
1647
+ }
1648
+ }
1649
+ if (features.skills) {
1650
+ const skills = await readCanonicalFiles(agentsDir, "skills");
1651
+ for (const skill of skills) {
1652
+ const { content, skip, overrides } = await applyCustomization(projectRoot, skill);
1653
+ if (skip) continue;
1654
+ const desc = overrides.description ?? skill.description;
1655
+ const fm = `---
1656
+ name: ${skill.id}
1657
+ description: ${desc}
1658
+ ---`;
1659
+ results.push(
1660
+ mdcOutput(`.cursor/skills/${toPrefixedId(skill.id)}/SKILL.md`, fm, content)
1661
+ );
1662
+ }
1663
+ }
1664
+ if (features.commands) {
1665
+ const commands = await readCanonicalFiles(agentsDir, "commands");
1666
+ for (const cmd of commands) {
1667
+ const { content, skip } = await applyCustomizationRaw(projectRoot, cmd);
1668
+ if (skip) continue;
1669
+ results.push(
1670
+ output(
1671
+ `.cursor/commands/${toPrefixedId(cmd.id)}.md`,
1672
+ wrapInManagedBlock(content),
1673
+ content
1674
+ )
1675
+ );
1676
+ }
1677
+ }
1678
+ if (features.mcp && manifest.mcp.servers.length > 0) {
1679
+ const mcpPath = join8(agentsDir, "mcp", "mcp.json");
1680
+ try {
1681
+ const mcpContent = await readFile8(mcpPath, "utf-8");
1682
+ results.push(output(".cursor/mcp.json", mcpContent));
1683
+ } catch (err) {
1684
+ console.warn(chalk9.yellow(` Warning: Could not read MCP config: ${err instanceof Error ? err.message : String(err)}`));
1685
+ }
1686
+ }
1687
+ if (features.hooks) {
1688
+ const { readHookDefinitions } = await import("./hooks-ZOTFDEA3.js");
1689
+ const hooks = await readHookDefinitions(agentsDir);
1690
+ for (const hook of hooks) {
1691
+ const globs = hook.condition?.globs || [];
1692
+ const globLine = globs.length > 0 ? `globs: [${globs.map((g) => `"${g}"`).join(", ")}]` : "alwaysApply: false";
1693
+ const fm = `---
1694
+ description: "Hook: ${hook.description}"
1695
+ ${globLine}
1696
+ ---`;
1697
+ const body = `# Hook: ${hook.id}
1698
+
1699
+ **Event:** ${hook.event}
1700
+ **Agent:** ${hook.agent}
1701
+
1702
+ ${hook.description}
1703
+
1704
+ When this hook's event (${hook.event}) is triggered${globs.length > 0 ? ` for files matching ${globs.join(", ")}` : ""}, activate the ${hook.agent} agent.`;
1705
+ results.push(
1706
+ mdcOutput(`.cursor/rules/${toPrefixedId(`hook-${hook.id}`)}.mdc`, fm, body)
1707
+ );
1708
+ }
1709
+ }
1710
+ const bridgeFm = `---
1711
+ description: Bridge to canonical agent instructions and mandatory orchestration directives
1712
+ alwaysApply: true
1713
+ ---`;
1714
+ const bridgeBody = `# Hatch3r Bridge
1715
+
1716
+ This project uses hatch3r for agentic coding setup.
1717
+ Canonical agent instructions live at \`/.agents/AGENTS.md\`.
1718
+
1719
+ ${BRIDGE_ORCHESTRATION}`;
1720
+ results.push(mdcOutput(".cursor/rules/hatch3r-bridge.mdc", bridgeFm, bridgeBody));
1721
+ if (manifest.tools.includes("cursor")) {
1722
+ const envConfig = {
1723
+ instructions: ["Read /.agents/AGENTS.md for project instructions"],
1724
+ mcpServers: {}
1725
+ };
1726
+ results.push(output(".cursor/environment.json", JSON.stringify(envConfig, null, 2) + "\n"));
1727
+ }
1728
+ return results;
1729
+ }
1730
+ };
1731
+
1732
+ // src/adapters/gemini.ts
1733
+ import { readFile as readFile9 } from "fs/promises";
1734
+ import { dirname as dirname8, join as join9 } from "path";
1735
+ import chalk10 from "chalk";
1736
+ function escapeTomlString(s) {
1737
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
1738
+ }
1739
+ function mapToGeminiEvent(event) {
1740
+ const mapping = {
1741
+ "pre-commit": "BeforeTool",
1742
+ "post-merge": "AfterTool",
1743
+ "ci-failure": "AfterAgent",
1744
+ "file-save": "AfterTool",
1745
+ "session-start": "SessionStart",
1746
+ "pre-push": "BeforeTool"
1747
+ };
1748
+ return mapping[event] || event;
1749
+ }
1750
+ var GeminiAdapter = class {
1751
+ name = "gemini";
1752
+ async generate(agentsDir, manifest) {
1753
+ const results = [];
1754
+ const { features } = manifest;
1755
+ const projectRoot = dirname8(agentsDir);
1756
+ const geminiMdLines = [
1757
+ "",
1758
+ "# Hatch3r Agent Instructions",
1759
+ "",
1760
+ "Full canonical agent instructions are at `.agents/AGENTS.md`.",
1761
+ "",
1762
+ BRIDGE_ORCHESTRATION,
1763
+ ""
1764
+ ];
1765
+ if (features.rules) {
1766
+ const rules = await readCanonicalFiles(agentsDir, "rules");
1767
+ for (const rule of rules) {
1768
+ const { content, skip, overrides } = await applyCustomization(projectRoot, rule);
1769
+ if (skip) continue;
1770
+ const desc = overrides.description ?? rule.description;
1771
+ geminiMdLines.push(`## ${rule.id}`);
1772
+ geminiMdLines.push("");
1773
+ geminiMdLines.push(desc);
1774
+ geminiMdLines.push("");
1775
+ geminiMdLines.push(content);
1776
+ geminiMdLines.push("");
1777
+ }
1778
+ }
1779
+ if (features.agents) {
1780
+ const agents = await readCanonicalFiles(agentsDir, "agents");
1781
+ for (const agent of agents) {
1782
+ const { content, skip, overrides } = await applyCustomization(projectRoot, agent);
1783
+ if (skip) continue;
1784
+ const model = resolveAgentModel(agent.id, agent, manifest, overrides);
1785
+ const desc = overrides.description ?? agent.description;
1786
+ geminiMdLines.push(`## Agent: ${agent.id}`);
1787
+ geminiMdLines.push("");
1788
+ geminiMdLines.push(desc);
1789
+ geminiMdLines.push("");
1790
+ geminiMdLines.push(content);
1791
+ if (model) {
1792
+ geminiMdLines.push("");
1793
+ geminiMdLines.push(`**Recommended model:** \`${model}\`. Set via \`gemini --model ${model}\` or select in Google AI Studio.`);
1794
+ }
1795
+ geminiMdLines.push("");
1796
+ }
1797
+ }
1798
+ const geminiInner = geminiMdLines.join("\n");
1799
+ results.push(output("GEMINI.md", wrapInManagedBlock(geminiInner), geminiInner));
1800
+ const settings = {
1801
+ context: {
1802
+ fileName: ["GEMINI.md", "AGENTS.md"]
1803
+ }
1804
+ };
1805
+ if (features.mcp && manifest.mcp.servers.length > 0) {
1806
+ const mcpPath = join9(agentsDir, "mcp", "mcp.json");
1807
+ try {
1808
+ const mcpRaw = await readFile9(mcpPath, "utf-8");
1809
+ const mcpParsed = JSON.parse(mcpRaw);
1810
+ if (mcpParsed.mcpServers) {
1811
+ const geminiMcp = {};
1812
+ for (const [name, server] of Object.entries(mcpParsed.mcpServers)) {
1813
+ if (server._disabled) continue;
1814
+ if (server.command) {
1815
+ geminiMcp[name] = {
1816
+ command: server.command,
1817
+ args: server.args || [],
1818
+ ...server.env && Object.keys(server.env).length > 0 ? { env: server.env } : {}
1819
+ };
1820
+ } else if (server.url) {
1821
+ geminiMcp[name] = { url: server.url };
1822
+ }
1823
+ }
1824
+ if (Object.keys(geminiMcp).length > 0) {
1825
+ settings.mcpServers = geminiMcp;
1826
+ }
1827
+ }
1828
+ } catch (err) {
1829
+ console.warn(chalk10.yellow(` Warning: Could not read MCP config: ${err instanceof Error ? err.message : String(err)}`));
1830
+ }
1831
+ }
1832
+ if (features.hooks) {
1833
+ const { readHookDefinitions } = await import("./hooks-ZOTFDEA3.js");
1834
+ const hooks = await readHookDefinitions(agentsDir);
1835
+ if (hooks.length > 0) {
1836
+ const hooksObj = {};
1837
+ for (const hook of hooks) {
1838
+ const geminiEvent = mapToGeminiEvent(hook.event);
1839
+ if (!hooksObj[geminiEvent]) {
1840
+ hooksObj[geminiEvent] = [];
1841
+ }
1842
+ const matcher = hook.condition?.globs?.join("|") || ".*";
1843
+ hooksObj[geminiEvent].push({
1844
+ matcher,
1845
+ hooks: [
1846
+ {
1847
+ type: "command",
1848
+ command: `echo "hatch3r hook: ${hook.id} \u2014 activate ${hook.agent} agent"`
1849
+ }
1850
+ ]
1851
+ });
1852
+ }
1853
+ settings.hooks = hooksObj;
1854
+ }
1855
+ }
1856
+ results.push(output(".gemini/settings.json", JSON.stringify(settings, null, 2)));
1857
+ if (features.skills) {
1858
+ const skills = await readCanonicalFiles(agentsDir, "skills");
1859
+ for (const skill of skills) {
1860
+ const { content, skip } = await applyCustomizationRaw(projectRoot, skill);
1861
+ if (skip) continue;
1862
+ results.push(
1863
+ output(
1864
+ `.gemini/skills/${toPrefixedId(skill.id)}/SKILL.md`,
1865
+ wrapInManagedBlock(content),
1866
+ content
1867
+ )
1868
+ );
1869
+ }
1870
+ }
1871
+ if (features.commands) {
1872
+ const commands = await readCanonicalFiles(agentsDir, "commands");
1873
+ for (const cmd of commands) {
1874
+ const { content, skip, overrides } = await applyCustomization(projectRoot, cmd);
1875
+ if (skip) continue;
1876
+ const desc = overrides.description ?? cmd.description;
1877
+ const toml = [
1878
+ `description = "${escapeTomlString(desc)}"`,
1879
+ `prompt = "${escapeTomlString(content)}"`
1880
+ ].join("\n");
1881
+ results.push(
1882
+ output(`.gemini/commands/${toPrefixedId(cmd.id)}.toml`, toml)
1883
+ );
1884
+ }
1885
+ }
1886
+ return results;
1887
+ }
1888
+ };
1889
+
1890
+ // src/adapters/goose.ts
1891
+ import { dirname as dirname9 } from "path";
1892
+ var GooseAdapter = class {
1893
+ name = "goose";
1894
+ async generate(agentsDir, manifest) {
1895
+ const results = [];
1896
+ const { features } = manifest;
1897
+ const projectRoot = dirname9(agentsDir);
1898
+ const hintLines = [
1899
+ "",
1900
+ "# Hatch3r Agent Instructions",
1901
+ "",
1902
+ "Full canonical agent instructions are at `/.agents/AGENTS.md`.",
1903
+ "",
1904
+ BRIDGE_ORCHESTRATION,
1905
+ ""
1906
+ ];
1907
+ if (features.rules) {
1908
+ const rules = await readCanonicalFiles(agentsDir, "rules");
1909
+ for (const rule of rules) {
1910
+ const { content, skip, overrides } = await applyCustomization(projectRoot, rule);
1911
+ if (skip) continue;
1912
+ const desc = overrides.description ?? rule.description;
1913
+ hintLines.push(`## ${rule.id}`);
1914
+ hintLines.push("");
1915
+ hintLines.push(desc);
1916
+ hintLines.push("");
1917
+ hintLines.push(content);
1918
+ hintLines.push("");
1919
+ }
1920
+ }
1921
+ if (features.agents) {
1922
+ const agents = await readCanonicalFiles(agentsDir, "agents");
1923
+ for (const agent of agents) {
1924
+ const { content, skip, overrides } = await applyCustomization(projectRoot, agent);
1925
+ if (skip) continue;
1926
+ const model = resolveAgentModel(agent.id, agent, manifest, overrides);
1927
+ const desc = overrides.description ?? agent.description;
1928
+ hintLines.push(`## Agent: ${agent.id}`);
1929
+ if (model) hintLines.push(`**Recommended model:** \`${model}\``);
1930
+ hintLines.push("");
1931
+ hintLines.push(desc);
1932
+ hintLines.push("");
1933
+ hintLines.push(content);
1934
+ hintLines.push("");
1935
+ }
1936
+ }
1937
+ if (features.skills) {
1938
+ const skills = await readCanonicalFiles(agentsDir, "skills");
1939
+ for (const skill of skills) {
1940
+ const { content, skip } = await applyCustomizationRaw(projectRoot, skill);
1941
+ if (skip) continue;
1942
+ hintLines.push(`## Skill: ${toPrefixedId(skill.id)}`);
1943
+ hintLines.push("");
1944
+ hintLines.push(content);
1945
+ hintLines.push("");
1946
+ }
1947
+ }
1948
+ const hintInner = hintLines.join("\n");
1949
+ results.push(output(".goosehints", wrapInManagedBlock(hintInner), hintInner));
1950
+ return results;
1951
+ }
1952
+ };
1953
+
1954
+ // src/adapters/kiro.ts
1955
+ import { readFile as readFile10 } from "fs/promises";
1956
+ import { dirname as dirname10, join as join10 } from "path";
1957
+ import chalk11 from "chalk";
1958
+ function steeringFrontmatter(globs) {
1959
+ if (!globs) return "";
1960
+ return `---
1961
+ inclusion: conditional
1962
+ globs: "${globs}"
1963
+ ---
1964
+
1965
+ `;
1966
+ }
1967
+ var KiroAdapter = class {
1968
+ name = "kiro";
1969
+ async generate(agentsDir, manifest) {
1970
+ const results = [];
1971
+ const { features } = manifest;
1972
+ const projectRoot = dirname10(agentsDir);
1973
+ const steeringLines = [
1974
+ "",
1975
+ "# Hatch3r Agent Instructions",
1976
+ "",
1977
+ "Full canonical agent instructions are at `/.agents/AGENTS.md`.",
1978
+ "",
1979
+ BRIDGE_ORCHESTRATION,
1980
+ ""
1981
+ ];
1982
+ if (features.rules) {
1983
+ const rules = await readCanonicalFiles(agentsDir, "rules");
1984
+ for (const rule of rules) {
1985
+ const { content, skip, overrides } = await applyCustomization(projectRoot, rule);
1986
+ if (skip) continue;
1987
+ const scope = overrides.scope ?? rule.scope;
1988
+ const desc = overrides.description ?? rule.description;
1989
+ if (scope && scope !== "always") {
1990
+ const globs = scope.includes("*") ? scope : `${scope}/**`;
1991
+ const fm = steeringFrontmatter(globs);
1992
+ const body = `# ${rule.id}
1993
+
1994
+ ${desc}
1995
+
1996
+ ${content}`;
1997
+ results.push(
1998
+ output(
1999
+ `.kiro/steering/${toPrefixedId(rule.id)}.md`,
2000
+ `${fm}${wrapInManagedBlock(body)}`,
2001
+ body
2002
+ )
2003
+ );
2004
+ } else {
2005
+ steeringLines.push(`## ${rule.id}`);
2006
+ steeringLines.push("");
2007
+ steeringLines.push(desc);
2008
+ steeringLines.push("");
2009
+ steeringLines.push(content);
2010
+ steeringLines.push("");
2011
+ }
2012
+ }
2013
+ }
2014
+ if (features.agents) {
2015
+ const agents = await readCanonicalFiles(agentsDir, "agents");
2016
+ for (const agent of agents) {
2017
+ const { content, skip, overrides } = await applyCustomization(projectRoot, agent);
2018
+ if (skip) continue;
2019
+ const model = resolveAgentModel(agent.id, agent, manifest, overrides);
2020
+ const desc = overrides.description ?? agent.description;
2021
+ steeringLines.push(`## Agent: ${agent.id}`);
2022
+ if (model) steeringLines.push(`**Recommended model:** \`${model}\``);
2023
+ steeringLines.push("");
2024
+ steeringLines.push(desc);
2025
+ steeringLines.push("");
2026
+ steeringLines.push(content);
2027
+ steeringLines.push("");
2028
+ }
2029
+ }
2030
+ const steeringInner = steeringLines.join("\n");
2031
+ results.push(
2032
+ output(
2033
+ ".kiro/steering/hatch3r-agents.md",
2034
+ wrapInManagedBlock(steeringInner),
2035
+ steeringInner
2036
+ )
2037
+ );
2038
+ if (features.skills) {
2039
+ const skills = await readCanonicalFiles(agentsDir, "skills");
2040
+ for (const skill of skills) {
2041
+ const { content, skip } = await applyCustomizationRaw(projectRoot, skill);
2042
+ if (skip) continue;
2043
+ results.push(
2044
+ output(
2045
+ `.kiro/steering/${toPrefixedId(skill.id)}.md`,
2046
+ wrapInManagedBlock(content),
2047
+ content
2048
+ )
2049
+ );
2050
+ }
2051
+ }
2052
+ if (features.mcp && manifest.mcp.servers.length > 0) {
2053
+ const mcpPath = join10(agentsDir, "mcp", "mcp.json");
2054
+ try {
2055
+ const mcpRaw = await readFile10(mcpPath, "utf-8");
2056
+ const mcpParsed = JSON.parse(mcpRaw);
2057
+ if (mcpParsed.mcpServers) {
2058
+ const kiroMcp = {};
2059
+ for (const [name, server] of Object.entries(mcpParsed.mcpServers)) {
2060
+ if (server._disabled) continue;
2061
+ if (server.command) {
2062
+ kiroMcp[name] = {
2063
+ command: server.command,
2064
+ args: server.args || [],
2065
+ ...server.env && Object.keys(server.env).length > 0 ? { env: server.env } : {}
2066
+ };
2067
+ } else if (server.url) {
2068
+ kiroMcp[name] = { url: server.url };
2069
+ }
2070
+ }
2071
+ if (Object.keys(kiroMcp).length > 0) {
2072
+ results.push(
2073
+ output(
2074
+ ".kiro/settings/mcp.json",
2075
+ JSON.stringify({ mcpServers: kiroMcp }, null, 2)
2076
+ )
2077
+ );
2078
+ }
2079
+ }
2080
+ } catch (err) {
2081
+ console.warn(chalk11.yellow(` Warning: Could not read MCP config: ${err instanceof Error ? err.message : String(err)}`));
2082
+ }
2083
+ }
2084
+ return results;
2085
+ }
2086
+ };
2087
+
2088
+ // src/adapters/opencode.ts
2089
+ import { readFile as readFile11 } from "fs/promises";
2090
+ import { dirname as dirname11, join as join11 } from "path";
2091
+ import chalk12 from "chalk";
2092
+ var OpenCodeAdapter = class {
2093
+ name = "opencode";
2094
+ async generate(agentsDir, manifest) {
2095
+ const results = [];
2096
+ const { features } = manifest;
2097
+ const projectRoot = dirname11(agentsDir);
2098
+ const instructions = [".agents/AGENTS.md"];
2099
+ if (features.rules) instructions.push(".agents/rules/*.md");
2100
+ if (features.agents) instructions.push(".agents/agents/*.md");
2101
+ if (features.skills) instructions.push(".agents/skills/*/SKILL.md");
2102
+ if (features.commands) instructions.push(".agents/commands/*.md");
2103
+ const opencodeConfig = {
2104
+ $schema: "https://opencode.ai/config-schema.json",
2105
+ instructions
2106
+ };
2107
+ if (features.mcp && manifest.mcp.servers.length > 0) {
2108
+ const mcpPath = join11(agentsDir, "mcp", "mcp.json");
2109
+ try {
2110
+ const mcpRaw = await readFile11(mcpPath, "utf-8");
2111
+ const mcpParsed = JSON.parse(mcpRaw);
2112
+ if (mcpParsed.mcpServers) {
2113
+ const mcp = {};
2114
+ for (const [name, server] of Object.entries(mcpParsed.mcpServers)) {
2115
+ if (server._disabled) continue;
2116
+ if (server.command) {
2117
+ const cmd = [server.command, ...server.args || []];
2118
+ mcp[name] = {
2119
+ type: "local",
2120
+ command: cmd,
2121
+ enabled: true,
2122
+ ...server.env && Object.keys(server.env).length > 0 ? { environment: server.env } : {}
2123
+ };
2124
+ } else if (server.url) {
2125
+ mcp[name] = {
2126
+ type: "remote",
2127
+ url: server.url,
2128
+ enabled: true
2129
+ };
2130
+ }
2131
+ }
2132
+ if (Object.keys(mcp).length > 0) {
2133
+ opencodeConfig.mcp = mcp;
2134
+ }
2135
+ }
2136
+ } catch (err) {
2137
+ console.warn(chalk12.yellow(` Warning: Could not read MCP config: ${err instanceof Error ? err.message : String(err)}`));
2138
+ }
2139
+ }
2140
+ results.push(output("opencode.json", JSON.stringify(opencodeConfig, null, 2)));
2141
+ if (features.agents) {
2142
+ const agents = await readCanonicalFiles(agentsDir, "agents");
2143
+ for (const agent of agents) {
2144
+ const { content, skip, overrides } = await applyCustomization(projectRoot, agent);
2145
+ if (skip) continue;
2146
+ const agentId = toPrefixedId(agent.id);
2147
+ const model = resolveAgentModel(agent.id, agent, manifest, overrides);
2148
+ const desc = overrides.description ?? agent.description;
2149
+ const lines = [`description: ${desc}`];
2150
+ if (model) lines.push(`model: ${withProviderPrefix(model)}`);
2151
+ const fm = `---
2152
+ ${lines.join("\n")}
2153
+ ---`;
2154
+ results.push(
2155
+ output(
2156
+ `.opencode/agents/${agentId}.md`,
2157
+ `${fm}
2158
+
2159
+ ${wrapInManagedBlock(content)}`,
2160
+ content
2161
+ )
2162
+ );
2163
+ }
2164
+ }
2165
+ if (features.skills) {
2166
+ const skills = await readCanonicalFiles(agentsDir, "skills");
2167
+ for (const skill of skills) {
2168
+ const { content, skip } = await applyCustomizationRaw(projectRoot, skill);
2169
+ if (skip) continue;
2170
+ results.push(
2171
+ output(
2172
+ `.opencode/skills/${toPrefixedId(skill.id)}/SKILL.md`,
2173
+ wrapInManagedBlock(content),
2174
+ content
2175
+ )
2176
+ );
2177
+ }
2178
+ }
2179
+ if (features.commands) {
2180
+ const commands = await readCanonicalFiles(agentsDir, "commands");
2181
+ for (const cmd of commands) {
2182
+ const { content, skip } = await applyCustomizationRaw(projectRoot, cmd);
2183
+ if (skip) continue;
2184
+ results.push(
2185
+ output(
2186
+ `.opencode/commands/${toPrefixedId(cmd.id)}.md`,
2187
+ wrapInManagedBlock(content),
2188
+ content
2189
+ )
2190
+ );
2191
+ }
2192
+ }
2193
+ return results;
2194
+ }
2195
+ };
2196
+
2197
+ // src/adapters/windsurf.ts
2198
+ import { readFile as readFile12 } from "fs/promises";
2199
+ import { dirname as dirname12, join as join12 } from "path";
2200
+ import chalk13 from "chalk";
2201
+ function isGlobPattern(scope) {
2202
+ return scope.includes("*") || scope.includes("?") || scope.includes("[");
2203
+ }
2204
+ function ruleTrigger(scope) {
2205
+ if (!scope) return "model_decision";
2206
+ if (scope === "always") return "always_on";
2207
+ return "glob_pattern";
2208
+ }
2209
+ var WindsurfAdapter = class {
2210
+ name = "windsurf";
2211
+ async generate(agentsDir, manifest) {
2212
+ const results = [];
2213
+ const { features } = manifest;
2214
+ const projectRoot = dirname12(agentsDir);
2215
+ const windsurfrulesLines = [
2216
+ "",
2217
+ "# Hatch3r Agent Instructions",
2218
+ "",
2219
+ "Full canonical agent instructions are at `/.agents/AGENTS.md`.",
2220
+ "Rules and skills are managed in `.windsurf/rules/` and `.windsurf/skills/`.",
2221
+ "",
2222
+ BRIDGE_ORCHESTRATION,
2223
+ ""
2224
+ ];
2225
+ if (features.agents) {
2226
+ const agents = await readCanonicalFiles(agentsDir, "agents");
2227
+ for (const agent of agents) {
2228
+ const { content, skip, overrides } = await applyCustomization(projectRoot, agent);
2229
+ if (skip) continue;
2230
+ const model = resolveAgentModel(agent.id, agent, manifest, overrides);
2231
+ windsurfrulesLines.push(`## Agent: ${agent.id}`);
2232
+ if (model) windsurfrulesLines.push(`**Recommended model:** \`${model}\``);
2233
+ windsurfrulesLines.push("");
2234
+ windsurfrulesLines.push(overrides.description ?? agent.description);
2235
+ windsurfrulesLines.push("");
2236
+ windsurfrulesLines.push(content);
2237
+ windsurfrulesLines.push("");
2238
+ }
2239
+ }
2240
+ const windsurfInner = windsurfrulesLines.join("\n");
2241
+ results.push(output(".windsurfrules", wrapInManagedBlock(windsurfInner), windsurfInner));
2242
+ if (features.rules) {
2243
+ const rules = await readCanonicalFiles(agentsDir, "rules");
2244
+ for (const rule of rules) {
2245
+ const { content, skip, overrides } = await applyCustomization(projectRoot, rule);
2246
+ if (skip) continue;
2247
+ const scope = overrides.scope ?? rule.scope;
2248
+ const trigger = ruleTrigger(scope);
2249
+ const globScope = trigger === "glob_pattern" && scope ? isGlobPattern(scope) ? scope : `${scope}/**` : void 0;
2250
+ const fm = `<!-- trigger: ${trigger}${globScope ? `, globs: ${globScope}` : ""} -->`;
2251
+ const desc = overrides.description ?? rule.description;
2252
+ const body = `# ${rule.id}
2253
+
2254
+ ${desc}
2255
+
2256
+ ${content}`;
2257
+ results.push(
2258
+ output(
2259
+ `.windsurf/rules/${toPrefixedId(rule.id)}.md`,
2260
+ `${fm}
2261
+
2262
+ ${wrapInManagedBlock(body)}`,
2263
+ body
2264
+ )
2265
+ );
2266
+ }
2267
+ }
2268
+ if (features.skills) {
2269
+ const skills = await readCanonicalFiles(agentsDir, "skills");
2270
+ for (const skill of skills) {
2271
+ const { content, skip, overrides } = await applyCustomization(projectRoot, skill);
2272
+ if (skip) continue;
2273
+ const desc = overrides.description ?? skill.description;
2274
+ const fm = `---
2275
+ name: ${skill.id}
2276
+ description: ${desc}
2277
+ ---`;
2278
+ results.push(
2279
+ output(
2280
+ `.windsurf/skills/${toPrefixedId(skill.id)}/SKILL.md`,
2281
+ `${fm}
2282
+
2283
+ ${wrapInManagedBlock(content)}`,
2284
+ content
2285
+ )
2286
+ );
2287
+ }
2288
+ }
2289
+ if (features.commands) {
2290
+ const commands = await readCanonicalFiles(agentsDir, "commands");
2291
+ for (const cmd of commands) {
2292
+ const { content, skip } = await applyCustomizationRaw(projectRoot, cmd);
2293
+ if (skip) continue;
2294
+ results.push(
2295
+ output(
2296
+ `.windsurf/workflows/${toPrefixedId(cmd.id)}.md`,
2297
+ wrapInManagedBlock(content),
2298
+ content
2299
+ )
2300
+ );
2301
+ }
2302
+ }
2303
+ if (features.mcp && manifest.mcp.servers.length > 0) {
2304
+ const mcpPath = join12(agentsDir, "mcp", "mcp.json");
2305
+ try {
2306
+ const mcpRaw = await readFile12(mcpPath, "utf-8");
2307
+ const mcpParsed = JSON.parse(mcpRaw);
2308
+ if (mcpParsed.mcpServers) {
2309
+ results.push(
2310
+ output(".windsurf/mcp.json", JSON.stringify(mcpParsed, null, 2) + "\n")
2311
+ );
2312
+ }
2313
+ } catch (err) {
2314
+ console.warn(chalk13.yellow(` Warning: Could not read MCP config: ${err instanceof Error ? err.message : String(err)}`));
2315
+ }
2316
+ }
2317
+ return results;
2318
+ }
2319
+ };
2320
+
2321
+ // src/adapters/zed.ts
2322
+ import { dirname as dirname13 } from "path";
2323
+ var ZedAdapter = class {
2324
+ name = "zed";
2325
+ async generate(agentsDir, manifest) {
2326
+ const results = [];
2327
+ const { features } = manifest;
2328
+ const projectRoot = dirname13(agentsDir);
2329
+ const rulesLines = [
2330
+ "",
2331
+ "# Hatch3r Agent Instructions",
2332
+ "",
2333
+ "Full canonical agent instructions are at `/.agents/AGENTS.md`.",
2334
+ "",
2335
+ BRIDGE_ORCHESTRATION,
2336
+ ""
2337
+ ];
2338
+ if (features.rules) {
2339
+ const rules = await readCanonicalFiles(agentsDir, "rules");
2340
+ for (const rule of rules) {
2341
+ const { content, skip, overrides } = await applyCustomization(projectRoot, rule);
2342
+ if (skip) continue;
2343
+ const desc = overrides.description ?? rule.description;
2344
+ rulesLines.push(`## ${rule.id}`);
2345
+ rulesLines.push("");
2346
+ rulesLines.push(desc);
2347
+ rulesLines.push("");
2348
+ rulesLines.push(content);
2349
+ rulesLines.push("");
2350
+ }
2351
+ }
2352
+ if (features.agents) {
2353
+ const agents = await readCanonicalFiles(agentsDir, "agents");
2354
+ for (const agent of agents) {
2355
+ const { content, skip, overrides } = await applyCustomization(projectRoot, agent);
2356
+ if (skip) continue;
2357
+ const model = resolveAgentModel(agent.id, agent, manifest, overrides);
2358
+ const desc = overrides.description ?? agent.description;
2359
+ rulesLines.push(`## Agent: ${agent.id}`);
2360
+ if (model) rulesLines.push(`**Recommended model:** \`${model}\``);
2361
+ rulesLines.push("");
2362
+ rulesLines.push(desc);
2363
+ rulesLines.push("");
2364
+ rulesLines.push(content);
2365
+ rulesLines.push("");
2366
+ }
2367
+ }
2368
+ const rulesInner = rulesLines.join("\n");
2369
+ results.push(output(".rules", wrapInManagedBlock(rulesInner), rulesInner));
2370
+ return results;
2371
+ }
2372
+ };
2373
+
2374
+ // src/adapters/index.ts
2375
+ var adapters = {
2376
+ cursor: new CursorAdapter(),
2377
+ copilot: new CopilotAdapter(),
2378
+ claude: new ClaudeAdapter(),
2379
+ opencode: new OpenCodeAdapter(),
2380
+ windsurf: new WindsurfAdapter(),
2381
+ amp: new AmpAdapter(),
2382
+ codex: new CodexAdapter(),
2383
+ gemini: new GeminiAdapter(),
2384
+ cline: new ClineAdapter(),
2385
+ aider: new AiderAdapter(),
2386
+ kiro: new KiroAdapter(),
2387
+ goose: new GooseAdapter(),
2388
+ zed: new ZedAdapter()
2389
+ };
2390
+ function getAdapter(tool) {
2391
+ const adapter = adapters[tool];
2392
+ if (!adapter) {
2393
+ throw new Error(`Unknown tool: ${tool}`);
2394
+ }
2395
+ return adapter;
2396
+ }
2397
+
2398
+ // src/manifest/hatchJson.ts
2399
+ import { readFile as readFile13, writeFile } from "fs/promises";
2400
+ import { join as join13 } from "path";
2401
+ import chalk14 from "chalk";
2402
+ function createMinimalBoardConfig(owner, repo, defaultBranch) {
2403
+ return {
2404
+ owner,
2405
+ repo,
2406
+ defaultBranch,
2407
+ projectNumber: null,
2408
+ statusFieldId: null,
2409
+ statusOptions: {
2410
+ backlog: null,
2411
+ ready: null,
2412
+ inProgress: null,
2413
+ inReview: null,
2414
+ done: null
2415
+ },
2416
+ labels: {
2417
+ types: ["type:bug", "type:feature", "type:refactor", "type:qa", "type:docs", "type:infra"],
2418
+ executors: ["executor:agent", "executor:human", "executor:hybrid"],
2419
+ statuses: ["status:triage", "status:ready", "status:in-progress", "status:in-review", "status:blocked"],
2420
+ meta: ["meta:board-overview"]
2421
+ },
2422
+ branchConvention: "{type}/{short-description}",
2423
+ areas: []
2424
+ };
2425
+ }
2426
+ function createManifest(options) {
2427
+ const owner = options.owner ?? "";
2428
+ const repo = options.repo ?? "";
2429
+ const manifest = {
2430
+ version: "1.0.0",
2431
+ hatch3rVersion: HATCH3R_VERSION,
2432
+ owner,
2433
+ repo,
2434
+ tools: options.tools,
2435
+ features: { ...DEFAULT_FEATURES, ...options.features },
2436
+ mcp: { servers: options.mcpServers ?? [] },
2437
+ managedFiles: []
2438
+ };
2439
+ if (options.defaultBranch) {
2440
+ manifest.board = createMinimalBoardConfig(owner, repo, options.defaultBranch);
2441
+ }
2442
+ return manifest;
2443
+ }
2444
+ function validateManifest(data) {
2445
+ if (!data || typeof data !== "object") return false;
2446
+ const obj = data;
2447
+ return typeof obj.version === "string" && typeof obj.hatch3rVersion === "string" && Array.isArray(obj.tools) && obj.features !== null && typeof obj.features === "object" && obj.mcp !== null && typeof obj.mcp === "object" && Array.isArray(obj.managedFiles);
2448
+ }
2449
+ async function readManifest(rootDir) {
2450
+ const manifestPath = join13(rootDir, AGENTS_DIR, MANIFEST_FILE);
2451
+ let raw;
2452
+ try {
2453
+ raw = await readFile13(manifestPath, "utf-8");
2454
+ } catch (err) {
2455
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
2456
+ return null;
2457
+ }
2458
+ throw err;
2459
+ }
2460
+ let parsed;
2461
+ try {
2462
+ parsed = JSON.parse(raw);
2463
+ } catch (err) {
2464
+ throw new Error(
2465
+ `Malformed JSON in ${manifestPath}: ${err instanceof Error ? err.message : String(err)}`
2466
+ );
2467
+ }
2468
+ if (!validateManifest(parsed)) {
2469
+ console.error(chalk14.red("Invalid .agents/hatch.json: missing required fields (version, hatch3rVersion, tools, features, managedFiles)"));
2470
+ return null;
2471
+ }
2472
+ return parsed;
2473
+ }
2474
+ async function writeManifest(rootDir, manifest) {
2475
+ const manifestPath = join13(rootDir, AGENTS_DIR, MANIFEST_FILE);
2476
+ await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
2477
+ }
2478
+ function addManagedFile(manifest, filePath) {
2479
+ if (!manifest.managedFiles.includes(filePath)) {
2480
+ manifest.managedFiles.push(filePath);
2481
+ }
2482
+ }
2483
+
2484
+ // src/merge/safeWrite.ts
2485
+ import {
2486
+ readFile as readFile14,
2487
+ writeFile as writeFile2,
2488
+ mkdir,
2489
+ access,
2490
+ copyFile
2491
+ } from "fs/promises";
2492
+ import { join as join14, dirname as dirname14, basename } from "path";
2493
+ async function fileExists(path) {
2494
+ try {
2495
+ await access(path);
2496
+ return true;
2497
+ } catch {
2498
+ return false;
2499
+ }
2500
+ }
2501
+ async function createBackup(filePath) {
2502
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2503
+ const backupDir = join14(dirname14(filePath), ".backups");
2504
+ await mkdir(backupDir, { recursive: true });
2505
+ const backupPath = join14(backupDir, `${timestamp}_${basename(filePath)}`);
2506
+ await copyFile(filePath, backupPath);
2507
+ return backupPath;
2508
+ }
2509
+ async function writeWithBackup(filePath, content, shouldBackup) {
2510
+ if (shouldBackup) {
2511
+ const backup = await createBackup(filePath);
2512
+ await writeFile2(filePath, content, "utf-8");
2513
+ return { path: filePath, action: "backed-up", backup };
2514
+ }
2515
+ await writeFile2(filePath, content, "utf-8");
2516
+ return { path: filePath, action: "updated" };
2517
+ }
2518
+ async function safeWriteFile(filePath, content, options = {}) {
2519
+ await mkdir(dirname14(filePath), { recursive: true });
2520
+ const exists = await fileExists(filePath);
2521
+ if (!exists) {
2522
+ await writeFile2(filePath, content, "utf-8");
2523
+ return { path: filePath, action: "created" };
2524
+ }
2525
+ const existingContent = await readFile14(filePath, "utf-8");
2526
+ if (options.managedContent) {
2527
+ if (!hasManagedBlock(existingContent)) {
2528
+ if (options.appendIfNoBlock) {
2529
+ const prepended = [content.trim(), "", existingContent.trimStart()].join("\n");
2530
+ return writeWithBackup(filePath, prepended, !!options.backup);
2531
+ }
2532
+ return { path: filePath, action: "skipped" };
2533
+ }
2534
+ const merged = insertManagedBlock(existingContent, options.managedContent);
2535
+ return writeWithBackup(filePath, merged, !!options.backup);
2536
+ }
2537
+ const fileName = basename(filePath) ?? "";
2538
+ const isManagedFile = fileName.startsWith(HATCH3R_PREFIX);
2539
+ if (isManagedFile) {
2540
+ return writeWithBackup(filePath, content, !!options.backup);
2541
+ }
2542
+ return { path: filePath, action: "skipped" };
2543
+ }
2544
+
2545
+ // src/detect/repoAnalyzer.ts
2546
+ import { access as access2, readFile as readFile15, readdir as readdir2 } from "fs/promises";
2547
+ import { join as join15 } from "path";
2548
+ async function analyzeRepo(rootDir) {
2549
+ const [languages, packageManager, isMonorepo, hasExistingAgents, existingTools] = await Promise.all([
2550
+ detectLanguages(rootDir),
2551
+ detectPackageManager(rootDir),
2552
+ detectMonorepo(rootDir),
2553
+ detectExistingAgents(rootDir),
2554
+ detectExistingTools(rootDir)
2555
+ ]);
2556
+ return {
2557
+ languages,
2558
+ packageManager,
2559
+ isMonorepo,
2560
+ hasExistingAgents,
2561
+ existingTools,
2562
+ rootDir
2563
+ };
2564
+ }
2565
+ async function detectLanguages(rootDir) {
2566
+ const languages = [];
2567
+ const indicators = {
2568
+ typescript: ["tsconfig.json", "tsconfig.base.json"],
2569
+ javascript: ["jsconfig.json"],
2570
+ python: ["pyproject.toml", "setup.py", "requirements.txt", "Pipfile"],
2571
+ rust: ["Cargo.toml"],
2572
+ go: ["go.mod"],
2573
+ java: ["pom.xml", "build.gradle"],
2574
+ kotlin: ["build.gradle.kts"],
2575
+ ruby: ["Gemfile"],
2576
+ php: ["composer.json"],
2577
+ swift: ["Package.swift"]
2578
+ };
2579
+ for (const [lang, files] of Object.entries(indicators)) {
2580
+ for (const file of files) {
2581
+ if (await pathExists(join15(rootDir, file))) {
2582
+ languages.push(lang);
2583
+ break;
2584
+ }
2585
+ }
2586
+ }
2587
+ try {
2588
+ const rootEntries = await readdir2(rootDir);
2589
+ if (rootEntries.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
2590
+ languages.push("csharp");
2591
+ }
2592
+ } catch {
2593
+ }
2594
+ if (languages.length === 0) {
2595
+ languages.push("unknown");
2596
+ }
2597
+ return languages;
2598
+ }
2599
+ async function detectPackageManager(rootDir) {
2600
+ if (await pathExists(join15(rootDir, "bun.lockb"))) return "bun";
2601
+ if (await pathExists(join15(rootDir, "pnpm-lock.yaml"))) return "pnpm";
2602
+ if (await pathExists(join15(rootDir, "yarn.lock"))) return "yarn";
2603
+ if (await pathExists(join15(rootDir, "package-lock.json"))) return "npm";
2604
+ if (await pathExists(join15(rootDir, "package.json"))) return "npm";
2605
+ return "unknown";
2606
+ }
2607
+ async function detectMonorepo(rootDir) {
2608
+ if (await pathExists(join15(rootDir, "pnpm-workspace.yaml"))) return true;
2609
+ if (await pathExists(join15(rootDir, "lerna.json"))) return true;
2610
+ if (await pathExists(join15(rootDir, "nx.json"))) return true;
2611
+ if (await pathExists(join15(rootDir, "turbo.json"))) return true;
2612
+ try {
2613
+ const pkgJson = await readFile15(join15(rootDir, "package.json"), "utf-8");
2614
+ const pkg = JSON.parse(pkgJson);
2615
+ if (pkg.workspaces) return true;
2616
+ } catch {
2617
+ }
2618
+ return false;
2619
+ }
2620
+ async function detectExistingAgents(rootDir) {
2621
+ return pathExists(join15(rootDir, ".agents"));
2622
+ }
2623
+ async function detectExistingTools(rootDir) {
2624
+ const tools = [];
2625
+ if (await pathExists(join15(rootDir, ".cursor"))) tools.push("cursor");
2626
+ if (await pathExists(join15(rootDir, ".github", "copilot-instructions.md")))
2627
+ tools.push("copilot");
2628
+ if (await pathExists(join15(rootDir, "CLAUDE.md")) || await pathExists(join15(rootDir, ".claude")))
2629
+ tools.push("claude");
2630
+ if (await pathExists(join15(rootDir, "opencode.json")) || await pathExists(join15(rootDir, "opencode.jsonc")))
2631
+ tools.push("opencode");
2632
+ if (await pathExists(join15(rootDir, ".windsurfrules")))
2633
+ tools.push("windsurf");
2634
+ if (await pathExists(join15(rootDir, ".amp")))
2635
+ tools.push("amp");
2636
+ if (await pathExists(join15(rootDir, ".codex"))) tools.push("codex");
2637
+ if (await pathExists(join15(rootDir, ".gemini")) || await pathExists(join15(rootDir, "GEMINI.md"))) tools.push("gemini");
2638
+ if (await pathExists(join15(rootDir, ".clinerules")) || await pathExists(join15(rootDir, ".roo")) || await pathExists(join15(rootDir, ".roomodes"))) tools.push("cline");
2639
+ if (await pathExists(join15(rootDir, ".aider.conf.yml"))) tools.push("aider");
2640
+ if (await pathExists(join15(rootDir, ".kiro"))) tools.push("kiro");
2641
+ if (await pathExists(join15(rootDir, ".goosehints")) || await pathExists(join15(rootDir, ".goose"))) tools.push("goose");
2642
+ if (await pathExists(join15(rootDir, ".rules"))) tools.push("zed");
2643
+ return tools;
2644
+ }
2645
+ async function pathExists(path) {
2646
+ try {
2647
+ await access2(path);
2648
+ return true;
2649
+ } catch {
2650
+ return false;
2651
+ }
2652
+ }
2653
+
2654
+ // src/env/mcpEnv.ts
2655
+ import { readFile as readFile16, writeFile as writeFile3 } from "fs/promises";
2656
+ import { existsSync as existsSync2 } from "fs";
2657
+ import { join as join16 } from "path";
2658
+ var ENV_MCP_FILE = ".env.mcp";
2659
+ var SOURCE_POSIX = "set -a && source .env.mcp && set +a";
2660
+ var SOURCE_POWERSHELL = "Get-Content .env.mcp | ForEach-Object { if ($_ -match '^\\s*([^#][^=]+)=(.*)$') { [Environment]::SetEnvironmentVariable($matches[1].Trim(), $matches[2].Trim(), 'Process') } }";
2661
+ function getSourceEnvMcpCommand() {
2662
+ return process.platform === "win32" ? SOURCE_POWERSHELL : SOURCE_POSIX;
2663
+ }
2664
+ function getSourceEnvMcpDisclaimer() {
2665
+ return [
2666
+ "# Cursor / Claude Code: Source this file, then start or restart your editor (VS Code/Copilot auto-loads it).",
2667
+ "# macOS/Linux (bash/zsh):",
2668
+ `# ${SOURCE_POSIX}`,
2669
+ "# Windows (PowerShell):",
2670
+ `# ${SOURCE_POWERSHELL}`,
2671
+ "# Windows (Git Bash): same as macOS/Linux",
2672
+ ""
2673
+ ].join("\n");
2674
+ }
2675
+ function collectRequiredEnvVars(servers) {
2676
+ const seen = /* @__PURE__ */ new Set();
2677
+ const vars = [];
2678
+ for (const id of servers) {
2679
+ const meta = AVAILABLE_MCP_SERVERS[id];
2680
+ if (!meta?.requiresEnv) continue;
2681
+ for (const name of meta.requiresEnv) {
2682
+ if (seen.has(name)) continue;
2683
+ seen.add(name);
2684
+ const help = ENV_VAR_HELP[name];
2685
+ vars.push({
2686
+ name,
2687
+ server: id,
2688
+ comment: help?.comment ?? id,
2689
+ url: help?.url ?? ""
2690
+ });
2691
+ }
2692
+ }
2693
+ return vars;
2694
+ }
2695
+ function generateEnvMcpContent(vars, existing = {}) {
2696
+ if (vars.length === 0) return "";
2697
+ const lines = [
2698
+ "# hatch3r MCP secrets",
2699
+ "# Fill in your values below. This file is gitignored \u2014 never commit it.",
2700
+ "# Docs: https://github.com/hatch3r-dev/hatch3r/blob/main/docs/mcp-setup.md",
2701
+ "",
2702
+ getSourceEnvMcpDisclaimer()
2703
+ ];
2704
+ for (const v of vars) {
2705
+ const urlPart = v.url ? ` \u2014 ${v.url}` : "";
2706
+ lines.push(`# ${v.comment}${urlPart}`);
2707
+ lines.push(`${v.name}=${existing[v.name] ?? ""}`);
2708
+ lines.push("");
2709
+ }
2710
+ return lines.join("\n");
2711
+ }
2712
+ function parseEnvFile(content) {
2713
+ const result = {};
2714
+ for (const raw of content.split("\n")) {
2715
+ const line = raw.trim();
2716
+ if (!line || line.startsWith("#")) continue;
2717
+ const stripped = line.startsWith("export ") ? line.slice(7) : line;
2718
+ const eqIdx = stripped.indexOf("=");
2719
+ if (eqIdx < 1) continue;
2720
+ const key = stripped.slice(0, eqIdx).trim();
2721
+ let val = stripped.slice(eqIdx + 1).trim();
2722
+ if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
2723
+ val = val.slice(1, -1);
2724
+ }
2725
+ result[key] = val;
2726
+ }
2727
+ return result;
2728
+ }
2729
+ async function ensureEnvMcp(rootDir, servers) {
2730
+ const envPath = join16(rootDir, ENV_MCP_FILE);
2731
+ const vars = collectRequiredEnvVars(servers);
2732
+ if (vars.length === 0) {
2733
+ return { action: "skipped", path: ENV_MCP_FILE, newVars: [] };
2734
+ }
2735
+ let existing = {};
2736
+ let hadFile = false;
2737
+ if (existsSync2(envPath)) {
2738
+ hadFile = true;
2739
+ const raw = await readFile16(envPath, "utf-8");
2740
+ existing = parseEnvFile(raw);
2741
+ }
2742
+ const newVars = vars.filter((v) => !(v.name in existing)).map((v) => v.name);
2743
+ if (hadFile && newVars.length === 0) {
2744
+ return { action: "skipped", path: ENV_MCP_FILE, newVars: [] };
2745
+ }
2746
+ const content = generateEnvMcpContent(vars, existing);
2747
+ await writeFile3(envPath, content, "utf-8");
2748
+ return {
2749
+ action: hadFile ? "updated" : "created",
2750
+ path: ENV_MCP_FILE,
2751
+ newVars
2752
+ };
2753
+ }
2754
+
2755
+ // src/cli/shared/paths.ts
2756
+ import { existsSync as existsSync3 } from "fs";
2757
+ import { dirname as dirname15, join as join17 } from "path";
2758
+ function findPackageRoot(startDir) {
2759
+ let dir = startDir;
2760
+ while (dir !== dirname15(dir)) {
2761
+ if (existsSync3(join17(dir, "package.json"))) return dir;
2762
+ dir = dirname15(dir);
2763
+ }
2764
+ return startDir;
2765
+ }
2766
+
2767
+ // src/cli/commands/init.ts
2768
+ var __dirname = dirname16(fileURLToPath(import.meta.url));
2769
+ var CONTENT_ROOT = findPackageRoot(__dirname);
2770
+ var CONTENT_DIRS = ["agents", "commands", "rules", "skills", "prompts", "github-agents", "mcp", "hooks"];
2771
+ var TOOL_CHOICES = [
2772
+ { name: "Cursor", value: "cursor" },
2773
+ { name: "GitHub Copilot", value: "copilot" },
2774
+ { name: "Claude Code", value: "claude" },
2775
+ { name: "OpenCode", value: "opencode" },
2776
+ { name: "Windsurf", value: "windsurf" },
2777
+ { name: "Amp", value: "amp" },
2778
+ { name: "Codex CLI", value: "codex" },
2779
+ { name: "Gemini CLI", value: "gemini" },
2780
+ { name: "Cline / Roo Code", value: "cline" },
2781
+ { name: "Aider", value: "aider" },
2782
+ { name: "Kiro", value: "kiro" },
2783
+ { name: "Goose", value: "goose" },
2784
+ { name: "Zed", value: "zed" }
2785
+ ];
2786
+ var FEATURE_CHOICES = [
2787
+ { name: "Agents", value: "agents" },
2788
+ { name: "Skills", value: "skills" },
2789
+ { name: "Rules", value: "rules" },
2790
+ { name: "Prompts", value: "prompts" },
2791
+ { name: "Commands", value: "commands" },
2792
+ { name: "MCP", value: "mcp" },
2793
+ { name: "Hooks", value: "hooks" },
2794
+ { name: "GitHub agents", value: "githubAgents" }
2795
+ ];
2796
+ var MCP_CHOICES = Object.entries(AVAILABLE_MCP_SERVERS).map(([id, meta]) => ({
2797
+ name: `${id}: ${meta.description}`,
2798
+ value: id
2799
+ }));
2800
+ var DEFAULT_TOOLS = ["cursor"];
2801
+ var VALID_TOOLS = ["cursor", "copilot", "claude", "opencode", "windsurf", "amp", "codex", "gemini", "cline", "aider", "kiro", "goose", "zed"];
2802
+ var DEFAULT_FEATURE_KEYS = Object.keys(DEFAULT_FEATURES);
2803
+ var DEFAULT_MCP = ["github", "context7", "filesystem", "playwright", "brave-search"];
2804
+ function sanitizeInput(value) {
2805
+ return value.replace(/[^a-zA-Z0-9._-]/g, "");
2806
+ }
2807
+ function parseGitRemote() {
2808
+ try {
2809
+ const url = execFileSync("git", ["remote", "get-url", "origin"], {
2810
+ stdio: "pipe"
2811
+ }).toString().trim();
2812
+ const sshMatch = url.match(/[:\/]([^/]+)\/([^/]+?)(?:\.git)?$/);
2813
+ if (sshMatch) {
2814
+ return { owner: sshMatch[1], repo: sshMatch[2] };
2815
+ }
2816
+ return { owner: "", repo: "" };
2817
+ } catch {
2818
+ return { owner: "", repo: "" };
2819
+ }
2820
+ }
2821
+ function parseGitDefaultBranch() {
2822
+ try {
2823
+ const ref = execFileSync("git", ["rev-parse", "--abbrev-ref", "origin/HEAD"], {
2824
+ stdio: "pipe"
2825
+ }).toString().trim();
2826
+ if (ref && ref.startsWith("origin/")) {
2827
+ return ref.replace(/^origin\//, "");
2828
+ }
2829
+ return "main";
2830
+ } catch {
2831
+ return "main";
2832
+ }
2833
+ }
2834
+ function toolDisplayName(tool) {
2835
+ const names = {
2836
+ cursor: "Cursor",
2837
+ copilot: "GitHub Copilot",
2838
+ claude: "Claude Code",
2839
+ opencode: "OpenCode",
2840
+ windsurf: "Windsurf",
2841
+ amp: "Amp",
2842
+ codex: "Codex CLI",
2843
+ gemini: "Gemini CLI",
2844
+ cline: "Cline",
2845
+ aider: "Aider",
2846
+ kiro: "Kiro",
2847
+ goose: "Goose",
2848
+ zed: "Zed"
2849
+ };
2850
+ return names[tool] ?? tool;
2851
+ }
2852
+ async function runInit(rootDir, owner, repo, defaultBranch, tools, features, mcpServers) {
2853
+ const agentsDir = join18(rootDir, AGENTS_DIR);
2854
+ const totalSteps = 4;
2855
+ const s1 = createSpinner(step(1, totalSteps, "Creating canonical files..."));
2856
+ s1.start();
2857
+ await mkdir2(agentsDir, { recursive: true });
2858
+ for (const dir of CONTENT_DIRS) {
2859
+ const srcDir = join18(CONTENT_ROOT, dir);
2860
+ const destDir = join18(agentsDir, dir);
2861
+ try {
2862
+ await cp(srcDir, destDir, { recursive: true, force: true });
2863
+ } catch {
2864
+ }
2865
+ }
2866
+ await mkdir2(join18(agentsDir, "learnings"), { recursive: true });
2867
+ const mcpPath = join18(agentsDir, "mcp", "mcp.json");
2868
+ try {
2869
+ const mcpRaw = await readFile17(mcpPath, "utf-8");
2870
+ const mcpParsed = JSON.parse(mcpRaw);
2871
+ if (mcpParsed.mcpServers) {
2872
+ const selected = new Set(mcpServers);
2873
+ const filtered = {};
2874
+ for (const [name, server] of Object.entries(mcpParsed.mcpServers)) {
2875
+ if (!selected.has(name)) continue;
2876
+ const entry = { ...server };
2877
+ delete entry._disabled;
2878
+ filtered[name] = entry;
2879
+ }
2880
+ await writeFile4(
2881
+ mcpPath,
2882
+ JSON.stringify({ mcpServers: filtered }, null, 2) + "\n",
2883
+ "utf-8"
2884
+ );
2885
+ }
2886
+ } catch {
2887
+ }
2888
+ await writeFile4(join18(agentsDir, "AGENTS.md"), CANONICAL_AGENTS_MD, "utf-8");
2889
+ s1.succeed(step(1, totalSteps, "Canonical files created"));
2890
+ const s2 = createSpinner(step(2, totalSteps, "Writing manifest..."));
2891
+ s2.start();
2892
+ const manifest = createManifest({ owner, repo, defaultBranch, tools, features, mcpServers });
2893
+ await writeManifest(rootDir, manifest);
2894
+ s2.succeed(step(2, totalSteps, "Manifest written"));
2895
+ const s3 = createSpinner(
2896
+ step(3, totalSteps, `Generating ${tools.map(toolDisplayName).join(", ")} output...`)
2897
+ );
2898
+ s3.start();
2899
+ await safeWriteFile(join18(rootDir, "AGENTS.md"), AGENTS_MD_FULL, {
2900
+ managedContent: AGENTS_MD_INNER,
2901
+ appendIfNoBlock: true
2902
+ });
2903
+ addManagedFile(manifest, "AGENTS.md");
2904
+ for (const tool of tools) {
2905
+ const adapter = getAdapter(tool);
2906
+ try {
2907
+ const outputs = await adapter.generate(agentsDir, manifest);
2908
+ for (const out of outputs) {
2909
+ await safeWriteFile(join18(rootDir, out.path), out.content, {
2910
+ managedContent: out.managedContent,
2911
+ appendIfNoBlock: true
2912
+ });
2913
+ addManagedFile(manifest, out.path);
2914
+ }
2915
+ } catch (err) {
2916
+ s3.fail(step(3, totalSteps, `Failed to generate ${toolDisplayName(tool)} output`));
2917
+ error(err instanceof Error ? err.message : String(err));
2918
+ process.exit(1);
2919
+ }
2920
+ }
2921
+ s3.succeed(step(3, totalSteps, "Adapter output generated"));
2922
+ const s4 = createSpinner(step(4, totalSteps, "Finalizing..."));
2923
+ s4.start();
2924
+ await writeManifest(rootDir, manifest);
2925
+ let envResult;
2926
+ if (features.mcp && mcpServers.length > 0) {
2927
+ envResult = await ensureEnvMcp(rootDir, mcpServers);
2928
+ }
2929
+ s4.succeed(step(4, totalSteps, "Done"));
2930
+ console.log();
2931
+ const enabledFeatures = Object.entries(features).filter(([, v]) => v).map(([k]) => k);
2932
+ const summaryLines = [
2933
+ label("Tools", tools.map(toolDisplayName).join(", ")),
2934
+ label("Features", enabledFeatures.join(", "))
2935
+ ];
2936
+ if (owner || repo) {
2937
+ summaryLines.push(label("GitHub", `${owner}/${repo}`));
2938
+ }
2939
+ if (defaultBranch) {
2940
+ summaryLines.push(label("Default branch", defaultBranch));
2941
+ }
2942
+ if (mcpServers.length > 0) {
2943
+ summaryLines.push(label("MCP", mcpServers.join(", ")));
2944
+ }
2945
+ if (envResult && envResult.action !== "skipped") {
2946
+ summaryLines.push(label("Secrets", `.env.mcp (fill in your API keys)`));
2947
+ }
2948
+ summaryLines.push("");
2949
+ summaryLines.push(label("Canonical", `${AGENTS_DIR}/`));
2950
+ summaryLines.push(label("Manifest", `${AGENTS_DIR}/hatch.json`));
2951
+ printBox("Hatch complete", summaryLines, "success");
2952
+ if (envResult && envResult.newVars.length > 0) {
2953
+ warn(
2954
+ `Add your secrets to .env.mcp: ${envResult.newVars.join(", ")}`
2955
+ );
2956
+ info(`Run this, then start or restart your editor: ${getSourceEnvMcpCommand()}`);
2957
+ }
2958
+ }
2959
+ async function checkExisting(rootDir, skipPrompt) {
2960
+ const hatchJsonPath = join18(rootDir, AGENTS_DIR, "hatch.json");
2961
+ try {
2962
+ await access3(hatchJsonPath);
2963
+ if (!skipPrompt) {
2964
+ const { proceed } = await inquirer.prompt([
2965
+ {
2966
+ type: "confirm",
2967
+ name: "proceed",
2968
+ message: "Existing .agents/ found. This will overwrite managed files. Continue?",
2969
+ default: false
2970
+ }
2971
+ ]);
2972
+ if (!proceed) {
2973
+ console.log(chalk15.dim("\n Init cancelled.\n"));
2974
+ process.exit(0);
2975
+ }
2976
+ }
2977
+ } catch {
2978
+ }
2979
+ }
2980
+ async function initCommand(opts = {}) {
2981
+ printBanner();
2982
+ const rootDir = process.cwd();
2983
+ const detectSpinner = createSpinner("Detecting repository...");
2984
+ detectSpinner.start();
2985
+ const repoInfo = await analyzeRepo(rootDir);
2986
+ const remote = parseGitRemote();
2987
+ detectSpinner.succeed("Repository detected");
2988
+ const detected = [];
2989
+ if (repoInfo.languages.length > 0 && repoInfo.languages[0] !== "unknown") {
2990
+ detected.push(...repoInfo.languages);
2991
+ }
2992
+ if (repoInfo.packageManager !== "unknown") {
2993
+ detected.push(repoInfo.packageManager);
2994
+ }
2995
+ if (repoInfo.isMonorepo) detected.push("monorepo");
2996
+ if (detected.length > 0) {
2997
+ info(chalk15.dim(`Detected: ${detected.join(", ")}`));
2998
+ }
2999
+ if (opts.yes) {
3000
+ const owner2 = sanitizeInput(remote.owner);
3001
+ const repo2 = sanitizeInput(remote.repo);
3002
+ let tools2;
3003
+ if (opts.tools) {
3004
+ const rawTools = opts.tools.split(",").map((t) => t.trim());
3005
+ const invalid = rawTools.filter((t) => !VALID_TOOLS.includes(t));
3006
+ if (invalid.length > 0) {
3007
+ error(`Invalid tool(s): ${invalid.join(", ")}`);
3008
+ console.log(chalk15.dim(` Valid tools: ${VALID_TOOLS.join(", ")}`));
3009
+ process.exit(1);
3010
+ }
3011
+ tools2 = rawTools;
3012
+ } else if (repoInfo.existingTools.length > 0) {
3013
+ tools2 = repoInfo.existingTools;
3014
+ } else {
3015
+ tools2 = DEFAULT_TOOLS;
3016
+ }
3017
+ const features2 = { ...DEFAULT_FEATURES };
3018
+ const mcpServers2 = features2.mcp ? DEFAULT_MCP : [];
3019
+ const defaultBranch2 = parseGitDefaultBranch();
3020
+ await checkExisting(rootDir, true);
3021
+ await runInit(rootDir, owner2, repo2, defaultBranch2, tools2, features2, mcpServers2);
3022
+ return;
3023
+ }
3024
+ console.log();
3025
+ const repoAnswers = await inquirer.prompt([
3026
+ {
3027
+ type: "input",
3028
+ name: "owner",
3029
+ message: "GitHub owner (org or username):",
3030
+ default: remote.owner || void 0
3031
+ },
3032
+ {
3033
+ type: "input",
3034
+ name: "repo",
3035
+ message: "Repository name:",
3036
+ default: remote.repo || void 0
3037
+ }
3038
+ ]);
3039
+ const owner = sanitizeInput(repoAnswers.owner);
3040
+ const repo = sanitizeInput(repoAnswers.repo);
3041
+ const defaultBranchDefault = parseGitDefaultBranch();
3042
+ const defaultBranchAnswers = await inquirer.prompt([
3043
+ {
3044
+ type: "input",
3045
+ name: "defaultBranch",
3046
+ message: "Default branch (for checkout, PR base, release):",
3047
+ default: defaultBranchDefault
3048
+ }
3049
+ ]);
3050
+ const defaultBranch = defaultBranchAnswers.defaultBranch.trim() || defaultBranchDefault;
3051
+ const toolDefaults = repoInfo.existingTools.length > 0 ? repoInfo.existingTools : DEFAULT_TOOLS;
3052
+ const toolAnswers = await inquirer.prompt([
3053
+ {
3054
+ type: "checkbox",
3055
+ name: "tools",
3056
+ message: "Select tools to configure:",
3057
+ choices: TOOL_CHOICES,
3058
+ default: toolDefaults
3059
+ }
3060
+ ]);
3061
+ const tools = toolAnswers.tools.length > 0 ? toolAnswers.tools : DEFAULT_TOOLS;
3062
+ const featureAnswers = await inquirer.prompt([
3063
+ {
3064
+ type: "checkbox",
3065
+ name: "features",
3066
+ message: "Select features:",
3067
+ choices: FEATURE_CHOICES,
3068
+ default: DEFAULT_FEATURE_KEYS
3069
+ }
3070
+ ]);
3071
+ const selectedFeatures = featureAnswers.features;
3072
+ const features = { ...DEFAULT_FEATURES };
3073
+ for (const k of Object.keys(features)) {
3074
+ features[k] = selectedFeatures.includes(k);
3075
+ }
3076
+ let mcpServers = [];
3077
+ if (features.mcp) {
3078
+ const mcpAnswers = await inquirer.prompt([
3079
+ {
3080
+ type: "checkbox",
3081
+ name: "mcp",
3082
+ message: "Select MCP servers:",
3083
+ choices: MCP_CHOICES,
3084
+ default: DEFAULT_MCP
3085
+ }
3086
+ ]);
3087
+ mcpServers = mcpAnswers.mcp ?? [];
3088
+ }
3089
+ await checkExisting(rootDir, false);
3090
+ await runInit(rootDir, owner, repo, defaultBranch, tools, features, mcpServers);
3091
+ }
3092
+
3093
+ // src/cli/commands/sync.ts
3094
+ import { mkdir as mkdir3, readFile as readFile18, writeFile as writeFile5 } from "fs/promises";
3095
+ import { dirname as dirname17, join as join19 } from "path";
3096
+ import chalk16 from "chalk";
3097
+ async function syncCommand() {
3098
+ printBanner(true);
3099
+ const rootDir = process.cwd();
3100
+ const agentsDir = join19(rootDir, AGENTS_DIR);
3101
+ const manifest = await readManifest(rootDir);
3102
+ if (!manifest) {
3103
+ error("No .agents/hatch.json found.");
3104
+ console.log(chalk16.dim(" Run `npx hatch3r init` to set up your project first.\n"));
3105
+ process.exit(1);
3106
+ }
3107
+ const m = manifest;
3108
+ const results = [];
3109
+ const totalSteps = m.tools.length + 1;
3110
+ let currentStep = 0;
3111
+ const s1 = createSpinner(step(++currentStep, totalSteps, "Syncing AGENTS.md..."));
3112
+ s1.start();
3113
+ const agentsMdResult = await safeWriteFile(join19(rootDir, "AGENTS.md"), AGENTS_MD_FULL, {
3114
+ managedContent: AGENTS_MD_INNER
3115
+ });
3116
+ results.push({ path: "AGENTS.md", action: agentsMdResult.action });
3117
+ await writeFile5(join19(agentsDir, "AGENTS.md"), CANONICAL_AGENTS_MD, "utf-8");
3118
+ results.push({ path: `${AGENTS_DIR}/AGENTS.md`, action: "updated" });
3119
+ s1.succeed(step(currentStep, totalSteps, "AGENTS.md synced"));
3120
+ for (const tool of m.tools) {
3121
+ const s = createSpinner(step(++currentStep, totalSteps, `Generating ${tool} output...`));
3122
+ s.start();
3123
+ try {
3124
+ const adapter = getAdapter(tool);
3125
+ const outputs = await adapter.generate(agentsDir, m);
3126
+ for (const out of outputs) {
3127
+ const fullPath = join19(rootDir, out.path);
3128
+ if (out.managedContent) {
3129
+ const result = await safeWriteFile(fullPath, out.content, {
3130
+ managedContent: out.managedContent
3131
+ });
3132
+ results.push({ path: out.path, action: result.action });
3133
+ } else {
3134
+ await mkdir3(dirname17(fullPath), { recursive: true });
3135
+ let action;
3136
+ try {
3137
+ const existing = await readFile18(fullPath, "utf-8");
3138
+ if (existing === out.content) {
3139
+ action = "skipped";
3140
+ } else {
3141
+ await writeFile5(fullPath, out.content, "utf-8");
3142
+ action = "updated";
3143
+ }
3144
+ } catch {
3145
+ await writeFile5(fullPath, out.content, "utf-8");
3146
+ action = "created";
3147
+ }
3148
+ results.push({ path: out.path, action });
3149
+ }
3150
+ }
3151
+ s.succeed(step(currentStep, totalSteps, `${tool} output generated`));
3152
+ } catch (err) {
3153
+ s.fail(step(currentStep, totalSteps, `Failed to generate ${tool} output`));
3154
+ error(err instanceof Error ? err.message : String(err));
3155
+ process.exit(1);
3156
+ }
3157
+ }
3158
+ if (m.features.mcp && m.mcp.servers.length > 0) {
3159
+ const envResult = await ensureEnvMcp(rootDir, m.mcp.servers);
3160
+ if (envResult.action !== "skipped") {
3161
+ results.push({ path: envResult.path, action: envResult.action });
3162
+ }
3163
+ if (envResult.newVars.length > 0) {
3164
+ warn(
3165
+ `New secrets needed in .env.mcp: ${envResult.newVars.join(", ")}`
3166
+ );
3167
+ info(`Run this, then start or restart your editor: ${getSourceEnvMcpCommand()}`);
3168
+ }
3169
+ }
3170
+ console.log();
3171
+ const icons = {
3172
+ created: chalk16.green("+"),
3173
+ updated: chalk16.yellow("~"),
3174
+ skipped: chalk16.dim("=")
3175
+ };
3176
+ const summaryLines = results.map((r) => {
3177
+ const icon = icons[r.action] ?? chalk16.dim(" ");
3178
+ return `${icon} ${r.path} ${chalk16.dim(`(${r.action})`)}`;
3179
+ });
3180
+ printBox("Sync complete", summaryLines, "success");
3181
+ }
3182
+
3183
+ // src/cli/commands/update.ts
3184
+ import { cp as cp2, mkdir as mkdir4, readdir as readdir3, readFile as readFile19, writeFile as writeFile6 } from "fs/promises";
3185
+ import { fileURLToPath as fileURLToPath2 } from "url";
3186
+ import { dirname as dirname18, join as join20 } from "path";
3187
+ import chalk17 from "chalk";
3188
+ var __dirname2 = dirname18(fileURLToPath2(import.meta.url));
3189
+ var CONTENT_ROOT2 = findPackageRoot(__dirname2);
3190
+ var CONTENT_DIRS2 = ["agents", "commands", "rules", "skills", "prompts", "github-agents", "mcp", "hooks"];
3191
+ async function copyHatch3rFiles(srcDir, destDir, insideHatch3rDir = false) {
3192
+ const copied = [];
3193
+ const entries = await readdir3(srcDir, { withFileTypes: true });
3194
+ for (const entry of entries) {
3195
+ const srcPath = join20(srcDir, entry.name);
3196
+ const destPath = join20(destDir, entry.name);
3197
+ if (entry.isDirectory()) {
3198
+ await mkdir4(destPath, { recursive: true });
3199
+ const subCopied = await copyHatch3rFiles(
3200
+ srcPath,
3201
+ destPath,
3202
+ entry.name.startsWith(HATCH3R_PREFIX)
3203
+ );
3204
+ copied.push(...subCopied.map((p) => join20(entry.name, p)));
3205
+ } else if (entry.name.startsWith(HATCH3R_PREFIX) || insideHatch3rDir) {
3206
+ await mkdir4(dirname18(destPath), { recursive: true });
3207
+ await cp2(srcPath, destPath, { force: true });
3208
+ copied.push(entry.name);
3209
+ }
3210
+ }
3211
+ return copied;
3212
+ }
3213
+ async function updateCommand(opts = {}) {
3214
+ printBanner(true);
3215
+ const rootDir = process.cwd();
3216
+ const agentsDir = join20(rootDir, AGENTS_DIR);
3217
+ const manifest = await readManifest(rootDir);
3218
+ if (!manifest) {
3219
+ error("No .agents/hatch.json found.");
3220
+ console.log(chalk17.dim(" Run `npx hatch3r init` to set up your project first.\n"));
3221
+ process.exit(1);
3222
+ }
3223
+ const m = manifest;
3224
+ const currentVersion = m.hatch3rVersion;
3225
+ const isUpToDate = currentVersion === HATCH3R_VERSION;
3226
+ if (isUpToDate) {
3227
+ info(`Already at hatch3r v${HATCH3R_VERSION}`);
3228
+ } else {
3229
+ info(`Updating from v${currentVersion} to v${HATCH3R_VERSION}`);
3230
+ }
3231
+ console.log();
3232
+ const totalSteps = 3;
3233
+ const s1 = createSpinner(step(1, totalSteps, "Updating canonical files..."));
3234
+ s1.start();
3235
+ const copied = [];
3236
+ for (const dir of CONTENT_DIRS2) {
3237
+ const srcDir = join20(CONTENT_ROOT2, dir);
3238
+ try {
3239
+ const dirCopied = await copyHatch3rFiles(srcDir, join20(agentsDir, dir));
3240
+ copied.push(...dirCopied.map((p) => join20(dir, p)));
3241
+ } catch {
3242
+ }
3243
+ }
3244
+ await writeFile6(join20(agentsDir, "AGENTS.md"), CANONICAL_AGENTS_MD, "utf-8");
3245
+ s1.succeed(step(1, totalSteps, `Updated ${copied.length} canonical files`));
3246
+ const s2 = createSpinner(step(2, totalSteps, "Re-syncing adapter output..."));
3247
+ s2.start();
3248
+ for (const tool of m.tools) {
3249
+ const adapter = getAdapter(tool);
3250
+ try {
3251
+ const outputs = await adapter.generate(agentsDir, m);
3252
+ for (const out of outputs) {
3253
+ const fullPath = join20(rootDir, out.path);
3254
+ if (out.managedContent) {
3255
+ await safeWriteFile(fullPath, out.content, {
3256
+ managedContent: out.managedContent,
3257
+ backup: opts.backup ?? true
3258
+ });
3259
+ } else {
3260
+ await mkdir4(dirname18(fullPath), { recursive: true });
3261
+ try {
3262
+ const existing = await readFile19(fullPath, "utf-8");
3263
+ if (existing !== out.content) {
3264
+ await writeFile6(fullPath, out.content, "utf-8");
3265
+ }
3266
+ } catch {
3267
+ await writeFile6(fullPath, out.content, "utf-8");
3268
+ }
3269
+ }
3270
+ }
3271
+ } catch (err) {
3272
+ s2.fail(step(2, totalSteps, `Failed to generate ${tool} output`));
3273
+ error(err instanceof Error ? err.message : String(err));
3274
+ process.exit(1);
3275
+ }
3276
+ }
3277
+ s2.succeed(step(2, totalSteps, `Re-synced ${m.tools.length} tool(s)`));
3278
+ const s3 = createSpinner(step(3, totalSteps, "Writing manifest..."));
3279
+ s3.start();
3280
+ m.hatch3rVersion = HATCH3R_VERSION;
3281
+ await writeManifest(rootDir, m);
3282
+ s3.succeed(step(3, totalSteps, "Manifest updated"));
3283
+ console.log();
3284
+ printBox("Update complete", [
3285
+ label("Files", `${copied.length} canonical files updated`),
3286
+ label("Tools", `${m.tools.length} tool(s) re-synced`),
3287
+ label("Version", `v${HATCH3R_VERSION}`)
3288
+ ], "success");
3289
+ }
3290
+
3291
+ // src/cli/commands/validate.ts
3292
+ import { readdir as readdir4, readFile as readFile20, access as access4 } from "fs/promises";
3293
+ import { join as join21, posix } from "path";
3294
+ import chalk18 from "chalk";
3295
+ import { parse as parseYaml3 } from "yaml";
3296
+ async function validateCommand() {
3297
+ printBanner(true);
3298
+ const rootDir = process.cwd();
3299
+ const agentsDir = join21(rootDir, AGENTS_DIR);
3300
+ const result = { errors: [], warnings: [] };
3301
+ const spinner = createSpinner("Validating .agents/ structure...");
3302
+ spinner.start();
3303
+ try {
3304
+ await access4(agentsDir);
3305
+ } catch {
3306
+ spinner.fail("Validation failed");
3307
+ error(".agents/ directory not found. Run `hatch3r init` first.");
3308
+ console.log();
3309
+ process.exit(1);
3310
+ }
3311
+ const manifest = await readManifest(rootDir);
3312
+ if (!manifest) {
3313
+ result.errors.push("Missing .agents/hatch.json manifest");
3314
+ } else {
3315
+ if (!manifest.version) result.errors.push("hatch.json: missing 'version' field");
3316
+ if (!manifest.tools || manifest.tools.length === 0) result.warnings.push("hatch.json: no tools configured");
3317
+ for (const managedFile of manifest.managedFiles ?? []) {
3318
+ try {
3319
+ await access4(join21(rootDir, managedFile));
3320
+ } catch {
3321
+ result.warnings.push(`Managed file missing from disk: ${managedFile}`);
3322
+ }
3323
+ }
3324
+ }
3325
+ const requiredDirs = ["agents", "skills", "rules"];
3326
+ const optionalDirs = ["commands", "prompts", "mcp", "policy", "github-agents"];
3327
+ for (const dir of requiredDirs) {
3328
+ try {
3329
+ await access4(join21(agentsDir, dir));
3330
+ } catch {
3331
+ result.errors.push(`Required directory missing: .agents/${dir}/`);
3332
+ }
3333
+ }
3334
+ for (const dir of optionalDirs) {
3335
+ try {
3336
+ await access4(join21(agentsDir, dir));
3337
+ } catch {
3338
+ result.warnings.push(`Optional directory missing: .agents/${dir}/`);
3339
+ }
3340
+ }
3341
+ for (const dir of [...requiredDirs, ...optionalDirs]) {
3342
+ const dirPath = join21(agentsDir, dir);
3343
+ try {
3344
+ const entries = await readdir4(dirPath, { withFileTypes: true });
3345
+ for (const entry of entries) {
3346
+ if (entry.isFile() && entry.name.endsWith(".md")) {
3347
+ const filePath = join21(dirPath, entry.name);
3348
+ const content = await readFile20(filePath, "utf-8");
3349
+ if (!content.startsWith("---")) {
3350
+ result.warnings.push(`Missing frontmatter: .agents/${dir}/${entry.name}`);
3351
+ } else {
3352
+ const endIdx = content.indexOf("---", 3);
3353
+ if (endIdx === -1) {
3354
+ result.errors.push(`Invalid frontmatter (no closing ---): .agents/${dir}/${entry.name}`);
3355
+ } else {
3356
+ const frontmatter = content.slice(3, endIdx).trim();
3357
+ const parsedFm = parseYaml3(frontmatter);
3358
+ if (!parsedFm || typeof parsedFm !== "object" || !parsedFm.id) {
3359
+ result.warnings.push(`Missing 'id' in frontmatter: .agents/${dir}/${entry.name}`);
3360
+ }
3361
+ if (!parsedFm || typeof parsedFm !== "object" || !parsedFm.type) {
3362
+ result.warnings.push(`Missing 'type' in frontmatter: .agents/${dir}/${entry.name}`);
3363
+ }
3364
+ }
3365
+ }
3366
+ } else if (entry.isDirectory()) {
3367
+ const skillPath = join21(dirPath, entry.name, "SKILL.md");
3368
+ try {
3369
+ await access4(skillPath);
3370
+ } catch {
3371
+ result.warnings.push(`Skill directory missing SKILL.md: .agents/${dir}/${entry.name}/`);
3372
+ }
3373
+ }
3374
+ }
3375
+ } catch {
3376
+ }
3377
+ }
3378
+ try {
3379
+ await access4(join21(agentsDir, "AGENTS.md"));
3380
+ } catch {
3381
+ result.warnings.push("Missing .agents/AGENTS.md");
3382
+ }
3383
+ if (manifest) {
3384
+ for (const managedFile of manifest.managedFiles ?? []) {
3385
+ const fileName = posix.basename(managedFile) || "";
3386
+ const isSharedFile = ["AGENTS.md", "CLAUDE.md", "copilot-instructions.md", ".windsurfrules", "mcp.json", "opencode.json", ".mcp.json", "copilot-setup-steps.yml", "settings.json"].some(
3387
+ (sf) => fileName === sf || managedFile.endsWith(sf)
3388
+ );
3389
+ if (!isSharedFile && !fileName.startsWith(HATCH3R_PREFIX) && !fileName.startsWith(".")) {
3390
+ result.warnings.push(`Managed file without hatch3r- prefix: ${managedFile}`);
3391
+ }
3392
+ }
3393
+ if (manifest.features.hooks) {
3394
+ const hooksDir = join21(agentsDir, "hooks");
3395
+ try {
3396
+ const hookFiles = await readdir4(hooksDir);
3397
+ const mdHooks = hookFiles.filter((f) => f.endsWith(".md"));
3398
+ if (mdHooks.length === 0) {
3399
+ result.warnings.push("Hooks feature enabled but no hook definitions found in .agents/hooks/");
3400
+ }
3401
+ let agentFiles;
3402
+ try {
3403
+ const agentEntries = await readdir4(join21(agentsDir, "agents"));
3404
+ agentFiles = new Set(agentEntries.filter((f) => f.endsWith(".md")));
3405
+ } catch {
3406
+ }
3407
+ for (const hookFile of mdHooks) {
3408
+ const hookContent = await readFile20(join21(hooksDir, hookFile), "utf-8");
3409
+ if (!hookContent.startsWith("---")) {
3410
+ result.warnings.push(`Hook missing frontmatter: .agents/hooks/${hookFile}`);
3411
+ continue;
3412
+ }
3413
+ const endIdx = hookContent.indexOf("---", 3);
3414
+ if (endIdx === -1) continue;
3415
+ const fm = parseYaml3(hookContent.slice(3, endIdx).trim());
3416
+ if (fm?.agent && typeof fm.agent === "string" && agentFiles) {
3417
+ const expectedFile = `${HATCH3R_PREFIX}${fm.agent}.md`;
3418
+ if (!agentFiles.has(expectedFile)) {
3419
+ result.errors.push(`Hook "${hookFile}" references agent "${fm.agent}" but .agents/agents/${expectedFile} does not exist`);
3420
+ }
3421
+ }
3422
+ }
3423
+ } catch {
3424
+ result.warnings.push("Hooks feature enabled but .agents/hooks/ directory not found");
3425
+ }
3426
+ }
3427
+ if (manifest.features.mcp && manifest.mcp.servers.length > 0) {
3428
+ const mcpPath = join21(agentsDir, "mcp", "mcp.json");
3429
+ try {
3430
+ const mcpContent = await readFile20(mcpPath, "utf-8");
3431
+ const mcpParsed = JSON.parse(mcpContent);
3432
+ if (!mcpParsed.mcpServers || typeof mcpParsed.mcpServers !== "object") {
3433
+ result.errors.push("MCP config missing 'mcpServers' key");
3434
+ }
3435
+ } catch (err) {
3436
+ if (err instanceof SyntaxError) {
3437
+ result.errors.push("Invalid JSON in .agents/mcp/mcp.json");
3438
+ } else {
3439
+ result.warnings.push("MCP servers configured but .agents/mcp/mcp.json not found");
3440
+ }
3441
+ }
3442
+ }
3443
+ if (manifest.models) {
3444
+ if (manifest.models.default && typeof manifest.models.default !== "string") {
3445
+ result.errors.push("hatch.json: models.default must be a string");
3446
+ }
3447
+ if (manifest.models.agents) {
3448
+ for (const [agentId, model] of Object.entries(manifest.models.agents)) {
3449
+ if (typeof model !== "string") {
3450
+ result.errors.push(`hatch.json: models.agents.${agentId} must be a string`);
3451
+ }
3452
+ }
3453
+ }
3454
+ }
3455
+ const customizeDir = join21(rootDir, ".hatch3r", "agents");
3456
+ try {
3457
+ const customizeFiles = await readdir4(customizeDir);
3458
+ for (const file of customizeFiles) {
3459
+ if (file.endsWith(".customize.yaml")) {
3460
+ const agentId = file.replace(".customize.yaml", "");
3461
+ const agentFile = join21(agentsDir, "agents", `${agentId}.md`);
3462
+ try {
3463
+ await access4(agentFile);
3464
+ } catch {
3465
+ result.warnings.push(`Customization file for non-existent agent: .hatch3r/agents/${file}`);
3466
+ }
3467
+ }
3468
+ }
3469
+ } catch {
3470
+ }
3471
+ }
3472
+ spinner.stop();
3473
+ if (result.errors.length === 0 && result.warnings.length === 0) {
3474
+ printBox("Validation", [chalk18.green("All checks passed")], "success");
3475
+ return;
3476
+ }
3477
+ console.log();
3478
+ if (result.errors.length > 0) {
3479
+ for (const err of result.errors) {
3480
+ error(err);
3481
+ }
3482
+ console.log();
3483
+ }
3484
+ if (result.warnings.length > 0) {
3485
+ for (const w of result.warnings) {
3486
+ warn(w);
3487
+ }
3488
+ console.log();
3489
+ }
3490
+ if (result.errors.length > 0) {
3491
+ const summaryLines = [
3492
+ `${chalk18.red("\u2716")} ${result.errors.length} error(s)`,
3493
+ `${chalk18.yellow("\u26A0")} ${result.warnings.length} warning(s)`
3494
+ ];
3495
+ printBox("Validation failed", summaryLines, "error");
3496
+ process.exit(1);
3497
+ } else {
3498
+ const summaryLines = [
3499
+ `${chalk18.green("\u2714")} 0 errors`,
3500
+ `${chalk18.yellow("\u26A0")} ${result.warnings.length} warning(s)`
3501
+ ];
3502
+ printBox("Validation passed", summaryLines, "success");
3503
+ }
3504
+ }
3505
+
3506
+ // src/cli/commands/status.ts
3507
+ import { readFile as readFile21 } from "fs/promises";
3508
+ import { join as join22 } from "path";
3509
+ import chalk19 from "chalk";
3510
+ async function statusCommand() {
3511
+ printBanner(true);
3512
+ const rootDir = process.cwd();
3513
+ const agentsDir = join22(rootDir, AGENTS_DIR);
3514
+ const manifest = await readManifest(rootDir);
3515
+ if (!manifest) {
3516
+ error("No .agents/hatch.json found.");
3517
+ console.log(chalk19.dim(" Run `npx hatch3r init` to set up your project first.\n"));
3518
+ process.exit(1);
3519
+ }
3520
+ const spinner = createSpinner("Checking sync status...");
3521
+ spinner.start();
3522
+ const stats = { synced: 0, drifted: 0, missing: 0 };
3523
+ const fileLines = [];
3524
+ for (const tool of manifest.tools) {
3525
+ const adapter = getAdapter(tool);
3526
+ const outputs = await adapter.generate(agentsDir, manifest);
3527
+ fileLines.push(chalk19.bold(`${tool}:`));
3528
+ for (const out of outputs) {
3529
+ const destPath = join22(rootDir, out.path);
3530
+ try {
3531
+ const existing = await readFile21(destPath, "utf-8");
3532
+ const existingBlock = extractManagedBlock(existing);
3533
+ const expectedBlock = out.managedContent ?? extractManagedBlock(out.content);
3534
+ if (existingBlock !== null && expectedBlock !== null ? existingBlock === expectedBlock : existing === out.content) {
3535
+ fileLines.push(` ${chalk19.green("=")} ${out.path}`);
3536
+ stats.synced++;
3537
+ } else {
3538
+ fileLines.push(` ${chalk19.yellow("~")} ${out.path} ${chalk19.dim("(drifted)")}`);
3539
+ stats.drifted++;
3540
+ }
3541
+ } catch {
3542
+ fileLines.push(` ${chalk19.red("+")} ${out.path} ${chalk19.dim("(missing)")}`);
3543
+ stats.missing++;
3544
+ }
3545
+ }
3546
+ }
3547
+ spinner.stop();
3548
+ console.log();
3549
+ for (const line of fileLines) {
3550
+ console.log(` ${line}`);
3551
+ }
3552
+ console.log();
3553
+ const summaryLines = [
3554
+ `${chalk19.green("=")} In sync: ${stats.synced}`
3555
+ ];
3556
+ if (stats.drifted > 0) {
3557
+ summaryLines.push(`${chalk19.yellow("~")} Drifted: ${stats.drifted}`);
3558
+ }
3559
+ if (stats.missing > 0) {
3560
+ summaryLines.push(`${chalk19.red("+")} Missing: ${stats.missing}`);
3561
+ }
3562
+ const style = stats.drifted > 0 || stats.missing > 0 ? "info" : "success";
3563
+ printBox("Status", summaryLines, style);
3564
+ if (stats.drifted > 0 || stats.missing > 0) {
3565
+ info(`Run ${chalk19.bold("hatch3r sync")} to regenerate drifted/missing files.`);
3566
+ console.log();
3567
+ }
3568
+ }
3569
+
3570
+ // src/cli/index.ts
3571
+ var program = new Command();
3572
+ program.name("hatch3r").description(
3573
+ "Battle-tested agentic coding setup framework. Crack the egg. Hatch better agents."
3574
+ ).version(HATCH3R_VERSION);
3575
+ program.command("init").description("Install a complete agent setup into the current repo").option(
3576
+ "--tools <tools>",
3577
+ "Comma-separated tools (cursor,copilot,claude,opencode,windsurf,amp,codex,gemini,cline)"
3578
+ ).option("--yes", "Skip interactive prompts, use defaults").action(initCommand);
3579
+ program.command("sync").description("Re-generate tool outputs from canonical .agents/ state").action(syncCommand);
3580
+ program.command("status").description("Check sync status between canonical .agents/ and generated files").action(statusCommand);
3581
+ program.command("update").description("Pull latest hatch3r templates with safe merge").option("--backup", "Create backups before overwriting", true).action(updateCommand);
3582
+ program.command("validate").description("Validate the canonical .agents/ structure").action(validateCommand);
3583
+ program.command("add [pack]").description("Install a community pack (coming soon)").action(addCommand);
3584
+ program.parse();