@sallmarta/eye-hate-agent 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +56 -304
  2. package/bin/eha.js +203 -118
  3. package/docs/templates/project-docs-template/index.md +208 -0
  4. package/docs/templates/project-docs-template/technical-guidelines/index.md +81 -0
  5. package/docs/templates/reusable-prompts/00-project-docs-bootstrap.md +40 -0
  6. package/docs/{vibes → templates}/reusable-prompts/00-project-docs-parity.md +4 -6
  7. package/docs/{vibes → templates}/reusable-prompts/00-project-docs-refresh.md +11 -11
  8. package/docs/{vibes → templates}/reusable-prompts/01-sdd-execute.md +1 -1
  9. package/docs/{vibes → templates}/reusable-prompts/02-sdd-discuss.md +3 -3
  10. package/{.agents/rules/agent.md → docs/templates/rules/agent-rules.md} +7 -12
  11. package/docs/{vibes → templates}/skills/api-design/SKILL.md +14 -25
  12. package/docs/{vibes/skills/full-verification → templates/skills/code-audit}/SKILL.md +36 -54
  13. package/docs/templates/skills/db-schema-design/SKILL.md +120 -0
  14. package/docs/templates/skills/devops-ci-cd/SKILL.md +99 -0
  15. package/docs/templates/skills/observability/SKILL.md +99 -0
  16. package/docs/{vibes/skills/parity → templates/skills/parity-audit}/SKILL.md +26 -52
  17. package/docs/templates/skills/refactor/SKILL.md +100 -0
  18. package/docs/templates/skills/security-audit/SKILL.md +149 -0
  19. package/docs/{vibes/skills/analysis → templates/skills/system-analysis}/SKILL.md +19 -41
  20. package/docs/{vibes/skills/test-authoring → templates/skills/system-tester}/SKILL.md +28 -46
  21. package/docs/templates/skills/ui-ux-design/SKILL.md +102 -0
  22. package/docs/templates/skills/wireframing/SKILL.md +88 -0
  23. package/package.json +4 -6
  24. package/src/engine/index.js +9 -12
  25. package/src/engine/install.js +67 -165
  26. package/src/engine/runtime-adapters.js +266 -50
  27. package/src/engine/skill-registry.js +72 -0
  28. package/src/engine/state.js +29 -7
  29. package/src/engine/workflow-registry.js +14 -23
  30. package/.claude/commands/eha/README.md +0 -3
  31. package/.claude/commands/eha/eha-bootstrap.md +0 -9
  32. package/.claude/commands/eha/eha-discuss.md +0 -9
  33. package/.claude/commands/eha/eha-execute.md +0 -9
  34. package/.claude/commands/eha/eha-parity.md +0 -9
  35. package/.claude/commands/eha/eha-refresh.md +0 -9
  36. package/.claude/commands/eha/eha-verify.md +0 -9
  37. package/.claude/rules/agent-rules.md +0 -64
  38. package/.github/instructions/agent-rules.instructions.md +0 -63
  39. package/.github/instructions/eha-workflows.instructions.md +0 -21
  40. package/docs/eyehateagent-contract.md +0 -475
  41. package/docs/eyehateagent-maintenance.md +0 -103
  42. package/docs/project-docs/changelog.md +0 -299
  43. package/docs/project-docs/foundation/architecture.md +0 -117
  44. package/docs/project-docs/foundation/status.md +0 -32
  45. package/docs/project-docs/foundation/workflow.md +0 -63
  46. package/docs/project-docs/index.md +0 -20
  47. package/docs/project-docs/testing.md +0 -73
  48. package/docs/vibes/project-docs-template/foundation/architecture.md +0 -79
  49. package/docs/vibes/project-docs-template/foundation/changelog.md +0 -53
  50. package/docs/vibes/project-docs-template/foundation/feature-inventory.md +0 -46
  51. package/docs/vibes/project-docs-template/foundation/phases.md +0 -60
  52. package/docs/vibes/project-docs-template/foundation/prd.md +0 -69
  53. package/docs/vibes/project-docs-template/foundation/status.md +0 -57
  54. package/docs/vibes/project-docs-template/foundation/workflow.md +0 -59
  55. package/docs/vibes/project-docs-template/getting-started.md +0 -52
  56. package/docs/vibes/project-docs-template/index.md +0 -66
  57. package/docs/vibes/project-docs-template/operations/ci-cd.md +0 -56
  58. package/docs/vibes/project-docs-template/operations/compliance.md +0 -46
  59. package/docs/vibes/project-docs-template/operations/governance.md +0 -46
  60. package/docs/vibes/project-docs-template/operations/observability.md +0 -53
  61. package/docs/vibes/project-docs-template/operations/production-runbook.md +0 -62
  62. package/docs/vibes/project-docs-template/operations/security.md +0 -49
  63. package/docs/vibes/project-docs-template/technical/api-contract.md +0 -49
  64. package/docs/vibes/project-docs-template/technical/database.md +0 -59
  65. package/docs/vibes/project-docs-template/technical/error-handling.md +0 -54
  66. package/docs/vibes/project-docs-template/technical/internationalization.md +0 -46
  67. package/docs/vibes/project-docs-template/technical/testing.md +0 -57
  68. package/docs/vibes/project-docs-template/technical/ui-ux.md +0 -68
  69. package/docs/vibes/project-docs-template/technical-guidelines/index.md +0 -52
  70. package/docs/vibes/reusable-prompts/00-project-docs-bootstrap.md +0 -59
  71. package/docs/vibes/skills/code-audit/SKILL.md +0 -170
  72. package/docs/vibes/skills/project-elevation/SKILL.md +0 -157
  73. package/docs/vibes/skills/test-authoring/references/patterns.md +0 -116
  74. package/docs/vibes/skills/test-authoring/references/test-types.md +0 -52
@@ -1,222 +1,124 @@
1
1
  const fs = require('node:fs');
2
2
  const path = require('node:path');
3
+ const { version: EHA_PACKAGE_VERSION } = require('../../package.json');
3
4
 
4
- const { listWorkflows, getWorkflow } = require('./workflow-registry');
5
- const { getRuntimeAdapter, listSupportedRuntimes } = require('./runtime-adapters');
5
+ const { listWorkflows } = require('./workflow-registry');
6
+ const { listSkills } = require('./skill-registry');
7
+ const { getRuntimeAdapter, listSupportedRuntimes, SUPPORTED_AGENT_IDS } = require('./runtime-adapters');
6
8
  const {
7
9
  ensureDir,
8
10
  getBundledAssetPath,
9
11
  getEnginePaths,
12
+ readConfig,
10
13
  readJsonIfExists,
11
14
  removeEmptyParents,
12
15
  removeFileIfExists,
16
+ writeConfig,
13
17
  writeJson,
14
18
  writeText,
15
19
  } = require('./state');
16
20
 
17
- const DEFAULT_RUNTIME_IDS = ['claude', 'copilot'];
18
- const BUNDLED_CONTRACT_PATH = path.join('docs', 'eyehateagent-contract.md');
19
-
20
- function resolveRuntimeIds(runtimes) {
21
- const runtimeIds = runtimes && runtimes.length > 0 ? runtimes : DEFAULT_RUNTIME_IDS;
22
- return [...new Set(runtimeIds.map((runtime) => String(runtime).trim().toLowerCase()).filter(Boolean))];
21
+ function resolveAgentId(agentId) {
22
+ const normalized = String(agentId || '').trim().toLowerCase();
23
+ if (!SUPPORTED_AGENT_IDS.includes(normalized)) {
24
+ throw new Error(`Unsupported agent: ${agentId}. Choose one of: ${SUPPORTED_AGENT_IDS.join(', ')}.`);
25
+ }
26
+ return normalized;
23
27
  }
24
28
 
25
29
  function readManifest(manifestPath) {
26
30
  return (
27
31
  readJsonIfExists(manifestPath) || {
28
- generatedOutputsInsideRepo: true,
29
32
  manifestVersion: 1,
30
- runtimes: {},
33
+ agent: null,
34
+ files: [],
31
35
  }
32
36
  );
33
37
  }
34
38
 
35
- function buildManifestSummary(runtimeId, adapter, files) {
36
- return {
37
- files: files.map((file) => file.relativePath),
38
- supportTier: adapter.supportTier,
39
- updatedAt: new Date().toISOString(),
40
- };
41
- }
42
-
43
- function getGeneratedSourceRelativePath(repoRelativePath) {
44
- return path.join('.eha', 'generated', 'sources', repoRelativePath);
45
- }
46
-
47
- function materializeBundledSources(rootDir, workflows) {
48
- const repoRelativePaths = new Set([BUNDLED_CONTRACT_PATH]);
49
- for (const workflow of workflows) {
50
- repoRelativePaths.add(workflow.repoRelativePath);
51
- }
52
-
53
- const generatedSourcePaths = {};
54
-
55
- for (const repoRelativePath of repoRelativePaths) {
56
- const sourcePath = getBundledAssetPath(repoRelativePath);
57
- const targetRelativePath = getGeneratedSourceRelativePath(repoRelativePath);
58
- const targetPath = path.join(rootDir, targetRelativePath);
59
- writeText(targetPath, fs.readFileSync(sourcePath, 'utf8'));
60
- generatedSourcePaths[repoRelativePath] = targetRelativePath;
61
- }
62
-
63
- return {
64
- contractPath: generatedSourcePaths[BUNDLED_CONTRACT_PATH],
65
- workflowPaths: generatedSourcePaths,
66
- };
67
- }
68
-
69
- function installRuntimes({ rootDir, runtimes }) {
70
- const runtimeIds = resolveRuntimeIds(runtimes);
71
- const enginePaths = getEnginePaths(rootDir);
72
- const manifest = readManifest(enginePaths.manifestPath);
39
+ function initProject({ rootDir, agentId }) {
40
+ const normalizedAgentId = resolveAgentId(agentId);
41
+ const adapter = getRuntimeAdapter(normalizedAgentId);
73
42
  const workflows = listWorkflows();
74
- const bundledSources = materializeBundledSources(rootDir, workflows);
75
- const runtimeSummaries = [];
76
-
77
- for (const runtimeId of runtimeIds) {
78
- const adapter = getRuntimeAdapter(runtimeId);
79
- const files = adapter.generateFiles(rootDir, workflows, bundledSources);
43
+ const skills = listSkills();
44
+ const files = adapter.generateFiles(rootDir, workflows, skills);
80
45
 
81
- for (const file of files) {
82
- const absolutePath = path.join(rootDir, file.relativePath);
83
- ensureDir(path.dirname(absolutePath));
84
- writeText(absolutePath, file.content);
85
- }
86
-
87
- manifest.runtimes[runtimeId] = buildManifestSummary(runtimeId, adapter, files);
88
- runtimeSummaries.push({
89
- id: runtimeId,
90
- supportTier: adapter.supportTier,
91
- fileCount: files.length,
92
- });
46
+ for (const file of files) {
47
+ const absolutePath = path.join(rootDir, file.relativePath);
48
+ ensureDir(path.dirname(absolutePath));
49
+ writeText(absolutePath, file.content);
93
50
  }
94
51
 
52
+ const enginePaths = getEnginePaths(rootDir);
53
+ const manifest = {
54
+ manifestVersion: 1,
55
+ agent: normalizedAgentId,
56
+ files: files.map((f) => f.relativePath),
57
+ updatedAt: new Date().toISOString(),
58
+ packageVersion: EHA_PACKAGE_VERSION,
59
+ };
95
60
  writeJson(enginePaths.manifestPath, manifest);
61
+ const config = writeConfig(rootDir, { agent: normalizedAgentId });
96
62
 
97
63
  return {
98
64
  rootDir,
99
- runtimes: runtimeIds,
100
- manifestPath: enginePaths.manifestPath,
101
- runtimeSummaries,
65
+ agentId: normalizedAgentId,
66
+ config,
67
+ files: manifest.files,
68
+ fileCount: files.length,
102
69
  };
103
70
  }
104
71
 
105
- function uninstallRuntimes({ rootDir, runtimes }) {
72
+ function removeProject({ rootDir }) {
106
73
  const enginePaths = getEnginePaths(rootDir);
107
74
  const manifest = readManifest(enginePaths.manifestPath);
108
- const runtimeIds = resolveRuntimeIds(runtimes);
109
- const runtimeSummaries = [];
110
-
111
- for (const runtimeId of runtimeIds) {
112
- const manifestEntry = manifest.runtimes[runtimeId];
113
- if (!manifestEntry) {
114
- runtimeSummaries.push({ id: runtimeId, supportTier: 'unknown', fileCount: 0 });
115
- continue;
116
- }
75
+ const removedFiles = [];
117
76
 
118
- for (const relativePath of manifestEntry.files) {
119
- const absolutePath = path.join(rootDir, relativePath);
120
- removeFileIfExists(absolutePath);
121
- removeEmptyParents(path.dirname(absolutePath), rootDir);
122
- }
123
-
124
- runtimeSummaries.push({
125
- id: runtimeId,
126
- supportTier: manifestEntry.supportTier,
127
- fileCount: manifestEntry.files.length,
128
- });
129
- delete manifest.runtimes[runtimeId];
130
- }
131
-
132
- if (Object.keys(manifest.runtimes).length === 0) {
133
- removeFileIfExists(enginePaths.manifestPath);
134
- removeEmptyParents(path.dirname(enginePaths.manifestPath), rootDir);
135
- } else {
136
- writeJson(enginePaths.manifestPath, manifest);
137
- }
138
-
139
- return {
140
- rootDir,
141
- runtimes: runtimeIds,
142
- manifestPath: enginePaths.manifestPath,
143
- runtimeSummaries,
144
- };
145
- }
146
-
147
- function buildPromptContent(workflow, sourcePaths, contextText, invokedAs) {
148
- const commandLabel = invokedAs || workflow.commandName;
149
-
150
- let content = `# EHA Workflow Dispatch\n\n`;
151
- content += `Read \`${sourcePaths.contractPath}\` first.\n\n`;
152
- content += `Execute \`${sourcePaths.workflowPath}\`.\n\n`;
153
- content += `Requested via CLI command: \`${commandLabel}\`\n`;
154
-
155
- if (workflow.capabilityNote) {
156
- content += `\n> Note: ${workflow.capabilityNote}\n`;
77
+ for (const relativePath of manifest.files || []) {
78
+ const absolutePath = path.join(rootDir, relativePath);
79
+ removeFileIfExists(absolutePath);
80
+ removeEmptyParents(path.dirname(absolutePath), rootDir);
81
+ removedFiles.push(relativePath);
157
82
  }
158
83
 
159
- if (contextText) {
160
- content += `\n## Additional CLI Context\n\n${contextText}\n`;
161
- }
84
+ removeFileIfExists(enginePaths.manifestPath);
85
+ removeEmptyParents(path.dirname(enginePaths.manifestPath), rootDir);
86
+ removeFileIfExists(enginePaths.configPath);
87
+ removeEmptyParents(path.dirname(enginePaths.configPath), rootDir);
162
88
 
163
- return content;
164
- }
165
-
166
- function prepareWorkflowRun({ rootDir, workflowId, contextText = '', invokedAs = null }) {
167
- const workflow = getWorkflow(workflowId);
168
- const enginePaths = getEnginePaths(rootDir);
169
- const bundledSources = materializeBundledSources(rootDir, [workflow]);
170
- const promptContent = buildPromptContent(
171
- workflow,
172
- {
173
- contractPath: bundledSources.contractPath,
174
- workflowPath: bundledSources.workflowPaths[workflow.repoRelativePath],
175
- },
176
- contextText,
177
- invokedAs,
178
- );
179
-
180
- writeText(enginePaths.lastPromptPath, promptContent);
181
- writeJson(enginePaths.lastRunPath, {
182
- workflowId: workflow.id,
183
- invokedAs: invokedAs || workflow.commandName,
184
- promptPath: path.relative(rootDir, enginePaths.lastPromptPath),
185
- sourceWorkflowPath: workflow.repoRelativePath,
186
- contextText,
187
- updatedAt: new Date().toISOString(),
188
- });
189
-
190
- return {
191
- workflow,
192
- promptContent,
193
- promptPath: path.relative(rootDir, enginePaths.lastPromptPath),
194
- contextText,
195
- };
89
+ return { rootDir, removedFiles };
196
90
  }
197
91
 
198
92
  function doctor({ rootDir }) {
199
93
  const enginePaths = getEnginePaths(rootDir);
200
94
  const manifest = readManifest(enginePaths.manifestPath);
201
- const installedRuntimes = Object.entries(manifest.runtimes).map(([id, entry]) => ({
202
- id,
203
- supportTier: entry.supportTier,
204
- fileCount: entry.files.length,
95
+ const config = readConfig(rootDir);
96
+
97
+ const generatedFiles = (manifest.files || []).map((relativePath) => ({
98
+ relativePath,
99
+ exists: fs.existsSync(path.join(rootDir, relativePath)),
205
100
  }));
206
101
 
207
102
  return {
208
103
  rootDir,
209
- generatedOutputsInsideRepo: true,
104
+ config,
105
+ agentId: config.agent,
210
106
  paths: enginePaths,
211
- supportedRuntimes: listSupportedRuntimes(),
212
- installedRuntimes,
107
+ supportedAgents: listSupportedRuntimes(),
108
+ generatedFiles,
109
+ isInitialized: Boolean(config.agent),
213
110
  };
214
111
  }
215
112
 
113
+ function readProjectManifest(rootDir) {
114
+ const { manifestPath } = getEnginePaths(rootDir);
115
+ return readManifest(manifestPath);
116
+ }
117
+
216
118
  module.exports = {
217
- DEFAULT_RUNTIME_IDS,
119
+ SUPPORTED_AGENT_IDS,
218
120
  doctor,
219
- installRuntimes,
220
- prepareWorkflowRun,
221
- uninstallRuntimes,
121
+ initProject,
122
+ readProjectManifest,
123
+ removeProject,
222
124
  };
@@ -1,106 +1,322 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
1
4
  const path = require('node:path');
2
5
 
3
- function renderClaudeCommand(workflow, sourcePaths) {
6
+ const { getBundledAssetPath } = require('./state');
7
+
8
+ const SUPPORTED_AGENT_IDS = ['claude', 'copilot', 'antigravity'];
9
+
10
+ // Compact EHA rules embedded in every generated command file so the agent
11
+ // has all structural context without needing any external file reference.
12
+ const EHA_COMPACT_RULES = `## EHA Project Doc Rules
13
+
14
+ **4-Layer Taxonomy.** All project docs live under \`docs/project-docs/\`:
15
+ - \`foundation/\` — prd, architecture, workflow, status, phases, changelog, feature-inventory
16
+ - \`operations/\` — ci-cd, production-runbook, governance, compliance, observability, security
17
+ - \`development/\` — testing, api-contract, database, ui-ux, error-handling, internationalization
18
+ - \`technical-guidelines/\` — domain-specific cross-cutting rules (API, database, logging, etc.)
19
+
20
+ **Legacy/Reference Docs:** Treat folders named \`archive/\`, \`docs-legacy/\`, or \`reference/\` as secondary migration input only, never as authoritative active truth.
21
+
22
+ **Mandatory core docs:** \`index.md\`, \`getting-started.md\`, \`foundation/prd.md\`, \`foundation/architecture.md\`, \`foundation/workflow.md\`, \`foundation/status.md\`, \`foundation/phases.md\`, \`operations/ci-cd.md\`, \`operations/production-runbook.md\`, \`development/testing.md\`, \`development/api-contract.md\`, \`development/database.md\`, \`development/ui-ux.md\`.
23
+
24
+ **Authority order:** project docs → codebase → agent judgment. When docs conflict, the owning doc wins. When code and docs conflict and authority is unclear, surface the conflict and ask the user — do not guess.
25
+
26
+ **Universal stable headings (every file):** Description, Important, Table of Contents, Scope, Goals, Non Goals.
27
+
28
+ **Key ownership rules:**
29
+ - Vision, personas, requirements → \`foundation/prd.md\`
30
+ - Stack and architecture → \`foundation/architecture.md\`
31
+ - Dev loop and PR process → \`foundation/workflow.md\`
32
+ - Verification commands and quality gates → \`development/testing.md\`
33
+ - Execution plan and progress → \`foundation/status.md\`
34
+ - Sprint tracking and backlogs → \`foundation/phases.md\`
35
+ - Optional doc inventory → \`index.md\`
36
+ - Domain-specific technical rules → \`technical-guidelines/\` (Create these only for durable cross-cutting rules; avoid placeholders).
37
+
38
+ **SDD rule:** Specifications dictate implementation. Follow a strict 4-step workflow: 1. Update project docs first, 2. Generate tests based on the specs, 3. Generate code to pass the tests, 4. Logically map every code change back to a spec requirement. Refuse to write code for features not in the spec.
39
+
40
+ **Flexible Baselines Principle:** Omit docs the repo doesn't need. Mark unknowns as \`TBD\` or \`Assumption\`. Mark inferred facts as \`Inferred from codebase\` until the user confirms them.`;
41
+
42
+ function loadPromptContent(workflow) {
43
+ const promptPath = getBundledAssetPath(workflow.repoRelativePath);
44
+ const raw = fs.readFileSync(promptPath, 'utf8');
45
+ // Remove the "Read `docs/eyehateagent-contract.md` first." line — the
46
+ // compact rules block replaces that dependency.
47
+ return raw
48
+ .split('\n')
49
+ .filter((line) => !line.includes('docs/eyehateagent-contract.md'))
50
+ .join('\n')
51
+ .replace(/^\n+/, '');
52
+ }
53
+
54
+ function loadSkillContent(skill) {
55
+ const promptPath = getBundledAssetPath(skill.repoRelativePath);
56
+ const raw = fs.readFileSync(promptPath, 'utf8');
57
+ const parts = raw.split(/^---\r?\n/m);
58
+ let body = parts.length > 2 ? parts.slice(2).join('---\n') : raw;
59
+ return body
60
+ .split('\n')
61
+ .filter((line) => !line.includes('docs/eyehateagent-contract.md'))
62
+ .join('\n')
63
+ .replace(/^\n+/, '');
64
+ }
65
+
66
+ function loadRuleContent(agentId) {
67
+ const rulePath = getBundledAssetPath(path.join('docs', 'templates', 'rules', 'agent-rules.md'));
68
+ let content = fs.readFileSync(rulePath, 'utf8').replace(/^\n+/, '');
69
+
70
+ if (agentId) {
71
+ const agentsToFilter = SUPPORTED_AGENT_IDS.filter(a => a !== agentId.toLowerCase());
72
+ for (const a of agentsToFilter) {
73
+ const regex = new RegExp(`^\\s*-\\s*\\*\\*${a}.*$\\n?`, 'gmi');
74
+ content = content.replace(regex, '');
75
+ }
76
+ }
77
+
78
+ return content;
79
+ }
80
+
81
+ function buildClaudeCommandFile(workflow) {
82
+ const promptContent = loadPromptContent(workflow);
4
83
  return `---
5
- description: "Run the EHA ${workflow.id} workflow"
84
+ description: "EHA ${workflow.id} — ${workflow.description}"
6
85
  ---
7
86
 
8
- Read \`${sourcePaths.contractPath}\` first.
87
+ ${EHA_COMPACT_RULES}
9
88
 
10
- Execute \`${sourcePaths.workflowPath}\`.
89
+ ---
11
90
 
12
- If the user provides additional context, apply it while preserving the workflow's output contract and the owning-doc rules in \`docs/project-docs/\`.
13
- `;
91
+ ${promptContent}`;
14
92
  }
15
93
 
16
- function renderCopilotInstructions(workflows, bundledSources) {
17
- const workflowTable = workflows
18
- .map(
19
- (workflow) =>
20
- `- \`${workflow.commandName}\` -> \`${bundledSources.workflowPaths[workflow.repoRelativePath]}\`${workflow.capabilityNote ? ` (${workflow.capabilityNote})` : ''}`,
21
- )
22
- .join('\n');
94
+ function buildCopilotPromptFile(workflow) {
95
+ const promptContent = loadPromptContent(workflow);
96
+ return `---
97
+ mode: agent
98
+ description: "EHA ${workflow.id} ${workflow.description}"
99
+ ---
100
+
101
+ ${EHA_COMPACT_RULES}
102
+
103
+ ---
104
+
105
+ ${promptContent}`;
106
+ }
23
107
 
108
+ function buildClaudeSkillFile(skill) {
24
109
  return `---
25
- description: "Generated EHA workflow routing for GitHub Copilot"
110
+ description: "EHA skill ${skill.commandName}"
111
+ ---
112
+
113
+ ${EHA_COMPACT_RULES}
114
+
115
+ ---
116
+
117
+ ${loadSkillContent(skill)}`;
118
+ }
119
+
120
+ function buildCopilotSkillFile(skill) {
121
+ return `---
122
+ mode: agent
123
+ description: "EHA skill — ${skill.commandName}"
124
+ ---
125
+
126
+ ${EHA_COMPACT_RULES}
127
+
128
+ ---
129
+
130
+ ${loadSkillContent(skill)}`;
131
+ }
132
+
133
+ function buildClaudeRuleFile() {
134
+ return `---
135
+ description: "EHA agent rules"
136
+ ---
137
+
138
+ ${loadRuleContent('claude')}`;
139
+ }
140
+
141
+ function buildCopilotRuleFile() {
142
+ return `---
143
+ description: "EHA agent rules"
26
144
  applyTo: "**"
27
145
  ---
28
146
 
29
- # EHA Engine Workflows
147
+ ${loadRuleContent('copilot')}`;
148
+ }
30
149
 
31
- When a user asks to run an EHA workflow, prefer the canonical reusable prompt file listed below and preserve its output contract.
150
+ function buildAntigravityCommandFile(workflow) {
151
+ const promptContent = loadPromptContent(workflow);
152
+ return `---
153
+ name: "eha-${workflow.commandName}"
154
+ description: "EHA ${workflow.id} — ${workflow.description}"
155
+ ---
32
156
 
33
- ${workflowTable}
157
+ ${EHA_COMPACT_RULES}
34
158
 
35
- ## Runtime support
159
+ ---
36
160
 
37
- - GitHub Copilot support tier is **guided**
38
- - this install generates repo-local instructions, not slash commands
39
- - bundled contract path: \`${bundledSources.contractPath}\`
40
- - generated outputs stay inside the repository for transparency
41
- `;
161
+ ${promptContent}`;
162
+ }
163
+
164
+ function buildAntigravitySkillFile(skill) {
165
+ return `---
166
+ name: "eha-${skill.commandName}"
167
+ description: "EHA skill — ${skill.commandName}"
168
+ ---
169
+
170
+ ${EHA_COMPACT_RULES}
171
+
172
+ ---
173
+
174
+ ${loadSkillContent(skill)}`;
175
+ }
176
+
177
+ function buildAntigravityRuleFile() {
178
+ return `---
179
+ name: "eha-agent-rules"
180
+ description: "EHA agent rules"
181
+ ---
182
+
183
+ ${loadRuleContent('antigravity')}`;
184
+ }
185
+
186
+ function buildCopilotInstructionsFile(workflows) {
187
+ const workflowTable = workflows
188
+ .map((w) => `- \`${w.commandName}\` → \`.github/prompts/eha-${w.commandName}.prompt.md\``)
189
+ .join('\n');
190
+
191
+ return `---
192
+ description: "EHA workflow routing for GitHub Copilot"
193
+ applyTo: "**"
194
+ ---
195
+
196
+ # EHA Workflows
197
+
198
+ When a user asks to run an EHA workflow, use the matching prompt file below.
199
+
200
+ ${workflowTable}`;
42
201
  }
43
202
 
44
203
  const RUNTIME_ADAPTERS = {
45
204
  claude: {
46
205
  id: 'claude',
47
206
  name: 'Claude',
48
- supportTier: 'full',
49
- description: 'Repo-local Claude command projection under .claude/commands/eha/',
50
- generateFiles(rootDir, workflows, bundledSources) {
207
+ description: 'Generates .claude/commands/eha/ slash command files, .claude/skills/ and .claude/rules/',
208
+ generateFiles(rootDir, workflows, skills) {
51
209
  const files = [];
52
210
  for (const workflow of workflows) {
53
211
  files.push({
54
212
  relativePath: path.join('.claude', 'commands', 'eha', `eha-${workflow.commandName}.md`),
55
- content: renderClaudeCommand(workflow, {
56
- contractPath: bundledSources.contractPath,
57
- workflowPath: bundledSources.workflowPaths[workflow.repoRelativePath],
58
- }),
213
+ content: buildClaudeCommandFile(workflow),
59
214
  });
60
215
  }
61
-
62
216
  files.push({
63
217
  relativePath: path.join('.claude', 'commands', 'eha', 'README.md'),
64
- content:
65
- '# Generated EHA Claude commands\n\nThese files are generated by `eha install` from the canonical workflow registry.\n',
218
+ content: `# EHA Claude commands\n\nGenerated by \`eha init\`. Use \`/eha-bootstrap\`, \`/eha-refresh\`, \`/eha-parity\`, or \`/eha-discuss\` in Claude.\n`,
66
219
  });
67
-
220
+
221
+ for (const skill of skills) {
222
+ files.push({
223
+ relativePath: path.join('.claude', 'skills', `eha-${skill.commandName}.md`),
224
+ content: buildClaudeSkillFile(skill),
225
+ });
226
+ }
227
+
228
+ files.push({
229
+ relativePath: path.join('.claude', 'rules', 'eha-agent-rules.md'),
230
+ content: buildClaudeRuleFile(),
231
+ });
232
+
68
233
  return files;
69
234
  },
70
235
  },
71
236
  copilot: {
72
237
  id: 'copilot',
73
238
  name: 'GitHub Copilot',
74
- supportTier: 'guided',
75
- description: 'Repo-local Copilot instruction projection under .github/instructions/',
76
- generateFiles(rootDir, workflows, bundledSources) {
77
- return [
78
- {
79
- relativePath: path.join('.github', 'instructions', 'eha-workflows.instructions.md'),
80
- content: renderCopilotInstructions(workflows, bundledSources),
81
- },
82
- ];
239
+ description: 'Generates .github/prompts/ reusable prompt files, skills, and always-on instruction rules',
240
+ generateFiles(rootDir, workflows, skills) {
241
+ const files = [];
242
+ for (const workflow of workflows) {
243
+ files.push({
244
+ relativePath: path.join('.github', 'prompts', `eha-${workflow.commandName}.prompt.md`),
245
+ content: buildCopilotPromptFile(workflow),
246
+ });
247
+ }
248
+ files.push({
249
+ relativePath: path.join('.github', 'instructions', 'eha-workflows.instructions.md'),
250
+ content: buildCopilotInstructionsFile(workflows),
251
+ });
252
+
253
+ for (const skill of skills) {
254
+ files.push({
255
+ relativePath: path.join('.github', 'prompts', 'skills', `eha-${skill.commandName}.prompt.md`),
256
+ content: buildCopilotSkillFile(skill),
257
+ });
258
+ }
259
+
260
+ files.push({
261
+ relativePath: path.join('.github', 'instructions', 'eha-agent-rules.instructions.md'),
262
+ content: buildCopilotRuleFile(),
263
+ });
264
+
265
+ return files;
266
+ },
267
+ },
268
+ antigravity: {
269
+ id: 'antigravity',
270
+ name: 'Antigravity',
271
+ description: 'Generates Antigravity-compatible skills in .agents/skills/',
272
+ generateFiles(rootDir, workflows, skills) {
273
+ const files = [];
274
+ for (const workflow of workflows) {
275
+ files.push({
276
+ relativePath: path.join('.agents', 'skills', `eha-${workflow.commandName}`, 'SKILL.md'),
277
+ content: buildAntigravityCommandFile(workflow),
278
+ });
279
+ }
280
+ files.push({
281
+ relativePath: path.join('.agents', 'commands', 'eha', 'README.md'),
282
+ content: `# EHA Antigravity commands\n\nGenerated by \`eha init\`. Call the generated skills directly.\n`,
283
+ });
284
+
285
+ for (const skill of skills) {
286
+ files.push({
287
+ relativePath: path.join('.agents', 'skills', `eha-${skill.commandName}`, 'SKILL.md'),
288
+ content: buildAntigravitySkillFile(skill),
289
+ });
290
+ }
291
+
292
+ files.push({
293
+ relativePath: path.join('.agents', 'skills', 'eha-agent-rules', 'SKILL.md'),
294
+ content: buildAntigravityRuleFile(),
295
+ });
296
+
297
+ return files;
83
298
  },
84
299
  },
85
300
  };
86
301
 
87
- function getRuntimeAdapter(runtimeId) {
88
- const adapter = RUNTIME_ADAPTERS[String(runtimeId).trim().toLowerCase()];
302
+ function getRuntimeAdapter(agentId) {
303
+ const adapter = RUNTIME_ADAPTERS[String(agentId).trim().toLowerCase()];
89
304
  if (!adapter) {
90
- throw new Error(`Unsupported runtime: ${runtimeId}`);
305
+ throw new Error(`Unsupported agent: ${agentId}. Choose one of: ${Object.keys(RUNTIME_ADAPTERS).join(', ')}.`);
91
306
  }
92
307
  return adapter;
93
308
  }
94
309
 
95
310
  function listSupportedRuntimes() {
96
- return Object.values(RUNTIME_ADAPTERS).map((runtime) => ({
97
- id: runtime.id,
98
- supportTier: runtime.supportTier,
99
- description: runtime.description,
311
+ return Object.values(RUNTIME_ADAPTERS).map((a) => ({
312
+ id: a.id,
313
+ name: a.name,
314
+ description: a.description,
100
315
  }));
101
316
  }
102
317
 
103
318
  module.exports = {
104
319
  getRuntimeAdapter,
105
320
  listSupportedRuntimes,
321
+ SUPPORTED_AGENT_IDS,
106
322
  };