forgehive 0.7.5 → 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.
@@ -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.