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 +268 -34
- package/dist/cli.js +434 -63
- package/forgehive/commands/fh-docs.md +15 -0
- package/package.json +1 -1
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-
|
|
18
|
-
<img src="https://img.shields.io/badge/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:
|
|
48
|
+
## Status: v0.7 — Stable
|
|
44
49
|
|
|
45
|
-
|
|
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 (~
|
|
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.
|
|
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
|
|
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
|
-
**
|
|
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
|
|
426
|
-
scan-result.yaml
|
|
427
|
-
.scan-hash
|
|
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
|
|
430
|
-
constraints.yaml
|
|
431
|
-
permissions.yaml
|
|
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
|
|
434
|
-
project.md
|
|
435
|
-
feedback.md
|
|
436
|
-
stack.md
|
|
437
|
-
adrs/
|
|
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/
|
|
440
|
-
expert/
|
|
441
|
-
workflows/
|
|
442
|
-
worktrees/
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
|
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
|
-
#
|
|
559
|
-
#
|
|
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
|
|
850
|
+
| Build | esbuild -> `dist/cli.js` (~247 KB, single bundle) |
|
|
617
851
|
| Type check | `tsc --noEmit` |
|
|
618
|
-
| Tests | `node:test` (native) + tsx ESM loader,
|
|
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
|
|
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
|
|
2755
|
-
import
|
|
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 =
|
|
6649
|
+
var forgehiveDir = path32.join(projectRoot, ".forgehive");
|
|
6461
6650
|
if (command === "--version" || command === "-v") {
|
|
6462
|
-
console.log("0.7.
|
|
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 =
|
|
6467
|
-
|
|
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 (!
|
|
6474
|
-
return
|
|
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
|
-
|
|
6489
|
-
const runtimeDir =
|
|
6490
|
-
|
|
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 (!
|
|
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] ??
|
|
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] ??
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
7112
|
-
|
|
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 =
|
|
7125
|
-
console.log(
|
|
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 =
|
|
7138
|
-
|
|
7139
|
-
const outPath =
|
|
7140
|
-
|
|
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
|
-
|
|
7148
|
-
|
|
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 =
|
|
7156
|
-
|
|
7157
|
-
|
|
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 ??
|
|
7433
|
+
const outputPath = outputArg ?? path32.join(projectRoot, "ONBOARDING.md");
|
|
7164
7434
|
const doc = generateOnboardingDoc(projectRoot, forgehiveDir);
|
|
7165
|
-
|
|
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 =
|
|
7176
|
-
if (
|
|
7177
|
-
const pkg = JSON.parse(
|
|
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 ??
|
|
7453
|
+
const outputPath = outputArg ?? path32.join(projectRoot, "CHANGELOG.md");
|
|
7184
7454
|
let existing = "";
|
|
7185
|
-
if (
|
|
7186
|
-
|
|
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,
|
|
7194
|
-
const metricsPath =
|
|
7195
|
-
|
|
7196
|
-
|
|
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 =
|
|
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: ${
|
|
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 =
|
|
7284
|
-
const storiesDir =
|
|
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: ${
|
|
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 =
|
|
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
|
-
|
|
7337
|
-
console.error(
|
|
7338
|
-
console.error("
|
|
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:
|