@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.
- package/README.md +56 -304
- package/bin/eha.js +203 -118
- package/docs/templates/project-docs-template/index.md +208 -0
- package/docs/templates/project-docs-template/technical-guidelines/index.md +81 -0
- package/docs/templates/reusable-prompts/00-project-docs-bootstrap.md +40 -0
- package/docs/{vibes → templates}/reusable-prompts/00-project-docs-parity.md +4 -6
- package/docs/{vibes → templates}/reusable-prompts/00-project-docs-refresh.md +11 -11
- package/docs/{vibes → templates}/reusable-prompts/01-sdd-execute.md +1 -1
- package/docs/{vibes → templates}/reusable-prompts/02-sdd-discuss.md +3 -3
- package/{.agents/rules/agent.md → docs/templates/rules/agent-rules.md} +7 -12
- package/docs/{vibes → templates}/skills/api-design/SKILL.md +14 -25
- package/docs/{vibes/skills/full-verification → templates/skills/code-audit}/SKILL.md +36 -54
- package/docs/templates/skills/db-schema-design/SKILL.md +120 -0
- package/docs/templates/skills/devops-ci-cd/SKILL.md +99 -0
- package/docs/templates/skills/observability/SKILL.md +99 -0
- package/docs/{vibes/skills/parity → templates/skills/parity-audit}/SKILL.md +26 -52
- package/docs/templates/skills/refactor/SKILL.md +100 -0
- package/docs/templates/skills/security-audit/SKILL.md +149 -0
- package/docs/{vibes/skills/analysis → templates/skills/system-analysis}/SKILL.md +19 -41
- package/docs/{vibes/skills/test-authoring → templates/skills/system-tester}/SKILL.md +28 -46
- package/docs/templates/skills/ui-ux-design/SKILL.md +102 -0
- package/docs/templates/skills/wireframing/SKILL.md +88 -0
- package/package.json +4 -6
- package/src/engine/index.js +9 -12
- package/src/engine/install.js +67 -165
- package/src/engine/runtime-adapters.js +266 -50
- package/src/engine/skill-registry.js +72 -0
- package/src/engine/state.js +29 -7
- package/src/engine/workflow-registry.js +14 -23
- package/.claude/commands/eha/README.md +0 -3
- package/.claude/commands/eha/eha-bootstrap.md +0 -9
- package/.claude/commands/eha/eha-discuss.md +0 -9
- package/.claude/commands/eha/eha-execute.md +0 -9
- package/.claude/commands/eha/eha-parity.md +0 -9
- package/.claude/commands/eha/eha-refresh.md +0 -9
- package/.claude/commands/eha/eha-verify.md +0 -9
- package/.claude/rules/agent-rules.md +0 -64
- package/.github/instructions/agent-rules.instructions.md +0 -63
- package/.github/instructions/eha-workflows.instructions.md +0 -21
- package/docs/eyehateagent-contract.md +0 -475
- package/docs/eyehateagent-maintenance.md +0 -103
- package/docs/project-docs/changelog.md +0 -299
- package/docs/project-docs/foundation/architecture.md +0 -117
- package/docs/project-docs/foundation/status.md +0 -32
- package/docs/project-docs/foundation/workflow.md +0 -63
- package/docs/project-docs/index.md +0 -20
- package/docs/project-docs/testing.md +0 -73
- package/docs/vibes/project-docs-template/foundation/architecture.md +0 -79
- package/docs/vibes/project-docs-template/foundation/changelog.md +0 -53
- package/docs/vibes/project-docs-template/foundation/feature-inventory.md +0 -46
- package/docs/vibes/project-docs-template/foundation/phases.md +0 -60
- package/docs/vibes/project-docs-template/foundation/prd.md +0 -69
- package/docs/vibes/project-docs-template/foundation/status.md +0 -57
- package/docs/vibes/project-docs-template/foundation/workflow.md +0 -59
- package/docs/vibes/project-docs-template/getting-started.md +0 -52
- package/docs/vibes/project-docs-template/index.md +0 -66
- package/docs/vibes/project-docs-template/operations/ci-cd.md +0 -56
- package/docs/vibes/project-docs-template/operations/compliance.md +0 -46
- package/docs/vibes/project-docs-template/operations/governance.md +0 -46
- package/docs/vibes/project-docs-template/operations/observability.md +0 -53
- package/docs/vibes/project-docs-template/operations/production-runbook.md +0 -62
- package/docs/vibes/project-docs-template/operations/security.md +0 -49
- package/docs/vibes/project-docs-template/technical/api-contract.md +0 -49
- package/docs/vibes/project-docs-template/technical/database.md +0 -59
- package/docs/vibes/project-docs-template/technical/error-handling.md +0 -54
- package/docs/vibes/project-docs-template/technical/internationalization.md +0 -46
- package/docs/vibes/project-docs-template/technical/testing.md +0 -57
- package/docs/vibes/project-docs-template/technical/ui-ux.md +0 -68
- package/docs/vibes/project-docs-template/technical-guidelines/index.md +0 -52
- package/docs/vibes/reusable-prompts/00-project-docs-bootstrap.md +0 -59
- package/docs/vibes/skills/code-audit/SKILL.md +0 -170
- package/docs/vibes/skills/project-elevation/SKILL.md +0 -157
- package/docs/vibes/skills/test-authoring/references/patterns.md +0 -116
- package/docs/vibes/skills/test-authoring/references/test-types.md +0 -52
package/src/engine/install.js
CHANGED
|
@@ -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
|
|
5
|
-
const {
|
|
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
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return
|
|
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
|
-
|
|
33
|
+
agent: null,
|
|
34
|
+
files: [],
|
|
31
35
|
}
|
|
32
36
|
);
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
75
|
-
const
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
65
|
+
agentId: normalizedAgentId,
|
|
66
|
+
config,
|
|
67
|
+
files: manifest.files,
|
|
68
|
+
fileCount: files.length,
|
|
102
69
|
};
|
|
103
70
|
}
|
|
104
71
|
|
|
105
|
-
function
|
|
72
|
+
function removeProject({ rootDir }) {
|
|
106
73
|
const enginePaths = getEnginePaths(rootDir);
|
|
107
74
|
const manifest = readManifest(enginePaths.manifestPath);
|
|
108
|
-
const
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
|
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
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
104
|
+
config,
|
|
105
|
+
agentId: config.agent,
|
|
210
106
|
paths: enginePaths,
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
119
|
+
SUPPORTED_AGENT_IDS,
|
|
218
120
|
doctor,
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
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: "
|
|
84
|
+
description: "EHA ${workflow.id} — ${workflow.description}"
|
|
6
85
|
---
|
|
7
86
|
|
|
8
|
-
|
|
87
|
+
${EHA_COMPACT_RULES}
|
|
9
88
|
|
|
10
|
-
|
|
89
|
+
---
|
|
11
90
|
|
|
12
|
-
|
|
13
|
-
`;
|
|
91
|
+
${promptContent}`;
|
|
14
92
|
}
|
|
15
93
|
|
|
16
|
-
function
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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: "
|
|
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
|
-
|
|
147
|
+
${loadRuleContent('copilot')}`;
|
|
148
|
+
}
|
|
30
149
|
|
|
31
|
-
|
|
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
|
-
${
|
|
157
|
+
${EHA_COMPACT_RULES}
|
|
34
158
|
|
|
35
|
-
|
|
159
|
+
---
|
|
36
160
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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:
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
{
|
|
79
|
-
relativePath: path.join('.github', '
|
|
80
|
-
content:
|
|
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(
|
|
88
|
-
const adapter = RUNTIME_ADAPTERS[String(
|
|
302
|
+
function getRuntimeAdapter(agentId) {
|
|
303
|
+
const adapter = RUNTIME_ADAPTERS[String(agentId).trim().toLowerCase()];
|
|
89
304
|
if (!adapter) {
|
|
90
|
-
throw new Error(`Unsupported
|
|
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((
|
|
97
|
-
id:
|
|
98
|
-
|
|
99
|
-
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
|
};
|