forgehive 0.7.2 → 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 +11 -6
- package/dist/cli.js +348 -62
- package/forgehive/commands/fh-docs.md +15 -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
|
}
|
|
@@ -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:
|