forgehive 0.7.1 → 0.7.3

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 CHANGED
@@ -14,8 +14,8 @@
14
14
  <p align="center">
15
15
  <img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen" alt="Node.js ≥ 18">
16
16
  <img src="https://img.shields.io/badge/typescript-5.8-blue" alt="TypeScript">
17
- <img src="https://img.shields.io/badge/tests-217%20passing-success" alt="217 tests">
18
- <img src="https://img.shields.io/badge/bundle-205KB-lightgrey" alt="205KB bundle">
17
+ <img src="https://img.shields.io/badge/tests-267%20passing-success" alt="267 tests">
18
+ <img src="https://img.shields.io/badge/bundle-247KB-lightgrey" alt="247KB bundle">
19
19
  <img src="https://img.shields.io/badge/license-MIT-green" alt="MIT">
20
20
  </p>
21
21
 
@@ -31,18 +31,23 @@ Claude loses all project knowledge between sessions. forgehive solves this by wr
31
31
 
32
32
  - **Stack Scanner** — detects tech stack, dependencies, and project structure automatically
33
33
  - **Persistent Memory** — context and session notes survive every restart
34
+ - **Story & Epic Tracking** — lightweight agile backlog inside `.forgehive/memory/`
35
+ - **Velocity Tracking** — sprint velocity history, rolling average, and capacity hints
34
36
  - **Party Mode** — specialized agent sets, each agent in its own isolated git worktree
35
37
  - **MCP Wiring** — preconfigured connectors for 10 services, API keys stored securely in `~/.forgehive/`
36
38
  - **Security Suite** — SAST, secret scanner, CVE check, CISO reports (GDPR/SOC2/HIPAA)
39
+ - **CI Integration** — CI health report and GitHub Actions template generator
40
+ - **Codebase Map** — file/line/import table for navigation and onboarding
41
+ - **Team Sync** — share memory across the team via a dedicated git branch
37
42
  - **AGENTS.md** — cross-tool standard (Cursor, Copilot, Gemini CLI, Codex, Windsurf)
38
43
 
39
44
  **Design principle:** writes exclusively to `.forgehive/` in the target project. No globbing outside the project root. Never overwrites files it didn't create.
40
45
 
41
46
  ---
42
47
 
43
- ## Status: Early Alpha (v0.6)
48
+ ## Status: v0.7 Stable
44
49
 
45
- This is an early release. The codebase is clean — TypeScript, 217 passing tests, single bundle but several features have not yet been tested against a real project.
50
+ 267 passing tests back the commands listed below. The v0.7 feature set has been validated end-to-end.
46
51
 
47
52
  **Stable — use with confidence:**
48
53
 
@@ -56,6 +61,16 @@ This is an early release. The codebase is clean — TypeScript, 217 passing test
56
61
  | `fh security report` — compliance reports | stable |
57
62
  | `fh memory` — show, clean, export, snapshot | stable |
58
63
  | MCP credential store (`fh mcp auth`) | stable |
64
+ | `fh ci` — CI health report + GitHub Actions template | stable |
65
+ | `fh map` — codebase structure map | stable |
66
+ | `fh onboard` — generates ONBOARDING.md | stable |
67
+ | `fh changelog` — semantic changelog from git | stable |
68
+ | `fh metrics` — developer metrics by author/type | stable |
69
+ | `fh sync` — team memory sharing via git branch | stable |
70
+ | `fh run` — background agent execution via `claude -p` | stable |
71
+ | `fh story` — story card create/list/show/done | stable |
72
+ | `fh epic` — epic create/list/show | stable |
73
+ | `fh velocity` — show history + record sprint data | stable |
59
74
 
60
75
  **Experimental — works in tests, not yet live-validated:**
61
76
 
@@ -74,7 +89,7 @@ If you run into issues, please open an issue on GitHub. The most valuable feedba
74
89
 
75
90
  - **Node.js ≥ 18** — check with `node --version`
76
91
  - **Claude Code** installed and configured (`claude` CLI available)
77
- - **git** — required for Party Mode worktrees and the guardrails hook
92
+ - **git** — required for Party Mode worktrees, `fh sync`, and the guardrails hook
78
93
 
79
94
  ---
80
95
 
@@ -94,14 +109,14 @@ This makes both `fh` and `forgehive` available globally.
94
109
  git clone https://github.com/matharnica/forgehive
95
110
  cd forgehive
96
111
  npm install
97
- npm run build # compiles to dist/cli.js (~205 KB)
112
+ npm run build # compiles to dist/cli.js (~247 KB)
98
113
  npm link # makes 'fh' available globally
99
114
  ```
100
115
 
101
116
  Verify the installation:
102
117
 
103
118
  ```bash
104
- fh --version # should print 0.6.0
119
+ fh --version # should print 0.7.2
105
120
  fh --help # lists all available commands
106
121
  ```
107
122
 
@@ -151,11 +166,15 @@ fh security report gdpr # generates a CISO-ready compliance report
151
166
 
152
167
  | Command | Description |
153
168
  |---|---|
154
- | `fh init` | Scan project, create `.forgehive/`, merge CLAUDE.md block, write AGENTS.md |
169
+ | `fh --help` | Show full command reference |
170
+ | `fh -h` | Same as --help |
171
+ | `fh init` | Set up forgehive in the current project. Use --force to re-initialize an existing setup |
155
172
  | `fh confirm` | Confirm `capabilities.yaml` — activates context for Claude |
156
173
  | `fh rollback` | Remove `.forgehive/` and the CLAUDE.md block cleanly |
157
174
  | `fh status` | Show current project status, drift warning if scan is outdated |
158
175
 
176
+ > Running `fh init` on a project that already has `.forgehive/` prints a warning and exits. Use `fh init --force` to re-initialize (overwrites `capabilities.yaml` and scans again). To update only the scan, use `fh scan --update`.
177
+
159
178
  **When to use `fh rollback`:** If you want to remove forgehive from a project entirely. It removes exactly the block it inserted into CLAUDE.md (nothing else) and deletes `.forgehive/`.
160
179
 
161
180
  ---
@@ -264,6 +283,91 @@ Shows `permissions.yaml` — the file access control list for each agent. Each a
264
283
 
265
284
  ---
266
285
 
286
+ ### CI
287
+
288
+ ```bash
289
+ fh ci # run CI health report (text output)
290
+ fh ci --format json # output report as JSON
291
+ fh ci --format markdown # output report as Markdown
292
+ fh ci --fail-on critical # exit 1 only on CRITICAL findings
293
+ fh ci --fail-on high # exit 1 on HIGH or CRITICAL
294
+ fh ci --fail-on any # exit 1 on any finding
295
+ fh ci --init # generate a GitHub Actions workflow template
296
+ ```
297
+
298
+ `fh ci` aggregates security scan, dependency audit, and test results into a single CI health report. Use `--init` to scaffold a `.github/workflows/forgehive.yml` file that runs the full check on every pull request. The `--fail-on` flag controls which severity level causes a non-zero exit code, giving you fine-grained control over what blocks a merge.
299
+
300
+ ---
301
+
302
+ ### Codebase Map
303
+
304
+ ```bash
305
+ fh map
306
+ ```
307
+
308
+ Prints a structured table of your codebase: file paths, line counts, and import relationships. Useful for onboarding a new engineer (or a new Claude session) to an unfamiliar codebase. Output goes to stdout; pipe it to a file if you want to save it.
309
+
310
+ ---
311
+
312
+ ### Onboarding
313
+
314
+ ```bash
315
+ fh onboard # generate ONBOARDING.md (stdout)
316
+ fh onboard --output ./ONBOARDING.md # write to file
317
+ ```
318
+
319
+ Generates a human-readable `ONBOARDING.md` from three sources: the detected stack in `capabilities.yaml`, the persistent memory in `.forgehive/memory/`, and the recent git log. The result is a self-contained document a new team member can read to understand the project architecture, conventions, and current state.
320
+
321
+ ---
322
+
323
+ ### Changelog
324
+
325
+ ```bash
326
+ fh changelog # full changelog from all commits
327
+ fh changelog --since v0.6.0 # changes since a specific tag
328
+ fh changelog --output CHANGELOG.md # write to file
329
+ ```
330
+
331
+ Parses conventional commits (`feat:`, `fix:`, `chore:`, `docs:`, etc.) and groups them into a semantic changelog. Breaking changes are surfaced separately. Requires conventional commit messages — standard for projects that use `fh git-conventions`.
332
+
333
+ ---
334
+
335
+ ### Developer Metrics
336
+
337
+ ```bash
338
+ fh metrics # all-time developer metrics
339
+ fh metrics --since 2025-01-01 # metrics since a date
340
+ ```
341
+
342
+ Shows commit activity broken down by author and commit type (feat, fix, chore, docs, test, refactor). Also shows rolling statistics: commit frequency, most active contributors, and type distribution. Useful for retrospectives and identifying contribution patterns.
343
+
344
+ ---
345
+
346
+ ### Team Memory Sync
347
+
348
+ ```bash
349
+ fh sync push # push memory to default remote/branch
350
+ fh sync push --remote origin --branch forgehive-memory # push to a specific remote branch
351
+ fh sync pull # pull memory from remote
352
+ fh sync pull --remote origin --branch forgehive-memory # pull from specific branch
353
+ ```
354
+
355
+ `fh sync` shares `.forgehive/memory/` across the team by pushing to (or pulling from) a dedicated git branch. This is an alternative to committing the memory directory into the main branch. On `pull`, existing local files are not overwritten — only new files are added, the same idempotent behavior as `fh memory snapshot import`.
356
+
357
+ ---
358
+
359
+ ### Background Agent Execution
360
+
361
+ ```bash
362
+ fh run <issue-url> # run agent on a GitHub/Linear issue URL
363
+ fh run <issue-url> --agent kai # use a specific agent
364
+ fh run <issue-url> --label ready-for-ai # filter by label
365
+ ```
366
+
367
+ `fh run` launches a background agent via `claude -p`, passes the issue content as context, and returns immediately. The agent reads the issue, applies forgehive context from `.forgehive/`, and works on the task in the background. Useful for automating repetitive issues or running agents overnight. Agent output is streamed to stdout and also written to `.forgehive/runs/`.
368
+
369
+ ---
370
+
267
371
  ### Memory
268
372
 
269
373
  Memory files live in `.forgehive/memory/` and are read by Claude at session start via the CLAUDE.md block.
@@ -297,6 +401,56 @@ Memory files live in `.forgehive/memory/` and are read by Claude at session star
297
401
 
298
402
  ---
299
403
 
404
+ ### Story Cards
405
+
406
+ Story cards are lightweight user stories stored in `.forgehive/memory/stories/`. They integrate with `/fh-sprint` for velocity-based sprint planning.
407
+
408
+ ```bash
409
+ fh story create "As a user I want to log in" # create a story (auto-numbered US-N)
410
+ fh story create "As a user I want to log in" --epic EPC-1 --points 3 # with epic + points
411
+ fh story list # list all backlog stories
412
+ fh story list --epic EPC-1 # filter by epic
413
+ fh story show US-1 # show full story card with acceptance criteria
414
+ fh story sprint US-1 # mark story as in-sprint (pulled into current sprint)
415
+ fh story done US-1 # mark story as done
416
+ fh story done US-1 --points 5 # mark done and record actual points
417
+ ```
418
+
419
+ Each story card is a Markdown file at `.forgehive/memory/stories/US-N.md`. The card includes a title, acceptance criteria placeholder, story points, epic link, and status (`backlog` | `in-sprint` | `done`). Stories move through `backlog → in-sprint → done`. When you run `/fh-sprint` in a Claude Code session, Claude reads the backlog and uses velocity data to suggest a realistic sprint scope.
420
+
421
+ ---
422
+
423
+ ### Epics
424
+
425
+ Epics group related stories and are stored in `.forgehive/memory/epics/`.
426
+
427
+ ```bash
428
+ fh epic create "User Authentication" # create an epic (auto-numbered EPC-N)
429
+ fh epic create "User Authentication" --goal "Users can log in securely" # with a goal statement
430
+ fh epic list # list all epics
431
+ fh epic show EPC-1 # show epic with linked stories and point total
432
+ ```
433
+
434
+ Each epic card is a Markdown file at `.forgehive/memory/epics/EPC-N.md`. `fh epic show` aggregates all stories linked to the epic and displays their status and point totals.
435
+
436
+ ---
437
+
438
+ ### Velocity
439
+
440
+ ```bash
441
+ fh velocity show # velocity history + rolling average + recommendation
442
+ fh velocity record sprint-3 --committed 21 --delivered 18 # record sprint data
443
+ ```
444
+
445
+ `fh velocity show` reads the velocity history from `.forgehive/memory/velocity.md` and prints:
446
+ - A table of past sprints (committed vs. delivered points)
447
+ - A rolling average (last 3 sprints)
448
+ - A capacity recommendation for the next sprint
449
+
450
+ `fh velocity record` appends a new entry to the velocity history. The `/fh-sprint` slash command reads this data automatically and uses it to calibrate the sprint scope it suggests.
451
+
452
+ ---
453
+
300
454
  ### Party Mode
301
455
 
302
456
  Party Mode runs specialized agent sets in parallel, each in an isolated git worktree.
@@ -320,7 +474,7 @@ fh party cleanup # remove finished worktrees
320
474
  | `security` | Vera + Sam | `/security-party` |
321
475
  | `full` | All 8 agents | `/full-party` |
322
476
 
323
- **Model mapping** — assign specific Claude models per agent:
477
+ **Multi-model routing** — `defaults.yaml` supports a `models:` key per agent set. Override via CLI:
324
478
 
325
479
  ```bash
326
480
  fh party --model-map "viktor:claude-opus-4-7,kai:claude-sonnet-4-6,sam:claude-haiku-4-5"
@@ -417,33 +571,57 @@ Clones the repo with `--depth=1`, copies all `.md` files from the `skills/expert
417
571
 
418
572
  ---
419
573
 
574
+ ### Slash Commands
575
+
576
+ The following slash commands are available inside Claude Code sessions:
577
+
578
+ | Command | Description |
579
+ |---|---|
580
+ | `/fh-sprint` | Sprint planning — reads backlog, velocity, and capacity; suggests scope using Fibonacci points (1/2/3/5/8/13); records velocity at end of sprint |
581
+ | `/fh-deploy` | Pre-deploy checklist — security scan, dependency audit, test run, final confirmation |
582
+ | `/fh-test-this` | Generate tests for the current file or selection — uses `testing-strategies` skill |
583
+ | `/fh-docs` | Generate documentation for the current file or module |
584
+ | `/party` | Start the `build` party (Viktor + Kai + Sam) |
585
+ | `/design-party` | Start the `design` party (Suki + Viktor) |
586
+ | `/review-party` | Start the `review` party (Kai + Sam + Eli) |
587
+ | `/security-party` | Start the `security` party (Vera + Sam) |
588
+ | `/full-party` | Start all 8 agents |
589
+
590
+ **`/fh-sprint` in detail:** Reads `.forgehive/memory/stories/` for backlog items, `.forgehive/memory/velocity.md` for historical capacity, and `.forgehive/memory/epics/` for epic context. Suggests a sprint scope as a list of story cards with Fibonacci point estimates. When you close the sprint, it prompts you to record delivered points so velocity history stays up to date. If Linear or GitHub MCP servers are connected, it can pull issues directly from those services.
591
+
592
+ ---
593
+
420
594
  ## What `fh init` Creates
421
595
 
422
596
  ```
423
597
  my-project/
424
598
  .forgehive/
425
- capabilities.yaml detected stack (status: draft confirmed)
426
- scan-result.yaml raw scanner output
427
- .scan-hash checksum for fh scan --check
599
+ capabilities.yaml <- detected stack (status: draft -> confirmed)
600
+ scan-result.yaml <- raw scanner output
601
+ .scan-hash <- checksum for fh scan --check
428
602
  harness/
429
- architecture.md codebase overview for Claude
430
- constraints.yaml max_lines, require_tests, style rules
431
- permissions.yaml per-agent file access control (read/write/deny)
603
+ architecture.md <- codebase overview for Claude
604
+ constraints.yaml <- max_lines, require_tests, style rules
605
+ permissions.yaml <- per-agent file access control (read/write/deny)
432
606
  memory/
433
- MEMORY.md index of all memory files
434
- project.md project context and open decisions
435
- feedback.md corrections + confirmed approaches
436
- stack.md stack details the scanner can't detect
437
- adrs/ architecture decision records
607
+ MEMORY.md <- index of all memory files
608
+ project.md <- project context and open decisions
609
+ feedback.md <- corrections + confirmed approaches
610
+ stack.md <- stack details the scanner can't detect
611
+ adrs/ <- architecture decision records
612
+ stories/ <- story cards (US-N.md)
613
+ epics/ <- epic cards (EPC-N.md)
614
+ velocity.md <- sprint velocity history
438
615
  skills/
439
- generated/ AI-generated skills (fh skills regen)
440
- expert/ 16 preinstalled expert skills
441
- workflows/ MCP workflow skills
442
- worktrees/ isolated git worktrees (fh party run)
443
- audit.log ← JSONL audit trail (fh security commands)
444
- cost-config.yaml ← spend limits (fh cost --limit/--alert)
445
- AGENTS.md ← cross-tool agent standard
446
- CLAUDE.md forgehive block inserted, rest preserved
616
+ generated/ <- AI-generated skills (fh skills regen)
617
+ expert/ <- 16 preinstalled expert skills
618
+ workflows/ <- MCP workflow skills
619
+ worktrees/ <- isolated git worktrees (fh party run)
620
+ runs/ <- background agent output (fh run)
621
+ audit.log <- JSONL audit trail (fh security commands)
622
+ cost-config.yaml <- spend limits (fh cost --limit/--alert)
623
+ AGENTS.md <- cross-tool agent standard
624
+ CLAUDE.md <- forgehive block inserted, rest preserved
447
625
  ```
448
626
 
449
627
  ---
@@ -520,7 +698,7 @@ The hook fires on every bash command execution in Claude Code and exits 1 if a m
520
698
  cd my-project
521
699
  fh init
522
700
  fh confirm
523
- # Open Claude Code Claude now has full context
701
+ # Open Claude Code -> Claude now has full context
524
702
  ```
525
703
 
526
704
  ### After adding new dependencies
@@ -539,6 +717,49 @@ fh security deps # check for new CVEs in dependencies
539
717
  fh security report gdpr # update compliance report
540
718
  ```
541
719
 
720
+ ### CI integration
721
+
722
+ ```bash
723
+ fh ci --init # scaffold .github/workflows/forgehive.yml
724
+ fh ci --format markdown --fail-on high # use in existing CI scripts
725
+ ```
726
+
727
+ ### Generating onboarding documentation
728
+
729
+ ```bash
730
+ fh map # review codebase structure
731
+ fh onboard --output ONBOARDING.md # generate onboarding document
732
+ git add ONBOARDING.md
733
+ git commit -m "docs: add forgehive-generated onboarding"
734
+ ```
735
+
736
+ ### Generating a changelog
737
+
738
+ ```bash
739
+ fh changelog --since v0.6.0 --output CHANGELOG.md
740
+ ```
741
+
742
+ ### Sprint planning
743
+
744
+ ```bash
745
+ # Set up your backlog
746
+ fh epic create "User Authentication" --goal "Users can log in securely"
747
+ fh story create "As a user I want to log in" --epic EPC-1 --points 3
748
+ fh story create "As a user I want to reset my password" --epic EPC-1 --points 2
749
+
750
+ # Check current velocity
751
+ fh velocity show
752
+
753
+ # Open Claude Code and run:
754
+ # /fh-sprint
755
+ # Claude reads velocity + backlog and suggests scope
756
+
757
+ # At end of sprint
758
+ fh velocity record sprint-1 --committed 13 --delivered 11
759
+ fh story done US-1
760
+ fh story done US-2
761
+ ```
762
+
542
763
  ### Sharing context with the team
543
764
 
544
765
  ```bash
@@ -548,6 +769,11 @@ git commit -m "chore: update shared forgehive context"
548
769
 
549
770
  # On another machine:
550
771
  fh memory snapshot import ./team-context.json
772
+
773
+ # Or use the sync approach (no committed file):
774
+ fh sync push --remote origin --branch forgehive-memory
775
+ # teammate runs:
776
+ fh sync pull --remote origin --branch forgehive-memory
551
777
  ```
552
778
 
553
779
  ### Starting a multi-agent build session
@@ -555,8 +781,8 @@ fh memory snapshot import ./team-context.json
555
781
  ```bash
556
782
  fh party --set build
557
783
  fh party run
558
- # Creates worktrees for Viktor (architect), Kai (engineer), Sam (QA)
559
- # Each Claude Code session opens in its own isolated worktree
784
+ # -> Creates worktrees for Viktor (architect), Kai (engineer), Sam (QA)
785
+ # -> Each Claude Code session opens in its own isolated worktree
560
786
 
561
787
  fh party status # check what's running
562
788
  fh party cleanup # clean up after the session
@@ -575,6 +801,14 @@ fh mcp auth add github GITHUB_TOKEN=ghp_...
575
801
  fh mcp auth list # verify both services are stored
576
802
  ```
577
803
 
804
+ ### Running a background agent on an issue
805
+
806
+ ```bash
807
+ fh run https://github.com/my-org/my-repo/issues/42 --agent kai
808
+ # Kai reads the issue, applies forgehive context, works in background
809
+ # Output streamed to stdout and saved to .forgehive/runs/
810
+ ```
811
+
578
812
  ---
579
813
 
580
814
  ## Configuration Files
@@ -613,13 +847,13 @@ Global credential store (chmod 600). Managed exclusively via `fh mcp auth` comma
613
847
  |---|---|
614
848
  | Runtime | Node.js ≥ 18, ESM |
615
849
  | Language | TypeScript |
616
- | Build | esbuild `dist/cli.js` (~205 KB, single bundle) |
850
+ | Build | esbuild -> `dist/cli.js` (~247 KB, single bundle) |
617
851
  | Type check | `tsc --noEmit` |
618
- | Tests | `node:test` (native) + tsx ESM loader, 217 tests |
852
+ | Tests | `node:test` (native) + tsx ESM loader, 267 tests |
619
853
  | Dependencies | `js-yaml` (sole runtime dependency) |
620
854
 
621
855
  ```bash
622
- npm run build # esbuild dist/cli.js
856
+ npm run build # esbuild -> dist/cli.js
623
857
  npm run typecheck # tsc --noEmit
624
858
  npm test # node --import tsx/esm --test test/*.test.ts
625
859
  ```
package/dist/cli.js CHANGED
@@ -2751,8 +2751,8 @@ var init_harness = __esm({
2751
2751
 
2752
2752
  // src/cli.ts
2753
2753
  init_js_yaml();
2754
- import fs30 from "node:fs";
2755
- import path31 from "node:path";
2754
+ import fs31 from "node:fs";
2755
+ import path32 from "node:path";
2756
2756
 
2757
2757
  // src/scanner.ts
2758
2758
  import fs from "node:fs";
@@ -6454,26 +6454,296 @@ function formatVelocityReport(history) {
6454
6454
  return lines.join("\n");
6455
6455
  }
6456
6456
 
6457
+ // src/docs.ts
6458
+ init_js_yaml();
6459
+ import fs30 from "node:fs";
6460
+ import path31 from "node:path";
6461
+ import { spawnSync as spawnSync11 } from "node:child_process";
6462
+ var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".py", ".go"];
6463
+ var IGNORE_DIRS3 = ["node_modules", ".git", "dist", ".forgehive", "coverage", ".next", "build", "test"];
6464
+ var EXPORT_PATTERNS = [
6465
+ /^export\s+(?:async\s+)?function\s+(\w+)/gm,
6466
+ /^export\s+(?:const|let|var)\s+(\w+)/gm,
6467
+ /^export\s+(?:class|interface|type|enum)\s+(\w+)/gm,
6468
+ /^export\s+default\s+(?:function\s+)?(\w+)?/gm
6469
+ ];
6470
+ function readCapabilities2(forgehiveDir2) {
6471
+ const capPath = path31.join(forgehiveDir2, "capabilities.yaml");
6472
+ if (!fs30.existsSync(capPath)) return {};
6473
+ try {
6474
+ return jsYaml.load(fs30.readFileSync(capPath, "utf8")) ?? {};
6475
+ } catch {
6476
+ return {};
6477
+ }
6478
+ }
6479
+ function readMemoryFiles2(forgehiveDir2) {
6480
+ const memDir = path31.join(forgehiveDir2, "memory");
6481
+ if (!fs30.existsSync(memDir)) return {};
6482
+ const result = {};
6483
+ for (const f of fs30.readdirSync(memDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md")) {
6484
+ result[f] = fs30.readFileSync(path31.join(memDir, f), "utf8");
6485
+ }
6486
+ return result;
6487
+ }
6488
+ function getRecentCommits2(projectRoot2, n = 10) {
6489
+ const result = spawnSync11("git", ["log", "--oneline", `-${n}`], { cwd: projectRoot2, encoding: "utf8" });
6490
+ if (result.status !== 0) return [];
6491
+ return result.stdout.trim().split("\n").filter(Boolean);
6492
+ }
6493
+ function extractExports(content) {
6494
+ const exports = [];
6495
+ for (const pattern of EXPORT_PATTERNS) {
6496
+ pattern.lastIndex = 0;
6497
+ let m;
6498
+ while ((m = pattern.exec(content)) !== null) {
6499
+ if (m[1]) exports.push(m[1]);
6500
+ }
6501
+ }
6502
+ return [...new Set(exports)];
6503
+ }
6504
+ function walkSourceFiles(dir) {
6505
+ const results = [];
6506
+ function walk(current) {
6507
+ if (!fs30.existsSync(current)) return;
6508
+ for (const entry of fs30.readdirSync(current, { withFileTypes: true })) {
6509
+ if (IGNORE_DIRS3.includes(entry.name)) continue;
6510
+ const full = path31.join(current, entry.name);
6511
+ if (entry.isDirectory()) walk(full);
6512
+ else if (entry.isFile() && SOURCE_EXTS.includes(path31.extname(entry.name))) results.push(full);
6513
+ }
6514
+ }
6515
+ walk(dir);
6516
+ return results;
6517
+ }
6518
+ function generateUserGuide(projectRoot2, forgehiveDir2) {
6519
+ const projectName = path31.basename(projectRoot2);
6520
+ const caps = readCapabilities2(forgehiveDir2);
6521
+ const memFiles = readMemoryFiles2(forgehiveDir2);
6522
+ const commits = getRecentCommits2(projectRoot2);
6523
+ const lines = [];
6524
+ lines.push(`# ${projectName} \u2014 User Guide`);
6525
+ lines.push("");
6526
+ lines.push(`> Generated by forgehive on ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`);
6527
+ lines.push("");
6528
+ lines.push("## Overview");
6529
+ lines.push("");
6530
+ if (memFiles["project.md"]) {
6531
+ const content = memFiles["project.md"].replace(/^---[\s\S]*?---\n/m, "").trim();
6532
+ lines.push(content);
6533
+ } else {
6534
+ lines.push(`${projectName} is a ${caps.language ?? "software"} application.`);
6535
+ }
6536
+ lines.push("");
6537
+ lines.push("## Requirements");
6538
+ lines.push("");
6539
+ const pm = caps.packageManager ?? "npm";
6540
+ const lang = caps.language;
6541
+ if (lang === "typescript" || lang === "javascript") {
6542
+ lines.push("- **Node.js** \u2265 18");
6543
+ lines.push(`- **${pm}** (package manager)`);
6544
+ } else if (lang === "python") {
6545
+ lines.push("- **Python** \u2265 3.9");
6546
+ lines.push("- **pip** or **poetry**");
6547
+ } else if (lang === "go") {
6548
+ lines.push("- **Go** \u2265 1.21");
6549
+ }
6550
+ lines.push("");
6551
+ lines.push("## Installation");
6552
+ lines.push("");
6553
+ lines.push("```bash");
6554
+ lines.push("# Clone the repository");
6555
+ lines.push(`git clone <repo-url>`);
6556
+ lines.push(`cd ${projectName}`);
6557
+ lines.push("");
6558
+ if (lang === "typescript" || lang === "javascript") {
6559
+ lines.push(`${pm === "yarn" ? "yarn" : pm === "pnpm" ? "pnpm install" : "npm install"}`);
6560
+ } else if (lang === "python") {
6561
+ lines.push("pip install -r requirements.txt");
6562
+ } else if (lang === "go") {
6563
+ lines.push("go mod download");
6564
+ }
6565
+ lines.push("```");
6566
+ lines.push("");
6567
+ const entryPoints = caps.entryPoints;
6568
+ if (Array.isArray(entryPoints) && entryPoints.length > 0) {
6569
+ lines.push("## Getting Started");
6570
+ lines.push("");
6571
+ lines.push("```bash");
6572
+ for (const ep of entryPoints.slice(0, 3)) lines.push(`node ${ep}`);
6573
+ lines.push("```");
6574
+ lines.push("");
6575
+ }
6576
+ if (memFiles["stack.md"]) {
6577
+ lines.push("## Configuration");
6578
+ lines.push("");
6579
+ const content = memFiles["stack.md"].replace(/^---[\s\S]*?---\n/m, "").trim();
6580
+ lines.push(content);
6581
+ lines.push("");
6582
+ }
6583
+ if (commits.length > 0) {
6584
+ lines.push("## Changelog");
6585
+ lines.push("");
6586
+ lines.push("Recent changes:");
6587
+ lines.push("");
6588
+ for (const c of commits.slice(0, 5)) lines.push(`- ${c}`);
6589
+ lines.push("");
6590
+ lines.push("Run `fh changelog` for the full changelog.");
6591
+ lines.push("");
6592
+ }
6593
+ lines.push("## Support");
6594
+ lines.push("");
6595
+ lines.push("For issues, open a ticket in the project repository.");
6596
+ return lines.join("\n");
6597
+ }
6598
+ function generateApiReference(projectRoot2) {
6599
+ const srcDir = path31.join(projectRoot2, "src");
6600
+ const searchDir = fs30.existsSync(srcDir) ? srcDir : projectRoot2;
6601
+ const files = walkSourceFiles(searchDir);
6602
+ const lines = [];
6603
+ lines.push("# API Reference");
6604
+ lines.push("");
6605
+ lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`);
6606
+ lines.push("");
6607
+ if (files.length === 0) {
6608
+ lines.push("No source files found.");
6609
+ return lines.join("\n");
6610
+ }
6611
+ for (const filePath of files) {
6612
+ let content = "";
6613
+ try {
6614
+ content = fs30.readFileSync(filePath, "utf8");
6615
+ } catch {
6616
+ continue;
6617
+ }
6618
+ const exports = extractExports(content);
6619
+ if (exports.length === 0) continue;
6620
+ const relPath = path31.relative(projectRoot2, filePath);
6621
+ lines.push(`## \`${relPath}\``);
6622
+ lines.push("");
6623
+ lines.push("**Exports:**");
6624
+ lines.push("");
6625
+ for (const exp of exports) lines.push(`- \`${exp}\``);
6626
+ lines.push("");
6627
+ }
6628
+ return lines.join("\n");
6629
+ }
6630
+ function listExistingDocs(projectRoot2) {
6631
+ const docs = [];
6632
+ const docsDir = path31.join(projectRoot2, "docs");
6633
+ if (fs30.existsSync(docsDir)) {
6634
+ for (const f of fs30.readdirSync(docsDir)) {
6635
+ if (f.endsWith(".md")) docs.push(path31.join(docsDir, f));
6636
+ }
6637
+ }
6638
+ const rootDocs = ["README.md", "CHANGELOG.md", "ONBOARDING.md", "CONTRIBUTING.md"];
6639
+ for (const f of rootDocs) {
6640
+ const full = path31.join(projectRoot2, f);
6641
+ if (fs30.existsSync(full)) docs.push(full);
6642
+ }
6643
+ return docs;
6644
+ }
6645
+
6457
6646
  // src/cli.ts
6458
6647
  var [, , command, subcommand, ...rest] = process.argv;
6459
6648
  var projectRoot = process.cwd();
6460
- var forgehiveDir = path31.join(projectRoot, ".forgehive");
6649
+ var forgehiveDir = path32.join(projectRoot, ".forgehive");
6461
6650
  if (command === "--version" || command === "-v") {
6462
- console.log("0.7.1");
6651
+ console.log("0.7.3");
6652
+ process.exit(0);
6653
+ }
6654
+ if (command === "--help" || command === "-h" || command === "help") {
6655
+ console.log(`
6656
+ forgehive v0.7.3 \u2014 Context-aware AI development environment
6657
+
6658
+ USAGE
6659
+ fh <command> [subcommand] [options]
6660
+
6661
+ SETUP
6662
+ fh init Set up forgehive in the current project
6663
+ fh confirm Activate capabilities (draft \u2192 confirmed)
6664
+ fh rollback Remove forgehive from the project
6665
+ fh status Show current project state
6666
+ fh scan --update Re-scan project after changes
6667
+ fh scan --check Check if scan is still current
6668
+
6669
+ SECURITY
6670
+ fh security scan Secrets + SAST scan
6671
+ fh security deps CVE check (npm audit)
6672
+ fh security report [gdpr|soc2|hipaa] Compliance report
6673
+ fh security audit Show audit trail
6674
+ fh security permissions Show agent file permissions
6675
+
6676
+ CI
6677
+ fh ci Run CI health report
6678
+ fh ci --format json|markdown Output format
6679
+ fh ci --fail-on critical|high|any Failure threshold
6680
+ fh ci --init Generate GitHub Actions workflow
6681
+
6682
+ CODEBASE
6683
+ fh map Codebase structure map
6684
+ fh onboard [--output path] Generate ONBOARDING.md
6685
+ fh changelog [--since tag] Semantic changelog from git
6686
+ fh metrics [--since date] Developer productivity metrics
6687
+ fh docs List existing documentation
6688
+ fh docs user [--output path] Generate user-facing guide (docs/user-guide.md)
6689
+ fh docs api [--output path] Generate API reference from exports (docs/api.md)
6690
+ fh docs onboard Generate onboarding doc
6691
+ fh docs changelog [--since tag] Generate changelog
6692
+ fh docs adr "<title>" Create Architecture Decision Record
6693
+
6694
+ SPRINT PLANNING
6695
+ fh story create <title> [--epic EPC-N] [--points N]
6696
+ fh story list [--epic EPC-N]
6697
+ fh story show <US-N>
6698
+ fh story sprint <US-N> Mark story as in-sprint
6699
+ fh story done <US-N> [--points N]
6700
+ fh epic create <title> [--goal <text>]
6701
+ fh epic list
6702
+ fh epic show <EPC-N>
6703
+ fh velocity show Velocity history + rolling average
6704
+ fh velocity record <N> --committed N --delivered N
6705
+
6706
+ TEAM
6707
+ fh sync push|pull [--remote origin --branch forgehive-memory]
6708
+ fh run <issue-url> [--agent name] [--label label]
6709
+ fh memory show|clean|export|prune|snapshot
6710
+ fh memory adr list|"<title>"
6711
+
6712
+ AGENTS & MCP
6713
+ fh party [--set name|run|status|cleanup]
6714
+ fh wire <service> Configure MCP server
6715
+ fh mcp auth add|list|remove Manage credentials
6716
+ fh mcp search <query> Search MCP registry
6717
+ fh skills list|regen|pull <url>
6718
+
6719
+ COST
6720
+ fh cost [today|week|all]
6721
+ fh cost --limit N --alert N
6722
+
6723
+ fh --version Show version
6724
+ fh --help Show this help
6725
+ `);
6463
6726
  process.exit(0);
6464
6727
  }
6465
6728
  function loadClaudeMdBlock() {
6466
- const templatePath = path31.join(
6467
- path31.dirname(new URL(import.meta.url).pathname),
6729
+ const templatePath = path32.join(
6730
+ path32.dirname(new URL(import.meta.url).pathname),
6468
6731
  "..",
6469
6732
  "forgehive",
6470
6733
  "templates",
6471
6734
  "claude-md.block.md"
6472
6735
  );
6473
- if (!fs30.existsSync(templatePath)) return "## forgehive\n\nSee .forgehive/ for configuration.";
6474
- return fs30.readFileSync(templatePath, "utf8");
6736
+ if (!fs31.existsSync(templatePath)) return "## forgehive\n\nSee .forgehive/ for configuration.";
6737
+ return fs31.readFileSync(templatePath, "utf8");
6475
6738
  }
6476
6739
  if (command === "init") {
6740
+ const forgehiveDirExists = fs31.existsSync(forgehiveDir);
6741
+ if (forgehiveDirExists && !rest.includes("--force")) {
6742
+ console.log(`\u26A0 .forgehive/ existiert bereits in diesem Projekt.`);
6743
+ console.log(` Nutze 'fh init --force' um neu zu initialisieren (\xFCberschreibt capabilities.yaml).`);
6744
+ console.log(` Nutze 'fh scan --update' um nur den Scan zu aktualisieren.`);
6745
+ process.exit(0);
6746
+ }
6477
6747
  console.log("\u{1F50D} Analysiere Projekt...\n");
6478
6748
  const scanResult = scan(projectRoot);
6479
6749
  const tierCount = [1, 2, 3].map((t) => scanResult.signals.filter((s) => s.tier === t).length);
@@ -6485,9 +6755,9 @@ if (command === "init") {
6485
6755
  const block = loadClaudeMdBlock();
6486
6756
  writeForgehiveDir(projectRoot, scanResult, capMap, block);
6487
6757
  const hash = computeHash(projectRoot);
6488
- fs30.writeFileSync(path31.join(forgehiveDir, ".scan-hash"), hash, "utf8");
6489
- const runtimeDir = path31.join(
6490
- path31.dirname(new URL(import.meta.url).pathname),
6758
+ fs31.writeFileSync(path32.join(forgehiveDir, ".scan-hash"), hash, "utf8");
6759
+ const runtimeDir = path32.join(
6760
+ path32.dirname(new URL(import.meta.url).pathname),
6491
6761
  "..",
6492
6762
  "forgehive"
6493
6763
  );
@@ -6519,7 +6789,7 @@ if (command === "init") {
6519
6789
  process.exit(1);
6520
6790
  }
6521
6791
  } else if (command === "memory") {
6522
- if (!fs30.existsSync(forgehiveDir)) {
6792
+ if (!fs31.existsSync(forgehiveDir)) {
6523
6793
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
6524
6794
  process.exit(1);
6525
6795
  }
@@ -6528,7 +6798,7 @@ if (command === "init") {
6528
6798
  } else if (subcommand === "clean") {
6529
6799
  cleanMemory(forgehiveDir);
6530
6800
  } else if (subcommand === "export") {
6531
- const outputPath = rest[0] ?? path31.join(projectRoot, "forgehive-memory-export.md");
6801
+ const outputPath = rest[0] ?? path32.join(projectRoot, "forgehive-memory-export.md");
6532
6802
  try {
6533
6803
  exportMemory(forgehiveDir, outputPath);
6534
6804
  } catch (err) {
@@ -6576,7 +6846,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
6576
6846
  } else if (subcommand === "snapshot") {
6577
6847
  const snapAction = rest[0];
6578
6848
  if (snapAction === "export") {
6579
- const outPath = rest[1] ?? path31.join(
6849
+ const outPath = rest[1] ?? path32.join(
6580
6850
  projectRoot,
6581
6851
  `forgehive-snapshot-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.json`
6582
6852
  );
@@ -6616,11 +6886,11 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
6616
6886
  process.exit(1);
6617
6887
  }
6618
6888
  } else if (command === "scan" && subcommand === "--update") {
6619
- if (!fs30.existsSync(forgehiveDir)) {
6889
+ if (!fs31.existsSync(forgehiveDir)) {
6620
6890
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
6621
6891
  process.exit(1);
6622
6892
  }
6623
- const savedHash = fs30.existsSync(path31.join(forgehiveDir, ".scan-hash")) ? fs30.readFileSync(path31.join(forgehiveDir, ".scan-hash"), "utf8").trim() : null;
6893
+ const savedHash = fs31.existsSync(path32.join(forgehiveDir, ".scan-hash")) ? fs31.readFileSync(path32.join(forgehiveDir, ".scan-hash"), "utf8").trim() : null;
6624
6894
  const currentHash = computeHash(projectRoot);
6625
6895
  if (savedHash === currentHash) {
6626
6896
  console.log("\u2713 Keine \xC4nderungen erkannt \u2014 capabilities.yaml ist aktuell");
@@ -6628,7 +6898,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
6628
6898
  }
6629
6899
  console.log("\u{1F50D} \xC4nderungen erkannt \u2014 scanne erneut...\n");
6630
6900
  const oldDoc = jsYaml.load(
6631
- fs30.readFileSync(path31.join(forgehiveDir, "capabilities.yaml"), "utf8")
6901
+ fs31.readFileSync(path32.join(forgehiveDir, "capabilities.yaml"), "utf8")
6632
6902
  );
6633
6903
  const oldMap = { confirmed: oldDoc.capabilities.confirmed ?? [], inferred: [] };
6634
6904
  const scanResult = scan(projectRoot);
@@ -6648,16 +6918,16 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
6648
6918
  console.log();
6649
6919
  const block = loadClaudeMdBlock();
6650
6920
  writeForgehiveDir(projectRoot, scanResult, newMap, block);
6651
- fs30.writeFileSync(path31.join(forgehiveDir, ".scan-hash"), currentHash, "utf8");
6921
+ fs31.writeFileSync(path32.join(forgehiveDir, ".scan-hash"), currentHash, "utf8");
6652
6922
  console.log("\u2713 scan-result.yaml und capabilities.yaml aktualisiert");
6653
6923
  console.log(" F\xFChre `fh confirm` aus um die \xC4nderungen zu best\xE4tigen");
6654
6924
  }
6655
6925
  } else if (command === "scan" && subcommand === "--check") {
6656
- if (!fs30.existsSync(path31.join(forgehiveDir, ".scan-hash"))) {
6926
+ if (!fs31.existsSync(path32.join(forgehiveDir, ".scan-hash"))) {
6657
6927
  console.log("Warnung: Kein Scan-Hash gefunden. F\xFChre `fh init` aus.");
6658
6928
  process.exit(1);
6659
6929
  }
6660
- const saved = fs30.readFileSync(path31.join(forgehiveDir, ".scan-hash"), "utf8").trim();
6930
+ const saved = fs31.readFileSync(path32.join(forgehiveDir, ".scan-hash"), "utf8").trim();
6661
6931
  const current = computeHash(projectRoot);
6662
6932
  if (saved !== current) {
6663
6933
  console.log("\u26A0 Codebase hat sich seit letztem Scan ge\xE4ndert.");
@@ -6666,7 +6936,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
6666
6936
  }
6667
6937
  console.log("\u2713 capabilities.yaml ist aktuell");
6668
6938
  } else if (command === "skills") {
6669
- if (!fs30.existsSync(forgehiveDir)) {
6939
+ if (!fs31.existsSync(forgehiveDir)) {
6670
6940
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
6671
6941
  process.exit(1);
6672
6942
  }
@@ -6702,7 +6972,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
6702
6972
  process.exit(1);
6703
6973
  }
6704
6974
  } else if (command === "party") {
6705
- if (!fs30.existsSync(forgehiveDir)) {
6975
+ if (!fs31.existsSync(forgehiveDir)) {
6706
6976
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
6707
6977
  process.exit(1);
6708
6978
  }
@@ -6823,7 +7093,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
6823
7093
  }
6824
7094
  }
6825
7095
  } else if (command === "wire") {
6826
- if (!fs30.existsSync(forgehiveDir)) {
7096
+ if (!fs31.existsSync(forgehiveDir)) {
6827
7097
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
6828
7098
  process.exit(1);
6829
7099
  }
@@ -6862,7 +7132,7 @@ N\xE4chster Schritt: Setze die erforderlichen Umgebungsvariablen und starte Clau
6862
7132
  const limitIdx = allArgs.indexOf("--limit");
6863
7133
  const alertIdx = allArgs.indexOf("--alert");
6864
7134
  if (limitIdx !== -1) {
6865
- if (!fs30.existsSync(forgehiveDir)) {
7135
+ if (!fs31.existsSync(forgehiveDir)) {
6866
7136
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
6867
7137
  process.exit(1);
6868
7138
  }
@@ -6888,14 +7158,14 @@ N\xE4chster Schritt: Setze die erforderlichen Umgebungsvariablen und starte Clau
6888
7158
  }
6889
7159
  const sessions = parseCostSessions(projectRoot);
6890
7160
  console.log(formatCostReport(sessions, range));
6891
- if (fs30.existsSync(forgehiveDir)) {
7161
+ if (fs31.existsSync(forgehiveDir)) {
6892
7162
  const total = sessions.reduce((s, x) => s + x.estimatedCostUsd, 0);
6893
7163
  const status = checkSpendStatus(forgehiveDir, total);
6894
7164
  if (status.message) console.log("\n" + status.message);
6895
7165
  }
6896
7166
  }
6897
7167
  } else if (command === "watch") {
6898
- if (!fs30.existsSync(forgehiveDir)) {
7168
+ if (!fs31.existsSync(forgehiveDir)) {
6899
7169
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
6900
7170
  process.exit(1);
6901
7171
  }
@@ -6911,7 +7181,7 @@ N\xE4chster Schritt: Setze die erforderlichen Umgebungsvariablen und starte Clau
6911
7181
  process.exit(0);
6912
7182
  });
6913
7183
  } else if (command === "mcp") {
6914
- if (!fs30.existsSync(forgehiveDir)) {
7184
+ if (!fs31.existsSync(forgehiveDir)) {
6915
7185
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
6916
7186
  process.exit(1);
6917
7187
  }
@@ -7022,7 +7292,7 @@ Setze diese Umgebungsvariablen:`);
7022
7292
  process.exit(1);
7023
7293
  }
7024
7294
  } else if (command === "security") {
7025
- if (!fs30.existsSync(forgehiveDir)) {
7295
+ if (!fs31.existsSync(forgehiveDir)) {
7026
7296
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
7027
7297
  process.exit(1);
7028
7298
  }
@@ -7108,8 +7378,8 @@ Setze diese Umgebungsvariablen:`);
7108
7378
  `);
7109
7379
  const report = generateSecurityReport(projectRoot, forgehiveDir, mode);
7110
7380
  const text = formatSecurityReport(report);
7111
- const reportPath = path31.join(forgehiveDir, "security-report.md");
7112
- fs30.writeFileSync(reportPath, text, "utf8");
7381
+ const reportPath = path32.join(forgehiveDir, "security-report.md");
7382
+ fs31.writeFileSync(reportPath, text, "utf8");
7113
7383
  console.log(text);
7114
7384
  console.log(`
7115
7385
  \u2713 Report gespeichert: ${reportPath}`);
@@ -7121,8 +7391,8 @@ Setze diese Umgebungsvariablen:`);
7121
7391
  } else if (subcommand === "permissions") {
7122
7392
  const { writePermissions: writePermissions2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
7123
7393
  writePermissions2(forgehiveDir);
7124
- const permPath = path31.join(forgehiveDir, "harness", "permissions.yaml");
7125
- console.log(fs30.readFileSync(permPath, "utf8"));
7394
+ const permPath = path32.join(forgehiveDir, "harness", "permissions.yaml");
7395
+ console.log(fs31.readFileSync(permPath, "utf8"));
7126
7396
  } else {
7127
7397
  console.error(`Unbekannter security-Subcommand: ${subcommand}`);
7128
7398
  console.error("Verf\xFCgbar: scan | deps | audit | report [gdpr|soc2|hipaa|none] | permissions");
@@ -7134,35 +7404,35 @@ Setze diese Umgebungsvariablen:`);
7134
7404
  const failOnArg = allCiArgs.includes("--fail-on") ? allCiArgs[allCiArgs.indexOf("--fail-on") + 1] : "high";
7135
7405
  const initFlag = allCiArgs.includes("--init");
7136
7406
  if (initFlag) {
7137
- const ghDir = path31.join(projectRoot, ".github", "workflows");
7138
- fs30.mkdirSync(ghDir, { recursive: true });
7139
- const outPath = path31.join(ghDir, "forgehive.yml");
7140
- fs30.writeFileSync(outPath, getGithubActionsTemplate(), "utf8");
7407
+ const ghDir = path32.join(projectRoot, ".github", "workflows");
7408
+ fs31.mkdirSync(ghDir, { recursive: true });
7409
+ const outPath = path32.join(ghDir, "forgehive.yml");
7410
+ fs31.writeFileSync(outPath, getGithubActionsTemplate(), "utf8");
7141
7411
  console.log(`\u2714 GitHub Actions workflow geschrieben: ${outPath}`);
7142
7412
  } else {
7143
7413
  const report = generateCiReport(projectRoot, forgehiveDir, failOnArg);
7144
7414
  const output = formatCiReport(report, format);
7145
7415
  console.log(output);
7146
7416
  if (format === "json") {
7147
- fs30.mkdirSync(forgehiveDir, { recursive: true });
7148
- fs30.writeFileSync(path31.join(forgehiveDir, "ci-report.json"), output, "utf8");
7417
+ fs31.mkdirSync(forgehiveDir, { recursive: true });
7418
+ fs31.writeFileSync(path32.join(forgehiveDir, "ci-report.json"), output, "utf8");
7149
7419
  }
7150
7420
  if (report.status === "fail") process.exit(1);
7151
7421
  }
7152
7422
  } else if (command === "map") {
7153
7423
  const map2 = generateMap(projectRoot);
7154
7424
  const md = formatMap(map2);
7155
- const mapPath = path31.join(forgehiveDir, "map.md");
7156
- fs30.mkdirSync(forgehiveDir, { recursive: true });
7157
- fs30.writeFileSync(mapPath, md, "utf8");
7425
+ const mapPath = path32.join(forgehiveDir, "map.md");
7426
+ fs31.mkdirSync(forgehiveDir, { recursive: true });
7427
+ fs31.writeFileSync(mapPath, md, "utf8");
7158
7428
  console.log(md);
7159
7429
  console.log(`
7160
7430
  \u2714 Codebase-Map gespeichert: ${mapPath}`);
7161
7431
  } else if (command === "onboard") {
7162
7432
  const outputArg = rest.includes("--output") ? rest[rest.indexOf("--output") + 1] : null;
7163
- const outputPath = outputArg ?? path31.join(projectRoot, "ONBOARDING.md");
7433
+ const outputPath = outputArg ?? path32.join(projectRoot, "ONBOARDING.md");
7164
7434
  const doc = generateOnboardingDoc(projectRoot, forgehiveDir);
7165
- fs30.writeFileSync(outputPath, doc, "utf8");
7435
+ fs31.writeFileSync(outputPath, doc, "utf8");
7166
7436
  console.log(`\u2714 Onboarding-Dokument geschrieben: ${outputPath}`);
7167
7437
  } else if (command === "changelog") {
7168
7438
  const sinceArg = rest.includes("--since") ? rest[rest.indexOf("--since") + 1] : null;
@@ -7172,28 +7442,28 @@ Setze diese Umgebungsvariablen:`);
7172
7442
  const commits = parseGitLog(rawLog);
7173
7443
  let version = "unreleased";
7174
7444
  try {
7175
- const pkgPath = path31.join(projectRoot, "package.json");
7176
- if (fs30.existsSync(pkgPath)) {
7177
- const pkg = JSON.parse(fs30.readFileSync(pkgPath, "utf8").replace(/^\s*\/\/.*$/gm, ""));
7445
+ const pkgPath = path32.join(projectRoot, "package.json");
7446
+ if (fs31.existsSync(pkgPath)) {
7447
+ const pkg = JSON.parse(fs31.readFileSync(pkgPath, "utf8").replace(/^\s*\/\/.*$/gm, ""));
7178
7448
  version = pkg.version ?? "unreleased";
7179
7449
  }
7180
7450
  } catch {
7181
7451
  }
7182
7452
  const md = formatChangelog(commits, version);
7183
- const outputPath = outputArg ?? path31.join(projectRoot, "CHANGELOG.md");
7453
+ const outputPath = outputArg ?? path32.join(projectRoot, "CHANGELOG.md");
7184
7454
  let existing = "";
7185
- if (fs30.existsSync(outputPath)) existing = fs30.readFileSync(outputPath, "utf8");
7186
- fs30.writeFileSync(outputPath, md + "\n\n" + existing, "utf8");
7455
+ if (fs31.existsSync(outputPath)) existing = fs31.readFileSync(outputPath, "utf8");
7456
+ fs31.writeFileSync(outputPath, md + "\n\n" + existing, "utf8");
7187
7457
  console.log(`\u2714 CHANGELOG.md aktualisiert: ${outputPath}`);
7188
7458
  console.log(` ${commits.length} Commits verarbeitet`);
7189
7459
  } else if (command === "metrics") {
7190
7460
  const sinceArg = rest.includes("--since") ? rest[rest.indexOf("--since") + 1] : void 0;
7191
7461
  const rawLog = getMetricsGitLog(projectRoot, sinceArg);
7192
7462
  const stats = parseCommitStats(rawLog);
7193
- const md = formatMetrics(stats, path31.basename(projectRoot));
7194
- const metricsPath = path31.join(forgehiveDir, "metrics.md");
7195
- fs30.mkdirSync(forgehiveDir, { recursive: true });
7196
- fs30.writeFileSync(metricsPath, md, "utf8");
7463
+ const md = formatMetrics(stats, path32.basename(projectRoot));
7464
+ const metricsPath = path32.join(forgehiveDir, "metrics.md");
7465
+ fs31.mkdirSync(forgehiveDir, { recursive: true });
7466
+ fs31.writeFileSync(metricsPath, md, "utf8");
7197
7467
  console.log(md);
7198
7468
  console.log(`
7199
7469
  \u2714 Metrics gespeichert: ${metricsPath}`);
@@ -7228,7 +7498,7 @@ Setze diese Umgebungsvariablen:`);
7228
7498
  console.log(`\u2714 ${result.message}`);
7229
7499
  console.log(` fh run status \u2014 aktive Sessions anzeigen`);
7230
7500
  } else if (command === "story") {
7231
- const storiesDir = path31.join(forgehiveDir, "memory", "stories");
7501
+ const storiesDir = path32.join(forgehiveDir, "memory", "stories");
7232
7502
  if (subcommand === "create") {
7233
7503
  const title = rest.filter((r) => !r.startsWith("--")).join(" ");
7234
7504
  const epicArg = rest.includes("--epic") ? rest[rest.indexOf("--epic") + 1] : void 0;
@@ -7239,7 +7509,7 @@ Setze diese Umgebungsvariablen:`);
7239
7509
  }
7240
7510
  const story = createStory(storiesDir, title, epicArg);
7241
7511
  if (pointsArg) updateStoryPoints(storiesDir, story.id, pointsArg);
7242
- console.log(`\u2714 ${story.id} erstellt: ${path31.join(storiesDir, story.id + ".md")}`);
7512
+ console.log(`\u2714 ${story.id} erstellt: ${path32.join(storiesDir, story.id + ".md")}`);
7243
7513
  console.log(` Bearbeite die Datei um Acceptance Criteria hinzuzuf\xFCgen.`);
7244
7514
  } else if (subcommand === "list") {
7245
7515
  const epicFilter = rest.includes("--epic") ? rest[rest.indexOf("--epic") + 1] : null;
@@ -7264,6 +7534,14 @@ Setze diese Umgebungsvariablen:`);
7264
7534
  if (pointsArg) updateStoryPoints(storiesDir, id, pointsArg);
7265
7535
  updateStoryStatus(storiesDir, id, "done");
7266
7536
  console.log(`\u2714 ${id} als done markiert`);
7537
+ } else if (subcommand === "sprint") {
7538
+ const id = rest[0];
7539
+ if (!id) {
7540
+ console.error("Usage: fh story sprint <US-N>");
7541
+ process.exit(1);
7542
+ }
7543
+ updateStoryStatus(storiesDir, id, "in-sprint");
7544
+ console.log(`\u2714 ${id} in Sprint gezogen`);
7267
7545
  } else if (subcommand === "show") {
7268
7546
  const id = rest[0];
7269
7547
  if (!id) {
@@ -7280,8 +7558,8 @@ Setze diese Umgebungsvariablen:`);
7280
7558
  console.error("Verf\xFCgbar: fh story create | list | show | done");
7281
7559
  }
7282
7560
  } else if (command === "epic") {
7283
- const epicsDir = path31.join(forgehiveDir, "memory", "epics");
7284
- const storiesDir = path31.join(forgehiveDir, "memory", "stories");
7561
+ const epicsDir = path32.join(forgehiveDir, "memory", "epics");
7562
+ const storiesDir = path32.join(forgehiveDir, "memory", "stories");
7285
7563
  if (subcommand === "create") {
7286
7564
  const title = rest.filter((r) => !r.startsWith("--")).join(" ");
7287
7565
  const goalArg = rest.includes("--goal") ? rest[rest.indexOf("--goal") + 1] : void 0;
@@ -7290,7 +7568,7 @@ Setze diese Umgebungsvariablen:`);
7290
7568
  process.exit(1);
7291
7569
  }
7292
7570
  const epic = createEpic(epicsDir, title, goalArg);
7293
- console.log(`\u2714 ${epic.id} erstellt: ${path31.join(epicsDir, epic.id + ".md")}`);
7571
+ console.log(`\u2714 ${epic.id} erstellt: ${path32.join(epicsDir, epic.id + ".md")}`);
7294
7572
  } else if (subcommand === "list") {
7295
7573
  const epics = listEpics(epicsDir);
7296
7574
  if (epics.length === 0) {
@@ -7316,7 +7594,7 @@ Setze diese Umgebungsvariablen:`);
7316
7594
  console.error("Verf\xFCgbar: fh epic create | list | show");
7317
7595
  }
7318
7596
  } else if (command === "velocity") {
7319
- const velocityFile = path31.join(forgehiveDir, "memory", "velocity.md");
7597
+ const velocityFile = path32.join(forgehiveDir, "memory", "velocity.md");
7320
7598
  if (subcommand === "record") {
7321
7599
  const sprintNum = parseInt(rest[0] ?? "0", 10);
7322
7600
  const committed = rest.includes("--committed") ? parseInt(rest[rest.indexOf("--committed") + 1], 10) : NaN;
@@ -7328,14 +7606,107 @@ Setze diese Umgebungsvariablen:`);
7328
7606
  recordVelocity(velocityFile, sprintNum, committed, delivered);
7329
7607
  const avg = getRollingAverage(getVelocityHistory(velocityFile));
7330
7608
  console.log(`\u2714 Sprint ${sprintNum} gespeichert. Rolling Average: ${avg} Punkte`);
7331
- } else {
7609
+ } else if (!subcommand || subcommand === "show") {
7332
7610
  const history = getVelocityHistory(velocityFile);
7333
7611
  console.log(formatVelocityReport(history));
7612
+ } else {
7613
+ console.error("Verf\xFCgbar: fh velocity show | record <N> --committed N --delivered N");
7614
+ }
7615
+ } else if (command === "docs") {
7616
+ const docsDir = path32.join(projectRoot, "docs");
7617
+ if (!subcommand || subcommand === "list") {
7618
+ const existing = listExistingDocs(projectRoot);
7619
+ if (existing.length === 0) {
7620
+ console.log("Keine Dokumentation gefunden.");
7621
+ console.log("");
7622
+ console.log("Erstelle Docs:");
7623
+ console.log(" fh docs user \u2014 User Guide (docs/user-guide.md)");
7624
+ console.log(" fh docs api \u2014 API-Referenz (docs/api.md)");
7625
+ console.log(" fh docs onboard \u2014 Onboarding (ONBOARDING.md)");
7626
+ console.log(" fh docs changelog \u2014 Changelog (CHANGELOG.md)");
7627
+ console.log(" fh docs adr <titel> \u2014 Architecture Decision Record");
7628
+ } else {
7629
+ console.log(`Vorhandene Dokumentation (${existing.length} Dateien):`);
7630
+ for (const d of existing) console.log(` ${path32.relative(projectRoot, d)}`);
7631
+ console.log("");
7632
+ console.log("Aktualisieren: fh docs user | api | onboard | changelog");
7633
+ }
7634
+ } else if (subcommand === "user") {
7635
+ fs31.mkdirSync(docsDir, { recursive: true });
7636
+ const outputArg = rest.includes("--output") ? rest[rest.indexOf("--output") + 1] : null;
7637
+ const outputPath = outputArg ?? path32.join(docsDir, "user-guide.md");
7638
+ const guide = generateUserGuide(projectRoot, forgehiveDir);
7639
+ fs31.writeFileSync(outputPath, guide, "utf8");
7640
+ console.log(`\u2714 User Guide geschrieben: ${outputPath}`);
7641
+ } else if (subcommand === "api") {
7642
+ fs31.mkdirSync(docsDir, { recursive: true });
7643
+ const outputArg = rest.includes("--output") ? rest[rest.indexOf("--output") + 1] : null;
7644
+ const outputPath = outputArg ?? path32.join(docsDir, "api.md");
7645
+ const ref = generateApiReference(projectRoot);
7646
+ fs31.writeFileSync(outputPath, ref, "utf8");
7647
+ console.log(`\u2714 API-Referenz geschrieben: ${outputPath}`);
7648
+ } else if (subcommand === "onboard") {
7649
+ const outputArg = rest.includes("--output") ? rest[rest.indexOf("--output") + 1] : null;
7650
+ const outputPath = outputArg ?? path32.join(projectRoot, "ONBOARDING.md");
7651
+ const doc = generateOnboardingDoc(projectRoot, forgehiveDir);
7652
+ fs31.writeFileSync(outputPath, doc, "utf8");
7653
+ console.log(`\u2714 Onboarding-Dokument geschrieben: ${outputPath}`);
7654
+ } else if (subcommand === "changelog") {
7655
+ const sinceArg = rest.includes("--since") ? rest[rest.indexOf("--since") + 1] : null;
7656
+ const outputArg = rest.includes("--output") ? rest[rest.indexOf("--output") + 1] : null;
7657
+ const since = sinceArg ?? getLatestTag(projectRoot) ?? void 0;
7658
+ const rawLog = getGitLogSince(projectRoot, since);
7659
+ const commits = parseGitLog(rawLog);
7660
+ let pkg = {};
7661
+ try {
7662
+ pkg = JSON.parse(fs31.readFileSync(path32.join(projectRoot, "package.json"), "utf8"));
7663
+ } catch {
7664
+ }
7665
+ const version = pkg.version ?? "unreleased";
7666
+ const md = formatChangelog(commits, version);
7667
+ const outputPath = outputArg ?? path32.join(projectRoot, "CHANGELOG.md");
7668
+ let existing = "";
7669
+ if (fs31.existsSync(outputPath)) existing = fs31.readFileSync(outputPath, "utf8");
7670
+ fs31.writeFileSync(outputPath, md + "\n\n" + existing, "utf8");
7671
+ console.log(`\u2714 CHANGELOG.md aktualisiert (${commits.length} Commits)`);
7672
+ } else if (subcommand === "adr") {
7673
+ const title = rest.join(" ");
7674
+ if (!title) {
7675
+ console.error("Usage: fh docs adr <titel>");
7676
+ process.exit(1);
7677
+ }
7678
+ const adrsDir = path32.join(forgehiveDir, "memory", "adrs");
7679
+ fs31.mkdirSync(adrsDir, { recursive: true });
7680
+ const existing = fs31.existsSync(adrsDir) ? fs31.readdirSync(adrsDir).filter((f) => f.endsWith(".md")).length : 0;
7681
+ const adrId = String(existing + 1).padStart(4, "0");
7682
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
7683
+ const filename = `${adrId}-${slug}.md`;
7684
+ const content = `# ADR-${adrId}: ${title}
7685
+
7686
+ **Datum:** ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
7687
+ **Status:** proposed
7688
+
7689
+ ## Kontext
7690
+
7691
+ (Beschreibe das Problem oder die Situation.)
7692
+
7693
+ ## Entscheidung
7694
+
7695
+ (Beschreibe die getroffene Entscheidung.)
7696
+
7697
+ ## Konsequenzen
7698
+
7699
+ (Beschreibe die Auswirkungen dieser Entscheidung.)
7700
+ `;
7701
+ fs31.writeFileSync(path32.join(adrsDir, filename), content, "utf8");
7702
+ console.log(`\u2714 ADR erstellt: .forgehive/memory/adrs/${filename}`);
7703
+ } else {
7704
+ console.error("Verf\xFCgbar: fh docs [list|user|api|onboard|changelog|adr <titel>]");
7334
7705
  }
7335
7706
  } else {
7336
- const cmd = [command, subcommand].filter(Boolean).join(" ") || "(kein)";
7337
- console.error(`Unbekannter Befehl: ${cmd}`);
7338
- console.error("Verf\xFCgbar: init | confirm | rollback | scan --update | scan --check | status | ci [--format json|markdown] [--fail-on critical|high|any] [--init] | map | onboard [--output path] | changelog [--since tag] | metrics [--since date] | sync [push|pull] [--remote origin --branch forgehive-memory] | run <issue-url> [--agent <name>] | cost [today|week|all] | cost --limit N --alert N | memory [show|clean|export|prune|snapshot] | memory adr [list|<titel>] | skills [list|regen|pull <url>] | party [--set <name>|run|status|cleanup] | wire <service> | mcp [auth|search|add] | security [scan|deps|report|audit|permissions]");
7707
+ console.error("Unbekannter Befehl: " + command);
7708
+ console.error("Verf\xFCgbar: init | confirm | rollback | scan | status | ci | map | onboard | changelog | metrics | docs | story [create|list|show|sprint|done] | epic [create|list|show] | velocity [show|record] | sync [push|pull] | run <issue-url> | cost | memory | skills | party | wire | mcp | security");
7709
+ console.error("Hilfe: fh --help");
7339
7710
  process.exit(1);
7340
7711
  }
7341
7712
  /*! Bundled license information:
@@ -2,6 +2,21 @@ You are Eli — Technical Writer. Your job is to write or update documentation.
2
2
 
3
3
  ## Documentation Protocol
4
4
 
5
+ ## Quick CLI Commands
6
+
7
+ Before using Claude interactively, check if these CLI commands cover your need:
8
+
9
+ ```bash
10
+ fh docs user # generate user-facing guide → docs/user-guide.md
11
+ fh docs api # generate API reference → docs/api.md
12
+ fh docs onboard # generate onboarding doc → ONBOARDING.md
13
+ fh docs changelog # update CHANGELOG.md from git
14
+ fh docs adr "<title>" # create Architecture Decision Record
15
+ fh docs # list all existing documentation
16
+ ```
17
+
18
+ For interactive generation with editing, continue below.
19
+
5
20
  Ask the user: **"Was soll ich dokumentieren?"**
6
21
 
7
22
  Options:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgehive",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "Context-aware AI development environment — one binary, your stack.",
5
5
  "type": "module",
6
6
  "bin": {