forgehive 0.7.6 → 0.7.7
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 +9 -0
- package/docs/superpowers/plans/2026-05-11-nextgen-v04-v05.md +1708 -0
- package/docs/superpowers/plans/2026-05-11-v05-nextgen-gaps.md +2364 -0
- package/docs/superpowers/plans/2026-05-14-sprint-planner-v2.md +1058 -0
- package/docs/superpowers/plans/2026-05-14-v07-nextgen.md +1980 -0
- package/docs/user-guide.md +337 -0
- package/package.json +2 -1
|
@@ -0,0 +1,1708 @@
|
|
|
1
|
+
# ForgeHive NextGen v0.4 + v0.5 Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Implement eight NextGen features that make ForgeHive the most capable AI OS for development teams: status dashboard, filesystem watcher, guardrail hooks, workflow slash commands, skills index, agent memory, 40 MCP services, and wire credential validation.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Each feature is a new module (`src/<name>.ts`) or an extension of `src/writer.ts`/`src/wire.ts`. Runtime assets (hook scripts, command templates, index files) live in `forgehive/` and are copied into the user's `.forgehive/` at `fh init` time. All new modules follow the existing ESM + `node:test` pattern.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript ESM, Node.js `node:fs`/`node:child_process`/`node:http`, js-yaml, esbuild, native `node:test`
|
|
10
|
+
|
|
11
|
+
**Repo path:** `/home/stefan/projekte/forgehive`
|
|
12
|
+
**Run tests:** `npm test` (inside repo root)
|
|
13
|
+
**Build:** `npm run build`
|
|
14
|
+
**Test runner:** `node --import tsx/esm --test test/*.test.ts`
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## File Map
|
|
19
|
+
|
|
20
|
+
### New files
|
|
21
|
+
| File | Purpose |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `src/status.ts` | `projectStatus()` — reads .forgehive/ and returns health report |
|
|
24
|
+
| `src/watch.ts` | `checkProjectHash()`, `watchProject()` — filesystem watcher |
|
|
25
|
+
| `src/guardrails.ts` | `writeGuardrailHooks()` — writes settings.json + hook script |
|
|
26
|
+
| `forgehive/hooks/guardrails.sh` | Hook script template for PreToolUse bash guardrails |
|
|
27
|
+
| `forgehive/commands/fh-start-task.md` | Slash command: start a new task |
|
|
28
|
+
| `forgehive/commands/fh-ship.md` | Slash command: pre-ship checklist |
|
|
29
|
+
| `forgehive/commands/fh-review.md` | Slash command: code review workflow |
|
|
30
|
+
| `forgehive/commands/fh-hotfix.md` | Slash command: hotfix protocol |
|
|
31
|
+
| `forgehive/skills/INDEX.yaml` | Skills index with tags for progressive loading |
|
|
32
|
+
| `test/status.test.ts` | Tests for status module |
|
|
33
|
+
| `test/watch.test.ts` | Tests for watch module |
|
|
34
|
+
| `test/guardrails.test.ts` | Tests for guardrails module |
|
|
35
|
+
|
|
36
|
+
### Modified files
|
|
37
|
+
| File | Changes |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `src/writer.ts` | `initForgehiveRuntime`: copy commands/, copy INDEX.yaml, create agent memory, call guardrails |
|
|
40
|
+
| `src/cli.ts` | Add `status`, `watch` command handlers; update `wire` to show validation |
|
|
41
|
+
| `src/skills.ts` | `listSkills`: show INDEX tags when available |
|
|
42
|
+
| `src/wire.ts` | Add 30 services (10→40); add `validateWireService()` export |
|
|
43
|
+
| `forgehive/templates/claude-md.block.md` | Add skills index + agent memory instructions |
|
|
44
|
+
| `test/writer.test.ts` | Tests for new init behaviors |
|
|
45
|
+
| `test/wire.test.ts` | Tests for new services + validation |
|
|
46
|
+
| `test/skills.test.ts` | Test INDEX-aware listSkills |
|
|
47
|
+
| `package.json` | Bump version to 0.4.0 |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Task 1: `fh status` command
|
|
52
|
+
|
|
53
|
+
**Files:**
|
|
54
|
+
- Create: `src/status.ts`
|
|
55
|
+
- Create: `test/status.test.ts`
|
|
56
|
+
- Modify: `src/cli.ts` (add status handler after the wire handler, before the else)
|
|
57
|
+
|
|
58
|
+
- [ ] **Step 1: Write failing test**
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// test/status.test.ts
|
|
62
|
+
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
63
|
+
import assert from "node:assert/strict";
|
|
64
|
+
import fs from "node:fs";
|
|
65
|
+
import path from "node:path";
|
|
66
|
+
import os from "node:os";
|
|
67
|
+
import yaml from "js-yaml";
|
|
68
|
+
import { projectStatus } from "../src/status.ts";
|
|
69
|
+
|
|
70
|
+
describe("projectStatus()", () => {
|
|
71
|
+
let tmpDir: string;
|
|
72
|
+
let forgehiveDir: string;
|
|
73
|
+
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "fh-status-"));
|
|
76
|
+
forgehiveDir = path.join(tmpDir, ".forgehive");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
afterEach(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
|
|
80
|
+
|
|
81
|
+
it("zeigt 'not initialized' wenn .forgehive/ fehlt", () => {
|
|
82
|
+
const out = projectStatus(tmpDir, forgehiveDir);
|
|
83
|
+
assert.ok(out.includes("fh init"));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("zeigt capabilities-Status", () => {
|
|
87
|
+
fs.mkdirSync(forgehiveDir, { recursive: true });
|
|
88
|
+
fs.writeFileSync(
|
|
89
|
+
path.join(forgehiveDir, "capabilities.yaml"),
|
|
90
|
+
yaml.dump({ status: "confirmed", capabilities: { confirmed: [{ id: "typescript" }], inferred: [] } }),
|
|
91
|
+
"utf8"
|
|
92
|
+
);
|
|
93
|
+
const out = projectStatus(tmpDir, forgehiveDir);
|
|
94
|
+
assert.ok(out.includes("confirmed"));
|
|
95
|
+
assert.ok(out.includes("1"));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("zeigt CLAUDE.md Block-Status", () => {
|
|
99
|
+
fs.mkdirSync(forgehiveDir, { recursive: true });
|
|
100
|
+
fs.writeFileSync(
|
|
101
|
+
path.join(tmpDir, "CLAUDE.md"),
|
|
102
|
+
"<!-- forgehive:start -->\n# forgehive\n<!-- forgehive:end -->",
|
|
103
|
+
"utf8"
|
|
104
|
+
);
|
|
105
|
+
const out = projectStatus(tmpDir, forgehiveDir);
|
|
106
|
+
assert.ok(out.includes("CLAUDE.md"));
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("zeigt Skills-Zählung", () => {
|
|
110
|
+
fs.mkdirSync(path.join(forgehiveDir, "skills", "expert"), { recursive: true });
|
|
111
|
+
fs.mkdirSync(path.join(forgehiveDir, "skills", "generated"), { recursive: true });
|
|
112
|
+
fs.mkdirSync(path.join(forgehiveDir, "skills", "workflows"), { recursive: true });
|
|
113
|
+
fs.writeFileSync(path.join(forgehiveDir, "skills", "expert", "test.md"), "# test", "utf8");
|
|
114
|
+
const out = projectStatus(tmpDir, forgehiveDir);
|
|
115
|
+
assert.ok(out.includes("expert"));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("zeigt MCP-Services wenn .mcp.json vorhanden", () => {
|
|
119
|
+
fs.mkdirSync(forgehiveDir, { recursive: true });
|
|
120
|
+
fs.writeFileSync(
|
|
121
|
+
path.join(tmpDir, ".mcp.json"),
|
|
122
|
+
JSON.stringify({ mcpServers: { linear: {}, slack: {} } }),
|
|
123
|
+
"utf8"
|
|
124
|
+
);
|
|
125
|
+
const out = projectStatus(tmpDir, forgehiveDir);
|
|
126
|
+
assert.ok(out.includes("linear"));
|
|
127
|
+
assert.ok(out.includes("slack"));
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- [ ] **Step 2: Run failing test**
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
cd /home/stefan/projekte/forgehive
|
|
136
|
+
npm test 2>&1 | grep "status\|FAIL\|Error" | head -20
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Expected: `Error: Cannot find module '../src/status.ts'`
|
|
140
|
+
|
|
141
|
+
- [ ] **Step 3: Implement `src/status.ts`**
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import fs from "node:fs";
|
|
145
|
+
import path from "node:path";
|
|
146
|
+
import yaml from "js-yaml";
|
|
147
|
+
|
|
148
|
+
function countMdFiles(dir: string): number {
|
|
149
|
+
if (!fs.existsSync(dir)) return 0;
|
|
150
|
+
return fs.readdirSync(dir).filter(f => f.endsWith(".md")).length;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function projectStatus(projectRoot: string, forgehiveDir: string): string {
|
|
154
|
+
const lines: string[] = ["ForgeHive Status", ""];
|
|
155
|
+
|
|
156
|
+
if (!fs.existsSync(forgehiveDir)) {
|
|
157
|
+
lines.push("✗ Not initialized — run: fh init");
|
|
158
|
+
return lines.join("\n");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Capabilities
|
|
162
|
+
const capsPath = path.join(forgehiveDir, "capabilities.yaml");
|
|
163
|
+
if (fs.existsSync(capsPath)) {
|
|
164
|
+
const caps = yaml.load(fs.readFileSync(capsPath, "utf8")) as {
|
|
165
|
+
status: string;
|
|
166
|
+
capabilities: { confirmed: unknown[]; inferred: unknown[] };
|
|
167
|
+
};
|
|
168
|
+
const confirmed = caps.capabilities?.confirmed?.length ?? 0;
|
|
169
|
+
const inferred = caps.capabilities?.inferred?.length ?? 0;
|
|
170
|
+
const icon = caps.status === "confirmed" ? "✓" : "⚠";
|
|
171
|
+
lines.push(`${icon} Capabilities: ${confirmed} confirmed, ${inferred} inferred (${caps.status})`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// CLAUDE.md block
|
|
175
|
+
const claudeMdPath = path.join(projectRoot, "CLAUDE.md");
|
|
176
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
177
|
+
const content = fs.readFileSync(claudeMdPath, "utf8");
|
|
178
|
+
const hasBlock = content.includes("<!-- forgehive:start -->");
|
|
179
|
+
lines.push(hasBlock ? "✓ CLAUDE.md block present" : "⚠ CLAUDE.md block missing — run: fh init");
|
|
180
|
+
} else {
|
|
181
|
+
lines.push("⚠ CLAUDE.md not found");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Skills
|
|
185
|
+
const skillsDir = path.join(forgehiveDir, "skills");
|
|
186
|
+
const generated = countMdFiles(path.join(skillsDir, "generated"));
|
|
187
|
+
const expert = countMdFiles(path.join(skillsDir, "expert"));
|
|
188
|
+
const workflows = countMdFiles(path.join(skillsDir, "workflows"));
|
|
189
|
+
lines.push(`✓ Skills: ${generated} generated, ${expert} expert, ${workflows} workflows`);
|
|
190
|
+
|
|
191
|
+
// MCP / Wire
|
|
192
|
+
const mcpPath = path.join(projectRoot, ".mcp.json");
|
|
193
|
+
if (fs.existsSync(mcpPath)) {
|
|
194
|
+
const mcp = JSON.parse(fs.readFileSync(mcpPath, "utf8")) as { mcpServers?: Record<string, unknown> };
|
|
195
|
+
const services = Object.keys(mcp.mcpServers ?? {});
|
|
196
|
+
lines.push(services.length > 0
|
|
197
|
+
? `✓ MCP: ${services.join(", ")}`
|
|
198
|
+
: " MCP: none configured"
|
|
199
|
+
);
|
|
200
|
+
} else {
|
|
201
|
+
lines.push(" MCP: none configured (fh wire <service>)");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Memory
|
|
205
|
+
const memoryDir = path.join(forgehiveDir, "memory");
|
|
206
|
+
if (fs.existsSync(memoryDir)) {
|
|
207
|
+
const files = fs.readdirSync(memoryDir).filter(f => f.endsWith(".md"));
|
|
208
|
+
lines.push(`✓ Memory: ${files.length} file(s)`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Scan freshness
|
|
212
|
+
const scanPath = path.join(forgehiveDir, "scan-result.yaml");
|
|
213
|
+
if (fs.existsSync(scanPath)) {
|
|
214
|
+
const scan = yaml.load(fs.readFileSync(scanPath, "utf8")) as { scanned_at?: string };
|
|
215
|
+
lines.push(`✓ Last scan: ${scan.scanned_at ?? "unknown"}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Agent memory
|
|
219
|
+
const agentMemDir = path.join(forgehiveDir, "agents", "memory");
|
|
220
|
+
if (fs.existsSync(agentMemDir)) {
|
|
221
|
+
const agents = fs.readdirSync(agentMemDir).filter(f => f.endsWith(".md"));
|
|
222
|
+
lines.push(`✓ Agent memory: ${agents.length} agent(s)`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return lines.join("\n");
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
- [ ] **Step 4: Add `status` command to `src/cli.ts`**
|
|
230
|
+
|
|
231
|
+
Add this block immediately before the final `else` block (around line 278 in the current file):
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
} else if (command === "status") {
|
|
235
|
+
const { projectStatus } = await import("./status.ts");
|
|
236
|
+
console.log(projectStatus(projectRoot, forgehiveDir));
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Wait — `cli.ts` uses static imports, not dynamic. Add the import at the top with the others:
|
|
241
|
+
|
|
242
|
+
At top of `src/cli.ts`, add after the `wire` import:
|
|
243
|
+
```typescript
|
|
244
|
+
import { projectStatus } from "./status.ts";
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Then add the command handler before the final `else`:
|
|
248
|
+
```typescript
|
|
249
|
+
} else if (command === "status") {
|
|
250
|
+
console.log(projectStatus(projectRoot, forgehiveDir));
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Also update the help text in the final `else` branch to include `status`:
|
|
255
|
+
```typescript
|
|
256
|
+
console.error("Verfügbar: init | confirm | rollback | scan --update | scan --check | memory [show|clean|export] | skills [list|regen] | party [--set <name>|--agents <a,b,c> --name <name>] | wire <service> | status | watch");
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
- [ ] **Step 5: Run tests**
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
cd /home/stefan/projekte/forgehive && npm test 2>&1 | tail -15
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Expected: all existing tests pass + 5 new status tests pass.
|
|
266
|
+
|
|
267
|
+
- [ ] **Step 6: Commit**
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
cd /home/stefan/projekte/forgehive
|
|
271
|
+
git add src/status.ts test/status.test.ts src/cli.ts
|
|
272
|
+
git commit -m "feat: add fh status — project health dashboard"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Task 2: `fh watch` command
|
|
278
|
+
|
|
279
|
+
**Files:**
|
|
280
|
+
- Create: `src/watch.ts`
|
|
281
|
+
- Create: `test/watch.test.ts`
|
|
282
|
+
- Modify: `src/cli.ts` (add watch handler)
|
|
283
|
+
|
|
284
|
+
- [ ] **Step 1: Write failing test**
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
// test/watch.test.ts
|
|
288
|
+
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
289
|
+
import assert from "node:assert/strict";
|
|
290
|
+
import fs from "node:fs";
|
|
291
|
+
import path from "node:path";
|
|
292
|
+
import os from "node:os";
|
|
293
|
+
import yaml from "js-yaml";
|
|
294
|
+
import { checkProjectHash } from "../src/watch.ts";
|
|
295
|
+
|
|
296
|
+
describe("checkProjectHash()", () => {
|
|
297
|
+
let tmpDir: string;
|
|
298
|
+
let forgehiveDir: string;
|
|
299
|
+
|
|
300
|
+
beforeEach(() => {
|
|
301
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "fh-watch-"));
|
|
302
|
+
forgehiveDir = path.join(tmpDir, ".forgehive");
|
|
303
|
+
fs.mkdirSync(forgehiveDir, { recursive: true });
|
|
304
|
+
fs.writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify({ name: "test" }), "utf8");
|
|
305
|
+
const scan = { scanned_at: "2026-05-11", project_root: tmpDir, signals: [], packages: [] };
|
|
306
|
+
fs.writeFileSync(path.join(forgehiveDir, "scan-result.yaml"), yaml.dump(scan), "utf8");
|
|
307
|
+
fs.writeFileSync(path.join(forgehiveDir, "capabilities.yaml"),
|
|
308
|
+
yaml.dump({ status: "confirmed", capabilities: { confirmed: [], inferred: [] } }), "utf8");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
afterEach(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
|
|
312
|
+
|
|
313
|
+
it("gibt false zurück wenn kein hash gespeichert ist", () => {
|
|
314
|
+
const changed = checkProjectHash(tmpDir, forgehiveDir, "## forgehive\n");
|
|
315
|
+
// First run: saves hash, returns true (first scan)
|
|
316
|
+
assert.strictEqual(typeof changed, "boolean");
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("gibt false zurück wenn hash unverändert", () => {
|
|
320
|
+
// First call saves the hash
|
|
321
|
+
checkProjectHash(tmpDir, forgehiveDir, "## forgehive\n");
|
|
322
|
+
// Second call with same state
|
|
323
|
+
const changed = checkProjectHash(tmpDir, forgehiveDir, "## forgehive\n");
|
|
324
|
+
assert.strictEqual(changed, false);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it("gibt true zurück wenn package.json sich ändert", () => {
|
|
328
|
+
checkProjectHash(tmpDir, forgehiveDir, "## forgehive\n");
|
|
329
|
+
// Change package.json
|
|
330
|
+
fs.writeFileSync(path.join(tmpDir, "package.json"),
|
|
331
|
+
JSON.stringify({ name: "test", version: "2.0.0" }), "utf8");
|
|
332
|
+
const changed = checkProjectHash(tmpDir, forgehiveDir, "## forgehive\n");
|
|
333
|
+
assert.strictEqual(changed, true);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("schreibt neuen hash nach Änderung", () => {
|
|
337
|
+
checkProjectHash(tmpDir, forgehiveDir, "## forgehive\n");
|
|
338
|
+
fs.writeFileSync(path.join(tmpDir, "package.json"),
|
|
339
|
+
JSON.stringify({ name: "test", extras: true }), "utf8");
|
|
340
|
+
checkProjectHash(tmpDir, forgehiveDir, "## forgehive\n");
|
|
341
|
+
assert.ok(fs.existsSync(path.join(forgehiveDir, ".scan-hash")));
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
- [ ] **Step 2: Run failing test**
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
cd /home/stefan/projekte/forgehive && npm test 2>&1 | grep "watch\|Error" | head -10
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Expected: `Error: Cannot find module '../src/watch.ts'`
|
|
353
|
+
|
|
354
|
+
- [ ] **Step 3: Implement `src/watch.ts`**
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import fs from "node:fs";
|
|
358
|
+
import path from "node:path";
|
|
359
|
+
import { computeHash } from "./update.ts";
|
|
360
|
+
import { scan } from "./scanner.ts";
|
|
361
|
+
import { mapSignalsToCapabilities } from "./lookup-table.ts";
|
|
362
|
+
import { writeForgehiveDir } from "./writer.ts";
|
|
363
|
+
|
|
364
|
+
// Returns true if the codebase changed and capabilities were updated.
|
|
365
|
+
export function checkProjectHash(
|
|
366
|
+
projectRoot: string,
|
|
367
|
+
forgehiveDir: string,
|
|
368
|
+
claudeMdBlock: string
|
|
369
|
+
): boolean {
|
|
370
|
+
const hashPath = path.join(forgehiveDir, ".scan-hash");
|
|
371
|
+
const savedHash = fs.existsSync(hashPath)
|
|
372
|
+
? fs.readFileSync(hashPath, "utf8").trim()
|
|
373
|
+
: null;
|
|
374
|
+
|
|
375
|
+
const currentHash = computeHash(projectRoot);
|
|
376
|
+
|
|
377
|
+
if (currentHash === savedHash) return false;
|
|
378
|
+
|
|
379
|
+
fs.writeFileSync(hashPath, currentHash, "utf8");
|
|
380
|
+
const scanResult = scan(projectRoot);
|
|
381
|
+
const capMap = mapSignalsToCapabilities(scanResult);
|
|
382
|
+
writeForgehiveDir(projectRoot, scanResult, capMap, claudeMdBlock);
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function watchProject(
|
|
387
|
+
projectRoot: string,
|
|
388
|
+
forgehiveDir: string,
|
|
389
|
+
claudeMdBlock: string,
|
|
390
|
+
onUpdate: (changed: boolean) => void = defaultOnUpdate
|
|
391
|
+
): () => void {
|
|
392
|
+
const candidates = [
|
|
393
|
+
"package.json",
|
|
394
|
+
"package-lock.json",
|
|
395
|
+
"yarn.lock",
|
|
396
|
+
"pnpm-lock.yaml",
|
|
397
|
+
"requirements.txt",
|
|
398
|
+
"Cargo.toml",
|
|
399
|
+
"go.mod",
|
|
400
|
+
"pyproject.toml",
|
|
401
|
+
"composer.json",
|
|
402
|
+
].map(f => path.join(projectRoot, f)).filter(f => fs.existsSync(f));
|
|
403
|
+
|
|
404
|
+
let debounce: ReturnType<typeof setTimeout> | null = null;
|
|
405
|
+
const watchers: fs.FSWatcher[] = [];
|
|
406
|
+
|
|
407
|
+
function schedule() {
|
|
408
|
+
if (debounce) clearTimeout(debounce);
|
|
409
|
+
debounce = setTimeout(() => {
|
|
410
|
+
const changed = checkProjectHash(projectRoot, forgehiveDir, claudeMdBlock);
|
|
411
|
+
onUpdate(changed);
|
|
412
|
+
}, 2000);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
for (const f of candidates) {
|
|
416
|
+
try {
|
|
417
|
+
watchers.push(fs.watch(f, schedule));
|
|
418
|
+
} catch {
|
|
419
|
+
// file may disappear — ignore
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Also watch for new lockfiles appearing
|
|
424
|
+
try {
|
|
425
|
+
watchers.push(fs.watch(projectRoot, schedule));
|
|
426
|
+
} catch {
|
|
427
|
+
// may fail on some systems without inotify
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return () => {
|
|
431
|
+
if (debounce) clearTimeout(debounce);
|
|
432
|
+
for (const w of watchers) { try { w.close(); } catch { /* ignore */ } }
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function defaultOnUpdate(changed: boolean): void {
|
|
437
|
+
if (changed) {
|
|
438
|
+
console.log(`[forgehive] ✓ Codebase changed — capabilities.yaml aktualisiert`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
- [ ] **Step 4: Add `watch` command to `src/cli.ts`**
|
|
444
|
+
|
|
445
|
+
Add import at top with the others:
|
|
446
|
+
```typescript
|
|
447
|
+
import { watchProject } from "./watch.ts";
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Add handler before the final `else` block (after the `status` handler added in Task 1):
|
|
451
|
+
```typescript
|
|
452
|
+
} else if (command === "watch") {
|
|
453
|
+
if (!fs.existsSync(forgehiveDir)) {
|
|
454
|
+
console.error("Fehler: .forgehive/ nicht gefunden — führe zuerst `fh init` aus");
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
const block = loadClaudeMdBlock();
|
|
458
|
+
console.log("👁 ForgeHive watch gestartet — beobachte Projekt-Dateien (Ctrl+C zum Beenden)\n");
|
|
459
|
+
const stop = watchProject(projectRoot, forgehiveDir, block);
|
|
460
|
+
process.on("SIGINT", () => { stop(); process.exit(0); });
|
|
461
|
+
process.on("SIGTERM", () => { stop(); process.exit(0); });
|
|
462
|
+
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
- [ ] **Step 5: Run tests**
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
cd /home/stefan/projekte/forgehive && npm test 2>&1 | tail -15
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Expected: all tests pass including 4 new watch tests.
|
|
472
|
+
|
|
473
|
+
- [ ] **Step 6: Commit**
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
cd /home/stefan/projekte/forgehive
|
|
477
|
+
git add src/watch.ts test/watch.test.ts src/cli.ts
|
|
478
|
+
git commit -m "feat: add fh watch — filesystem watcher for auto-update"
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
## Task 3: Guardrail hooks
|
|
484
|
+
|
|
485
|
+
**Files:**
|
|
486
|
+
- Create: `src/guardrails.ts`
|
|
487
|
+
- Create: `forgehive/hooks/guardrails.sh`
|
|
488
|
+
- Create: `test/guardrails.test.ts`
|
|
489
|
+
- Modify: `src/writer.ts` (call `writeGuardrailHooks` from `initForgehiveRuntime`)
|
|
490
|
+
- Modify: `test/writer.test.ts` (test guardrails integration)
|
|
491
|
+
|
|
492
|
+
- [ ] **Step 1: Write failing tests**
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
// test/guardrails.test.ts
|
|
496
|
+
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
497
|
+
import assert from "node:assert/strict";
|
|
498
|
+
import fs from "node:fs";
|
|
499
|
+
import path from "node:path";
|
|
500
|
+
import os from "node:os";
|
|
501
|
+
import { writeGuardrailHooks } from "../src/guardrails.ts";
|
|
502
|
+
|
|
503
|
+
describe("writeGuardrailHooks()", () => {
|
|
504
|
+
let tmpDir: string;
|
|
505
|
+
let forgehiveDir: string;
|
|
506
|
+
|
|
507
|
+
beforeEach(() => {
|
|
508
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "fh-guard-"));
|
|
509
|
+
forgehiveDir = path.join(tmpDir, ".forgehive");
|
|
510
|
+
fs.mkdirSync(path.join(forgehiveDir, "hooks"), { recursive: true });
|
|
511
|
+
fs.mkdirSync(path.join(tmpDir, ".claude"), { recursive: true });
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
afterEach(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
|
|
515
|
+
|
|
516
|
+
it("schreibt .forgehive/hooks/guardrails.sh", () => {
|
|
517
|
+
writeGuardrailHooks(tmpDir, forgehiveDir);
|
|
518
|
+
assert.ok(fs.existsSync(path.join(forgehiveDir, "hooks", "guardrails.sh")));
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it("schreibt .claude/settings.json mit PreToolUse hook", () => {
|
|
522
|
+
writeGuardrailHooks(tmpDir, forgehiveDir);
|
|
523
|
+
const settingsPath = path.join(tmpDir, ".claude", "settings.json");
|
|
524
|
+
assert.ok(fs.existsSync(settingsPath));
|
|
525
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
526
|
+
assert.ok(settings.hooks?.PreToolUse);
|
|
527
|
+
assert.ok(Array.isArray(settings.hooks.PreToolUse));
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("merged mit bestehendem settings.json", () => {
|
|
531
|
+
const settingsPath = path.join(tmpDir, ".claude", "settings.json");
|
|
532
|
+
fs.writeFileSync(settingsPath, JSON.stringify({ permissions: { allow: ["*"] } }), "utf8");
|
|
533
|
+
writeGuardrailHooks(tmpDir, forgehiveDir);
|
|
534
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
535
|
+
assert.ok(settings.permissions?.allow);
|
|
536
|
+
assert.ok(settings.hooks?.PreToolUse);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it("ist idempotent — überschreibt hook-Eintrag nicht doppelt", () => {
|
|
540
|
+
writeGuardrailHooks(tmpDir, forgehiveDir);
|
|
541
|
+
writeGuardrailHooks(tmpDir, forgehiveDir);
|
|
542
|
+
const settings = JSON.parse(
|
|
543
|
+
fs.readFileSync(path.join(tmpDir, ".claude", "settings.json"), "utf8")
|
|
544
|
+
);
|
|
545
|
+
const bashHooks = settings.hooks.PreToolUse.filter((h: { matcher: string }) => h.matcher === "Bash");
|
|
546
|
+
assert.strictEqual(bashHooks.length, 1);
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
- [ ] **Step 2: Run failing test**
|
|
552
|
+
|
|
553
|
+
```bash
|
|
554
|
+
cd /home/stefan/projekte/forgehive && npm test 2>&1 | grep "guardrails\|Error" | head -10
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
Expected: `Error: Cannot find module '../src/guardrails.ts'`
|
|
558
|
+
|
|
559
|
+
- [ ] **Step 3: Create `forgehive/hooks/guardrails.sh`**
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
#!/usr/bin/env bash
|
|
563
|
+
# ForgeHive Guardrails — PreToolUse hook for Bash
|
|
564
|
+
# Receives tool input as JSON on stdin. Exit 1 to block, 0 to allow.
|
|
565
|
+
|
|
566
|
+
INPUT=$(cat 2>/dev/null || echo "{}")
|
|
567
|
+
COMMAND=$(echo "$INPUT" | node -e "
|
|
568
|
+
let d='';
|
|
569
|
+
process.stdin.on('data',c=>d+=c);
|
|
570
|
+
process.stdin.on('end',()=>{
|
|
571
|
+
try { const p=JSON.parse(d); process.stdout.write(p.command||''); }
|
|
572
|
+
catch { process.stdout.write(''); }
|
|
573
|
+
});
|
|
574
|
+
" 2>/dev/null || echo "")
|
|
575
|
+
|
|
576
|
+
# Patterns that require explicit intent
|
|
577
|
+
DANGEROUS=(
|
|
578
|
+
"git push --force"
|
|
579
|
+
"git push -f "
|
|
580
|
+
"git reset --hard"
|
|
581
|
+
"rm -rf /"
|
|
582
|
+
"DROP TABLE"
|
|
583
|
+
"DROP DATABASE"
|
|
584
|
+
"truncate "
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
for PATTERN in "${DANGEROUS[@]}"; do
|
|
588
|
+
if echo "$COMMAND" | grep -qi "$PATTERN"; then
|
|
589
|
+
echo "🛡 ForgeHive Guardrail: '$PATTERN' erkannt."
|
|
590
|
+
echo " Füge einen Kommentar hinzu der erklärt warum, dann erneut versuchen."
|
|
591
|
+
exit 1
|
|
592
|
+
fi
|
|
593
|
+
done
|
|
594
|
+
|
|
595
|
+
exit 0
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
- [ ] **Step 4: Implement `src/guardrails.ts`**
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
import fs from "node:fs";
|
|
602
|
+
import path from "node:path";
|
|
603
|
+
|
|
604
|
+
const GUARDRAILS_SH = `#!/usr/bin/env bash
|
|
605
|
+
# ForgeHive Guardrails — PreToolUse hook for Bash
|
|
606
|
+
# Receives tool input as JSON on stdin. Exit 1 to block, exit 0 to allow.
|
|
607
|
+
|
|
608
|
+
INPUT=$(cat 2>/dev/null || echo "{}")
|
|
609
|
+
COMMAND=$(node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{process.stdout.write(JSON.parse(d).command||'')}catch{process.stdout.write('')}})" 2>/dev/null <<< "$INPUT" || echo "")
|
|
610
|
+
|
|
611
|
+
DANGEROUS=("git push --force" "git push -f " "git reset --hard" "rm -rf /" "DROP TABLE" "DROP DATABASE")
|
|
612
|
+
|
|
613
|
+
for PATTERN in "\${DANGEROUS[@]}"; do
|
|
614
|
+
if echo "$COMMAND" | grep -qi "$PATTERN"; then
|
|
615
|
+
echo "🛡 ForgeHive Guardrail: '$PATTERN' geblockt. Erkläre die Absicht und versuche erneut."
|
|
616
|
+
exit 1
|
|
617
|
+
fi
|
|
618
|
+
done
|
|
619
|
+
|
|
620
|
+
exit 0
|
|
621
|
+
`;
|
|
622
|
+
|
|
623
|
+
interface ClaudeSettings {
|
|
624
|
+
hooks?: {
|
|
625
|
+
PreToolUse?: Array<{ matcher: string; hooks: Array<{ type: string; command: string }> }>;
|
|
626
|
+
};
|
|
627
|
+
[key: string]: unknown;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
export function writeGuardrailHooks(projectRoot: string, forgehiveDir: string): void {
|
|
631
|
+
// Write hook script
|
|
632
|
+
const hooksDir = path.join(forgehiveDir, "hooks");
|
|
633
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
634
|
+
const scriptPath = path.join(hooksDir, "guardrails.sh");
|
|
635
|
+
fs.writeFileSync(scriptPath, GUARDRAILS_SH, "utf8");
|
|
636
|
+
try { fs.chmodSync(scriptPath, 0o755); } catch { /* ignore on systems without chmod */ }
|
|
637
|
+
|
|
638
|
+
// Write/merge .claude/settings.json
|
|
639
|
+
const claudeDir = path.join(projectRoot, ".claude");
|
|
640
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
641
|
+
const settingsPath = path.join(claudeDir, "settings.json");
|
|
642
|
+
|
|
643
|
+
let settings: ClaudeSettings = {};
|
|
644
|
+
if (fs.existsSync(settingsPath)) {
|
|
645
|
+
try {
|
|
646
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, "utf8")) as ClaudeSettings;
|
|
647
|
+
} catch {
|
|
648
|
+
settings = {};
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (!settings.hooks) settings.hooks = {};
|
|
653
|
+
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
654
|
+
|
|
655
|
+
// Remove existing forgehive guardrail entry to avoid duplicates
|
|
656
|
+
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(
|
|
657
|
+
h => !(h.matcher === "Bash" && h.hooks?.[0]?.command?.includes("guardrails.sh"))
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
// Add fresh entry
|
|
661
|
+
settings.hooks.PreToolUse.push({
|
|
662
|
+
matcher: "Bash",
|
|
663
|
+
hooks: [
|
|
664
|
+
{
|
|
665
|
+
type: "command",
|
|
666
|
+
command: `bash "${scriptPath}"`,
|
|
667
|
+
},
|
|
668
|
+
],
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf8");
|
|
672
|
+
}
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
- [ ] **Step 5: Add `writeGuardrailHooks` call to `src/writer.ts`**
|
|
676
|
+
|
|
677
|
+
Add import at top of `src/writer.ts`:
|
|
678
|
+
```typescript
|
|
679
|
+
import { writeGuardrailHooks } from "./guardrails.ts";
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
At the end of `initForgehiveRuntime`, add:
|
|
683
|
+
```typescript
|
|
684
|
+
writeGuardrailHooks(path.join(forgehiveDir, ".."), forgehiveDir);
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
Wait — `initForgehiveRuntime` takes `forgehiveDir` (which is `<projectRoot>/.forgehive`) so `projectRoot` = `path.join(forgehiveDir, "..")`. Update the call:
|
|
688
|
+
|
|
689
|
+
The existing `initForgehiveRuntime` signature is:
|
|
690
|
+
```typescript
|
|
691
|
+
export function initForgehiveRuntime(forgehiveDir: string, runtimeDir: string): void
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
Add this at the end of the function body:
|
|
695
|
+
```typescript
|
|
696
|
+
const projectRoot = path.join(forgehiveDir, "..");
|
|
697
|
+
writeGuardrailHooks(projectRoot, forgehiveDir);
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
- [ ] **Step 6: Run tests**
|
|
701
|
+
|
|
702
|
+
```bash
|
|
703
|
+
cd /home/stefan/projekte/forgehive && npm test 2>&1 | tail -15
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
Expected: all tests pass including 4 new guardrails tests.
|
|
707
|
+
|
|
708
|
+
- [ ] **Step 7: Commit**
|
|
709
|
+
|
|
710
|
+
```bash
|
|
711
|
+
cd /home/stefan/projekte/forgehive
|
|
712
|
+
git add src/guardrails.ts forgehive/hooks/guardrails.sh test/guardrails.test.ts src/writer.ts
|
|
713
|
+
git commit -m "feat: add guardrail hooks — PreToolUse bash protection via .claude/settings.json"
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## Task 4: Workflow slash commands
|
|
719
|
+
|
|
720
|
+
**Files:**
|
|
721
|
+
- Create: `forgehive/commands/fh-start-task.md`
|
|
722
|
+
- Create: `forgehive/commands/fh-ship.md`
|
|
723
|
+
- Create: `forgehive/commands/fh-review.md`
|
|
724
|
+
- Create: `forgehive/commands/fh-hotfix.md`
|
|
725
|
+
- Modify: `src/writer.ts` (`initForgehiveRuntime` copies commands to `.claude/commands/`)
|
|
726
|
+
- Modify: `test/writer.test.ts`
|
|
727
|
+
|
|
728
|
+
- [ ] **Step 1: Create `forgehive/commands/fh-start-task.md`**
|
|
729
|
+
|
|
730
|
+
```markdown
|
|
731
|
+
You are starting a new development task using the ForgeHive workflow.
|
|
732
|
+
|
|
733
|
+
## Pre-Task Checklist
|
|
734
|
+
|
|
735
|
+
1. Ask the user: **"Was baust du heute?"** (or "What are you building today?")
|
|
736
|
+
2. Read `.forgehive/capabilities.yaml` — understand the available stack
|
|
737
|
+
3. Check `.forgehive/memory/MEMORY.md` — load relevant project context
|
|
738
|
+
4. Run `fh scan --check` to verify the codebase snapshot is current
|
|
739
|
+
5. Create a feature branch:
|
|
740
|
+
- Convention from `git-conventions` skill: `feat/<short-description>`, `fix/<issue>`, `chore/<task>`
|
|
741
|
+
- Run: `git checkout -b <branch-name>`
|
|
742
|
+
6. Summarize: what you're building, which files are likely to change, which capabilities apply
|
|
743
|
+
7. Check `.forgehive/skills/expert/` for relevant skill files (see `.forgehive/skills/INDEX.yaml`)
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
- [ ] **Step 2: Create `forgehive/commands/fh-ship.md`**
|
|
747
|
+
|
|
748
|
+
```markdown
|
|
749
|
+
You are running the ForgeHive ship workflow. Follow all steps before declaring ready to merge.
|
|
750
|
+
|
|
751
|
+
## Pre-Ship Checklist
|
|
752
|
+
|
|
753
|
+
1. **Tests** — run the test suite from `package.json` scripts
|
|
754
|
+
- If tests fail: stop here, fix before proceeding
|
|
755
|
+
2. **Diff summary** — `git diff main...HEAD --stat`
|
|
756
|
+
- Show the user what changed
|
|
757
|
+
3. **Uncommitted work** — `git status`
|
|
758
|
+
- Commit or stash before proceeding
|
|
759
|
+
4. **Code quality scan**
|
|
760
|
+
- Any `console.log` / `debugger` / TODO in the diff?
|
|
761
|
+
- Any commented-out code?
|
|
762
|
+
- Any hardcoded secrets or API keys?
|
|
763
|
+
5. **PR draft**
|
|
764
|
+
- Title: under 70 chars, describes the change (not "fix stuff")
|
|
765
|
+
- Body: 2-3 bullets of what changed and why
|
|
766
|
+
6. **Ask**: "Soll ich den PR erstellen?" — only create if confirmed
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
- [ ] **Step 3: Create `forgehive/commands/fh-review.md`**
|
|
770
|
+
|
|
771
|
+
```markdown
|
|
772
|
+
You are running a ForgeHive code review. Load the review skill first.
|
|
773
|
+
|
|
774
|
+
## Review Process
|
|
775
|
+
|
|
776
|
+
1. Read `.forgehive/skills/expert/code-review.md`
|
|
777
|
+
2. Get the diff: `git diff main...HEAD`
|
|
778
|
+
3. Review in priority order:
|
|
779
|
+
|
|
780
|
+
**[BUG]** Correctness issues — will this break in production?
|
|
781
|
+
**[DESIGN]** Architecture issues — does it follow existing patterns?
|
|
782
|
+
**[NIT]** Minor style issues — use sparingly
|
|
783
|
+
**[Q]** Questions — non-blocking, want to understand intent
|
|
784
|
+
**[+]** Good work — acknowledge what's done well
|
|
785
|
+
|
|
786
|
+
4. For PRs > 400 lines: flag as too large, suggest how to split
|
|
787
|
+
5. End with: overall verdict (approve / request changes) and count of blocking issues
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
- [ ] **Step 4: Create `forgehive/commands/fh-hotfix.md`**
|
|
791
|
+
|
|
792
|
+
```markdown
|
|
793
|
+
You are running the ForgeHive hotfix protocol.
|
|
794
|
+
|
|
795
|
+
## Hotfix Rules
|
|
796
|
+
|
|
797
|
+
**Constraint:** A hotfix changes as few lines as possible. If the fix requires > 50 lines, it's a feature, not a hotfix.
|
|
798
|
+
|
|
799
|
+
## Steps
|
|
800
|
+
|
|
801
|
+
1. **Identify** — `git log --oneline -20` — find the commit that introduced the bug
|
|
802
|
+
2. **Branch** — `git checkout -b hotfix/<short-description> main`
|
|
803
|
+
3. **Minimal fix** — fix only the bug, no refactoring, no cleanup
|
|
804
|
+
4. **Regression test** — write one test that fails before the fix and passes after
|
|
805
|
+
5. **Verify** — run full test suite
|
|
806
|
+
6. **Ship** — use `/fh-ship` when ready
|
|
807
|
+
|
|
808
|
+
If this turns out to be more than 50 lines: stop, switch to `/fh-start-task` for a proper feature branch.
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
- [ ] **Step 5: Add commands copy to `src/writer.ts`**
|
|
812
|
+
|
|
813
|
+
In `initForgehiveRuntime`, add a commands copy block after the party copy block (before the skills mkdir loop):
|
|
814
|
+
|
|
815
|
+
```typescript
|
|
816
|
+
// Copy slash commands to .claude/commands/
|
|
817
|
+
const commandsSrc = path.join(runtimeDir, "commands");
|
|
818
|
+
const claudeDir = path.join(forgehiveDir, "..", ".claude");
|
|
819
|
+
const commandsDst = path.join(claudeDir, "commands");
|
|
820
|
+
fs.mkdirSync(commandsDst, { recursive: true });
|
|
821
|
+
if (fs.existsSync(commandsSrc)) {
|
|
822
|
+
for (const file of fs.readdirSync(commandsSrc)) {
|
|
823
|
+
const dst = path.join(commandsDst, file);
|
|
824
|
+
if (!fs.existsSync(dst)) {
|
|
825
|
+
fs.copyFileSync(path.join(commandsSrc, file), dst);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
- [ ] **Step 6: Add test to `test/writer.test.ts`**
|
|
832
|
+
|
|
833
|
+
Add inside the `initForgehiveRuntime()` describe block, after the existing tests:
|
|
834
|
+
|
|
835
|
+
```typescript
|
|
836
|
+
it("kopiert commands nach .claude/commands/", () => {
|
|
837
|
+
// Create a fake runtimeDir/commands/ with a test command
|
|
838
|
+
const runtimeDir = path.join(tmpDir, "fh-runtime");
|
|
839
|
+
const commandsSrc = path.join(runtimeDir, "commands");
|
|
840
|
+
fs.mkdirSync(commandsSrc, { recursive: true });
|
|
841
|
+
fs.writeFileSync(path.join(commandsSrc, "fh-ship.md"), "# /fh-ship\nship it", "utf8");
|
|
842
|
+
|
|
843
|
+
initForgehiveRuntime(forgehiveDir, runtimeDir);
|
|
844
|
+
|
|
845
|
+
assert.ok(
|
|
846
|
+
fs.existsSync(path.join(tmpDir, ".claude", "commands", "fh-ship.md"))
|
|
847
|
+
);
|
|
848
|
+
});
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
- [ ] **Step 7: Run tests**
|
|
852
|
+
|
|
853
|
+
```bash
|
|
854
|
+
cd /home/stefan/projekte/forgehive && npm test 2>&1 | tail -15
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
Expected: all tests pass including new writer test.
|
|
858
|
+
|
|
859
|
+
- [ ] **Step 8: Commit**
|
|
860
|
+
|
|
861
|
+
```bash
|
|
862
|
+
cd /home/stefan/projekte/forgehive
|
|
863
|
+
git add forgehive/commands/ src/writer.ts test/writer.test.ts
|
|
864
|
+
git commit -m "feat: add workflow slash commands — /fh-start-task /fh-ship /fh-review /fh-hotfix"
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
---
|
|
868
|
+
|
|
869
|
+
## Task 5: Skills INDEX + progressive loading
|
|
870
|
+
|
|
871
|
+
**Files:**
|
|
872
|
+
- Create: `forgehive/skills/INDEX.yaml`
|
|
873
|
+
- Modify: `src/writer.ts` (copy INDEX.yaml during init)
|
|
874
|
+
- Modify: `src/skills.ts` (`listSkills` shows tags from INDEX)
|
|
875
|
+
- Modify: `forgehive/templates/claude-md.block.md`
|
|
876
|
+
- Modify: `test/writer.test.ts`
|
|
877
|
+
- Modify: `test/skills.test.ts`
|
|
878
|
+
|
|
879
|
+
- [ ] **Step 1: Create `forgehive/skills/INDEX.yaml`**
|
|
880
|
+
|
|
881
|
+
```yaml
|
|
882
|
+
# ForgeHive Skills Index
|
|
883
|
+
# Claude: load only the skills relevant to your current task.
|
|
884
|
+
# Check 'when' field to decide which skills to load.
|
|
885
|
+
|
|
886
|
+
skills:
|
|
887
|
+
typescript-patterns:
|
|
888
|
+
file: expert/typescript-patterns.md
|
|
889
|
+
tags: [typescript, types, generics, decorators, utility-types]
|
|
890
|
+
when: "TypeScript types, generics, utility types, decorators"
|
|
891
|
+
|
|
892
|
+
testing-strategies:
|
|
893
|
+
file: expert/testing-strategies.md
|
|
894
|
+
tags: [testing, tdd, mocking, assertions, coverage, vitest, jest]
|
|
895
|
+
when: "Writing tests, TDD, mocking dependencies, test design"
|
|
896
|
+
|
|
897
|
+
api-design:
|
|
898
|
+
file: expert/api-design.md
|
|
899
|
+
tags: [api, rest, graphql, openapi, versioning, endpoints]
|
|
900
|
+
when: "Designing API endpoints, REST conventions, OpenAPI specs"
|
|
901
|
+
|
|
902
|
+
git-conventions:
|
|
903
|
+
file: expert/git-conventions.md
|
|
904
|
+
tags: [git, commits, branches, pr, conventional-commits, merge]
|
|
905
|
+
when: "Git workflow, commit messages, branch naming, PR process"
|
|
906
|
+
|
|
907
|
+
error-handling:
|
|
908
|
+
file: expert/error-handling.md
|
|
909
|
+
tags: [errors, exceptions, result-types, retry, fallbacks, typescript]
|
|
910
|
+
when: "Error handling, exceptions, Result/Either types, retry logic"
|
|
911
|
+
|
|
912
|
+
security-checklist:
|
|
913
|
+
file: expert/security-checklist.md
|
|
914
|
+
tags: [security, auth, xss, sql-injection, owasp, jwt, csrf]
|
|
915
|
+
when: "Security review, authentication, authorization, input validation"
|
|
916
|
+
|
|
917
|
+
performance-patterns:
|
|
918
|
+
file: expert/performance-patterns.md
|
|
919
|
+
tags: [performance, caching, profiling, optimization, lazy-loading, memoization]
|
|
920
|
+
when: "Performance optimization, caching strategies, profiling"
|
|
921
|
+
|
|
922
|
+
code-review:
|
|
923
|
+
file: expert/code-review.md
|
|
924
|
+
tags: [review, quality, feedback, pr-review, checklist]
|
|
925
|
+
when: "Code review, PR feedback, quality assessment"
|
|
926
|
+
|
|
927
|
+
clean-architecture:
|
|
928
|
+
file: expert/clean-architecture.md
|
|
929
|
+
tags: [architecture, domain, use-cases, clean-arch, ddd, layers]
|
|
930
|
+
when: "Architecture design, layer separation, domain modeling, DDD"
|
|
931
|
+
|
|
932
|
+
database-patterns:
|
|
933
|
+
file: expert/database-patterns.md
|
|
934
|
+
tags: [database, sql, migrations, indexing, orm, postgres, prisma]
|
|
935
|
+
when: "Database design, migrations, query optimization, indexing"
|
|
936
|
+
|
|
937
|
+
monorepo-patterns:
|
|
938
|
+
file: expert/monorepo-patterns.md
|
|
939
|
+
tags: [monorepo, workspaces, turborepo, nx, packages, pnpm]
|
|
940
|
+
when: "Monorepo setup, workspace management, build orchestration"
|
|
941
|
+
|
|
942
|
+
observability:
|
|
943
|
+
file: expert/observability.md
|
|
944
|
+
tags: [logging, metrics, tracing, opentelemetry, monitoring, datadog, sentry]
|
|
945
|
+
when: "Structured logging, metrics (RED/USE), distributed tracing"
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
- [ ] **Step 2: Add INDEX copy to `src/writer.ts`**
|
|
949
|
+
|
|
950
|
+
In `initForgehiveRuntime`, in the skills directory section — after the `for (const dir of ["generated", "expert", "workflows"])` loop, add:
|
|
951
|
+
|
|
952
|
+
```typescript
|
|
953
|
+
// Copy INDEX.yaml and expert skills to .forgehive/skills/
|
|
954
|
+
const expertSrc = path.join(runtimeDir, "skills", "expert");
|
|
955
|
+
const expertDst = path.join(forgehiveDir, "skills", "expert");
|
|
956
|
+
if (fs.existsSync(expertSrc)) {
|
|
957
|
+
for (const file of fs.readdirSync(expertSrc)) {
|
|
958
|
+
const dst = path.join(expertDst, file);
|
|
959
|
+
if (!fs.existsSync(dst)) {
|
|
960
|
+
fs.copyFileSync(path.join(expertSrc, file), dst);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
const indexSrc = path.join(runtimeDir, "skills", "INDEX.yaml");
|
|
965
|
+
const indexDst = path.join(forgehiveDir, "skills", "INDEX.yaml");
|
|
966
|
+
if (fs.existsSync(indexSrc) && !fs.existsSync(indexDst)) {
|
|
967
|
+
fs.copyFileSync(indexSrc, indexDst);
|
|
968
|
+
}
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
- [ ] **Step 3: Update `src/skills.ts` — `listSkills` shows INDEX info**
|
|
972
|
+
|
|
973
|
+
In `listSkills`, add INDEX loading after the header line:
|
|
974
|
+
|
|
975
|
+
Replace the existing `listSkills` function body with:
|
|
976
|
+
|
|
977
|
+
```typescript
|
|
978
|
+
export function listSkills(forgehiveDir: string): string {
|
|
979
|
+
const skillsDir = path.join(forgehiveDir, "skills");
|
|
980
|
+
const categories = ["generated", "expert", "workflows"];
|
|
981
|
+
const lines: string[] = [`Skills in .forgehive/skills/:\n`];
|
|
982
|
+
|
|
983
|
+
// Load INDEX for tag display
|
|
984
|
+
const indexPath = path.join(skillsDir, "INDEX.yaml");
|
|
985
|
+
const index = fs.existsSync(indexPath)
|
|
986
|
+
? (yaml.load(fs.readFileSync(indexPath, "utf8")) as {
|
|
987
|
+
skills: Record<string, { file: string; tags: string[]; when: string }>;
|
|
988
|
+
})
|
|
989
|
+
: null;
|
|
990
|
+
|
|
991
|
+
for (const cat of categories) {
|
|
992
|
+
const catDir = path.join(skillsDir, cat);
|
|
993
|
+
if (!fs.existsSync(catDir)) {
|
|
994
|
+
lines.push(`${cat}/ (leer)`);
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
const files = fs.readdirSync(catDir).filter(f => f.endsWith(".md") || f.endsWith(".yaml"));
|
|
998
|
+
if (files.length === 0) {
|
|
999
|
+
lines.push(`${cat}/ (leer)`);
|
|
1000
|
+
} else {
|
|
1001
|
+
lines.push(`${cat}/ (${files.length})`);
|
|
1002
|
+
for (const f of files) {
|
|
1003
|
+
const skillKey = f.replace(".md", "");
|
|
1004
|
+
const indexEntry = index?.skills?.[skillKey];
|
|
1005
|
+
if (indexEntry) {
|
|
1006
|
+
lines.push(` ${f} [${indexEntry.tags.slice(0, 3).join(", ")}]`);
|
|
1007
|
+
} else {
|
|
1008
|
+
lines.push(` ${f}`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
return lines.join("\n");
|
|
1015
|
+
}
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
Also add `import yaml from "js-yaml";` at the top of `src/skills.ts` (it doesn't have it yet).
|
|
1019
|
+
|
|
1020
|
+
- [ ] **Step 4: Add test for INDEX-aware listSkills in `test/skills.test.ts`**
|
|
1021
|
+
|
|
1022
|
+
Add inside the `listSkills()` describe block:
|
|
1023
|
+
|
|
1024
|
+
```typescript
|
|
1025
|
+
it("zeigt tags wenn INDEX.yaml vorhanden", () => {
|
|
1026
|
+
fs.writeFileSync(
|
|
1027
|
+
path.join(forgehiveDir, "skills", "expert", "typescript-patterns.md"),
|
|
1028
|
+
"# TS",
|
|
1029
|
+
"utf8"
|
|
1030
|
+
);
|
|
1031
|
+
fs.writeFileSync(
|
|
1032
|
+
path.join(forgehiveDir, "skills", "INDEX.yaml"),
|
|
1033
|
+
`skills:\n typescript-patterns:\n file: expert/typescript-patterns.md\n tags: [typescript, types]\n when: "TypeScript"\n`,
|
|
1034
|
+
"utf8"
|
|
1035
|
+
);
|
|
1036
|
+
const output = listSkills(forgehiveDir);
|
|
1037
|
+
assert.ok(output.includes("typescript"));
|
|
1038
|
+
});
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
- [ ] **Step 5: Update `forgehive/templates/claude-md.block.md`**
|
|
1042
|
+
|
|
1043
|
+
Add a new section before `### Party Mode`:
|
|
1044
|
+
|
|
1045
|
+
```markdown
|
|
1046
|
+
### Skills — Progressive Loading
|
|
1047
|
+
|
|
1048
|
+
Before starting a technical task, read `.forgehive/skills/INDEX.yaml` to find relevant skills.
|
|
1049
|
+
Load only the skills matching your current task — not all skills at once.
|
|
1050
|
+
|
|
1051
|
+
Examples:
|
|
1052
|
+
- Working with TypeScript types → load `expert/typescript-patterns.md`
|
|
1053
|
+
- Database migration → load `expert/database-patterns.md`
|
|
1054
|
+
- Reviewing a PR → load `expert/code-review.md`
|
|
1055
|
+
- Security review → load `expert/security-checklist.md`
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
Also add a section for workflow slash commands before `### Party Mode`:
|
|
1059
|
+
|
|
1060
|
+
```markdown
|
|
1061
|
+
### Workflow Commands
|
|
1062
|
+
|
|
1063
|
+
ForgeHive installs slash commands in `.claude/commands/`:
|
|
1064
|
+
- `/fh-start-task` — start a new feature branch with context loaded
|
|
1065
|
+
- `/fh-ship` — pre-ship checklist: tests, diff review, PR draft
|
|
1066
|
+
- `/fh-review` — structured code review using the review skill
|
|
1067
|
+
- `/fh-hotfix` — minimal hotfix protocol
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
- [ ] **Step 6: Run tests**
|
|
1071
|
+
|
|
1072
|
+
```bash
|
|
1073
|
+
cd /home/stefan/projekte/forgehive && npm test 2>&1 | tail -15
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
Expected: all tests pass.
|
|
1077
|
+
|
|
1078
|
+
- [ ] **Step 7: Commit**
|
|
1079
|
+
|
|
1080
|
+
```bash
|
|
1081
|
+
cd /home/stefan/projekte/forgehive
|
|
1082
|
+
git add forgehive/skills/INDEX.yaml src/writer.ts src/skills.ts forgehive/templates/claude-md.block.md test/skills.test.ts test/writer.test.ts
|
|
1083
|
+
git commit -m "feat: add skills INDEX with tags — progressive loading for Claude context"
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
---
|
|
1087
|
+
|
|
1088
|
+
## Task 6: Agent memory persistence
|
|
1089
|
+
|
|
1090
|
+
**Files:**
|
|
1091
|
+
- Modify: `src/writer.ts` (`initForgehiveRuntime` creates agent memory files)
|
|
1092
|
+
- Modify: `forgehive/templates/claude-md.block.md`
|
|
1093
|
+
- Modify: `test/writer.test.ts`
|
|
1094
|
+
|
|
1095
|
+
- [ ] **Step 1: Add test to `test/writer.test.ts`**
|
|
1096
|
+
|
|
1097
|
+
Add inside the `initForgehiveRuntime()` describe block:
|
|
1098
|
+
|
|
1099
|
+
```typescript
|
|
1100
|
+
it("erstellt agents/memory/ Verzeichnis und Memory-Dateien", () => {
|
|
1101
|
+
const runtimeDir = path.join(tmpDir, "fh-runtime");
|
|
1102
|
+
const agentsSrc = path.join(runtimeDir, "agents");
|
|
1103
|
+
fs.mkdirSync(agentsSrc, { recursive: true });
|
|
1104
|
+
fs.writeFileSync(path.join(agentsSrc, "nora.yaml"), "id: nora\nname: Nora", "utf8");
|
|
1105
|
+
fs.writeFileSync(path.join(agentsSrc, "kai.yaml"), "id: kai\nname: Kai", "utf8");
|
|
1106
|
+
|
|
1107
|
+
initForgehiveRuntime(forgehiveDir, runtimeDir);
|
|
1108
|
+
|
|
1109
|
+
assert.ok(fs.existsSync(path.join(forgehiveDir, "agents", "memory")));
|
|
1110
|
+
assert.ok(fs.existsSync(path.join(forgehiveDir, "agents", "memory", "nora.md")));
|
|
1111
|
+
assert.ok(fs.existsSync(path.join(forgehiveDir, "agents", "memory", "kai.md")));
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
it("überschreibt bestehende Memory-Datei nicht", () => {
|
|
1115
|
+
const runtimeDir = path.join(tmpDir, "fh-runtime");
|
|
1116
|
+
const agentsSrc = path.join(runtimeDir, "agents");
|
|
1117
|
+
fs.mkdirSync(agentsSrc, { recursive: true });
|
|
1118
|
+
fs.writeFileSync(path.join(agentsSrc, "nora.yaml"), "id: nora\nname: Nora", "utf8");
|
|
1119
|
+
|
|
1120
|
+
initForgehiveRuntime(forgehiveDir, runtimeDir);
|
|
1121
|
+
|
|
1122
|
+
const memPath = path.join(forgehiveDir, "agents", "memory", "nora.md");
|
|
1123
|
+
fs.writeFileSync(memPath, "# Custom memory content", "utf8");
|
|
1124
|
+
|
|
1125
|
+
initForgehiveRuntime(forgehiveDir, runtimeDir);
|
|
1126
|
+
|
|
1127
|
+
const content = fs.readFileSync(memPath, "utf8");
|
|
1128
|
+
assert.ok(content.includes("Custom memory content"));
|
|
1129
|
+
});
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
- [ ] **Step 2: Run failing tests**
|
|
1133
|
+
|
|
1134
|
+
```bash
|
|
1135
|
+
cd /home/stefan/projekte/forgehive && npm test 2>&1 | grep "agents\|FAIL" | head -10
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
Expected: 2 new tests fail (agents/memory not created yet).
|
|
1139
|
+
|
|
1140
|
+
- [ ] **Step 3: Add agent memory creation to `src/writer.ts`**
|
|
1141
|
+
|
|
1142
|
+
In `initForgehiveRuntime`, add after the agents copy loop:
|
|
1143
|
+
|
|
1144
|
+
```typescript
|
|
1145
|
+
// Create agent memory directory and per-agent memory files
|
|
1146
|
+
const agentMemoryDir = path.join(agentsDst, "memory");
|
|
1147
|
+
fs.mkdirSync(agentMemoryDir, { recursive: true });
|
|
1148
|
+
if (fs.existsSync(agentsSrc)) {
|
|
1149
|
+
for (const file of fs.readdirSync(agentsSrc).filter(f => f.endsWith(".yaml"))) {
|
|
1150
|
+
const agentId = file.replace(".yaml", "");
|
|
1151
|
+
const agentName = agentId.charAt(0).toUpperCase() + agentId.slice(1);
|
|
1152
|
+
const memFile = path.join(agentMemoryDir, `${agentId}.md`);
|
|
1153
|
+
if (!fs.existsSync(memFile)) {
|
|
1154
|
+
fs.writeFileSync(
|
|
1155
|
+
memFile,
|
|
1156
|
+
`# ${agentName} — Agent Memory\n\nPersistenter Kontext für ${agentName} zwischen Sessions.\nWird von Claude aktualisiert wenn ${agentName} Entscheidungen trifft.\n\n## Entscheidungen\n<!-- [datum] entscheidung -->\n\n## Projekt-Kontext\n<!-- Spezifisches Wissen das ${agentName} über dieses Projekt aufgebaut hat -->\n`,
|
|
1157
|
+
"utf8"
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
- [ ] **Step 4: Update `forgehive/templates/claude-md.block.md`**
|
|
1165
|
+
|
|
1166
|
+
Add after the Skills section and before Party Mode:
|
|
1167
|
+
|
|
1168
|
+
```markdown
|
|
1169
|
+
### Agent Memory
|
|
1170
|
+
|
|
1171
|
+
Each agent has persistent memory in `.forgehive/agents/memory/<name>.md`.
|
|
1172
|
+
|
|
1173
|
+
- Before activating a party set, read the relevant agent memory files
|
|
1174
|
+
- After a session where an agent made a significant decision, update their memory file
|
|
1175
|
+
- Format: `[YYYY-MM-DD] <decision or learned context>`
|
|
1176
|
+
```
|
|
1177
|
+
|
|
1178
|
+
- [ ] **Step 5: Run tests**
|
|
1179
|
+
|
|
1180
|
+
```bash
|
|
1181
|
+
cd /home/stefan/projekte/forgehive && npm test 2>&1 | tail -15
|
|
1182
|
+
```
|
|
1183
|
+
|
|
1184
|
+
Expected: all tests pass including 2 new writer tests.
|
|
1185
|
+
|
|
1186
|
+
- [ ] **Step 6: Commit**
|
|
1187
|
+
|
|
1188
|
+
```bash
|
|
1189
|
+
cd /home/stefan/projekte/forgehive
|
|
1190
|
+
git add src/writer.ts forgehive/templates/claude-md.block.md test/writer.test.ts
|
|
1191
|
+
git commit -m "feat: add agent memory files — persistent per-agent context across sessions"
|
|
1192
|
+
```
|
|
1193
|
+
|
|
1194
|
+
---
|
|
1195
|
+
|
|
1196
|
+
## Task 7: Expand MCP services (10 → 40)
|
|
1197
|
+
|
|
1198
|
+
**Files:**
|
|
1199
|
+
- Modify: `src/wire.ts` (add 30 new services)
|
|
1200
|
+
- Modify: `test/wire.test.ts`
|
|
1201
|
+
|
|
1202
|
+
This task adds 30 new services to the `SERVICES` object in `src/wire.ts`. Each follows the exact same shape as the existing 10 entries.
|
|
1203
|
+
|
|
1204
|
+
- [ ] **Step 1: Read current `src/wire.ts` to know exact SERVICES structure**
|
|
1205
|
+
|
|
1206
|
+
Run:
|
|
1207
|
+
```bash
|
|
1208
|
+
cd /home/stefan/projekte/forgehive && head -80 src/wire.ts
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
Confirm the SERVICES object shape (each entry has: `package`, `envVars`, `description`, `workflowSkill` string).
|
|
1212
|
+
|
|
1213
|
+
- [ ] **Step 2: Add test for new services in `test/wire.test.ts`**
|
|
1214
|
+
|
|
1215
|
+
Add inside the existing `listWireServices()` describe block:
|
|
1216
|
+
|
|
1217
|
+
```typescript
|
|
1218
|
+
it("enthält alle 40 Services", () => {
|
|
1219
|
+
const services = listWireServices();
|
|
1220
|
+
assert.ok(services.length >= 40);
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
it("enthält vercel", () => {
|
|
1224
|
+
assert.ok(listWireServices().includes("vercel"));
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
it("enthält stripe", () => {
|
|
1228
|
+
assert.ok(listWireServices().includes("stripe"));
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
it("enthält supabase", () => {
|
|
1232
|
+
assert.ok(listWireServices().includes("supabase"));
|
|
1233
|
+
});
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
- [ ] **Step 3: Run failing test**
|
|
1237
|
+
|
|
1238
|
+
```bash
|
|
1239
|
+
cd /home/stefan/projekte/forgehive && npm test 2>&1 | grep "wire\|FAIL" | head -10
|
|
1240
|
+
```
|
|
1241
|
+
|
|
1242
|
+
Expected: `AssertionError: 10 >= 40` (only 10 services currently)
|
|
1243
|
+
|
|
1244
|
+
- [ ] **Step 4: Add 30 services to `src/wire.ts`**
|
|
1245
|
+
|
|
1246
|
+
In `src/wire.ts`, inside the `SERVICES` object (after the existing `figma` entry), add:
|
|
1247
|
+
|
|
1248
|
+
```typescript
|
|
1249
|
+
vercel: {
|
|
1250
|
+
package: "@vercel/mcp-adapter",
|
|
1251
|
+
envVars: ["VERCEL_TOKEN"],
|
|
1252
|
+
description: "Vercel deployments, projects, domains",
|
|
1253
|
+
workflowSkill: `# Vercel Workflow\n\n## Common Tasks\n- List deployments: use list-deployments tool\n- Check build logs: use get-deployment-logs tool\n- Manage env vars: use list-env-vars, create-env-var tools\n\n## Guardrails\n- Never delete production deployments without confirmation\n- Check build logs before promoting to production\n`,
|
|
1254
|
+
},
|
|
1255
|
+
netlify: {
|
|
1256
|
+
package: "@netlify/mcp",
|
|
1257
|
+
envVars: ["NETLIFY_ACCESS_TOKEN"],
|
|
1258
|
+
description: "Netlify sites, deploys, forms, functions",
|
|
1259
|
+
workflowSkill: `# Netlify Workflow\n\n## Common Tasks\n- List sites: list-sites tool\n- Trigger deploy: create-deploy tool\n- Manage env: list-env-vars, set-env-var tools\n`,
|
|
1260
|
+
},
|
|
1261
|
+
railway: {
|
|
1262
|
+
package: "railway-mcp",
|
|
1263
|
+
envVars: ["RAILWAY_API_TOKEN"],
|
|
1264
|
+
description: "Railway projects, services, deployments",
|
|
1265
|
+
workflowSkill: `# Railway Workflow\n\n## Common Tasks\n- List projects and services\n- View deployment logs\n- Manage environment variables\n`,
|
|
1266
|
+
},
|
|
1267
|
+
render: {
|
|
1268
|
+
package: "mcp-server-render",
|
|
1269
|
+
envVars: ["RENDER_API_KEY"],
|
|
1270
|
+
description: "Render web services, databases, deploys",
|
|
1271
|
+
workflowSkill: `# Render Workflow\n\n## Common Tasks\n- List services\n- Trigger manual deploys\n- Check deploy logs\n`,
|
|
1272
|
+
},
|
|
1273
|
+
"fly-io": {
|
|
1274
|
+
package: "mcp-server-fly",
|
|
1275
|
+
envVars: ["FLY_API_TOKEN"],
|
|
1276
|
+
description: "Fly.io apps, machines, deployments",
|
|
1277
|
+
workflowSkill: `# Fly.io Workflow\n\n## Common Tasks\n- List apps\n- View machine status\n- Check deployment logs\n`,
|
|
1278
|
+
},
|
|
1279
|
+
supabase: {
|
|
1280
|
+
package: "@supabase/mcp-server-supabase",
|
|
1281
|
+
envVars: ["SUPABASE_URL", "SUPABASE_SERVICE_ROLE_KEY"],
|
|
1282
|
+
description: "Supabase database, auth, storage, edge functions",
|
|
1283
|
+
workflowSkill: `# Supabase Workflow\n\n## Common Tasks\n- Query tables: use SQL query tool\n- Manage auth users\n- Deploy edge functions\n\n## Guardrails\n- Never modify auth.users directly without confirmation\n- Always test migrations on a branch first\n`,
|
|
1284
|
+
},
|
|
1285
|
+
neon: {
|
|
1286
|
+
package: "@neondatabase/mcp-server-neon",
|
|
1287
|
+
envVars: ["NEON_API_KEY"],
|
|
1288
|
+
description: "Neon serverless Postgres — branches, databases",
|
|
1289
|
+
workflowSkill: `# Neon Workflow\n\n## Common Tasks\n- Create database branches for testing\n- Run SQL queries\n- Compare branch schemas\n`,
|
|
1290
|
+
},
|
|
1291
|
+
planetscale: {
|
|
1292
|
+
package: "mcp-server-planetscale",
|
|
1293
|
+
envVars: ["PLANETSCALE_SERVICE_TOKEN", "PLANETSCALE_ORG"],
|
|
1294
|
+
description: "PlanetScale MySQL branches and deploys",
|
|
1295
|
+
workflowSkill: `# PlanetScale Workflow\n\n## Common Tasks\n- Create branches for schema changes\n- Open deploy requests\n- Check schema diffs\n`,
|
|
1296
|
+
},
|
|
1297
|
+
mongodb: {
|
|
1298
|
+
package: "@mongodb-js/mcp-server-mongodb",
|
|
1299
|
+
envVars: ["MONGODB_URI"],
|
|
1300
|
+
description: "MongoDB Atlas cluster management and queries",
|
|
1301
|
+
workflowSkill: `# MongoDB Workflow\n\n## Common Tasks\n- List collections\n- Run aggregation pipelines\n- Check index usage\n`,
|
|
1302
|
+
},
|
|
1303
|
+
redis: {
|
|
1304
|
+
package: "mcp-server-redis",
|
|
1305
|
+
envVars: ["REDIS_URL"],
|
|
1306
|
+
description: "Redis key-value operations and pub/sub",
|
|
1307
|
+
workflowSkill: `# Redis Workflow\n\n## Common Tasks\n- Inspect keys and TTLs\n- Flush specific key patterns (never FLUSHALL without confirmation)\n- Monitor pub/sub channels\n`,
|
|
1308
|
+
},
|
|
1309
|
+
stripe: {
|
|
1310
|
+
package: "@stripe/mcp",
|
|
1311
|
+
envVars: ["STRIPE_SECRET_KEY"],
|
|
1312
|
+
description: "Stripe payments, customers, subscriptions",
|
|
1313
|
+
workflowSkill: `# Stripe Workflow\n\n## Common Tasks\n- List customers and subscriptions\n- Check payment intent status\n- View webhook events\n\n## Guardrails\n- Never issue refunds without explicit user confirmation\n- Use test mode keys in development (sk_test_...)\n`,
|
|
1314
|
+
},
|
|
1315
|
+
"lemon-squeezy": {
|
|
1316
|
+
package: "mcp-server-lemon-squeezy",
|
|
1317
|
+
envVars: ["LEMON_SQUEEZY_API_KEY"],
|
|
1318
|
+
description: "Lemon Squeezy products, orders, subscriptions",
|
|
1319
|
+
workflowSkill: `# Lemon Squeezy Workflow\n\n## Common Tasks\n- List products and variants\n- Check order status\n- Manage subscriptions\n`,
|
|
1320
|
+
},
|
|
1321
|
+
resend: {
|
|
1322
|
+
package: "mcp-server-resend",
|
|
1323
|
+
envVars: ["RESEND_API_KEY"],
|
|
1324
|
+
description: "Resend email sending and domain management",
|
|
1325
|
+
workflowSkill: `# Resend Workflow\n\n## Common Tasks\n- Send transactional emails\n- Check delivery status\n- Manage domains and API keys\n`,
|
|
1326
|
+
},
|
|
1327
|
+
sendgrid: {
|
|
1328
|
+
package: "mcp-server-sendgrid",
|
|
1329
|
+
envVars: ["SENDGRID_API_KEY"],
|
|
1330
|
+
description: "SendGrid email campaigns and transactional mail",
|
|
1331
|
+
workflowSkill: `# SendGrid Workflow\n\n## Common Tasks\n- Send emails\n- Check suppression lists\n- View email activity\n`,
|
|
1332
|
+
},
|
|
1333
|
+
cloudflare: {
|
|
1334
|
+
package: "@cloudflare/mcp-server-cloudflare",
|
|
1335
|
+
envVars: ["CLOUDFLARE_API_TOKEN"],
|
|
1336
|
+
description: "Cloudflare DNS, Workers, R2, KV, D1",
|
|
1337
|
+
workflowSkill: `# Cloudflare Workflow\n\n## Common Tasks\n- Manage DNS records\n- Deploy Workers\n- Query D1 databases\n- List R2 buckets\n`,
|
|
1338
|
+
},
|
|
1339
|
+
aws: {
|
|
1340
|
+
package: "@aws/mcp-server",
|
|
1341
|
+
envVars: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_DEFAULT_REGION"],
|
|
1342
|
+
description: "AWS S3, Lambda, CloudWatch, EC2",
|
|
1343
|
+
workflowSkill: `# AWS Workflow\n\n## Common Tasks\n- List S3 buckets and objects\n- Invoke Lambda functions\n- Check CloudWatch logs\n\n## Guardrails\n- Never delete S3 buckets without explicit confirmation\n- Never terminate EC2 instances without confirmation\n`,
|
|
1344
|
+
},
|
|
1345
|
+
terraform: {
|
|
1346
|
+
package: "mcp-server-terraform",
|
|
1347
|
+
envVars: ["TF_TOKEN"],
|
|
1348
|
+
description: "Terraform Cloud workspaces and runs",
|
|
1349
|
+
workflowSkill: `# Terraform Workflow\n\n## Common Tasks\n- List workspaces\n- Trigger plan runs\n- Check run status\n- Review plan output before applying\n`,
|
|
1350
|
+
},
|
|
1351
|
+
kubernetes: {
|
|
1352
|
+
package: "mcp-server-kubernetes",
|
|
1353
|
+
envVars: ["KUBECONFIG"],
|
|
1354
|
+
description: "Kubernetes cluster management and pod operations",
|
|
1355
|
+
workflowSkill: `# Kubernetes Workflow\n\n## Common Tasks\n- List pods and deployments\n- Check pod logs\n- Apply manifests\n\n## Guardrails\n- Never delete namespaces or PVCs without confirmation\n- Always check resource usage before scaling down\n`,
|
|
1356
|
+
},
|
|
1357
|
+
twilio: {
|
|
1358
|
+
package: "mcp-server-twilio",
|
|
1359
|
+
envVars: ["TWILIO_ACCOUNT_SID", "TWILIO_AUTH_TOKEN"],
|
|
1360
|
+
description: "Twilio SMS, voice, phone numbers",
|
|
1361
|
+
workflowSkill: `# Twilio Workflow\n\n## Common Tasks\n- List phone numbers\n- Check message logs\n- Manage webhooks\n`,
|
|
1362
|
+
},
|
|
1363
|
+
hubspot: {
|
|
1364
|
+
package: "mcp-server-hubspot",
|
|
1365
|
+
envVars: ["HUBSPOT_ACCESS_TOKEN"],
|
|
1366
|
+
description: "HubSpot CRM contacts, deals, pipelines",
|
|
1367
|
+
workflowSkill: `# HubSpot Workflow\n\n## Common Tasks\n- List contacts and deals\n- Update deal stages\n- Check engagement history\n`,
|
|
1368
|
+
},
|
|
1369
|
+
amplitude: {
|
|
1370
|
+
package: "mcp-server-amplitude",
|
|
1371
|
+
envVars: ["AMPLITUDE_API_KEY", "AMPLITUDE_SECRET_KEY"],
|
|
1372
|
+
description: "Amplitude product analytics and funnels",
|
|
1373
|
+
workflowSkill: `# Amplitude Workflow\n\n## Common Tasks\n- Query event data\n- Check conversion funnels\n- Export user segments\n`,
|
|
1374
|
+
},
|
|
1375
|
+
segment: {
|
|
1376
|
+
package: "mcp-server-segment",
|
|
1377
|
+
envVars: ["SEGMENT_WRITE_KEY"],
|
|
1378
|
+
description: "Segment analytics event tracking",
|
|
1379
|
+
workflowSkill: `# Segment Workflow\n\n## Common Tasks\n- Track events\n- List sources and destinations\n- Check event delivery\n`,
|
|
1380
|
+
},
|
|
1381
|
+
mixpanel: {
|
|
1382
|
+
package: "mcp-server-mixpanel",
|
|
1383
|
+
envVars: ["MIXPANEL_TOKEN"],
|
|
1384
|
+
description: "Mixpanel product analytics and reports",
|
|
1385
|
+
workflowSkill: `# Mixpanel Workflow\n\n## Common Tasks\n- Query funnel reports\n- Export events\n- Check user properties\n`,
|
|
1386
|
+
},
|
|
1387
|
+
posthog: {
|
|
1388
|
+
package: "mcp-server-posthog",
|
|
1389
|
+
envVars: ["POSTHOG_API_KEY", "POSTHOG_HOST"],
|
|
1390
|
+
description: "PostHog product analytics and feature flags",
|
|
1391
|
+
workflowSkill: `# PostHog Workflow\n\n## Common Tasks\n- Query insights\n- Manage feature flags\n- Check session recordings\n`,
|
|
1392
|
+
},
|
|
1393
|
+
grafana: {
|
|
1394
|
+
package: "mcp-server-grafana",
|
|
1395
|
+
envVars: ["GRAFANA_URL", "GRAFANA_SERVICE_ACCOUNT_TOKEN"],
|
|
1396
|
+
description: "Grafana dashboards, alerts, datasources",
|
|
1397
|
+
workflowSkill: `# Grafana Workflow\n\n## Common Tasks\n- Query dashboards\n- Check alert status\n- Search metrics\n`,
|
|
1398
|
+
},
|
|
1399
|
+
openai: {
|
|
1400
|
+
package: "mcp-server-openai",
|
|
1401
|
+
envVars: ["OPENAI_API_KEY"],
|
|
1402
|
+
description: "OpenAI API — completions, embeddings, fine-tuning",
|
|
1403
|
+
workflowSkill: `# OpenAI Workflow\n\n## Common Tasks\n- List fine-tuned models\n- Check usage and billing\n- Manage vector stores\n`,
|
|
1404
|
+
},
|
|
1405
|
+
"github-actions": {
|
|
1406
|
+
package: "mcp-server-github-actions",
|
|
1407
|
+
envVars: ["GITHUB_TOKEN"],
|
|
1408
|
+
description: "GitHub Actions workflow runs and artifacts",
|
|
1409
|
+
workflowSkill: `# GitHub Actions Workflow\n\n## Common Tasks\n- List workflow runs\n- Check job logs\n- Re-trigger failed runs\n- Download artifacts\n`,
|
|
1410
|
+
},
|
|
1411
|
+
mailchimp: {
|
|
1412
|
+
package: "mcp-server-mailchimp",
|
|
1413
|
+
envVars: ["MAILCHIMP_API_KEY"],
|
|
1414
|
+
description: "Mailchimp email campaigns and audience management",
|
|
1415
|
+
workflowSkill: `# Mailchimp Workflow\n\n## Common Tasks\n- List campaigns\n- Check campaign stats\n- Manage audience segments\n`,
|
|
1416
|
+
},
|
|
1417
|
+
intercom: {
|
|
1418
|
+
package: "mcp-server-intercom",
|
|
1419
|
+
envVars: ["INTERCOM_ACCESS_TOKEN"],
|
|
1420
|
+
description: "Intercom customer messaging and support",
|
|
1421
|
+
workflowSkill: `# Intercom Workflow\n\n## Common Tasks\n- List conversations\n- Search contacts\n- Send messages\n`,
|
|
1422
|
+
},
|
|
1423
|
+
zendesk: {
|
|
1424
|
+
package: "mcp-server-zendesk",
|
|
1425
|
+
envVars: ["ZENDESK_SUBDOMAIN", "ZENDESK_EMAIL", "ZENDESK_TOKEN"],
|
|
1426
|
+
description: "Zendesk tickets, users, organizations",
|
|
1427
|
+
workflowSkill: `# Zendesk Workflow\n\n## Common Tasks\n- List open tickets\n- Update ticket status\n- Search users\n`,
|
|
1428
|
+
},
|
|
1429
|
+
```
|
|
1430
|
+
|
|
1431
|
+
- [ ] **Step 5: Run tests**
|
|
1432
|
+
|
|
1433
|
+
```bash
|
|
1434
|
+
cd /home/stefan/projekte/forgehive && npm test 2>&1 | tail -15
|
|
1435
|
+
```
|
|
1436
|
+
|
|
1437
|
+
Expected: all tests pass including 4 new wire tests (count ≥ 40, vercel/stripe/supabase present).
|
|
1438
|
+
|
|
1439
|
+
- [ ] **Step 6: Commit**
|
|
1440
|
+
|
|
1441
|
+
```bash
|
|
1442
|
+
cd /home/stefan/projekte/forgehive
|
|
1443
|
+
git add src/wire.ts test/wire.test.ts
|
|
1444
|
+
git commit -m "feat: expand wire services from 10 to 40 — vercel, stripe, supabase, aws, k8s + 20 more"
|
|
1445
|
+
```
|
|
1446
|
+
|
|
1447
|
+
---
|
|
1448
|
+
|
|
1449
|
+
## Task 8: Wire credential validation
|
|
1450
|
+
|
|
1451
|
+
**Files:**
|
|
1452
|
+
- Modify: `src/wire.ts` (export `WireValidationResult` type + `validateWireService()`)
|
|
1453
|
+
- Modify: `src/cli.ts` (show validation after `fh wire <service>`)
|
|
1454
|
+
- Modify: `test/wire.test.ts`
|
|
1455
|
+
|
|
1456
|
+
- [ ] **Step 1: Add tests to `test/wire.test.ts`**
|
|
1457
|
+
|
|
1458
|
+
Add a new `describe` block at the end of the test file:
|
|
1459
|
+
|
|
1460
|
+
```typescript
|
|
1461
|
+
describe("validateWireService()", () => {
|
|
1462
|
+
it("gibt ready: true zurück wenn alle ENV-Vars gesetzt sind", () => {
|
|
1463
|
+
const originalEnv = process.env.LINEAR_API_KEY;
|
|
1464
|
+
process.env.LINEAR_API_KEY = "test-key";
|
|
1465
|
+
const result = validateWireService("linear");
|
|
1466
|
+
assert.strictEqual(result.ready, true);
|
|
1467
|
+
assert.ok(result.present.includes("LINEAR_API_KEY"));
|
|
1468
|
+
assert.strictEqual(result.missing.length, 0);
|
|
1469
|
+
if (originalEnv === undefined) {
|
|
1470
|
+
delete process.env.LINEAR_API_KEY;
|
|
1471
|
+
} else {
|
|
1472
|
+
process.env.LINEAR_API_KEY = originalEnv;
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
it("gibt ready: false zurück wenn ENV-Vars fehlen", () => {
|
|
1477
|
+
const original = process.env.LINEAR_API_KEY;
|
|
1478
|
+
delete process.env.LINEAR_API_KEY;
|
|
1479
|
+
const result = validateWireService("linear");
|
|
1480
|
+
assert.strictEqual(result.ready, false);
|
|
1481
|
+
assert.ok(result.missing.includes("LINEAR_API_KEY"));
|
|
1482
|
+
if (original !== undefined) process.env.LINEAR_API_KEY = original;
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
it("wirft Fehler für unbekannten Service", () => {
|
|
1486
|
+
assert.throws(
|
|
1487
|
+
() => validateWireService("nonexistent-service"),
|
|
1488
|
+
/Unbekannter Service/
|
|
1489
|
+
);
|
|
1490
|
+
});
|
|
1491
|
+
});
|
|
1492
|
+
```
|
|
1493
|
+
|
|
1494
|
+
- [ ] **Step 2: Run failing test**
|
|
1495
|
+
|
|
1496
|
+
```bash
|
|
1497
|
+
cd /home/stefan/projekte/forgehive && npm test 2>&1 | grep "validateWire\|Error" | head -10
|
|
1498
|
+
```
|
|
1499
|
+
|
|
1500
|
+
Expected: `TypeError: validateWireService is not a function`
|
|
1501
|
+
|
|
1502
|
+
- [ ] **Step 3: Add `validateWireService` to `src/wire.ts`**
|
|
1503
|
+
|
|
1504
|
+
Add type and function at the end of `src/wire.ts`, before the closing of the file:
|
|
1505
|
+
|
|
1506
|
+
```typescript
|
|
1507
|
+
export interface WireValidationResult {
|
|
1508
|
+
service: string;
|
|
1509
|
+
ready: boolean;
|
|
1510
|
+
present: string[];
|
|
1511
|
+
missing: string[];
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
export function validateWireService(service: string): WireValidationResult {
|
|
1515
|
+
if (!SERVICES[service]) {
|
|
1516
|
+
throw new Error(`Unbekannter Service: ${service}`);
|
|
1517
|
+
}
|
|
1518
|
+
const envVars = SERVICES[service].envVars;
|
|
1519
|
+
const present = envVars.filter(v => !!process.env[v]);
|
|
1520
|
+
const missing = envVars.filter(v => !process.env[v]);
|
|
1521
|
+
return { service, ready: missing.length === 0, present, missing };
|
|
1522
|
+
}
|
|
1523
|
+
```
|
|
1524
|
+
|
|
1525
|
+
- [ ] **Step 4: Update `src/cli.ts` wire handler to show validation**
|
|
1526
|
+
|
|
1527
|
+
In `src/cli.ts`, import the new function:
|
|
1528
|
+
```typescript
|
|
1529
|
+
import { wireService, listWireServices, validateWireService } from "./wire.ts";
|
|
1530
|
+
```
|
|
1531
|
+
|
|
1532
|
+
In the `wire` command handler, after the `wireService(...)` call and its log messages, add:
|
|
1533
|
+
|
|
1534
|
+
```typescript
|
|
1535
|
+
const validation = validateWireService(service);
|
|
1536
|
+
if (validation.ready) {
|
|
1537
|
+
console.log(`✓ Credentials: alle ENV-Vars gesetzt`);
|
|
1538
|
+
} else {
|
|
1539
|
+
console.log(`\n⚠ Fehlende ENV-Vars (Service nicht bereit):`);
|
|
1540
|
+
for (const v of validation.missing) {
|
|
1541
|
+
console.log(` export ${v}="<your-key>"`);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
```
|
|
1545
|
+
|
|
1546
|
+
- [ ] **Step 5: Run tests**
|
|
1547
|
+
|
|
1548
|
+
```bash
|
|
1549
|
+
cd /home/stefan/projekte/forgehive && npm test 2>&1 | tail -15
|
|
1550
|
+
```
|
|
1551
|
+
|
|
1552
|
+
Expected: all tests pass including 3 new validation tests.
|
|
1553
|
+
|
|
1554
|
+
- [ ] **Step 6: Commit**
|
|
1555
|
+
|
|
1556
|
+
```bash
|
|
1557
|
+
cd /home/stefan/projekte/forgehive
|
|
1558
|
+
git add src/wire.ts src/cli.ts test/wire.test.ts
|
|
1559
|
+
git commit -m "feat: wire credential validation — shows missing ENV vars after fh wire <service>"
|
|
1560
|
+
```
|
|
1561
|
+
|
|
1562
|
+
---
|
|
1563
|
+
|
|
1564
|
+
## Task 9: Version bump + CLAUDE.md block update
|
|
1565
|
+
|
|
1566
|
+
**Files:**
|
|
1567
|
+
- Modify: `package.json`
|
|
1568
|
+
- Modify: `forgehive/templates/claude-md.block.md` (integrate all new sections cleanly)
|
|
1569
|
+
- Run: `npm run build`
|
|
1570
|
+
|
|
1571
|
+
- [ ] **Step 1: Bump version in `package.json`**
|
|
1572
|
+
|
|
1573
|
+
Change:
|
|
1574
|
+
```json
|
|
1575
|
+
"version": "0.3.0",
|
|
1576
|
+
```
|
|
1577
|
+
To:
|
|
1578
|
+
```json
|
|
1579
|
+
"version": "0.4.0",
|
|
1580
|
+
```
|
|
1581
|
+
|
|
1582
|
+
- [ ] **Step 2: Rewrite `forgehive/templates/claude-md.block.md` with all new sections**
|
|
1583
|
+
|
|
1584
|
+
Replace the entire file with:
|
|
1585
|
+
|
|
1586
|
+
```markdown
|
|
1587
|
+
## forgehive
|
|
1588
|
+
|
|
1589
|
+
This project uses **forgehive** for structured AI-assisted development.
|
|
1590
|
+
|
|
1591
|
+
### Session Start (Required)
|
|
1592
|
+
|
|
1593
|
+
1. Read `.forgehive/capabilities.yaml`
|
|
1594
|
+
- If `status: draft` → tell the user: "Run `fh confirm` to activate capabilities."
|
|
1595
|
+
- If `status: confirmed` → load silently and apply throughout the session
|
|
1596
|
+
2. Read `.forgehive/memory/MEMORY.md` — follow the index links to load project context
|
|
1597
|
+
3. Run `fh scan --check` to verify the stack snapshot is current
|
|
1598
|
+
|
|
1599
|
+
### During the Session
|
|
1600
|
+
|
|
1601
|
+
- Only suggest tools and libraries listed in `capabilities.yaml`
|
|
1602
|
+
- If a capability has a `check` field: verify it before use
|
|
1603
|
+
- If a capability has a `fulfill` field and the check fails: fulfill it
|
|
1604
|
+
- At session end: append brief notes to `.forgehive/state/YYYY-MM-DD.md`
|
|
1605
|
+
- If you learn something non-obvious about the project, offer to persist it to memory
|
|
1606
|
+
|
|
1607
|
+
### Skills — Progressive Loading
|
|
1608
|
+
|
|
1609
|
+
Before starting a technical task, read `.forgehive/skills/INDEX.yaml` to find relevant skills.
|
|
1610
|
+
Load only the skills matching your current task — not all skills at once.
|
|
1611
|
+
|
|
1612
|
+
Examples:
|
|
1613
|
+
- Working with TypeScript types → load `expert/typescript-patterns.md`
|
|
1614
|
+
- Database migration → load `expert/database-patterns.md`
|
|
1615
|
+
- Reviewing a PR → load `expert/code-review.md`
|
|
1616
|
+
- Security review → load `expert/security-checklist.md`
|
|
1617
|
+
- Performance issue → load `expert/performance-patterns.md`
|
|
1618
|
+
|
|
1619
|
+
### Workflow Commands
|
|
1620
|
+
|
|
1621
|
+
ForgeHive installs slash commands in `.claude/commands/`:
|
|
1622
|
+
- `/fh-start-task` — start a new feature branch with full context loaded
|
|
1623
|
+
- `/fh-ship` — pre-ship checklist: tests, diff review, PR draft
|
|
1624
|
+
- `/fh-review` — structured code review using the review skill
|
|
1625
|
+
- `/fh-hotfix` — minimal hotfix protocol (< 50 lines rule)
|
|
1626
|
+
|
|
1627
|
+
### Agent Memory
|
|
1628
|
+
|
|
1629
|
+
Each agent has persistent memory in `.forgehive/agents/memory/<name>.md`.
|
|
1630
|
+
Before activating a party set, read the relevant agent memory files.
|
|
1631
|
+
Update memory files when agents make significant decisions.
|
|
1632
|
+
Format: `[YYYY-MM-DD] <decision or learned context>`
|
|
1633
|
+
|
|
1634
|
+
### Party Mode
|
|
1635
|
+
|
|
1636
|
+
Slash commands auto-configured by forgehive:
|
|
1637
|
+
- `/party` — activate build agents (Viktor + Kai + Sam)
|
|
1638
|
+
- `/design-party` — activate design agents (Suki + Viktor)
|
|
1639
|
+
- `/review-party` — activate review agents (Kai + Sam + Eli)
|
|
1640
|
+
- `/full-party` — activate all agents
|
|
1641
|
+
|
|
1642
|
+
### Prohibited
|
|
1643
|
+
|
|
1644
|
+
- Writing to paths outside the project root
|
|
1645
|
+
- Modifying `.forgehive/scan-result.yaml` manually
|
|
1646
|
+
- Skipping the session-start capability and memory check
|
|
1647
|
+
- Suggesting tools not in `capabilities.yaml` without explicitly noting the deviation
|
|
1648
|
+
- Activating party agents without reading their memory files first
|
|
1649
|
+
```
|
|
1650
|
+
|
|
1651
|
+
- [ ] **Step 3: Run full test suite**
|
|
1652
|
+
|
|
1653
|
+
```bash
|
|
1654
|
+
cd /home/stefan/projekte/forgehive && npm test 2>&1 | tail -20
|
|
1655
|
+
```
|
|
1656
|
+
|
|
1657
|
+
Expected: all tests pass (should be ~120+ tests across all files).
|
|
1658
|
+
|
|
1659
|
+
- [ ] **Step 4: Build binary**
|
|
1660
|
+
|
|
1661
|
+
```bash
|
|
1662
|
+
cd /home/stefan/projekte/forgehive && npm run build
|
|
1663
|
+
```
|
|
1664
|
+
|
|
1665
|
+
Expected: `dist/cli.js` rebuilt, no errors.
|
|
1666
|
+
|
|
1667
|
+
- [ ] **Step 5: Run typecheck**
|
|
1668
|
+
|
|
1669
|
+
```bash
|
|
1670
|
+
cd /home/stefan/projekte/forgehive && npm run typecheck
|
|
1671
|
+
```
|
|
1672
|
+
|
|
1673
|
+
Expected: 0 errors.
|
|
1674
|
+
|
|
1675
|
+
- [ ] **Step 6: Commit**
|
|
1676
|
+
|
|
1677
|
+
```bash
|
|
1678
|
+
cd /home/stefan/projekte/forgehive
|
|
1679
|
+
git add package.json forgehive/templates/claude-md.block.md dist/cli.js
|
|
1680
|
+
git commit -m "chore: bump version to 0.4.0, update CLAUDE.md block with all NextGen sections"
|
|
1681
|
+
```
|
|
1682
|
+
|
|
1683
|
+
---
|
|
1684
|
+
|
|
1685
|
+
## Self-Review
|
|
1686
|
+
|
|
1687
|
+
**Spec coverage:**
|
|
1688
|
+
| Feature | Task |
|
|
1689
|
+
|---|---|
|
|
1690
|
+
| `fh status` health dashboard | Task 1 ✓ |
|
|
1691
|
+
| `fh watch` filesystem watcher | Task 2 ✓ |
|
|
1692
|
+
| Guardrail hooks | Task 3 ✓ |
|
|
1693
|
+
| Workflow slash commands | Task 4 ✓ |
|
|
1694
|
+
| Skills INDEX + progressive loading | Task 5 ✓ |
|
|
1695
|
+
| Agent memory persistence | Task 6 ✓ |
|
|
1696
|
+
| 40 MCP services | Task 7 ✓ |
|
|
1697
|
+
| Wire credential validation | Task 8 ✓ |
|
|
1698
|
+
| Version bump + block update | Task 9 ✓ |
|
|
1699
|
+
|
|
1700
|
+
**Type consistency check:**
|
|
1701
|
+
- `WireValidationResult` defined in Task 8 `wire.ts` and imported in `cli.ts` ✓
|
|
1702
|
+
- `checkProjectHash` exported from `watch.ts`, imported in `cli.ts` via `watchProject` ✓
|
|
1703
|
+
- `writeGuardrailHooks` exported from `guardrails.ts`, imported in `writer.ts` ✓
|
|
1704
|
+
- `projectStatus` exported from `status.ts`, imported in `cli.ts` ✓
|
|
1705
|
+
|
|
1706
|
+
**No placeholders:** All code blocks contain full implementations. All test files have full test code. ✓
|
|
1707
|
+
|
|
1708
|
+
**Deferred (Plan 4):** Multi-model router (`fh router`) — separate subsystem, own plan.
|