forgehive 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -6
- package/dist/cli.js +348 -62
- package/forgehive/commands/design-party.md +69 -0
- package/forgehive/commands/fh-docs.md +15 -0
- package/forgehive/commands/full-party.md +88 -0
- package/forgehive/commands/party.md +64 -0
- package/forgehive/commands/review-party.md +72 -0
- package/forgehive/commands/security-party.md +78 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
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
17
|
<img src="https://img.shields.io/badge/tests-267%20passing-success" alt="267 tests">
|
|
18
|
-
<img src="https://img.shields.io/badge/bundle-
|
|
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
|
|
|
@@ -109,14 +109,14 @@ This makes both `fh` and `forgehive` available globally.
|
|
|
109
109
|
git clone https://github.com/matharnica/forgehive
|
|
110
110
|
cd forgehive
|
|
111
111
|
npm install
|
|
112
|
-
npm run build # compiles to dist/cli.js (~
|
|
112
|
+
npm run build # compiles to dist/cli.js (~247 KB)
|
|
113
113
|
npm link # makes 'fh' available globally
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
Verify the installation:
|
|
117
117
|
|
|
118
118
|
```bash
|
|
119
|
-
fh --version # should print 0.7.
|
|
119
|
+
fh --version # should print 0.7.2
|
|
120
120
|
fh --help # lists all available commands
|
|
121
121
|
```
|
|
122
122
|
|
|
@@ -166,11 +166,15 @@ fh security report gdpr # generates a CISO-ready compliance report
|
|
|
166
166
|
|
|
167
167
|
| Command | Description |
|
|
168
168
|
|---|---|
|
|
169
|
-
| `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 |
|
|
170
172
|
| `fh confirm` | Confirm `capabilities.yaml` — activates context for Claude |
|
|
171
173
|
| `fh rollback` | Remove `.forgehive/` and the CLAUDE.md block cleanly |
|
|
172
174
|
| `fh status` | Show current project status, drift warning if scan is outdated |
|
|
173
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
|
+
|
|
174
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/`.
|
|
175
179
|
|
|
176
180
|
---
|
|
@@ -407,11 +411,12 @@ fh story create "As a user I want to log in" --epic EPC-1 --points 3 # with ep
|
|
|
407
411
|
fh story list # list all backlog stories
|
|
408
412
|
fh story list --epic EPC-1 # filter by epic
|
|
409
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)
|
|
410
415
|
fh story done US-1 # mark story as done
|
|
411
416
|
fh story done US-1 --points 5 # mark done and record actual points
|
|
412
417
|
```
|
|
413
418
|
|
|
414
|
-
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` | `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.
|
|
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.
|
|
415
420
|
|
|
416
421
|
---
|
|
417
422
|
|
|
@@ -842,7 +847,7 @@ Global credential store (chmod 600). Managed exclusively via `fh mcp auth` comma
|
|
|
842
847
|
|---|---|
|
|
843
848
|
| Runtime | Node.js ≥ 18, ESM |
|
|
844
849
|
| Language | TypeScript |
|
|
845
|
-
| Build | esbuild -> `dist/cli.js` (~
|
|
850
|
+
| Build | esbuild -> `dist/cli.js` (~247 KB, single bundle) |
|
|
846
851
|
| Type check | `tsc --noEmit` |
|
|
847
852
|
| Tests | `node:test` (native) + tsx ESM loader, 267 tests |
|
|
848
853
|
| Dependencies | `js-yaml` (sole runtime dependency) |
|
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,17 +6454,206 @@ 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");
|
|
6463
6652
|
process.exit(0);
|
|
6464
6653
|
}
|
|
6465
6654
|
if (command === "--help" || command === "-h" || command === "help") {
|
|
6466
6655
|
console.log(`
|
|
6467
|
-
forgehive v0.7.
|
|
6656
|
+
forgehive v0.7.3 \u2014 Context-aware AI development environment
|
|
6468
6657
|
|
|
6469
6658
|
USAGE
|
|
6470
6659
|
fh <command> [subcommand] [options]
|
|
@@ -6495,6 +6684,12 @@ CODEBASE
|
|
|
6495
6684
|
fh onboard [--output path] Generate ONBOARDING.md
|
|
6496
6685
|
fh changelog [--since tag] Semantic changelog from git
|
|
6497
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
|
|
6498
6693
|
|
|
6499
6694
|
SPRINT PLANNING
|
|
6500
6695
|
fh story create <title> [--epic EPC-N] [--points N]
|
|
@@ -6531,18 +6726,18 @@ COST
|
|
|
6531
6726
|
process.exit(0);
|
|
6532
6727
|
}
|
|
6533
6728
|
function loadClaudeMdBlock() {
|
|
6534
|
-
const templatePath =
|
|
6535
|
-
|
|
6729
|
+
const templatePath = path32.join(
|
|
6730
|
+
path32.dirname(new URL(import.meta.url).pathname),
|
|
6536
6731
|
"..",
|
|
6537
6732
|
"forgehive",
|
|
6538
6733
|
"templates",
|
|
6539
6734
|
"claude-md.block.md"
|
|
6540
6735
|
);
|
|
6541
|
-
if (!
|
|
6542
|
-
return
|
|
6736
|
+
if (!fs31.existsSync(templatePath)) return "## forgehive\n\nSee .forgehive/ for configuration.";
|
|
6737
|
+
return fs31.readFileSync(templatePath, "utf8");
|
|
6543
6738
|
}
|
|
6544
6739
|
if (command === "init") {
|
|
6545
|
-
const forgehiveDirExists =
|
|
6740
|
+
const forgehiveDirExists = fs31.existsSync(forgehiveDir);
|
|
6546
6741
|
if (forgehiveDirExists && !rest.includes("--force")) {
|
|
6547
6742
|
console.log(`\u26A0 .forgehive/ existiert bereits in diesem Projekt.`);
|
|
6548
6743
|
console.log(` Nutze 'fh init --force' um neu zu initialisieren (\xFCberschreibt capabilities.yaml).`);
|
|
@@ -6560,9 +6755,9 @@ if (command === "init") {
|
|
|
6560
6755
|
const block = loadClaudeMdBlock();
|
|
6561
6756
|
writeForgehiveDir(projectRoot, scanResult, capMap, block);
|
|
6562
6757
|
const hash = computeHash(projectRoot);
|
|
6563
|
-
|
|
6564
|
-
const runtimeDir =
|
|
6565
|
-
|
|
6758
|
+
fs31.writeFileSync(path32.join(forgehiveDir, ".scan-hash"), hash, "utf8");
|
|
6759
|
+
const runtimeDir = path32.join(
|
|
6760
|
+
path32.dirname(new URL(import.meta.url).pathname),
|
|
6566
6761
|
"..",
|
|
6567
6762
|
"forgehive"
|
|
6568
6763
|
);
|
|
@@ -6594,7 +6789,7 @@ if (command === "init") {
|
|
|
6594
6789
|
process.exit(1);
|
|
6595
6790
|
}
|
|
6596
6791
|
} else if (command === "memory") {
|
|
6597
|
-
if (!
|
|
6792
|
+
if (!fs31.existsSync(forgehiveDir)) {
|
|
6598
6793
|
console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
|
|
6599
6794
|
process.exit(1);
|
|
6600
6795
|
}
|
|
@@ -6603,7 +6798,7 @@ if (command === "init") {
|
|
|
6603
6798
|
} else if (subcommand === "clean") {
|
|
6604
6799
|
cleanMemory(forgehiveDir);
|
|
6605
6800
|
} else if (subcommand === "export") {
|
|
6606
|
-
const outputPath = rest[0] ??
|
|
6801
|
+
const outputPath = rest[0] ?? path32.join(projectRoot, "forgehive-memory-export.md");
|
|
6607
6802
|
try {
|
|
6608
6803
|
exportMemory(forgehiveDir, outputPath);
|
|
6609
6804
|
} catch (err) {
|
|
@@ -6651,7 +6846,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
|
|
|
6651
6846
|
} else if (subcommand === "snapshot") {
|
|
6652
6847
|
const snapAction = rest[0];
|
|
6653
6848
|
if (snapAction === "export") {
|
|
6654
|
-
const outPath = rest[1] ??
|
|
6849
|
+
const outPath = rest[1] ?? path32.join(
|
|
6655
6850
|
projectRoot,
|
|
6656
6851
|
`forgehive-snapshot-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.json`
|
|
6657
6852
|
);
|
|
@@ -6691,11 +6886,11 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
|
|
|
6691
6886
|
process.exit(1);
|
|
6692
6887
|
}
|
|
6693
6888
|
} else if (command === "scan" && subcommand === "--update") {
|
|
6694
|
-
if (!
|
|
6889
|
+
if (!fs31.existsSync(forgehiveDir)) {
|
|
6695
6890
|
console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
|
|
6696
6891
|
process.exit(1);
|
|
6697
6892
|
}
|
|
6698
|
-
const savedHash =
|
|
6893
|
+
const savedHash = fs31.existsSync(path32.join(forgehiveDir, ".scan-hash")) ? fs31.readFileSync(path32.join(forgehiveDir, ".scan-hash"), "utf8").trim() : null;
|
|
6699
6894
|
const currentHash = computeHash(projectRoot);
|
|
6700
6895
|
if (savedHash === currentHash) {
|
|
6701
6896
|
console.log("\u2713 Keine \xC4nderungen erkannt \u2014 capabilities.yaml ist aktuell");
|
|
@@ -6703,7 +6898,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
|
|
|
6703
6898
|
}
|
|
6704
6899
|
console.log("\u{1F50D} \xC4nderungen erkannt \u2014 scanne erneut...\n");
|
|
6705
6900
|
const oldDoc = jsYaml.load(
|
|
6706
|
-
|
|
6901
|
+
fs31.readFileSync(path32.join(forgehiveDir, "capabilities.yaml"), "utf8")
|
|
6707
6902
|
);
|
|
6708
6903
|
const oldMap = { confirmed: oldDoc.capabilities.confirmed ?? [], inferred: [] };
|
|
6709
6904
|
const scanResult = scan(projectRoot);
|
|
@@ -6723,16 +6918,16 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
|
|
|
6723
6918
|
console.log();
|
|
6724
6919
|
const block = loadClaudeMdBlock();
|
|
6725
6920
|
writeForgehiveDir(projectRoot, scanResult, newMap, block);
|
|
6726
|
-
|
|
6921
|
+
fs31.writeFileSync(path32.join(forgehiveDir, ".scan-hash"), currentHash, "utf8");
|
|
6727
6922
|
console.log("\u2713 scan-result.yaml und capabilities.yaml aktualisiert");
|
|
6728
6923
|
console.log(" F\xFChre `fh confirm` aus um die \xC4nderungen zu best\xE4tigen");
|
|
6729
6924
|
}
|
|
6730
6925
|
} else if (command === "scan" && subcommand === "--check") {
|
|
6731
|
-
if (!
|
|
6926
|
+
if (!fs31.existsSync(path32.join(forgehiveDir, ".scan-hash"))) {
|
|
6732
6927
|
console.log("Warnung: Kein Scan-Hash gefunden. F\xFChre `fh init` aus.");
|
|
6733
6928
|
process.exit(1);
|
|
6734
6929
|
}
|
|
6735
|
-
const saved =
|
|
6930
|
+
const saved = fs31.readFileSync(path32.join(forgehiveDir, ".scan-hash"), "utf8").trim();
|
|
6736
6931
|
const current = computeHash(projectRoot);
|
|
6737
6932
|
if (saved !== current) {
|
|
6738
6933
|
console.log("\u26A0 Codebase hat sich seit letztem Scan ge\xE4ndert.");
|
|
@@ -6741,7 +6936,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
|
|
|
6741
6936
|
}
|
|
6742
6937
|
console.log("\u2713 capabilities.yaml ist aktuell");
|
|
6743
6938
|
} else if (command === "skills") {
|
|
6744
|
-
if (!
|
|
6939
|
+
if (!fs31.existsSync(forgehiveDir)) {
|
|
6745
6940
|
console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
|
|
6746
6941
|
process.exit(1);
|
|
6747
6942
|
}
|
|
@@ -6777,7 +6972,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
|
|
|
6777
6972
|
process.exit(1);
|
|
6778
6973
|
}
|
|
6779
6974
|
} else if (command === "party") {
|
|
6780
|
-
if (!
|
|
6975
|
+
if (!fs31.existsSync(forgehiveDir)) {
|
|
6781
6976
|
console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
|
|
6782
6977
|
process.exit(1);
|
|
6783
6978
|
}
|
|
@@ -6898,7 +7093,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
|
|
|
6898
7093
|
}
|
|
6899
7094
|
}
|
|
6900
7095
|
} else if (command === "wire") {
|
|
6901
|
-
if (!
|
|
7096
|
+
if (!fs31.existsSync(forgehiveDir)) {
|
|
6902
7097
|
console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
|
|
6903
7098
|
process.exit(1);
|
|
6904
7099
|
}
|
|
@@ -6937,7 +7132,7 @@ N\xE4chster Schritt: Setze die erforderlichen Umgebungsvariablen und starte Clau
|
|
|
6937
7132
|
const limitIdx = allArgs.indexOf("--limit");
|
|
6938
7133
|
const alertIdx = allArgs.indexOf("--alert");
|
|
6939
7134
|
if (limitIdx !== -1) {
|
|
6940
|
-
if (!
|
|
7135
|
+
if (!fs31.existsSync(forgehiveDir)) {
|
|
6941
7136
|
console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
|
|
6942
7137
|
process.exit(1);
|
|
6943
7138
|
}
|
|
@@ -6963,14 +7158,14 @@ N\xE4chster Schritt: Setze die erforderlichen Umgebungsvariablen und starte Clau
|
|
|
6963
7158
|
}
|
|
6964
7159
|
const sessions = parseCostSessions(projectRoot);
|
|
6965
7160
|
console.log(formatCostReport(sessions, range));
|
|
6966
|
-
if (
|
|
7161
|
+
if (fs31.existsSync(forgehiveDir)) {
|
|
6967
7162
|
const total = sessions.reduce((s, x) => s + x.estimatedCostUsd, 0);
|
|
6968
7163
|
const status = checkSpendStatus(forgehiveDir, total);
|
|
6969
7164
|
if (status.message) console.log("\n" + status.message);
|
|
6970
7165
|
}
|
|
6971
7166
|
}
|
|
6972
7167
|
} else if (command === "watch") {
|
|
6973
|
-
if (!
|
|
7168
|
+
if (!fs31.existsSync(forgehiveDir)) {
|
|
6974
7169
|
console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
|
|
6975
7170
|
process.exit(1);
|
|
6976
7171
|
}
|
|
@@ -6986,7 +7181,7 @@ N\xE4chster Schritt: Setze die erforderlichen Umgebungsvariablen und starte Clau
|
|
|
6986
7181
|
process.exit(0);
|
|
6987
7182
|
});
|
|
6988
7183
|
} else if (command === "mcp") {
|
|
6989
|
-
if (!
|
|
7184
|
+
if (!fs31.existsSync(forgehiveDir)) {
|
|
6990
7185
|
console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
|
|
6991
7186
|
process.exit(1);
|
|
6992
7187
|
}
|
|
@@ -7097,7 +7292,7 @@ Setze diese Umgebungsvariablen:`);
|
|
|
7097
7292
|
process.exit(1);
|
|
7098
7293
|
}
|
|
7099
7294
|
} else if (command === "security") {
|
|
7100
|
-
if (!
|
|
7295
|
+
if (!fs31.existsSync(forgehiveDir)) {
|
|
7101
7296
|
console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
|
|
7102
7297
|
process.exit(1);
|
|
7103
7298
|
}
|
|
@@ -7183,8 +7378,8 @@ Setze diese Umgebungsvariablen:`);
|
|
|
7183
7378
|
`);
|
|
7184
7379
|
const report = generateSecurityReport(projectRoot, forgehiveDir, mode);
|
|
7185
7380
|
const text = formatSecurityReport(report);
|
|
7186
|
-
const reportPath =
|
|
7187
|
-
|
|
7381
|
+
const reportPath = path32.join(forgehiveDir, "security-report.md");
|
|
7382
|
+
fs31.writeFileSync(reportPath, text, "utf8");
|
|
7188
7383
|
console.log(text);
|
|
7189
7384
|
console.log(`
|
|
7190
7385
|
\u2713 Report gespeichert: ${reportPath}`);
|
|
@@ -7196,8 +7391,8 @@ Setze diese Umgebungsvariablen:`);
|
|
|
7196
7391
|
} else if (subcommand === "permissions") {
|
|
7197
7392
|
const { writePermissions: writePermissions2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
|
|
7198
7393
|
writePermissions2(forgehiveDir);
|
|
7199
|
-
const permPath =
|
|
7200
|
-
console.log(
|
|
7394
|
+
const permPath = path32.join(forgehiveDir, "harness", "permissions.yaml");
|
|
7395
|
+
console.log(fs31.readFileSync(permPath, "utf8"));
|
|
7201
7396
|
} else {
|
|
7202
7397
|
console.error(`Unbekannter security-Subcommand: ${subcommand}`);
|
|
7203
7398
|
console.error("Verf\xFCgbar: scan | deps | audit | report [gdpr|soc2|hipaa|none] | permissions");
|
|
@@ -7209,35 +7404,35 @@ Setze diese Umgebungsvariablen:`);
|
|
|
7209
7404
|
const failOnArg = allCiArgs.includes("--fail-on") ? allCiArgs[allCiArgs.indexOf("--fail-on") + 1] : "high";
|
|
7210
7405
|
const initFlag = allCiArgs.includes("--init");
|
|
7211
7406
|
if (initFlag) {
|
|
7212
|
-
const ghDir =
|
|
7213
|
-
|
|
7214
|
-
const outPath =
|
|
7215
|
-
|
|
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");
|
|
7216
7411
|
console.log(`\u2714 GitHub Actions workflow geschrieben: ${outPath}`);
|
|
7217
7412
|
} else {
|
|
7218
7413
|
const report = generateCiReport(projectRoot, forgehiveDir, failOnArg);
|
|
7219
7414
|
const output = formatCiReport(report, format);
|
|
7220
7415
|
console.log(output);
|
|
7221
7416
|
if (format === "json") {
|
|
7222
|
-
|
|
7223
|
-
|
|
7417
|
+
fs31.mkdirSync(forgehiveDir, { recursive: true });
|
|
7418
|
+
fs31.writeFileSync(path32.join(forgehiveDir, "ci-report.json"), output, "utf8");
|
|
7224
7419
|
}
|
|
7225
7420
|
if (report.status === "fail") process.exit(1);
|
|
7226
7421
|
}
|
|
7227
7422
|
} else if (command === "map") {
|
|
7228
7423
|
const map2 = generateMap(projectRoot);
|
|
7229
7424
|
const md = formatMap(map2);
|
|
7230
|
-
const mapPath =
|
|
7231
|
-
|
|
7232
|
-
|
|
7425
|
+
const mapPath = path32.join(forgehiveDir, "map.md");
|
|
7426
|
+
fs31.mkdirSync(forgehiveDir, { recursive: true });
|
|
7427
|
+
fs31.writeFileSync(mapPath, md, "utf8");
|
|
7233
7428
|
console.log(md);
|
|
7234
7429
|
console.log(`
|
|
7235
7430
|
\u2714 Codebase-Map gespeichert: ${mapPath}`);
|
|
7236
7431
|
} else if (command === "onboard") {
|
|
7237
7432
|
const outputArg = rest.includes("--output") ? rest[rest.indexOf("--output") + 1] : null;
|
|
7238
|
-
const outputPath = outputArg ??
|
|
7433
|
+
const outputPath = outputArg ?? path32.join(projectRoot, "ONBOARDING.md");
|
|
7239
7434
|
const doc = generateOnboardingDoc(projectRoot, forgehiveDir);
|
|
7240
|
-
|
|
7435
|
+
fs31.writeFileSync(outputPath, doc, "utf8");
|
|
7241
7436
|
console.log(`\u2714 Onboarding-Dokument geschrieben: ${outputPath}`);
|
|
7242
7437
|
} else if (command === "changelog") {
|
|
7243
7438
|
const sinceArg = rest.includes("--since") ? rest[rest.indexOf("--since") + 1] : null;
|
|
@@ -7247,28 +7442,28 @@ Setze diese Umgebungsvariablen:`);
|
|
|
7247
7442
|
const commits = parseGitLog(rawLog);
|
|
7248
7443
|
let version = "unreleased";
|
|
7249
7444
|
try {
|
|
7250
|
-
const pkgPath =
|
|
7251
|
-
if (
|
|
7252
|
-
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, ""));
|
|
7253
7448
|
version = pkg.version ?? "unreleased";
|
|
7254
7449
|
}
|
|
7255
7450
|
} catch {
|
|
7256
7451
|
}
|
|
7257
7452
|
const md = formatChangelog(commits, version);
|
|
7258
|
-
const outputPath = outputArg ??
|
|
7453
|
+
const outputPath = outputArg ?? path32.join(projectRoot, "CHANGELOG.md");
|
|
7259
7454
|
let existing = "";
|
|
7260
|
-
if (
|
|
7261
|
-
|
|
7455
|
+
if (fs31.existsSync(outputPath)) existing = fs31.readFileSync(outputPath, "utf8");
|
|
7456
|
+
fs31.writeFileSync(outputPath, md + "\n\n" + existing, "utf8");
|
|
7262
7457
|
console.log(`\u2714 CHANGELOG.md aktualisiert: ${outputPath}`);
|
|
7263
7458
|
console.log(` ${commits.length} Commits verarbeitet`);
|
|
7264
7459
|
} else if (command === "metrics") {
|
|
7265
7460
|
const sinceArg = rest.includes("--since") ? rest[rest.indexOf("--since") + 1] : void 0;
|
|
7266
7461
|
const rawLog = getMetricsGitLog(projectRoot, sinceArg);
|
|
7267
7462
|
const stats = parseCommitStats(rawLog);
|
|
7268
|
-
const md = formatMetrics(stats,
|
|
7269
|
-
const metricsPath =
|
|
7270
|
-
|
|
7271
|
-
|
|
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");
|
|
7272
7467
|
console.log(md);
|
|
7273
7468
|
console.log(`
|
|
7274
7469
|
\u2714 Metrics gespeichert: ${metricsPath}`);
|
|
@@ -7303,7 +7498,7 @@ Setze diese Umgebungsvariablen:`);
|
|
|
7303
7498
|
console.log(`\u2714 ${result.message}`);
|
|
7304
7499
|
console.log(` fh run status \u2014 aktive Sessions anzeigen`);
|
|
7305
7500
|
} else if (command === "story") {
|
|
7306
|
-
const storiesDir =
|
|
7501
|
+
const storiesDir = path32.join(forgehiveDir, "memory", "stories");
|
|
7307
7502
|
if (subcommand === "create") {
|
|
7308
7503
|
const title = rest.filter((r) => !r.startsWith("--")).join(" ");
|
|
7309
7504
|
const epicArg = rest.includes("--epic") ? rest[rest.indexOf("--epic") + 1] : void 0;
|
|
@@ -7314,7 +7509,7 @@ Setze diese Umgebungsvariablen:`);
|
|
|
7314
7509
|
}
|
|
7315
7510
|
const story = createStory(storiesDir, title, epicArg);
|
|
7316
7511
|
if (pointsArg) updateStoryPoints(storiesDir, story.id, pointsArg);
|
|
7317
|
-
console.log(`\u2714 ${story.id} erstellt: ${
|
|
7512
|
+
console.log(`\u2714 ${story.id} erstellt: ${path32.join(storiesDir, story.id + ".md")}`);
|
|
7318
7513
|
console.log(` Bearbeite die Datei um Acceptance Criteria hinzuzuf\xFCgen.`);
|
|
7319
7514
|
} else if (subcommand === "list") {
|
|
7320
7515
|
const epicFilter = rest.includes("--epic") ? rest[rest.indexOf("--epic") + 1] : null;
|
|
@@ -7363,8 +7558,8 @@ Setze diese Umgebungsvariablen:`);
|
|
|
7363
7558
|
console.error("Verf\xFCgbar: fh story create | list | show | done");
|
|
7364
7559
|
}
|
|
7365
7560
|
} else if (command === "epic") {
|
|
7366
|
-
const epicsDir =
|
|
7367
|
-
const storiesDir =
|
|
7561
|
+
const epicsDir = path32.join(forgehiveDir, "memory", "epics");
|
|
7562
|
+
const storiesDir = path32.join(forgehiveDir, "memory", "stories");
|
|
7368
7563
|
if (subcommand === "create") {
|
|
7369
7564
|
const title = rest.filter((r) => !r.startsWith("--")).join(" ");
|
|
7370
7565
|
const goalArg = rest.includes("--goal") ? rest[rest.indexOf("--goal") + 1] : void 0;
|
|
@@ -7373,7 +7568,7 @@ Setze diese Umgebungsvariablen:`);
|
|
|
7373
7568
|
process.exit(1);
|
|
7374
7569
|
}
|
|
7375
7570
|
const epic = createEpic(epicsDir, title, goalArg);
|
|
7376
|
-
console.log(`\u2714 ${epic.id} erstellt: ${
|
|
7571
|
+
console.log(`\u2714 ${epic.id} erstellt: ${path32.join(epicsDir, epic.id + ".md")}`);
|
|
7377
7572
|
} else if (subcommand === "list") {
|
|
7378
7573
|
const epics = listEpics(epicsDir);
|
|
7379
7574
|
if (epics.length === 0) {
|
|
@@ -7399,7 +7594,7 @@ Setze diese Umgebungsvariablen:`);
|
|
|
7399
7594
|
console.error("Verf\xFCgbar: fh epic create | list | show");
|
|
7400
7595
|
}
|
|
7401
7596
|
} else if (command === "velocity") {
|
|
7402
|
-
const velocityFile =
|
|
7597
|
+
const velocityFile = path32.join(forgehiveDir, "memory", "velocity.md");
|
|
7403
7598
|
if (subcommand === "record") {
|
|
7404
7599
|
const sprintNum = parseInt(rest[0] ?? "0", 10);
|
|
7405
7600
|
const committed = rest.includes("--committed") ? parseInt(rest[rest.indexOf("--committed") + 1], 10) : NaN;
|
|
@@ -7417,9 +7612,100 @@ Setze diese Umgebungsvariablen:`);
|
|
|
7417
7612
|
} else {
|
|
7418
7613
|
console.error("Verf\xFCgbar: fh velocity show | record <N> --committed N --delivered N");
|
|
7419
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>]");
|
|
7705
|
+
}
|
|
7420
7706
|
} else {
|
|
7421
7707
|
console.error("Unbekannter Befehl: " + command);
|
|
7422
|
-
console.error("Verf\xFCgbar: init | confirm | rollback | scan | status | ci | map | onboard | changelog | metrics | 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");
|
|
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");
|
|
7423
7709
|
console.error("Hilfe: fh --help");
|
|
7424
7710
|
process.exit(1);
|
|
7425
7711
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
You are running a Design Party using ForgeHive.
|
|
2
|
+
|
|
3
|
+
## Design Party
|
|
4
|
+
|
|
5
|
+
**Agents:** Suki (Experience Designer), Viktor (Systems Architect)
|
|
6
|
+
|
|
7
|
+
**Mission:** UX design and technical architecture in parallel — align on what to build and how to build it before any code is written.
|
|
8
|
+
|
|
9
|
+
## Protocol
|
|
10
|
+
|
|
11
|
+
### Step 1: Launch the party
|
|
12
|
+
|
|
13
|
+
Run:
|
|
14
|
+
```bash
|
|
15
|
+
fh party run --set design
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This spins up isolated worktrees for Suki and Viktor. Both work from the same brief in parallel.
|
|
19
|
+
|
|
20
|
+
### Step 2: Write the brief
|
|
21
|
+
|
|
22
|
+
Before the party starts, write a one-paragraph brief:
|
|
23
|
+
- What problem are we solving?
|
|
24
|
+
- Who is the user?
|
|
25
|
+
- What outcome do we want?
|
|
26
|
+
|
|
27
|
+
This brief is the shared input for both agents.
|
|
28
|
+
|
|
29
|
+
### Step 3: Parallel design work
|
|
30
|
+
|
|
31
|
+
**Suki** (Experience Designer) — in her worktree:
|
|
32
|
+
- Maps user journeys: what does the user want to accomplish?
|
|
33
|
+
- Designs interaction flows: what steps does the user take?
|
|
34
|
+
- Identifies friction points and moments of delight
|
|
35
|
+
- Produces: a user journey map and a list of UX requirements
|
|
36
|
+
- Empathy-first: designs with users, not for them
|
|
37
|
+
|
|
38
|
+
**Viktor** (Systems Architect) — in his worktree:
|
|
39
|
+
- Designs the system architecture for the feature
|
|
40
|
+
- Defines module boundaries, data models, and API contracts
|
|
41
|
+
- Identifies infrastructure requirements and integration points
|
|
42
|
+
- Flags technical constraints that will affect the UX
|
|
43
|
+
- Produces: an architecture diagram (text-based) and a list of technical constraints
|
|
44
|
+
|
|
45
|
+
### Step 4: Check status
|
|
46
|
+
|
|
47
|
+
After both agents complete:
|
|
48
|
+
```bash
|
|
49
|
+
fh party status
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Step 5: Alignment review
|
|
53
|
+
|
|
54
|
+
Present both outputs side by side:
|
|
55
|
+
|
|
56
|
+
1. **UX requirements** (Suki) — what the system must feel like
|
|
57
|
+
2. **Technical constraints** (Viktor) — what the system can and cannot do
|
|
58
|
+
3. **Conflicts** — where UX desires clash with technical reality
|
|
59
|
+
4. **Resolution** — agree on trade-offs before any implementation begins
|
|
60
|
+
|
|
61
|
+
Ask the user: **"Sind Suki und Viktor aligned? Soll ich mit der Implementierung beginnen?"**
|
|
62
|
+
|
|
63
|
+
Do NOT proceed to implementation until alignment is confirmed.
|
|
64
|
+
|
|
65
|
+
### Step 6: Cleanup
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
fh party cleanup
|
|
69
|
+
```
|
|
@@ -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:
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
You are running a Full Party using ForgeHive.
|
|
2
|
+
|
|
3
|
+
## Full Party
|
|
4
|
+
|
|
5
|
+
**Agents:** Nora (Senior Research Analyst), Eli (Documentation Architect), Remy (Product Strategist), Suki (Experience Designer), Viktor (Systems Architect), Kai (Principal Engineer), Sam (Quality Architect)
|
|
6
|
+
|
|
7
|
+
**Mission:** Comprehensive multi-discipline review of a major feature or release — all seven specialists working in parallel on their domain.
|
|
8
|
+
|
|
9
|
+
> **Warning:** This party spins up 7 worktrees simultaneously. Reserve this for major milestones, release candidates, and significant architectural changes. For smaller reviews, use `/review-party` or `/design-party` instead.
|
|
10
|
+
|
|
11
|
+
## Protocol
|
|
12
|
+
|
|
13
|
+
### Step 1: Launch the party
|
|
14
|
+
|
|
15
|
+
Run:
|
|
16
|
+
```bash
|
|
17
|
+
fh party run --set full
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This spins up isolated worktrees for all 7 agents. Each works their specialization in parallel.
|
|
21
|
+
|
|
22
|
+
### Step 2: Define the milestone
|
|
23
|
+
|
|
24
|
+
Before the party starts, describe what is being reviewed — be specific:
|
|
25
|
+
- Which release or feature?
|
|
26
|
+
- What are the success criteria?
|
|
27
|
+
- Are there known risk areas to highlight?
|
|
28
|
+
|
|
29
|
+
### Step 3: Parallel work by specialization
|
|
30
|
+
|
|
31
|
+
**Nora** (Senior Research Analyst) — structured evidence, surfaces hidden constraints:
|
|
32
|
+
- Reviews assumptions and surfaces what we don't know
|
|
33
|
+
- Checks for hidden constraints in requirements or architecture
|
|
34
|
+
- Produces: a risk and assumptions report
|
|
35
|
+
|
|
36
|
+
**Eli** (Documentation Architect) — turns systems into navigable knowledge:
|
|
37
|
+
- Audits all documentation for accuracy and completeness
|
|
38
|
+
- Flags missing docs for new features and changed APIs
|
|
39
|
+
- Produces: a documentation gap report
|
|
40
|
+
|
|
41
|
+
**Remy** (Product Strategist) — jobs-to-be-done, user value over technical elegance:
|
|
42
|
+
- Reviews the feature against user jobs-to-be-done
|
|
43
|
+
- Checks if technical decisions serve user value
|
|
44
|
+
- Produces: a product alignment report
|
|
45
|
+
|
|
46
|
+
**Suki** (Experience Designer) — empathy-first design:
|
|
47
|
+
- Reviews UX and interaction flows
|
|
48
|
+
- Flags friction points and accessibility concerns
|
|
49
|
+
- Produces: a UX review with specific improvement suggestions
|
|
50
|
+
|
|
51
|
+
**Viktor** (Systems Architect) — proven technology, developer experience as architecture:
|
|
52
|
+
- Reviews architecture for scalability, maintainability, and DX
|
|
53
|
+
- Flags over-engineering and missing abstractions
|
|
54
|
+
- Produces: an architecture review
|
|
55
|
+
|
|
56
|
+
**Kai** (Principal Engineer) — test-first, ship-ready, zero ambiguity:
|
|
57
|
+
- Reviews code quality, correctness, and implementation discipline
|
|
58
|
+
- Flags ambiguity, untested code, and production risks
|
|
59
|
+
- Produces: a code review with blocking / non-blocking findings
|
|
60
|
+
|
|
61
|
+
**Sam** (Quality Architect) — risk-based testing, full-stack automation:
|
|
62
|
+
- Reviews test coverage and testing strategy
|
|
63
|
+
- Identifies high-risk paths with insufficient coverage
|
|
64
|
+
- Produces: a test coverage and risk report
|
|
65
|
+
|
|
66
|
+
### Step 4: Check status
|
|
67
|
+
|
|
68
|
+
After all agents complete:
|
|
69
|
+
```bash
|
|
70
|
+
fh party status
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Step 5: Synthesize into a unified report
|
|
74
|
+
|
|
75
|
+
Collect all 7 reports and synthesize:
|
|
76
|
+
|
|
77
|
+
1. **Blockers** — anything that must be resolved before release
|
|
78
|
+
2. **Risks** — issues to monitor post-release
|
|
79
|
+
3. **Improvements** — non-blocking suggestions by priority
|
|
80
|
+
4. **Overall verdict**: ready to release / needs work
|
|
81
|
+
|
|
82
|
+
Present the unified report and ask: **"Soll ich die Blocker direkt angehen?"**
|
|
83
|
+
|
|
84
|
+
### Step 6: Cleanup
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
fh party cleanup
|
|
88
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
You are running a Build Party using ForgeHive.
|
|
2
|
+
|
|
3
|
+
## Build Party
|
|
4
|
+
|
|
5
|
+
**Agents:** Viktor (Systems Architect), Kai (Principal Engineer), Sam (Quality Architect)
|
|
6
|
+
|
|
7
|
+
**Mission:** Architecture, implementation, and QA in parallel — design the system, write the code, and test it simultaneously.
|
|
8
|
+
|
|
9
|
+
## Protocol
|
|
10
|
+
|
|
11
|
+
### Step 1: Launch the party
|
|
12
|
+
|
|
13
|
+
Run:
|
|
14
|
+
```bash
|
|
15
|
+
fh party run --set build
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This spins up isolated git worktrees for Viktor, Kai, and Sam. Each agent works in their own worktree in parallel.
|
|
19
|
+
|
|
20
|
+
### Step 2: Define the scope
|
|
21
|
+
|
|
22
|
+
Before the party starts, state clearly what is being built — one paragraph is enough. Each agent will interpret this through their own lens.
|
|
23
|
+
|
|
24
|
+
### Step 3: Parallel work
|
|
25
|
+
|
|
26
|
+
**Viktor** (Systems Architect) — in his worktree:
|
|
27
|
+
- Proposes the architecture: module boundaries, data flow, API shape
|
|
28
|
+
- Documents key decisions and trade-offs
|
|
29
|
+
- Flags any infrastructure or dependency requirements
|
|
30
|
+
|
|
31
|
+
**Kai** (Principal Engineer) — in his worktree:
|
|
32
|
+
- Implements the feature following Viktor's architecture proposal
|
|
33
|
+
- Writes ship-ready code — no placeholders, no TODOs
|
|
34
|
+
- Follows test-first discipline: failing test → implementation → passing test
|
|
35
|
+
|
|
36
|
+
**Sam** (Quality Architect) — in his worktree:
|
|
37
|
+
- Writes the test suite for the feature in parallel with Kai's implementation
|
|
38
|
+
- Covers happy paths, edge cases, and error states
|
|
39
|
+
- Identifies risk areas that need additional coverage
|
|
40
|
+
|
|
41
|
+
### Step 4: Check status
|
|
42
|
+
|
|
43
|
+
After each agent signals completion:
|
|
44
|
+
```bash
|
|
45
|
+
fh party status
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Review what each agent produced before merging.
|
|
49
|
+
|
|
50
|
+
### Step 5: Review and integrate
|
|
51
|
+
|
|
52
|
+
1. Read Viktor's architecture notes
|
|
53
|
+
2. Review Kai's implementation against Viktor's proposal — flag divergence
|
|
54
|
+
3. Run Sam's tests against Kai's implementation — all must pass
|
|
55
|
+
4. Resolve any conflicts between the three worktrees
|
|
56
|
+
5. Merge the agreed result into main
|
|
57
|
+
|
|
58
|
+
### Step 6: Cleanup
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
fh party cleanup
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This tears down all worktrees and cleans up the party state.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
You are running a Review Party using ForgeHive.
|
|
2
|
+
|
|
3
|
+
## Review Party
|
|
4
|
+
|
|
5
|
+
**Agents:** Kai (Principal Engineer), Sam (Quality Architect), Eli (Documentation Architect)
|
|
6
|
+
|
|
7
|
+
**Mission:** Code review, test coverage check, and docs audit in parallel — three disciplines reviewing the same changeset simultaneously.
|
|
8
|
+
|
|
9
|
+
## Protocol
|
|
10
|
+
|
|
11
|
+
### Step 1: Launch the party
|
|
12
|
+
|
|
13
|
+
Run:
|
|
14
|
+
```bash
|
|
15
|
+
fh party run --set review
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This spins up isolated git worktrees for Kai, Sam, and Eli. Each agent reviews the current diff in parallel.
|
|
19
|
+
|
|
20
|
+
### Step 2: Set the scope
|
|
21
|
+
|
|
22
|
+
Before the party starts, specify what is being reviewed — a branch name, a PR, or a commit range:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git diff main...HEAD --stat # show what changed
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Share this with the party so all three agents review the same surface area.
|
|
29
|
+
|
|
30
|
+
### Step 3: Parallel review
|
|
31
|
+
|
|
32
|
+
**Kai** (Principal Engineer) — in his worktree:
|
|
33
|
+
- Reviews code quality and correctness
|
|
34
|
+
- Flags bugs, logic errors, and anti-patterns
|
|
35
|
+
- Checks for ambiguous naming, hidden coupling, and missing error handling
|
|
36
|
+
- Labels findings: `[BUG]`, `[DESIGN]`, `[NIT]`, `[Q]`, `[+]`
|
|
37
|
+
|
|
38
|
+
**Sam** (Quality Architect) — in his worktree:
|
|
39
|
+
- Checks test coverage for the changed code
|
|
40
|
+
- Identifies missing edge cases, error states, and boundary conditions
|
|
41
|
+
- Flags paths that are high-risk but untested
|
|
42
|
+
- Proposes specific tests for any gaps found
|
|
43
|
+
|
|
44
|
+
**Eli** (Documentation Architect) — in his worktree:
|
|
45
|
+
- Checks if README and docs reflect the changes
|
|
46
|
+
- Flags missing or outdated inline documentation
|
|
47
|
+
- Reviews any CHANGELOG entries for accuracy
|
|
48
|
+
- Checks that public API changes are documented
|
|
49
|
+
|
|
50
|
+
### Step 4: Check status
|
|
51
|
+
|
|
52
|
+
After each agent completes their review:
|
|
53
|
+
```bash
|
|
54
|
+
fh party status
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Step 5: Synthesize findings
|
|
58
|
+
|
|
59
|
+
Collect the three reports and merge them into a unified review:
|
|
60
|
+
|
|
61
|
+
1. **Blocking issues** — anything from Kai or Sam that must be fixed before merge
|
|
62
|
+
2. **Test gaps** — Sam's missing coverage items
|
|
63
|
+
3. **Docs debt** — Eli's documentation updates needed
|
|
64
|
+
4. **Overall verdict**: approve / request changes
|
|
65
|
+
|
|
66
|
+
Present the synthesized report to the user and ask: **"Soll ich die Blocking Issues direkt fixen?"**
|
|
67
|
+
|
|
68
|
+
### Step 6: Cleanup
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
fh party cleanup
|
|
72
|
+
```
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
You are running a Security Party using ForgeHive.
|
|
2
|
+
|
|
3
|
+
## Security Party
|
|
4
|
+
|
|
5
|
+
**Agents:** Vera (Security Analyst), Sam (Quality Architect)
|
|
6
|
+
|
|
7
|
+
**Mission:** Security review and QA in parallel — audit for vulnerabilities while checking test coverage for security-critical paths.
|
|
8
|
+
|
|
9
|
+
## Protocol
|
|
10
|
+
|
|
11
|
+
### Step 1: Launch the party
|
|
12
|
+
|
|
13
|
+
Run:
|
|
14
|
+
```bash
|
|
15
|
+
fh party run --set security
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This spins up isolated worktrees for Vera and Sam. Both work in parallel on the current codebase or changeset.
|
|
19
|
+
|
|
20
|
+
### Step 2: Set the scope
|
|
21
|
+
|
|
22
|
+
Specify what is being reviewed:
|
|
23
|
+
- A specific branch or PR: `git diff main...HEAD --stat`
|
|
24
|
+
- A full security audit of the codebase
|
|
25
|
+
- A pre-release security check
|
|
26
|
+
|
|
27
|
+
### Step 3: Parallel security work
|
|
28
|
+
|
|
29
|
+
**Vera** (Security Analyst) — in her worktree:
|
|
30
|
+
|
|
31
|
+
Run the automated scan first:
|
|
32
|
+
```bash
|
|
33
|
+
fh security scan
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then perform a manual audit covering:
|
|
37
|
+
- **OWASP Top 10** — injection, broken auth, XSS, IDOR, security misconfiguration, etc.
|
|
38
|
+
- **Auth/Auth review** — authentication flows, authorization checks, session handling
|
|
39
|
+
- **Secrets and credentials** — hardcoded tokens, API keys, or credentials in code
|
|
40
|
+
- **Dependency vulnerabilities** — outdated packages with known CVEs
|
|
41
|
+
- **Compliance flags** — GDPR, SOC2, or HIPAA relevant data handling
|
|
42
|
+
|
|
43
|
+
Produces: a security findings report with severity ratings (CRITICAL, HIGH, MEDIUM, LOW)
|
|
44
|
+
|
|
45
|
+
**Sam** (Quality Architect) — in his worktree:
|
|
46
|
+
- Identifies security-critical code paths (auth, payments, data access, input handling)
|
|
47
|
+
- Checks test coverage for each identified path
|
|
48
|
+
- Flags paths that are high-risk but have no security-focused tests
|
|
49
|
+
- Proposes specific security tests: auth bypass attempts, injection tests, boundary inputs
|
|
50
|
+
|
|
51
|
+
Produces: a security test coverage report with specific test recommendations
|
|
52
|
+
|
|
53
|
+
### Step 4: Check status
|
|
54
|
+
|
|
55
|
+
After both agents complete:
|
|
56
|
+
```bash
|
|
57
|
+
fh party status
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Step 5: Merge into a security + quality report
|
|
61
|
+
|
|
62
|
+
Combine both outputs:
|
|
63
|
+
|
|
64
|
+
1. **CRITICAL findings** (Vera) — must fix before any deployment
|
|
65
|
+
2. **HIGH findings** (Vera) — fix before production release
|
|
66
|
+
3. **Untested security paths** (Sam) — add tests before release
|
|
67
|
+
4. **MEDIUM/LOW findings** — schedule for next sprint
|
|
68
|
+
5. **Overall verdict**: safe to deploy / needs remediation
|
|
69
|
+
|
|
70
|
+
Ask: **"Soll ich die CRITICAL und HIGH Findings direkt fixen?"**
|
|
71
|
+
|
|
72
|
+
Do NOT deploy if CRITICAL or HIGH findings are unresolved.
|
|
73
|
+
|
|
74
|
+
### Step 6: Cleanup
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
fh party cleanup
|
|
78
|
+
```
|