forgehive 0.9.0 → 1.0.0

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.
Files changed (3) hide show
  1. package/README.md +166 -11
  2. package/dist/cli.js +1249 -397
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2749,12 +2749,401 @@ var init_harness = __esm({
2749
2749
  }
2750
2750
  });
2751
2751
 
2752
+ // src/outcomes.ts
2753
+ var outcomes_exports = {};
2754
+ __export(outcomes_exports, {
2755
+ aggregateByAgent: () => aggregateByAgent,
2756
+ aggregateByTaskType: () => aggregateByTaskType,
2757
+ appendOutcome: () => appendOutcome,
2758
+ readOutcomes: () => readOutcomes,
2759
+ updateRating: () => updateRating
2760
+ });
2761
+ import fs13 from "node:fs";
2762
+ import path13 from "node:path";
2763
+ function readOutcomes(forgehiveDir2) {
2764
+ const filePath = path13.join(forgehiveDir2, OUTCOMES_FILE);
2765
+ if (!fs13.existsSync(filePath)) return [];
2766
+ return fs13.readFileSync(filePath, "utf8").trim().split("\n").filter(Boolean).map((line) => {
2767
+ try {
2768
+ return JSON.parse(line);
2769
+ } catch {
2770
+ return null;
2771
+ }
2772
+ }).filter((e) => e !== null);
2773
+ }
2774
+ function writeOutcomes(forgehiveDir2, entries) {
2775
+ const filePath = path13.join(forgehiveDir2, OUTCOMES_FILE);
2776
+ fs13.writeFileSync(filePath, entries.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
2777
+ }
2778
+ function appendOutcome(forgehiveDir2, entry) {
2779
+ const filePath = path13.join(forgehiveDir2, OUTCOMES_FILE);
2780
+ fs13.appendFileSync(filePath, JSON.stringify(entry) + "\n", "utf8");
2781
+ const entries = readOutcomes(forgehiveDir2);
2782
+ if (entries.length >= MAX_ENTRIES) {
2783
+ const kept = entries.slice(-KEEP_AFTER_ROTATION);
2784
+ writeOutcomes(forgehiveDir2, kept);
2785
+ }
2786
+ }
2787
+ function updateRating(forgehiveDir2, runId, rating) {
2788
+ const entries = readOutcomes(forgehiveDir2);
2789
+ const updated = entries.map((e) => e.runId === runId ? { ...e, rating } : e);
2790
+ writeOutcomes(forgehiveDir2, updated);
2791
+ }
2792
+ function aggregateByAgent(forgehiveDir2) {
2793
+ const entries = readOutcomes(forgehiveDir2);
2794
+ const result = {};
2795
+ for (const entry of entries) {
2796
+ for (const agent of entry.agents) {
2797
+ if (!result[agent]) {
2798
+ result[agent] = { avgDurationMs: 0, taskTypes: {}, avgRating: null };
2799
+ }
2800
+ const stats = result[agent];
2801
+ if (!stats.taskTypes[entry.taskType]) {
2802
+ stats.taskTypes[entry.taskType] = { count: 0, avgDurationMs: 0 };
2803
+ }
2804
+ const ttStats = stats.taskTypes[entry.taskType];
2805
+ const prevCount = ttStats.count;
2806
+ ttStats.avgDurationMs = (ttStats.avgDurationMs * prevCount + entry.durationMs) / (prevCount + 1);
2807
+ ttStats.count++;
2808
+ }
2809
+ }
2810
+ for (const agent of Object.keys(result)) {
2811
+ const agentEntries = entries.filter((e) => e.agents.includes(agent));
2812
+ result[agent].avgDurationMs = agentEntries.reduce((sum, e) => sum + e.durationMs, 0) / agentEntries.length;
2813
+ const rated = agentEntries.filter((e) => e.rating !== null);
2814
+ result[agent].avgRating = rated.length > 0 ? rated.reduce((sum, e) => sum + e.rating, 0) / rated.length : null;
2815
+ }
2816
+ return result;
2817
+ }
2818
+ function aggregateByTaskType(forgehiveDir2) {
2819
+ const entries = readOutcomes(forgehiveDir2);
2820
+ const result = {};
2821
+ for (const entry of entries) {
2822
+ if (!result[entry.taskType]) {
2823
+ result[entry.taskType] = { count: 0, avgDurationMs: 0 };
2824
+ }
2825
+ const agg = result[entry.taskType];
2826
+ agg.avgDurationMs = (agg.avgDurationMs * agg.count + entry.durationMs) / (agg.count + 1);
2827
+ agg.count++;
2828
+ }
2829
+ return result;
2830
+ }
2831
+ var OUTCOMES_FILE, MAX_ENTRIES, KEEP_AFTER_ROTATION;
2832
+ var init_outcomes = __esm({
2833
+ "src/outcomes.ts"() {
2834
+ "use strict";
2835
+ OUTCOMES_FILE = "outcomes.jsonl";
2836
+ MAX_ENTRIES = 500;
2837
+ KEEP_AFTER_ROTATION = 400;
2838
+ }
2839
+ });
2840
+
2841
+ // src/next.ts
2842
+ var next_exports = {};
2843
+ __export(next_exports, {
2844
+ PHASE_LABELS: () => PHASE_LABELS,
2845
+ buildRecommendation: () => buildRecommendation,
2846
+ detectPhase: () => detectPhase,
2847
+ detectTaskTypeFromTitle: () => detectTaskTypeFromTitle,
2848
+ formatNextCompact: () => formatNextCompact,
2849
+ formatNextFull: () => formatNextFull,
2850
+ loadNextState: () => loadNextState
2851
+ });
2852
+ import fs16 from "node:fs";
2853
+ import path16 from "node:path";
2854
+ function detectPhase(state) {
2855
+ if (state.securityScanAgeMs > SEVEN_DAYS_MS) return "health-check";
2856
+ if (state.storiesInSprint.length > 0 && state.activeRunIds.length > 0) return "implementing";
2857
+ if (state.storiesInSprint.length > 0 && state.openPRNumbers.length > 0 && state.lastOutcomeType !== "party") return "review";
2858
+ if (state.storiesInSprint.length > 0) return "implementing";
2859
+ if (state.storiesDone.length > 0 && state.lastVelocityAgeMs > FOURTEEN_DAYS_MS) {
2860
+ return "closing-sprint";
2861
+ }
2862
+ if (state.epicsWithoutStories.length > 0) return "solutioning";
2863
+ if (state.storiesBacklog.length > 0) return "planning";
2864
+ return "kickoff";
2865
+ }
2866
+ function detectTaskTypeFromTitle(title) {
2867
+ const lower = title.toLowerCase();
2868
+ for (const [keywords, taskType] of TASK_KEYWORDS) {
2869
+ if (keywords.some((kw) => lower.includes(kw))) return taskType;
2870
+ }
2871
+ return "feature";
2872
+ }
2873
+ function getAgentRefinement(forgehiveDir2, taskType) {
2874
+ const outcomesPath = path16.join(forgehiveDir2, "outcomes.jsonl");
2875
+ if (!fs16.existsSync(outcomesPath)) return null;
2876
+ const entries = fs16.readFileSync(outcomesPath, "utf8").trim().split("\n").filter(Boolean).map((l) => {
2877
+ try {
2878
+ return JSON.parse(l);
2879
+ } catch {
2880
+ return null;
2881
+ }
2882
+ }).filter((e) => e !== null);
2883
+ const agentData = {};
2884
+ for (const entry of entries) {
2885
+ if (entry.taskType !== taskType) continue;
2886
+ for (const agent of entry.agents) {
2887
+ if (!agentData[agent]) agentData[agent] = [];
2888
+ agentData[agent].push(entry.durationMs);
2889
+ }
2890
+ }
2891
+ const eligible = Object.entries(agentData).filter(([, durations]) => durations.length >= 3);
2892
+ if (eligible.length < 2) return null;
2893
+ const avgByAgent = eligible.map(([agent, durations]) => ({
2894
+ agent,
2895
+ avg: durations.reduce((a, b) => a + b, 0) / durations.length
2896
+ }));
2897
+ avgByAgent.sort((a, b) => a.avg - b.avg);
2898
+ const best = avgByAgent[0];
2899
+ const second = avgByAgent[1];
2900
+ const pct = Math.round((1 - best.avg / second.avg) * 100);
2901
+ const bestMinutes = (best.avg / 6e4).toFixed(0);
2902
+ return `${best.agent} empfohlen \u2014 ${pct}% schneller bei ${taskType} (\xD8 ${bestMinutes} Min)`;
2903
+ }
2904
+ function getSecondaryRecommendation(state, phase) {
2905
+ if (phase !== "health-check" && state.securityScanAgeMs > SECONDARY_SCAN_AGE_MS) {
2906
+ const days = Math.floor(state.securityScanAgeMs / (24 * 60 * 60 * 1e3));
2907
+ return { command: "fh security scan", reason: `letzter Scan vor ${days} Tagen` };
2908
+ }
2909
+ if (phase !== "closing-sprint" && state.storiesDone.length > 0 && state.lastVelocityAgeMs > SECONDARY_VELOCITY_AGE_MS) {
2910
+ return { command: "fh velocity record", reason: "Velocity nicht erfasst seit > 14 Tagen" };
2911
+ }
2912
+ return null;
2913
+ }
2914
+ function buildRecommendation(phase, state, forgehiveDir2) {
2915
+ const secondary = getSecondaryRecommendation(state, phase);
2916
+ const baseRec = {
2917
+ phase,
2918
+ secondary: secondary?.command ?? null,
2919
+ secondaryReason: secondary?.reason ?? null
2920
+ };
2921
+ switch (phase) {
2922
+ case "health-check":
2923
+ return { ...baseRec, primary: "fh security scan", primaryHint: null };
2924
+ case "implementing": {
2925
+ const story = state.storiesInSprint[0] ?? "US-?";
2926
+ const hint = getAgentRefinement(forgehiveDir2, "feature");
2927
+ return { ...baseRec, primary: `fh auto ${story}`, primaryHint: hint };
2928
+ }
2929
+ case "review":
2930
+ return { ...baseRec, primary: "/review-party", primaryHint: null };
2931
+ case "closing-sprint": {
2932
+ const sprint = state.storiesDone.length;
2933
+ return { ...baseRec, primary: `fh velocity record sprint-${sprint}`, primaryHint: null };
2934
+ }
2935
+ case "planning":
2936
+ return { ...baseRec, primary: "/fh-sprint", primaryHint: null };
2937
+ case "kickoff":
2938
+ return { ...baseRec, primary: 'fh story create "<first story>"', primaryHint: null };
2939
+ case "solutioning": {
2940
+ const epic = state.epicsWithoutStories[0] ?? "EPC-?";
2941
+ return { ...baseRec, primary: `fh story create --epic ${epic}`, primaryHint: null };
2942
+ }
2943
+ }
2944
+ }
2945
+ function loadNextState(forgehiveDir2, projectRoot2) {
2946
+ const storiesDir = path16.join(forgehiveDir2, "memory", "stories");
2947
+ const epicsDir = path16.join(forgehiveDir2, "memory", "epics");
2948
+ function readIds(dir, statusFilter) {
2949
+ if (!fs16.existsSync(dir)) return [];
2950
+ const results = [];
2951
+ for (const filename of fs16.readdirSync(dir)) {
2952
+ if (!filename.endsWith(".md")) continue;
2953
+ try {
2954
+ const content = fs16.readFileSync(path16.join(dir, filename), "utf8");
2955
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
2956
+ if (!match) continue;
2957
+ const data = jsYaml.load(match[1]);
2958
+ if (!data?.id) continue;
2959
+ if (statusFilter && data.status !== statusFilter) continue;
2960
+ results.push(data.id);
2961
+ } catch {
2962
+ }
2963
+ }
2964
+ return results;
2965
+ }
2966
+ function getEpicsWithoutStories() {
2967
+ if (!fs16.existsSync(epicsDir)) return [];
2968
+ const allStoryEpicIds = /* @__PURE__ */ new Set();
2969
+ if (fs16.existsSync(storiesDir)) {
2970
+ for (const f of fs16.readdirSync(storiesDir)) {
2971
+ if (!f.endsWith(".md")) continue;
2972
+ try {
2973
+ const content = fs16.readFileSync(path16.join(storiesDir, f), "utf8");
2974
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
2975
+ if (!match) continue;
2976
+ const data = jsYaml.load(match[1]);
2977
+ if (data?.epicId) allStoryEpicIds.add(data.epicId);
2978
+ } catch {
2979
+ }
2980
+ }
2981
+ }
2982
+ const result = [];
2983
+ for (const f of fs16.readdirSync(epicsDir)) {
2984
+ if (!f.endsWith(".md")) continue;
2985
+ try {
2986
+ const content = fs16.readFileSync(path16.join(epicsDir, f), "utf8");
2987
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
2988
+ if (!match) continue;
2989
+ const data = jsYaml.load(match[1]);
2990
+ if (data?.id && !allStoryEpicIds.has(data.id)) result.push(data.id);
2991
+ } catch {
2992
+ }
2993
+ }
2994
+ return result;
2995
+ }
2996
+ let securityScanAgeMs = 0;
2997
+ const secPath = path16.join(forgehiveDir2, "security-last-scan.txt");
2998
+ if (fs16.existsSync(secPath)) {
2999
+ try {
3000
+ const ts = fs16.readFileSync(secPath, "utf8").trim();
3001
+ securityScanAgeMs = Date.now() - new Date(ts).getTime();
3002
+ } catch {
3003
+ securityScanAgeMs = Infinity;
3004
+ }
3005
+ } else {
3006
+ securityScanAgeMs = Infinity;
3007
+ }
3008
+ let lastVelocityAgeMs = Infinity;
3009
+ const velocityPath = path16.join(forgehiveDir2, "memory", "velocity.md");
3010
+ if (fs16.existsSync(velocityPath)) {
3011
+ try {
3012
+ const content = fs16.readFileSync(velocityPath, "utf8");
3013
+ const dateMatches = content.match(/\|\s*(\d{4}-\d{2}-\d{2})\s*\|/g);
3014
+ if (dateMatches && dateMatches.length > 0) {
3015
+ const lastDate = dateMatches[dateMatches.length - 1].replace(/[\|\s]/g, "");
3016
+ lastVelocityAgeMs = Date.now() - new Date(lastDate).getTime();
3017
+ }
3018
+ } catch {
3019
+ }
3020
+ }
3021
+ const runsDir = path16.join(forgehiveDir2, "runs");
3022
+ const activeRunIds = [];
3023
+ if (fs16.existsSync(runsDir)) {
3024
+ for (const runDir of fs16.readdirSync(runsDir)) {
3025
+ const statePath = path16.join(runsDir, runDir, "state.yaml");
3026
+ if (!fs16.existsSync(statePath)) continue;
3027
+ try {
3028
+ const state = jsYaml.load(fs16.readFileSync(statePath, "utf8"));
3029
+ if (state?.status && !["done", "gate-stuck"].includes(state.status) && state.runId) {
3030
+ activeRunIds.push(state.runId);
3031
+ }
3032
+ } catch {
3033
+ }
3034
+ }
3035
+ }
3036
+ let openPRNumbers = [];
3037
+ const prCachePath = path16.join(forgehiveDir2, "github-pr-cache.json");
3038
+ if (fs16.existsSync(prCachePath)) {
3039
+ try {
3040
+ const cache = JSON.parse(fs16.readFileSync(prCachePath, "utf8"));
3041
+ openPRNumbers = cache.open ?? [];
3042
+ } catch {
3043
+ }
3044
+ }
3045
+ let lastOutcomeType = null;
3046
+ const outcomesPath = path16.join(forgehiveDir2, "outcomes.jsonl");
3047
+ if (fs16.existsSync(outcomesPath)) {
3048
+ try {
3049
+ const lines = fs16.readFileSync(outcomesPath, "utf8").trim().split("\n").filter(Boolean);
3050
+ if (lines.length > 0) {
3051
+ const last = JSON.parse(lines[lines.length - 1]);
3052
+ if (last.type === "auto" || last.type === "party") {
3053
+ lastOutcomeType = last.type;
3054
+ }
3055
+ }
3056
+ } catch {
3057
+ }
3058
+ }
3059
+ return {
3060
+ storiesInSprint: readIds(storiesDir, "in-sprint"),
3061
+ storiesBacklog: readIds(storiesDir, "backlog"),
3062
+ storiesDone: readIds(storiesDir, "done"),
3063
+ epicsWithoutStories: getEpicsWithoutStories(),
3064
+ openPRNumbers,
3065
+ activeRunIds,
3066
+ securityScanAgeMs,
3067
+ lastVelocityAgeMs,
3068
+ lastOutcomeType
3069
+ };
3070
+ }
3071
+ function formatNextFull(projectRoot2, forgehiveDir2) {
3072
+ const state = loadNextState(forgehiveDir2, projectRoot2);
3073
+ const phase = detectPhase(state);
3074
+ const rec = buildRecommendation(phase, state, forgehiveDir2);
3075
+ const phaseLabel = PHASE_LABELS[phase];
3076
+ const lines = [
3077
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
3078
+ "fh next",
3079
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
3080
+ `Phase: ${phaseLabel}`,
3081
+ ""
3082
+ ];
3083
+ if (state.storiesInSprint.length > 0) {
3084
+ lines.push(` ${state.storiesInSprint.length} Stories in Sprint:`);
3085
+ for (const id of state.storiesInSprint) {
3086
+ lines.push(` ${id}`);
3087
+ }
3088
+ lines.push("");
3089
+ }
3090
+ lines.push("Empfehlung:");
3091
+ lines.push(` \u2192 ${rec.primary}`);
3092
+ if (rec.primaryHint) {
3093
+ lines.push(` ${rec.primaryHint}`);
3094
+ }
3095
+ if (rec.secondary) {
3096
+ lines.push("");
3097
+ lines.push("Auch f\xE4llig:");
3098
+ lines.push(` \u2192 ${rec.secondary} (${rec.secondaryReason})`);
3099
+ }
3100
+ lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
3101
+ return lines.join("\n");
3102
+ }
3103
+ function formatNextCompact(projectRoot2, forgehiveDir2) {
3104
+ const state = loadNextState(forgehiveDir2, projectRoot2);
3105
+ const phase = detectPhase(state);
3106
+ const rec = buildRecommendation(phase, state, forgehiveDir2);
3107
+ const phaseLabel = PHASE_LABELS[phase];
3108
+ const hint = rec.primaryHint ? ` (${rec.primaryHint})` : "";
3109
+ return `## Next
3110
+ Phase: ${phaseLabel}
3111
+ \u2192 ${rec.primary}${hint}`;
3112
+ }
3113
+ var SEVEN_DAYS_MS, FOURTEEN_DAYS_MS, TASK_KEYWORDS, SECONDARY_SCAN_AGE_MS, SECONDARY_VELOCITY_AGE_MS, PHASE_LABELS;
3114
+ var init_next = __esm({
3115
+ "src/next.ts"() {
3116
+ "use strict";
3117
+ init_js_yaml();
3118
+ SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1e3;
3119
+ FOURTEEN_DAYS_MS = 14 * 24 * 60 * 60 * 1e3;
3120
+ TASK_KEYWORDS = [
3121
+ [["fix", "bug", "crash", "error", "broken"], "bug-fix"],
3122
+ [["test", "coverage", "spec", "assertion"], "test-gap"],
3123
+ [["security", "vulnerability", "auth", "cve"], "security"],
3124
+ [["design", "ui", "ux", "layout"], "design"],
3125
+ [["docs", "readme", "onboarding"], "documentation"]
3126
+ ];
3127
+ SECONDARY_SCAN_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
3128
+ SECONDARY_VELOCITY_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
3129
+ PHASE_LABELS = {
3130
+ "health-check": "Health Check",
3131
+ "implementing": "Implementation",
3132
+ "review": "Review",
3133
+ "closing-sprint": "Sprint Abschluss",
3134
+ "planning": "Planung",
3135
+ "kickoff": "Kickoff",
3136
+ "solutioning": "Solutioning"
3137
+ };
3138
+ }
3139
+ });
3140
+
2752
3141
  // src/cli.ts
2753
3142
  init_js_yaml();
2754
- import fs35 from "node:fs";
2755
- import path36 from "node:path";
2756
- import { spawnSync as spawnSync13 } from "node:child_process";
2757
- import { createInterface } from "node:readline";
3143
+ import fs39 from "node:fs";
3144
+ import path40 from "node:path";
3145
+ import { spawnSync as spawnSync15 } from "node:child_process";
3146
+ import { createInterface as createInterface2 } from "node:readline";
2758
3147
 
2759
3148
  // src/scanner.ts
2760
3149
  import fs from "node:fs";
@@ -3837,19 +4226,24 @@ function pullRemoteSkills(forgehiveDir2, gitUrl) {
3837
4226
 
3838
4227
  // src/party.ts
3839
4228
  init_js_yaml();
3840
- import fs13 from "node:fs";
3841
- import path13 from "node:path";
4229
+ import fs14 from "node:fs";
4230
+ import path14 from "node:path";
3842
4231
  import { spawnSync as spawnSync3 } from "node:child_process";
4232
+
4233
+ // src/party-subagent.ts
4234
+ init_outcomes();
4235
+
4236
+ // src/party.ts
3843
4237
  function loadConfig(forgehiveDir2) {
3844
- const configPath = path13.join(forgehiveDir2, "party", "config.yaml");
3845
- if (!fs13.existsSync(configPath)) {
4238
+ const configPath = path14.join(forgehiveDir2, "party", "config.yaml");
4239
+ if (!fs14.existsSync(configPath)) {
3846
4240
  throw new Error("party/config.yaml nicht gefunden \u2014 f\xFChre `fh init` aus");
3847
4241
  }
3848
- return jsYaml.load(fs13.readFileSync(configPath, "utf8"));
4242
+ return jsYaml.load(fs14.readFileSync(configPath, "utf8"));
3849
4243
  }
3850
4244
  function saveConfig(forgehiveDir2, config) {
3851
- fs13.writeFileSync(
3852
- path13.join(forgehiveDir2, "party", "config.yaml"),
4245
+ fs14.writeFileSync(
4246
+ path14.join(forgehiveDir2, "party", "config.yaml"),
3853
4247
  jsYaml.dump(config),
3854
4248
  "utf8"
3855
4249
  );
@@ -3899,12 +4293,12 @@ function runParty(forgehiveDir2, projectRoot2, setName) {
3899
4293
  );
3900
4294
  }
3901
4295
  const sessionId = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/[:-]/g, "");
3902
- const wtBaseDir = path13.join(forgehiveDir2, "worktrees");
3903
- fs13.mkdirSync(wtBaseDir, { recursive: true });
4296
+ const wtBaseDir = path14.join(forgehiveDir2, "worktrees");
4297
+ fs14.mkdirSync(wtBaseDir, { recursive: true });
3904
4298
  const state = { setName, sessionId, worktrees: [] };
3905
4299
  for (const agent of config.sets[setName].agents) {
3906
4300
  const branch = `forgehive/party/${sessionId}/${agent}`;
3907
- const wtPath = path13.join(wtBaseDir, `${sessionId}-${agent}`);
4301
+ const wtPath = path14.join(wtBaseDir, `${sessionId}-${agent}`);
3908
4302
  const result = spawnSync3("git", ["worktree", "add", "-b", branch, wtPath], {
3909
4303
  cwd: projectRoot2,
3910
4304
  encoding: "utf8"
@@ -3916,8 +4310,8 @@ function runParty(forgehiveDir2, projectRoot2, setName) {
3916
4310
  spawnSync3("git", ["worktree", "prune"], { cwd: projectRoot2, encoding: "utf8" });
3917
4311
  throw result.error ?? new Error(`Worktree f\xFCr ${agent} fehlgeschlagen: ${result.stderr?.trim()}`);
3918
4312
  }
3919
- fs13.writeFileSync(
3920
- path13.join(wtPath, ".forgehive-agent-dispatch.md"),
4313
+ fs14.writeFileSync(
4314
+ path14.join(wtPath, ".forgehive-agent-dispatch.md"),
3921
4315
  [
3922
4316
  `# ${agent} \u2014 Party Session ${sessionId}`,
3923
4317
  "",
@@ -3935,18 +4329,18 @@ function runParty(forgehiveDir2, projectRoot2, setName) {
3935
4329
  );
3936
4330
  state.worktrees.push({ agent, branch, path: wtPath, status: "pending" });
3937
4331
  }
3938
- fs13.writeFileSync(
3939
- path13.join(forgehiveDir2, "party", "party-session.yaml"),
4332
+ fs14.writeFileSync(
4333
+ path14.join(forgehiveDir2, "party", "party-session.yaml"),
3940
4334
  jsYaml.dump(state),
3941
4335
  "utf8"
3942
4336
  );
3943
4337
  return state;
3944
4338
  }
3945
4339
  function getPartyStatus(forgehiveDir2) {
3946
- const statePath = path13.join(forgehiveDir2, "party", "party-session.yaml");
3947
- if (!fs13.existsSync(statePath)) return null;
4340
+ const statePath = path14.join(forgehiveDir2, "party", "party-session.yaml");
4341
+ if (!fs14.existsSync(statePath)) return null;
3948
4342
  try {
3949
- return jsYaml.load(fs13.readFileSync(statePath, "utf8"));
4343
+ return jsYaml.load(fs14.readFileSync(statePath, "utf8"));
3950
4344
  } catch {
3951
4345
  return null;
3952
4346
  }
@@ -3956,7 +4350,7 @@ function cleanupPartyWorktrees(forgehiveDir2, projectRoot2) {
3956
4350
  if (!state) throw new Error("Keine aktive Party-Session gefunden");
3957
4351
  const removed = [];
3958
4352
  for (const wt of state.worktrees) {
3959
- if (fs13.existsSync(wt.path)) {
4353
+ if (fs14.existsSync(wt.path)) {
3960
4354
  const result = spawnSync3("git", ["worktree", "remove", "--force", wt.path], {
3961
4355
  cwd: projectRoot2,
3962
4356
  encoding: "utf8"
@@ -3965,8 +4359,8 @@ function cleanupPartyWorktrees(forgehiveDir2, projectRoot2) {
3965
4359
  }
3966
4360
  }
3967
4361
  spawnSync3("git", ["worktree", "prune"], { cwd: projectRoot2, encoding: "utf8" });
3968
- const statePath = path13.join(forgehiveDir2, "party", "party-session.yaml");
3969
- if (fs13.existsSync(statePath)) fs13.unlinkSync(statePath);
4362
+ const statePath = path14.join(forgehiveDir2, "party", "party-session.yaml");
4363
+ if (fs14.existsSync(statePath)) fs14.unlinkSync(statePath);
3970
4364
  return removed;
3971
4365
  }
3972
4366
  function getPartyDefaultSet(forgehiveDir2) {
@@ -3989,8 +4383,8 @@ function setAgentModel(forgehiveDir2, setName, agentName, modelId) {
3989
4383
  }
3990
4384
 
3991
4385
  // src/wire.ts
3992
- import fs14 from "node:fs";
3993
- import path14 from "node:path";
4386
+ import fs15 from "node:fs";
4387
+ import path15 from "node:path";
3994
4388
  var SERVICES = {
3995
4389
  linear: {
3996
4390
  mcp: {
@@ -4827,19 +5221,19 @@ function wireService(projectRoot2, forgehiveDir2, service) {
4827
5221
  `Unbekannter Service: "${service}". Verf\xFCgbar: ${Object.keys(SERVICES).join(", ")}`
4828
5222
  );
4829
5223
  }
4830
- const mcpJsonPath = path14.join(projectRoot2, ".mcp.json");
5224
+ const mcpJsonPath = path15.join(projectRoot2, ".mcp.json");
4831
5225
  let mcpConfig = { mcpServers: {} };
4832
- if (fs14.existsSync(mcpJsonPath)) {
4833
- mcpConfig = JSON.parse(fs14.readFileSync(mcpJsonPath, "utf8"));
5226
+ if (fs15.existsSync(mcpJsonPath)) {
5227
+ mcpConfig = JSON.parse(fs15.readFileSync(mcpJsonPath, "utf8"));
4834
5228
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
4835
5229
  }
4836
5230
  if (!mcpConfig.mcpServers[service]) {
4837
5231
  mcpConfig.mcpServers[service] = def.mcp;
4838
- fs14.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf8");
5232
+ fs15.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf8");
4839
5233
  }
4840
- const skillPath = path14.join(forgehiveDir2, "skills", "workflows", `${service}.md`);
4841
- fs14.mkdirSync(path14.dirname(skillPath), { recursive: true });
4842
- fs14.writeFileSync(skillPath, def.skillContent, "utf8");
5234
+ const skillPath = path15.join(forgehiveDir2, "skills", "workflows", `${service}.md`);
5235
+ fs15.mkdirSync(path15.dirname(skillPath), { recursive: true });
5236
+ fs15.writeFileSync(skillPath, def.skillContent, "utf8");
4843
5237
  }
4844
5238
  function validateWireService(service) {
4845
5239
  if (!SERVICES[service]) {
@@ -4853,20 +5247,21 @@ function validateWireService(service) {
4853
5247
 
4854
5248
  // src/status.ts
4855
5249
  init_js_yaml();
4856
- import fs15 from "node:fs";
4857
- import path15 from "node:path";
5250
+ init_next();
5251
+ import fs17 from "node:fs";
5252
+ import path17 from "node:path";
4858
5253
  import { spawnSync as spawnSync4 } from "node:child_process";
4859
5254
  function countMdFiles(dir) {
4860
- if (!fs15.existsSync(dir)) return 0;
4861
- return fs15.readdirSync(dir).filter((f) => f.endsWith(".md")).length;
5255
+ if (!fs17.existsSync(dir)) return 0;
5256
+ return fs17.readdirSync(dir).filter((f) => f.endsWith(".md")).length;
4862
5257
  }
4863
5258
  function readYamlFrontmatterFiles(dir) {
4864
- if (!fs15.existsSync(dir)) return [];
5259
+ if (!fs17.existsSync(dir)) return [];
4865
5260
  const results = [];
4866
- for (const filename of fs15.readdirSync(dir)) {
5261
+ for (const filename of fs17.readdirSync(dir)) {
4867
5262
  if (!filename.endsWith(".md")) continue;
4868
5263
  try {
4869
- const content = fs15.readFileSync(path15.join(dir, filename), "utf8");
5264
+ const content = fs17.readFileSync(path17.join(dir, filename), "utf8");
4870
5265
  const match = content.match(/^---\n([\s\S]*?)\n---/);
4871
5266
  if (!match) continue;
4872
5267
  const parsed = jsYaml.load(match[1]);
@@ -4877,13 +5272,13 @@ function readYamlFrontmatterFiles(dir) {
4877
5272
  return results;
4878
5273
  }
4879
5274
  function formatSprintSection(forgehiveDir2) {
4880
- const storiesDir = path15.join(forgehiveDir2, "memory", "stories");
4881
- if (!fs15.existsSync(storiesDir)) return "";
5275
+ const storiesDir = path17.join(forgehiveDir2, "memory", "stories");
5276
+ if (!fs17.existsSync(storiesDir)) return "";
4882
5277
  const stories = readYamlFrontmatterFiles(storiesDir);
4883
5278
  const open = stories.filter((s) => s.status !== "done");
4884
5279
  const inSprint = stories.filter((s) => s.status === "in-sprint");
4885
5280
  const next = inSprint[0] ?? open[0] ?? null;
4886
- const epicsDir = path15.join(forgehiveDir2, "memory", "epics");
5281
+ const epicsDir = path17.join(forgehiveDir2, "memory", "epics");
4887
5282
  const activeEpics = readYamlFrontmatterFiles(epicsDir).filter((e) => e.status === "active" || e.status === "in-progress");
4888
5283
  const lines = ["SPRINT"];
4889
5284
  lines.push(` \u25CF ${open.length} offen \u25CF ${inSprint.length} in-sprint`);
@@ -4908,10 +5303,10 @@ function formatCodeHealthSection(projectRoot2, forgehiveDir2) {
4908
5303
  changed === 0 ? " \u2713 Arbeitsbaum sauber" : ` \u26A0 ${changed} ge\xE4nderte Datei(en) \u2014 nicht committed`
4909
5304
  );
4910
5305
  }
4911
- const secPath = path15.join(forgehiveDir2, "security-last-scan.txt");
4912
- if (fs15.existsSync(secPath)) {
5306
+ const secPath = path17.join(forgehiveDir2, "security-last-scan.txt");
5307
+ if (fs17.existsSync(secPath)) {
4913
5308
  try {
4914
- const ts = fs15.readFileSync(secPath, "utf8").trim();
5309
+ const ts = fs17.readFileSync(secPath, "utf8").trim();
4915
5310
  const days = Math.floor((Date.now() - new Date(ts).getTime()) / 864e5);
4916
5311
  lines.push(
4917
5312
  days > 7 ? ` \u26A0 Security-Scan: ${days} Tage alt \u2014 fh security scan` : ` \u2713 Security-Scan: ${days} Tage alt`
@@ -4919,22 +5314,42 @@ function formatCodeHealthSection(projectRoot2, forgehiveDir2) {
4919
5314
  } catch {
4920
5315
  }
4921
5316
  }
4922
- const prCachePath = path15.join(forgehiveDir2, "github-pr-cache.json");
4923
- if (fs15.existsSync(prCachePath)) {
5317
+ const prCachePath = path17.join(forgehiveDir2, "github-pr-cache.json");
5318
+ if (fs17.existsSync(prCachePath)) {
4924
5319
  try {
4925
- const cache = JSON.parse(fs15.readFileSync(prCachePath, "utf8"));
5320
+ const cache = JSON.parse(fs17.readFileSync(prCachePath, "utf8"));
4926
5321
  if (cache.open.length > 0) {
4927
5322
  lines.push(` \u25CF ${cache.open.length} offene PR(s): #${cache.open.join(", #")}`);
4928
5323
  }
4929
5324
  } catch {
4930
5325
  }
4931
5326
  }
5327
+ const outcomesPath = path17.join(forgehiveDir2, "outcomes.jsonl");
5328
+ if (fs17.existsSync(outcomesPath)) {
5329
+ try {
5330
+ const rawLines = fs17.readFileSync(outcomesPath, "utf8").trim().split("\n").filter(Boolean);
5331
+ const lastParty = rawLines.map((l) => {
5332
+ try {
5333
+ return JSON.parse(l);
5334
+ } catch {
5335
+ return null;
5336
+ }
5337
+ }).filter((e) => e !== null && e.type === "party").pop();
5338
+ if (lastParty) {
5339
+ const days = Math.floor((Date.now() - new Date(lastParty.ts).getTime()) / 864e5);
5340
+ const set2 = lastParty.partySet ?? "party";
5341
+ const findings = lastParty.signals?.findingsCount ?? 0;
5342
+ lines.push(` \u25CF Letzter Review (${set2}): vor ${days} Tag(en) \u2014 ${findings} Findings`);
5343
+ }
5344
+ } catch {
5345
+ }
5346
+ }
4932
5347
  return lines.join("\n");
4933
5348
  }
4934
5349
  function formatWatchSection(forgehiveDir2) {
4935
- const logPath = path15.join(forgehiveDir2, "watch-events.jsonl");
4936
- if (!fs15.existsSync(logPath)) return "";
4937
- const events = fs15.readFileSync(logPath, "utf8").trim().split("\n").filter(Boolean).map((line) => {
5350
+ const logPath = path17.join(forgehiveDir2, "watch-events.jsonl");
5351
+ if (!fs17.existsSync(logPath)) return "";
5352
+ const events = fs17.readFileSync(logPath, "utf8").trim().split("\n").filter(Boolean).map((line) => {
4938
5353
  try {
4939
5354
  return JSON.parse(line);
4940
5355
  } catch {
@@ -4958,19 +5373,20 @@ function formatDashboard(projectRoot2, forgehiveDir2) {
4958
5373
  const sections = [
4959
5374
  formatSprintSection(forgehiveDir2),
4960
5375
  formatCodeHealthSection(projectRoot2, forgehiveDir2),
4961
- formatWatchSection(forgehiveDir2)
5376
+ formatWatchSection(forgehiveDir2),
5377
+ formatNextCompact(projectRoot2, forgehiveDir2)
4962
5378
  ].filter(Boolean);
4963
5379
  if (sections.length === 0) return "";
4964
5380
  return "\n" + sections.join("\n\n");
4965
5381
  }
4966
5382
  function checkDrift(projectRoot2, forgehiveDir2) {
4967
- const scanPath = path15.join(forgehiveDir2, "scan-result.yaml");
4968
- if (!fs15.existsSync(scanPath)) {
5383
+ const scanPath = path17.join(forgehiveDir2, "scan-result.yaml");
5384
+ if (!fs17.existsSync(scanPath)) {
4969
5385
  return { daysSinceScan: null, recentCommits: 0, isDrifted: false };
4970
5386
  }
4971
5387
  let scannedAt = null;
4972
5388
  try {
4973
- const raw = jsYaml.load(fs15.readFileSync(scanPath, "utf8"));
5389
+ const raw = jsYaml.load(fs17.readFileSync(scanPath, "utf8"));
4974
5390
  scannedAt = raw?.scanned_at ?? null;
4975
5391
  } catch {
4976
5392
  }
@@ -4993,14 +5409,14 @@ function checkDrift(projectRoot2, forgehiveDir2) {
4993
5409
  }
4994
5410
  function projectStatus(projectRoot2, forgehiveDir2) {
4995
5411
  const lines = ["ForgeHive Status", ""];
4996
- if (!fs15.existsSync(forgehiveDir2)) {
5412
+ if (!fs17.existsSync(forgehiveDir2)) {
4997
5413
  lines.push("\u2717 Not initialized \u2014 run: fh init");
4998
5414
  return lines.join("\n");
4999
5415
  }
5000
- const capsPath = path15.join(forgehiveDir2, "capabilities.yaml");
5001
- if (fs15.existsSync(capsPath)) {
5416
+ const capsPath = path17.join(forgehiveDir2, "capabilities.yaml");
5417
+ if (fs17.existsSync(capsPath)) {
5002
5418
  try {
5003
- const raw = jsYaml.load(fs15.readFileSync(capsPath, "utf8"));
5419
+ const raw = jsYaml.load(fs17.readFileSync(capsPath, "utf8"));
5004
5420
  const caps = raw && typeof raw === "object" ? raw : {};
5005
5421
  const confirmed = caps.capabilities?.confirmed?.length ?? 0;
5006
5422
  const inferred = caps.capabilities?.inferred?.length ?? 0;
@@ -5011,10 +5427,10 @@ function projectStatus(projectRoot2, forgehiveDir2) {
5011
5427
  lines.push("\u26A0 Capabilities: capabilities.yaml unlesbar");
5012
5428
  }
5013
5429
  }
5014
- const claudeMdPath = path15.join(projectRoot2, "CLAUDE.md");
5015
- if (fs15.existsSync(claudeMdPath)) {
5430
+ const claudeMdPath = path17.join(projectRoot2, "CLAUDE.md");
5431
+ if (fs17.existsSync(claudeMdPath)) {
5016
5432
  try {
5017
- const content = fs15.readFileSync(claudeMdPath, "utf8");
5433
+ const content = fs17.readFileSync(claudeMdPath, "utf8");
5018
5434
  const hasBlock = content.includes("<!-- forgehive:start -->");
5019
5435
  lines.push(hasBlock ? "\u2713 CLAUDE.md block present" : "\u26A0 CLAUDE.md block missing \u2014 run: fh init");
5020
5436
  } catch {
@@ -5023,15 +5439,15 @@ function projectStatus(projectRoot2, forgehiveDir2) {
5023
5439
  } else {
5024
5440
  lines.push("\u26A0 CLAUDE.md not found");
5025
5441
  }
5026
- const skillsDir = path15.join(forgehiveDir2, "skills");
5027
- const generated = countMdFiles(path15.join(skillsDir, "generated"));
5028
- const expert = countMdFiles(path15.join(skillsDir, "expert"));
5029
- const workflows = countMdFiles(path15.join(skillsDir, "workflows"));
5442
+ const skillsDir = path17.join(forgehiveDir2, "skills");
5443
+ const generated = countMdFiles(path17.join(skillsDir, "generated"));
5444
+ const expert = countMdFiles(path17.join(skillsDir, "expert"));
5445
+ const workflows = countMdFiles(path17.join(skillsDir, "workflows"));
5030
5446
  lines.push(`\u2713 Skills: ${generated} generated, ${expert} expert, ${workflows} workflows`);
5031
- const mcpPath = path15.join(projectRoot2, ".mcp.json");
5032
- if (fs15.existsSync(mcpPath)) {
5447
+ const mcpPath = path17.join(projectRoot2, ".mcp.json");
5448
+ if (fs17.existsSync(mcpPath)) {
5033
5449
  try {
5034
- const mcp = JSON.parse(fs15.readFileSync(mcpPath, "utf8"));
5450
+ const mcp = JSON.parse(fs17.readFileSync(mcpPath, "utf8"));
5035
5451
  const services = Object.keys(mcp.mcpServers ?? {});
5036
5452
  lines.push(
5037
5453
  services.length > 0 ? `\u2713 MCP: ${services.join(", ")}` : " MCP: none configured"
@@ -5042,17 +5458,17 @@ function projectStatus(projectRoot2, forgehiveDir2) {
5042
5458
  } else {
5043
5459
  lines.push(" MCP: none configured (fh wire <service>)");
5044
5460
  }
5045
- const memoryDir = path15.join(forgehiveDir2, "memory");
5046
- if (fs15.existsSync(memoryDir)) {
5047
- const files = fs15.readdirSync(memoryDir).filter((f) => f.endsWith(".md"));
5461
+ const memoryDir = path17.join(forgehiveDir2, "memory");
5462
+ if (fs17.existsSync(memoryDir)) {
5463
+ const files = fs17.readdirSync(memoryDir).filter((f) => f.endsWith(".md"));
5048
5464
  lines.push(`\u2713 Memory: ${files.length} file(s)`);
5049
5465
  } else {
5050
5466
  lines.push(" Memory: not initialized");
5051
5467
  }
5052
- const scanPath = path15.join(forgehiveDir2, "scan-result.yaml");
5053
- if (fs15.existsSync(scanPath)) {
5468
+ const scanPath = path17.join(forgehiveDir2, "scan-result.yaml");
5469
+ if (fs17.existsSync(scanPath)) {
5054
5470
  try {
5055
- const raw = jsYaml.load(fs15.readFileSync(scanPath, "utf8"));
5471
+ const raw = jsYaml.load(fs17.readFileSync(scanPath, "utf8"));
5056
5472
  const scan2 = raw && typeof raw === "object" ? raw : {};
5057
5473
  lines.push(`\u2713 Last scan: ${scan2.scanned_at ?? "unknown"}`);
5058
5474
  } catch {
@@ -5066,24 +5482,24 @@ function projectStatus(projectRoot2, forgehiveDir2) {
5066
5482
  } else if (drift.daysSinceScan !== null) {
5067
5483
  lines.push(`\u2713 Kontext aktuell (${drift.daysSinceScan} Tage, ${drift.recentCommits} Commits seit Scan)`);
5068
5484
  }
5069
- const agentMemDir = path15.join(forgehiveDir2, "agents", "memory");
5070
- if (fs15.existsSync(agentMemDir)) {
5071
- const agents = fs15.readdirSync(agentMemDir).filter((f) => f.endsWith(".md"));
5485
+ const agentMemDir = path17.join(forgehiveDir2, "agents", "memory");
5486
+ if (fs17.existsSync(agentMemDir)) {
5487
+ const agents = fs17.readdirSync(agentMemDir).filter((f) => f.endsWith(".md"));
5072
5488
  lines.push(`\u2713 Agent memory: ${agents.length} agent(s)`);
5073
5489
  }
5074
5490
  return lines.join("\n");
5075
5491
  }
5076
5492
 
5077
5493
  // src/watch.ts
5078
- import fs16 from "node:fs";
5079
- import path16 from "node:path";
5494
+ import fs18 from "node:fs";
5495
+ import path18 from "node:path";
5080
5496
  import { spawnSync as spawnSync5 } from "node:child_process";
5081
5497
  function checkProjectHash(projectRoot2, forgehiveDir2, claudeMdBlock) {
5082
- const hashPath = path16.join(forgehiveDir2, ".scan-hash");
5083
- const savedHash = fs16.existsSync(hashPath) ? fs16.readFileSync(hashPath, "utf8").trim() : null;
5498
+ const hashPath = path18.join(forgehiveDir2, ".scan-hash");
5499
+ const savedHash = fs18.existsSync(hashPath) ? fs18.readFileSync(hashPath, "utf8").trim() : null;
5084
5500
  const currentHash = computeHash(projectRoot2);
5085
5501
  if (currentHash === savedHash) return false;
5086
- fs16.writeFileSync(hashPath, currentHash, "utf8");
5502
+ fs18.writeFileSync(hashPath, currentHash, "utf8");
5087
5503
  const scanResult = scan(projectRoot2);
5088
5504
  const capMap = mapSignalsToCapabilities(scanResult);
5089
5505
  writeForgehiveDir(projectRoot2, scanResult, capMap, claudeMdBlock);
@@ -5100,7 +5516,7 @@ function watchProject(projectRoot2, forgehiveDir2, claudeMdBlock, onUpdate = def
5100
5516
  "go.mod",
5101
5517
  "pyproject.toml",
5102
5518
  "composer.json"
5103
- ].map((f) => path16.join(projectRoot2, f)).filter((f) => fs16.existsSync(f));
5519
+ ].map((f) => path18.join(projectRoot2, f)).filter((f) => fs18.existsSync(f));
5104
5520
  let debounce = null;
5105
5521
  const watchers = [];
5106
5522
  function schedule() {
@@ -5112,12 +5528,12 @@ function watchProject(projectRoot2, forgehiveDir2, claudeMdBlock, onUpdate = def
5112
5528
  }
5113
5529
  for (const f of candidates) {
5114
5530
  try {
5115
- watchers.push(fs16.watch(f, schedule));
5531
+ watchers.push(fs18.watch(f, schedule));
5116
5532
  } catch {
5117
5533
  }
5118
5534
  }
5119
5535
  try {
5120
- watchers.push(fs16.watch(projectRoot2, schedule));
5536
+ watchers.push(fs18.watch(projectRoot2, schedule));
5121
5537
  } catch {
5122
5538
  }
5123
5539
  return () => {
@@ -5136,14 +5552,14 @@ function defaultOnUpdate(changed) {
5136
5552
  }
5137
5553
  }
5138
5554
  function appendWatchEvent(forgehiveDir2, event) {
5139
- const logPath = path16.join(forgehiveDir2, "watch-events.jsonl");
5140
- fs16.appendFileSync(logPath, JSON.stringify(event) + "\n", "utf8");
5555
+ const logPath = path18.join(forgehiveDir2, "watch-events.jsonl");
5556
+ fs18.appendFileSync(logPath, JSON.stringify(event) + "\n", "utf8");
5141
5557
  try {
5142
- const stat = fs16.statSync(logPath);
5558
+ const stat = fs18.statSync(logPath);
5143
5559
  if (stat.size > 25e3) {
5144
- const lines = fs16.readFileSync(logPath, "utf8").trim().split("\n").filter(Boolean);
5560
+ const lines = fs18.readFileSync(logPath, "utf8").trim().split("\n").filter(Boolean);
5145
5561
  if (lines.length >= 500) {
5146
- fs16.writeFileSync(logPath, lines.slice(-500).join("\n") + "\n", "utf8");
5562
+ fs18.writeFileSync(logPath, lines.slice(-500).join("\n") + "\n", "utf8");
5147
5563
  }
5148
5564
  }
5149
5565
  } catch {
@@ -5166,9 +5582,9 @@ function detectErrorType(output) {
5166
5582
  return null;
5167
5583
  }
5168
5584
  function smartWatch(projectRoot2, forgehiveDir2, testCmd, onSuggest) {
5169
- const srcDir = path16.join(projectRoot2, "src");
5170
- const testDir = path16.join(projectRoot2, "test");
5171
- const watchDirs = [srcDir, testDir].filter((d) => fs16.existsSync(d));
5585
+ const srcDir = path18.join(projectRoot2, "src");
5586
+ const testDir = path18.join(projectRoot2, "test");
5587
+ const watchDirs = [srcDir, testDir].filter((d) => fs18.existsSync(d));
5172
5588
  let debounce = null;
5173
5589
  const watchers = [];
5174
5590
  function runAndAnalyze(changedFile) {
@@ -5181,7 +5597,7 @@ function smartWatch(projectRoot2, forgehiveDir2, testCmd, onSuggest) {
5181
5597
  const output = (result.stdout ?? "") + (result.stderr ?? "");
5182
5598
  const detection = detectErrorType(output);
5183
5599
  if (!detection) return;
5184
- const relFile = path16.relative(projectRoot2, changedFile);
5600
+ const relFile = path18.relative(projectRoot2, changedFile);
5185
5601
  console.log(`
5186
5602
  \u26A0 ${relFile} \u2014 ${detection.reason}`);
5187
5603
  console.log(` \u2192 ${detection.agent} ist der richtige Agent.`);
@@ -5201,9 +5617,9 @@ function smartWatch(projectRoot2, forgehiveDir2, testCmd, onSuggest) {
5201
5617
  }
5202
5618
  for (const dir of watchDirs) {
5203
5619
  try {
5204
- const watcher = fs16.watch(dir, { recursive: true }, (_evt, filename) => {
5620
+ const watcher = fs18.watch(dir, { recursive: true }, (_evt, filename) => {
5205
5621
  if (filename && (filename.endsWith(".ts") || filename.endsWith(".js"))) {
5206
- schedule(path16.join(dir, filename));
5622
+ schedule(path18.join(dir, filename));
5207
5623
  }
5208
5624
  });
5209
5625
  watchers.push(watcher);
@@ -5222,8 +5638,8 @@ function smartWatch(projectRoot2, forgehiveDir2, testCmd, onSuggest) {
5222
5638
  }
5223
5639
 
5224
5640
  // src/cost.ts
5225
- import fs17 from "node:fs";
5226
- import path17 from "node:path";
5641
+ import fs19 from "node:fs";
5642
+ import path19 from "node:path";
5227
5643
  import os2 from "node:os";
5228
5644
  var PRICING = {
5229
5645
  input: 3,
@@ -5234,31 +5650,31 @@ var PRICING = {
5234
5650
  function calcCost(inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens) {
5235
5651
  return inputTokens / 1e6 * PRICING.input + outputTokens / 1e6 * PRICING.output + cacheCreationTokens / 1e6 * PRICING.cacheCreation + cacheReadTokens / 1e6 * PRICING.cacheRead;
5236
5652
  }
5237
- function parseCostSessions(_projectRoot, claudeHome = path17.join(os2.homedir(), ".claude")) {
5238
- const projectsDir = path17.join(claudeHome, "projects");
5239
- if (!fs17.existsSync(projectsDir)) return [];
5240
- const matchDirs = fs17.readdirSync(projectsDir);
5653
+ function parseCostSessions(_projectRoot, claudeHome = path19.join(os2.homedir(), ".claude")) {
5654
+ const projectsDir = path19.join(claudeHome, "projects");
5655
+ if (!fs19.existsSync(projectsDir)) return [];
5656
+ const matchDirs = fs19.readdirSync(projectsDir);
5241
5657
  const sessions = [];
5242
5658
  for (const dir of matchDirs) {
5243
- const dirPath = path17.join(projectsDir, dir);
5659
+ const dirPath = path19.join(projectsDir, dir);
5244
5660
  let stat;
5245
5661
  try {
5246
- stat = fs17.statSync(dirPath);
5662
+ stat = fs19.statSync(dirPath);
5247
5663
  } catch {
5248
5664
  continue;
5249
5665
  }
5250
5666
  if (!stat.isDirectory()) continue;
5251
5667
  let files;
5252
5668
  try {
5253
- files = fs17.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
5669
+ files = fs19.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
5254
5670
  } catch {
5255
5671
  continue;
5256
5672
  }
5257
5673
  for (const jsonlFile of files) {
5258
- const filePath = path17.join(dirPath, jsonlFile);
5674
+ const filePath = path19.join(dirPath, jsonlFile);
5259
5675
  let inputTokens = 0, outputTokens = 0, cacheCreationTokens = 0, cacheReadTokens = 0;
5260
5676
  try {
5261
- const lines = fs17.readFileSync(filePath, "utf8").split("\n").filter(Boolean);
5677
+ const lines = fs19.readFileSync(filePath, "utf8").split("\n").filter(Boolean);
5262
5678
  for (const line of lines) {
5263
5679
  try {
5264
5680
  const entry = JSON.parse(line);
@@ -5271,7 +5687,7 @@ function parseCostSessions(_projectRoot, claudeHome = path17.join(os2.homedir(),
5271
5687
  } catch {
5272
5688
  }
5273
5689
  }
5274
- const mtime = fs17.statSync(filePath).mtime;
5690
+ const mtime = fs19.statSync(filePath).mtime;
5275
5691
  sessions.push({
5276
5692
  sessionFile: jsonlFile,
5277
5693
  date: mtime.toISOString().slice(0, 10),
@@ -5319,28 +5735,28 @@ function formatCostReport(sessions, range) {
5319
5735
  }
5320
5736
 
5321
5737
  // src/mcp-auth.ts
5322
- import fs18 from "node:fs";
5323
- import path18 from "node:path";
5738
+ import fs20 from "node:fs";
5739
+ import path20 from "node:path";
5324
5740
  import os3 from "node:os";
5325
5741
  function getCredsPath() {
5326
- const dir = path18.join(os3.homedir(), ".forgehive");
5327
- fs18.mkdirSync(dir, { recursive: true });
5328
- return path18.join(dir, "credentials.json");
5742
+ const dir = path20.join(os3.homedir(), ".forgehive");
5743
+ fs20.mkdirSync(dir, { recursive: true });
5744
+ return path20.join(dir, "credentials.json");
5329
5745
  }
5330
5746
  function loadStore() {
5331
5747
  const p = getCredsPath();
5332
- if (!fs18.existsSync(p)) return {};
5748
+ if (!fs20.existsSync(p)) return {};
5333
5749
  try {
5334
- return JSON.parse(fs18.readFileSync(p, "utf8"));
5750
+ return JSON.parse(fs20.readFileSync(p, "utf8"));
5335
5751
  } catch {
5336
5752
  return {};
5337
5753
  }
5338
5754
  }
5339
5755
  function saveStore(store) {
5340
5756
  const p = getCredsPath();
5341
- fs18.writeFileSync(p, JSON.stringify(store, null, 2), "utf8");
5757
+ fs20.writeFileSync(p, JSON.stringify(store, null, 2), "utf8");
5342
5758
  try {
5343
- fs18.chmodSync(p, 384);
5759
+ fs20.chmodSync(p, 384);
5344
5760
  } catch {
5345
5761
  }
5346
5762
  }
@@ -5387,8 +5803,8 @@ function checkMcpTrust(packageName) {
5387
5803
  }
5388
5804
 
5389
5805
  // src/mcp-registry.ts
5390
- import fs19 from "node:fs";
5391
- import path19 from "node:path";
5806
+ import fs21 from "node:fs";
5807
+ import path21 from "node:path";
5392
5808
  import { spawnSync as spawnSync6 } from "node:child_process";
5393
5809
  function parseRegistryResponse(raw) {
5394
5810
  if (!raw || typeof raw !== "object") return [];
@@ -5425,11 +5841,11 @@ function searchRegistry(query) {
5425
5841
  }
5426
5842
  function addMcpFromRegistry(projectRoot2, forgehiveDir2, packageName, envKeys) {
5427
5843
  const serverId = packageName.replace(/^@[^/]+\//, "") || packageName;
5428
- const mcpPath = path19.join(projectRoot2, ".mcp.json");
5844
+ const mcpPath = path21.join(projectRoot2, ".mcp.json");
5429
5845
  let mcpConfig = { mcpServers: {} };
5430
- if (fs19.existsSync(mcpPath)) {
5846
+ if (fs21.existsSync(mcpPath)) {
5431
5847
  try {
5432
- mcpConfig = JSON.parse(fs19.readFileSync(mcpPath, "utf8"));
5848
+ mcpConfig = JSON.parse(fs21.readFileSync(mcpPath, "utf8"));
5433
5849
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
5434
5850
  } catch {
5435
5851
  mcpConfig = { mcpServers: {} };
@@ -5444,12 +5860,12 @@ function addMcpFromRegistry(projectRoot2, forgehiveDir2, packageName, envKeys) {
5444
5860
  args: ["-y", packageName],
5445
5861
  ...envKeys.length > 0 ? { env: envObj } : {}
5446
5862
  };
5447
- fs19.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
5448
- const skillDir = path19.join(forgehiveDir2, "skills", "workflows");
5449
- fs19.mkdirSync(skillDir, { recursive: true });
5450
- const skillPath = path19.join(skillDir, `${serverId}.md`);
5451
- if (!fs19.existsSync(skillPath)) {
5452
- fs19.writeFileSync(skillPath, [
5863
+ fs21.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
5864
+ const skillDir = path21.join(forgehiveDir2, "skills", "workflows");
5865
+ fs21.mkdirSync(skillDir, { recursive: true });
5866
+ const skillPath = path21.join(skillDir, `${serverId}.md`);
5867
+ if (!fs21.existsSync(skillPath)) {
5868
+ fs21.writeFileSync(skillPath, [
5453
5869
  `# ${serverId} Workflow Skill`,
5454
5870
  "",
5455
5871
  `## Setup`,
@@ -5464,8 +5880,8 @@ function addMcpFromRegistry(projectRoot2, forgehiveDir2, packageName, envKeys) {
5464
5880
  }
5465
5881
 
5466
5882
  // src/security-scan.ts
5467
- import fs20 from "node:fs";
5468
- import path20 from "node:path";
5883
+ import fs22 from "node:fs";
5884
+ import path22 from "node:path";
5469
5885
  import { spawnSync as spawnSync7 } from "node:child_process";
5470
5886
  var SECRET_PATTERNS = [
5471
5887
  { name: "Anthropic Key", pattern: /sk-ant-[a-zA-Z0-9-]{20,}/g },
@@ -5484,16 +5900,16 @@ function collectScanFiles(dir, exts = SCAN_EXTS) {
5484
5900
  function walk(current) {
5485
5901
  let entries;
5486
5902
  try {
5487
- entries = fs20.readdirSync(current, { withFileTypes: true });
5903
+ entries = fs22.readdirSync(current, { withFileTypes: true });
5488
5904
  } catch {
5489
5905
  return;
5490
5906
  }
5491
5907
  for (const entry of entries) {
5492
5908
  if (IGNORE_DIRS.includes(entry.name)) continue;
5493
- const full = path20.join(current, entry.name);
5909
+ const full = path22.join(current, entry.name);
5494
5910
  if (entry.isDirectory()) {
5495
5911
  walk(full);
5496
- } else if (entry.isFile() && exts.includes(path20.extname(entry.name))) {
5912
+ } else if (entry.isFile() && exts.includes(path22.extname(entry.name))) {
5497
5913
  results.push(full);
5498
5914
  }
5499
5915
  }
@@ -5507,7 +5923,7 @@ function scanSecrets(projectRoot2) {
5507
5923
  for (const file of files) {
5508
5924
  let content;
5509
5925
  try {
5510
- content = fs20.readFileSync(file, "utf8");
5926
+ content = fs22.readFileSync(file, "utf8");
5511
5927
  } catch {
5512
5928
  continue;
5513
5929
  }
@@ -5574,7 +5990,7 @@ function scanSast(projectRoot2) {
5574
5990
  for (const file of files) {
5575
5991
  let content;
5576
5992
  try {
5577
- content = fs20.readFileSync(file, "utf8");
5993
+ content = fs22.readFileSync(file, "utf8");
5578
5994
  } catch {
5579
5995
  continue;
5580
5996
  }
@@ -5599,8 +6015,8 @@ function scanSast(projectRoot2) {
5599
6015
  return findings;
5600
6016
  }
5601
6017
  function scanDeps(projectRoot2) {
5602
- const pkgJsonPath = path20.join(projectRoot2, "package.json");
5603
- if (!fs20.existsSync(pkgJsonPath)) return [];
6018
+ const pkgJsonPath = path22.join(projectRoot2, "package.json");
6019
+ if (!fs22.existsSync(pkgJsonPath)) return [];
5604
6020
  const result = spawnSync7("npm", ["audit", "--json"], {
5605
6021
  cwd: projectRoot2,
5606
6022
  encoding: "utf8",
@@ -5629,19 +6045,19 @@ function scanDeps(projectRoot2) {
5629
6045
  }
5630
6046
 
5631
6047
  // src/audit.ts
5632
- import fs21 from "node:fs";
5633
- import path21 from "node:path";
6048
+ import fs23 from "node:fs";
6049
+ import path23 from "node:path";
5634
6050
  function getAuditPath(forgehiveDir2) {
5635
- return path21.join(forgehiveDir2, "audit.log");
6051
+ return path23.join(forgehiveDir2, "audit.log");
5636
6052
  }
5637
6053
  function logAuditEvent(forgehiveDir2, event) {
5638
6054
  const line = JSON.stringify(event) + "\n";
5639
- fs21.appendFileSync(getAuditPath(forgehiveDir2), line, "utf8");
6055
+ fs23.appendFileSync(getAuditPath(forgehiveDir2), line, "utf8");
5640
6056
  }
5641
6057
  function getAuditLog(forgehiveDir2, limit = 50) {
5642
6058
  const p = getAuditPath(forgehiveDir2);
5643
- if (!fs21.existsSync(p)) return [];
5644
- const lines = fs21.readFileSync(p, "utf8").trim().split("\n").filter(Boolean);
6059
+ if (!fs23.existsSync(p)) return [];
6060
+ const lines = fs23.readFileSync(p, "utf8").trim().split("\n").filter(Boolean);
5645
6061
  const recent = lines.slice(-limit);
5646
6062
  return recent.map((l) => {
5647
6063
  try {
@@ -5666,19 +6082,19 @@ function formatAuditReport(events) {
5666
6082
 
5667
6083
  // src/spend-limits.ts
5668
6084
  init_js_yaml();
5669
- import fs22 from "node:fs";
5670
- import path22 from "node:path";
6085
+ import fs24 from "node:fs";
6086
+ import path24 from "node:path";
5671
6087
  function getConfigPath(forgehiveDir2) {
5672
- return path22.join(forgehiveDir2, "cost-config.yaml");
6088
+ return path24.join(forgehiveDir2, "cost-config.yaml");
5673
6089
  }
5674
6090
  function setSpendLimit(forgehiveDir2, config) {
5675
- fs22.writeFileSync(getConfigPath(forgehiveDir2), jsYaml.dump(config), "utf8");
6091
+ fs24.writeFileSync(getConfigPath(forgehiveDir2), jsYaml.dump(config), "utf8");
5676
6092
  }
5677
6093
  function getSpendConfig(forgehiveDir2) {
5678
6094
  const p = getConfigPath(forgehiveDir2);
5679
- if (!fs22.existsSync(p)) return {};
6095
+ if (!fs24.existsSync(p)) return {};
5680
6096
  try {
5681
- return jsYaml.load(fs22.readFileSync(p, "utf8"));
6097
+ return jsYaml.load(fs24.readFileSync(p, "utf8"));
5682
6098
  } catch {
5683
6099
  return {};
5684
6100
  }
@@ -5773,7 +6189,7 @@ function formatSecurityReport(report) {
5773
6189
  }
5774
6190
 
5775
6191
  // src/ci.ts
5776
- import path23 from "node:path";
6192
+ import path25 from "node:path";
5777
6193
  function generateCiReport(projectRoot2, forgehiveDir2, failOn = "high") {
5778
6194
  const secrets = scanSecrets(projectRoot2);
5779
6195
  const sast = scanSast(projectRoot2);
@@ -5790,7 +6206,7 @@ function generateCiReport(projectRoot2, forgehiveDir2, failOn = "high") {
5790
6206
  }
5791
6207
  return {
5792
6208
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5793
- project: path23.basename(projectRoot2),
6209
+ project: path25.basename(projectRoot2),
5794
6210
  status: failedOn ? "fail" : "pass",
5795
6211
  secrets,
5796
6212
  sast,
@@ -5864,8 +6280,8 @@ jobs:
5864
6280
  }
5865
6281
 
5866
6282
  // src/map.ts
5867
- import fs23 from "node:fs";
5868
- import path24 from "node:path";
6283
+ import fs25 from "node:fs";
6284
+ import path26 from "node:path";
5869
6285
  var MAP_EXTS = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"];
5870
6286
  var IGNORE_DIRS2 = ["node_modules", ".git", "dist", ".forgehive", "coverage", ".next", "build"];
5871
6287
  var IMPORT_PATTERNS = [
@@ -5887,11 +6303,11 @@ function extractImports(content) {
5887
6303
  function walkFiles(dir) {
5888
6304
  const results = [];
5889
6305
  function walk(current) {
5890
- for (const entry of fs23.readdirSync(current, { withFileTypes: true })) {
6306
+ for (const entry of fs25.readdirSync(current, { withFileTypes: true })) {
5891
6307
  if (IGNORE_DIRS2.includes(entry.name)) continue;
5892
- const full = path24.join(current, entry.name);
6308
+ const full = path26.join(current, entry.name);
5893
6309
  if (entry.isDirectory()) walk(full);
5894
- else if (entry.isFile() && MAP_EXTS.includes(path24.extname(entry.name)))
6310
+ else if (entry.isFile() && MAP_EXTS.includes(path26.extname(entry.name)))
5895
6311
  results.push(full);
5896
6312
  }
5897
6313
  }
@@ -5903,7 +6319,7 @@ function generateMap(projectRoot2) {
5903
6319
  const files = filePaths.map((filePath) => {
5904
6320
  let content = "";
5905
6321
  try {
5906
- content = fs23.readFileSync(filePath, "utf8");
6322
+ content = fs25.readFileSync(filePath, "utf8");
5907
6323
  } catch {
5908
6324
  }
5909
6325
  const rawLines = content.split("\n");
@@ -5911,7 +6327,7 @@ function generateMap(projectRoot2) {
5911
6327
  const imports = extractImports(content);
5912
6328
  return {
5913
6329
  path: filePath,
5914
- relativePath: path24.relative(projectRoot2, filePath),
6330
+ relativePath: path26.relative(projectRoot2, filePath),
5915
6331
  lines,
5916
6332
  imports
5917
6333
  };
@@ -6000,8 +6416,8 @@ function formatMap(map2, semantic) {
6000
6416
  }
6001
6417
 
6002
6418
  // src/semantic-map.ts
6003
- import fs24 from "node:fs";
6004
- import path25 from "node:path";
6419
+ import fs26 from "node:fs";
6420
+ import path27 from "node:path";
6005
6421
  var IGNORE_DIRS3 = ["node_modules", ".git", "dist", ".forgehive", "coverage", ".next", "build"];
6006
6422
  var MAP_EXTS2 = [".ts", ".tsx", ".js", ".jsx", ".py", ".go"];
6007
6423
  var IMPORT_PATTERNS2 = {
@@ -6052,15 +6468,15 @@ function walkFiles2(dir) {
6052
6468
  function walk(current) {
6053
6469
  let entries;
6054
6470
  try {
6055
- entries = fs24.readdirSync(current, { withFileTypes: true });
6471
+ entries = fs26.readdirSync(current, { withFileTypes: true });
6056
6472
  } catch {
6057
6473
  return;
6058
6474
  }
6059
6475
  for (const entry of entries) {
6060
6476
  if (IGNORE_DIRS3.includes(entry.name)) continue;
6061
- const full = path25.join(current, entry.name);
6477
+ const full = path27.join(current, entry.name);
6062
6478
  if (entry.isDirectory()) walk(full);
6063
- else if (entry.isFile() && MAP_EXTS2.includes(path25.extname(entry.name))) {
6479
+ else if (entry.isFile() && MAP_EXTS2.includes(path27.extname(entry.name))) {
6064
6480
  results.push(full);
6065
6481
  }
6066
6482
  }
@@ -6106,15 +6522,15 @@ function _extractExports(content, lang) {
6106
6522
  }
6107
6523
  function _resolveImport(fromFile, importPath, allFiles) {
6108
6524
  if (!importPath.startsWith(".")) return null;
6109
- const fromDir = path25.dirname(fromFile);
6110
- const base = path25.resolve(fromDir, importPath);
6525
+ const fromDir = path27.dirname(fromFile);
6526
+ const base = path27.resolve(fromDir, importPath);
6111
6527
  if (allFiles.includes(base)) return base;
6112
6528
  for (const ext of [".ts", ".tsx", ".js", ".jsx", ".py", ".go"]) {
6113
6529
  const candidate = base + ext;
6114
6530
  if (allFiles.includes(candidate)) return candidate;
6115
6531
  }
6116
6532
  for (const ext of [".ts", ".tsx", ".js", ".jsx"]) {
6117
- const candidate = path25.join(base, "index" + ext);
6533
+ const candidate = path27.join(base, "index" + ext);
6118
6534
  if (allFiles.includes(candidate)) return candidate;
6119
6535
  }
6120
6536
  return null;
@@ -6162,10 +6578,10 @@ function buildSemanticMap(projectRoot2) {
6162
6578
  for (const filePath of allFiles) {
6163
6579
  let content = "";
6164
6580
  try {
6165
- content = fs24.readFileSync(filePath, "utf8");
6581
+ content = fs26.readFileSync(filePath, "utf8");
6166
6582
  } catch {
6167
6583
  }
6168
- const ext = path25.extname(filePath);
6584
+ const ext = path27.extname(filePath);
6169
6585
  const lang = langOf(ext);
6170
6586
  const rawImps = _extractImports(content, lang);
6171
6587
  const resolvedImps = [];
@@ -6183,8 +6599,8 @@ function buildSemanticMap(projectRoot2) {
6183
6599
 
6184
6600
  // src/onboard.ts
6185
6601
  init_js_yaml();
6186
- import fs25 from "node:fs";
6187
- import path26 from "node:path";
6602
+ import fs27 from "node:fs";
6603
+ import path28 from "node:path";
6188
6604
  import { spawnSync as spawnSync8 } from "node:child_process";
6189
6605
  function getRecentCommits(projectRoot2, n = 20) {
6190
6606
  const result = spawnSync8("git", ["log", `--oneline`, `-${n}`], {
@@ -6195,30 +6611,30 @@ function getRecentCommits(projectRoot2, n = 20) {
6195
6611
  return result.stdout.trim().split("\n").filter(Boolean);
6196
6612
  }
6197
6613
  function readCapabilities(forgehiveDir2) {
6198
- const capPath = path26.join(forgehiveDir2, "capabilities.yaml");
6199
- if (!fs25.existsSync(capPath)) return {};
6614
+ const capPath = path28.join(forgehiveDir2, "capabilities.yaml");
6615
+ if (!fs27.existsSync(capPath)) return {};
6200
6616
  try {
6201
- return jsYaml.load(fs25.readFileSync(capPath, "utf8")) ?? {};
6617
+ return jsYaml.load(fs27.readFileSync(capPath, "utf8")) ?? {};
6202
6618
  } catch {
6203
6619
  return {};
6204
6620
  }
6205
6621
  }
6206
6622
  function readMemoryFiles(forgehiveDir2) {
6207
- const memDir = path26.join(forgehiveDir2, "memory");
6208
- if (!fs25.existsSync(memDir)) return {};
6623
+ const memDir = path28.join(forgehiveDir2, "memory");
6624
+ if (!fs27.existsSync(memDir)) return {};
6209
6625
  const result = {};
6210
- for (const f of fs25.readdirSync(memDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md")) {
6211
- result[f] = fs25.readFileSync(path26.join(memDir, f), "utf8");
6626
+ for (const f of fs27.readdirSync(memDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md")) {
6627
+ result[f] = fs27.readFileSync(path28.join(memDir, f), "utf8");
6212
6628
  }
6213
6629
  return result;
6214
6630
  }
6215
6631
  function listAdrs2(forgehiveDir2) {
6216
- const adrsDir = path26.join(forgehiveDir2, "memory", "adrs");
6217
- if (!fs25.existsSync(adrsDir)) return [];
6218
- return fs25.readdirSync(adrsDir).filter((f) => f.endsWith(".md"));
6632
+ const adrsDir = path28.join(forgehiveDir2, "memory", "adrs");
6633
+ if (!fs27.existsSync(adrsDir)) return [];
6634
+ return fs27.readdirSync(adrsDir).filter((f) => f.endsWith(".md"));
6219
6635
  }
6220
6636
  function generateOnboardingDoc(projectRoot2, forgehiveDir2) {
6221
- const projectName = path26.basename(projectRoot2);
6637
+ const projectName = path28.basename(projectRoot2);
6222
6638
  const caps = readCapabilities(forgehiveDir2);
6223
6639
  const memFiles = readMemoryFiles(forgehiveDir2);
6224
6640
  const commits = getRecentCommits(projectRoot2);
@@ -6433,13 +6849,13 @@ function getMetricsGitLog(projectRoot2, since) {
6433
6849
  }
6434
6850
 
6435
6851
  // src/sync.ts
6436
- import fs26 from "node:fs";
6437
- import path27 from "node:path";
6852
+ import fs28 from "node:fs";
6853
+ import path29 from "node:path";
6438
6854
  import { spawnSync as spawnSync11 } from "node:child_process";
6439
6855
  function getSyncStatus(forgehiveDir2) {
6440
- const memDir = path27.join(forgehiveDir2, "memory");
6441
- const files = fs26.existsSync(memDir) ? fs26.readdirSync(memDir).filter((f) => f.endsWith(".md")).length : 0;
6442
- const projectRoot2 = path27.dirname(forgehiveDir2);
6856
+ const memDir = path29.join(forgehiveDir2, "memory");
6857
+ const files = fs28.existsSync(memDir) ? fs28.readdirSync(memDir).filter((f) => f.endsWith(".md")).length : 0;
6858
+ const projectRoot2 = path29.dirname(forgehiveDir2);
6443
6859
  const configResult = spawnSync11(
6444
6860
  "git",
6445
6861
  ["config", "--get", "forgehive.sync-remote"],
@@ -6455,18 +6871,18 @@ function getSyncStatus(forgehiveDir2) {
6455
6871
  return { files, hasRemote: remote !== null, remote, branch };
6456
6872
  }
6457
6873
  function pushSync(forgehiveDir2, remote = "origin", branch = "forgehive-memory") {
6458
- const projectRoot2 = path27.dirname(forgehiveDir2);
6459
- const memDir = path27.join(forgehiveDir2, "memory");
6460
- if (!fs26.existsSync(memDir)) {
6874
+ const projectRoot2 = path29.dirname(forgehiveDir2);
6875
+ const memDir = path29.join(forgehiveDir2, "memory");
6876
+ if (!fs28.existsSync(memDir)) {
6461
6877
  return { success: false, message: "Kein Memory-Verzeichnis gefunden.", filesCommitted: 0 };
6462
6878
  }
6463
- const files = fs26.readdirSync(memDir).filter((f) => f.endsWith(".md"));
6879
+ const files = fs28.readdirSync(memDir).filter((f) => f.endsWith(".md"));
6464
6880
  if (files.length === 0) {
6465
6881
  return { success: false, message: "Keine Memory-Dateien gefunden.", filesCommitted: 0 };
6466
6882
  }
6467
6883
  const addResult = spawnSync11(
6468
6884
  "git",
6469
- ["add", path27.join(".forgehive", "memory")],
6885
+ ["add", path29.join(".forgehive", "memory")],
6470
6886
  { cwd: projectRoot2, encoding: "utf8" }
6471
6887
  );
6472
6888
  if (addResult.status !== 0) {
@@ -6494,9 +6910,9 @@ function pushSync(forgehiveDir2, remote = "origin", branch = "forgehive-memory")
6494
6910
  return { success: true, message: `Memory gepusht nach ${remote}/${branch}`, filesCommitted: files.length };
6495
6911
  }
6496
6912
  function pullSync(forgehiveDir2, remote = "origin", branch = "forgehive-memory") {
6497
- const projectRoot2 = path27.dirname(forgehiveDir2);
6498
- const memDir = path27.join(forgehiveDir2, "memory");
6499
- fs26.mkdirSync(memDir, { recursive: true });
6913
+ const projectRoot2 = path29.dirname(forgehiveDir2);
6914
+ const memDir = path29.join(forgehiveDir2, "memory");
6915
+ fs28.mkdirSync(memDir, { recursive: true });
6500
6916
  const fetchResult = spawnSync11(
6501
6917
  "git",
6502
6918
  ["fetch", remote, branch],
@@ -6516,16 +6932,16 @@ function pullSync(forgehiveDir2, remote = "origin", branch = "forgehive-memory")
6516
6932
  const remoteFiles = listResult.stdout.trim().split("\n").filter(Boolean);
6517
6933
  const imported = [];
6518
6934
  for (const remotePath of remoteFiles) {
6519
- const filename = path27.basename(remotePath);
6520
- const localPath = path27.join(memDir, filename);
6521
- if (!fs26.existsSync(localPath)) {
6935
+ const filename = path29.basename(remotePath);
6936
+ const localPath = path29.join(memDir, filename);
6937
+ if (!fs28.existsSync(localPath)) {
6522
6938
  const contentResult = spawnSync11(
6523
6939
  "git",
6524
6940
  ["show", `${remote}/${branch}:${remotePath}`],
6525
6941
  { cwd: projectRoot2, encoding: "utf8" }
6526
6942
  );
6527
6943
  if (contentResult.status === 0) {
6528
- fs26.writeFileSync(localPath, contentResult.stdout, "utf8");
6944
+ fs28.writeFileSync(localPath, contentResult.stdout, "utf8");
6529
6945
  imported.push(filename);
6530
6946
  }
6531
6947
  }
@@ -6538,8 +6954,8 @@ function pullSync(forgehiveDir2, remote = "origin", branch = "forgehive-memory")
6538
6954
  }
6539
6955
 
6540
6956
  // src/background.ts
6541
- import fs27 from "node:fs";
6542
- import path28 from "node:path";
6957
+ import fs29 from "node:fs";
6958
+ import path30 from "node:path";
6543
6959
  import { spawn } from "node:child_process";
6544
6960
  var AGENT_ROLES = {
6545
6961
  kai: "Senior Engineer \u2014 implements features and fixes bugs",
@@ -6586,23 +7002,23 @@ Instructions:
6586
7002
  Work autonomously. Do not ask for clarification \u2014 use your best judgment based on the issue description and codebase context.`;
6587
7003
  }
6588
7004
  function runBackgroundAgent(forgehiveDir2, issueUrl, agentId) {
6589
- const logsDir = path28.join(forgehiveDir2, "background-runs");
6590
- fs27.mkdirSync(logsDir, { recursive: true });
7005
+ const logsDir = path30.join(forgehiveDir2, "background-runs");
7006
+ fs29.mkdirSync(logsDir, { recursive: true });
6591
7007
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
6592
- const logFile = path28.join(logsDir, `${agentId}-${timestamp2}.log`);
7008
+ const logFile = path30.join(logsDir, `${agentId}-${timestamp2}.log`);
6593
7009
  const prompt = buildAgentPrompt(issueUrl, agentId);
6594
- const logStream = fs27.openSync(logFile, "w");
7010
+ const logStream = fs29.openSync(logFile, "w");
6595
7011
  const child = spawn(
6596
7012
  "claude",
6597
7013
  ["-p", prompt, "--output-format", "text"],
6598
7014
  {
6599
- cwd: path28.dirname(forgehiveDir2),
7015
+ cwd: path30.dirname(forgehiveDir2),
6600
7016
  detached: true,
6601
7017
  stdio: ["ignore", logStream, logStream]
6602
7018
  }
6603
7019
  );
6604
7020
  child.unref();
6605
- fs27.closeSync(logStream);
7021
+ fs29.closeSync(logStream);
6606
7022
  return {
6607
7023
  pid: child.pid,
6608
7024
  logFile,
@@ -6612,11 +7028,11 @@ function runBackgroundAgent(forgehiveDir2, issueUrl, agentId) {
6612
7028
 
6613
7029
  // src/stories.ts
6614
7030
  init_js_yaml();
6615
- import fs28 from "node:fs";
6616
- import path29 from "node:path";
7031
+ import fs30 from "node:fs";
7032
+ import path31 from "node:path";
6617
7033
  function nextStoryId(storiesDir) {
6618
- if (!fs28.existsSync(storiesDir)) return "US-1";
6619
- const existing = fs28.readdirSync(storiesDir).filter((f) => f.match(/^US-\d+\.md$/)).map((f) => parseInt(f.replace("US-", "").replace(".md", ""), 10)).filter((n) => !isNaN(n));
7034
+ if (!fs30.existsSync(storiesDir)) return "US-1";
7035
+ const existing = fs30.readdirSync(storiesDir).filter((f) => f.match(/^US-\d+\.md$/)).map((f) => parseInt(f.replace("US-", "").replace(".md", ""), 10)).filter((n) => !isNaN(n));
6620
7036
  const max = existing.length > 0 ? Math.max(...existing) : 0;
6621
7037
  return `US-${max + 1}`;
6622
7038
  }
@@ -6648,7 +7064,7 @@ ${acLines || "- [ ] (noch nicht definiert)"}
6648
7064
  }
6649
7065
  function parseStoryFile(filePath) {
6650
7066
  try {
6651
- const content = fs28.readFileSync(filePath, "utf8");
7067
+ const content = fs30.readFileSync(filePath, "utf8");
6652
7068
  const match = content.match(/^---\n([\s\S]*?)\n---/);
6653
7069
  if (!match) return null;
6654
7070
  const data = jsYaml.load(match[1]);
@@ -6668,7 +7084,7 @@ function parseStoryFile(filePath) {
6668
7084
  }
6669
7085
  }
6670
7086
  function createStory(storiesDir, title, epicId) {
6671
- fs28.mkdirSync(storiesDir, { recursive: true });
7087
+ fs30.mkdirSync(storiesDir, { recursive: true });
6672
7088
  const id = nextStoryId(storiesDir);
6673
7089
  const story = {
6674
7090
  id,
@@ -6681,33 +7097,33 @@ function createStory(storiesDir, title, epicId) {
6681
7097
  epicId: epicId ?? null,
6682
7098
  status: "backlog"
6683
7099
  };
6684
- fs28.writeFileSync(path29.join(storiesDir, `${id}.md`), storyToMarkdown(story), "utf8");
7100
+ fs30.writeFileSync(path31.join(storiesDir, `${id}.md`), storyToMarkdown(story), "utf8");
6685
7101
  return story;
6686
7102
  }
6687
7103
  function listStories(storiesDir) {
6688
- if (!fs28.existsSync(storiesDir)) return [];
6689
- return fs28.readdirSync(storiesDir).filter((f) => f.match(/^US-\d+\.md$/)).map((f) => parseStoryFile(path29.join(storiesDir, f))).filter((s) => s !== null).sort((a, b) => {
7104
+ if (!fs30.existsSync(storiesDir)) return [];
7105
+ return fs30.readdirSync(storiesDir).filter((f) => f.match(/^US-\d+\.md$/)).map((f) => parseStoryFile(path31.join(storiesDir, f))).filter((s) => s !== null).sort((a, b) => {
6690
7106
  const na = parseInt(a.id.replace("US-", ""), 10);
6691
7107
  const nb = parseInt(b.id.replace("US-", ""), 10);
6692
7108
  return na - nb;
6693
7109
  });
6694
7110
  }
6695
7111
  function getStory(storiesDir, id) {
6696
- const filePath = path29.join(storiesDir, `${id}.md`);
6697
- if (!fs28.existsSync(filePath)) return null;
7112
+ const filePath = path31.join(storiesDir, `${id}.md`);
7113
+ if (!fs30.existsSync(filePath)) return null;
6698
7114
  return parseStoryFile(filePath);
6699
7115
  }
6700
7116
  function updateStoryPoints(storiesDir, id, points) {
6701
7117
  const story = getStory(storiesDir, id);
6702
7118
  if (!story) throw new Error(`Story ${id} nicht gefunden`);
6703
7119
  story.points = points;
6704
- fs28.writeFileSync(path29.join(storiesDir, `${id}.md`), storyToMarkdown(story), "utf8");
7120
+ fs30.writeFileSync(path31.join(storiesDir, `${id}.md`), storyToMarkdown(story), "utf8");
6705
7121
  }
6706
7122
  function updateStoryStatus(storiesDir, id, status) {
6707
7123
  const story = getStory(storiesDir, id);
6708
7124
  if (!story) throw new Error(`Story ${id} nicht gefunden`);
6709
7125
  story.status = status;
6710
- fs28.writeFileSync(path29.join(storiesDir, `${id}.md`), storyToMarkdown(story), "utf8");
7126
+ fs30.writeFileSync(path31.join(storiesDir, `${id}.md`), storyToMarkdown(story), "utf8");
6711
7127
  }
6712
7128
  function formatStoryCard(story) {
6713
7129
  const points = story.points !== null ? ` \xB7 ${story.points} Punkte` : "";
@@ -6726,11 +7142,11 @@ function formatStoryCard(story) {
6726
7142
 
6727
7143
  // src/epics.ts
6728
7144
  init_js_yaml();
6729
- import fs29 from "node:fs";
6730
- import path30 from "node:path";
7145
+ import fs31 from "node:fs";
7146
+ import path32 from "node:path";
6731
7147
  function nextEpicId(epicsDir) {
6732
- if (!fs29.existsSync(epicsDir)) return "EPC-1";
6733
- const existing = fs29.readdirSync(epicsDir).filter((f) => f.match(/^EPC-\d+\.md$/)).map((f) => parseInt(f.replace("EPC-", "").replace(".md", ""), 10)).filter((n) => !isNaN(n));
7148
+ if (!fs31.existsSync(epicsDir)) return "EPC-1";
7149
+ const existing = fs31.readdirSync(epicsDir).filter((f) => f.match(/^EPC-\d+\.md$/)).map((f) => parseInt(f.replace("EPC-", "").replace(".md", ""), 10)).filter((n) => !isNaN(n));
6734
7150
  const max = existing.length > 0 ? Math.max(...existing) : 0;
6735
7151
  return `EPC-${max + 1}`;
6736
7152
  }
@@ -6758,7 +7174,7 @@ ${storyLines || "(noch keine Stories)"}
6758
7174
  }
6759
7175
  function parseEpicFile(filePath) {
6760
7176
  try {
6761
- const content = fs29.readFileSync(filePath, "utf8");
7177
+ const content = fs31.readFileSync(filePath, "utf8");
6762
7178
  const match = content.match(/^---\n([\s\S]*?)\n---/);
6763
7179
  if (!match) return null;
6764
7180
  const data = jsYaml.load(match[1]);
@@ -6775,23 +7191,23 @@ function parseEpicFile(filePath) {
6775
7191
  }
6776
7192
  }
6777
7193
  function createEpic(epicsDir, title, goal, prdId) {
6778
- fs29.mkdirSync(epicsDir, { recursive: true });
7194
+ fs31.mkdirSync(epicsDir, { recursive: true });
6779
7195
  const id = nextEpicId(epicsDir);
6780
7196
  const epic = { id, title, goal: goal ?? "", stories: [], status: "active", prdId: prdId ?? null };
6781
- fs29.writeFileSync(path30.join(epicsDir, `${id}.md`), epicToMarkdown(epic), "utf8");
7197
+ fs31.writeFileSync(path32.join(epicsDir, `${id}.md`), epicToMarkdown(epic), "utf8");
6782
7198
  return epic;
6783
7199
  }
6784
7200
  function listEpics(epicsDir) {
6785
- if (!fs29.existsSync(epicsDir)) return [];
6786
- return fs29.readdirSync(epicsDir).filter((f) => f.match(/^EPC-\d+\.md$/)).map((f) => parseEpicFile(path30.join(epicsDir, f))).filter((e) => e !== null).sort((a, b) => {
7201
+ if (!fs31.existsSync(epicsDir)) return [];
7202
+ return fs31.readdirSync(epicsDir).filter((f) => f.match(/^EPC-\d+\.md$/)).map((f) => parseEpicFile(path32.join(epicsDir, f))).filter((e) => e !== null).sort((a, b) => {
6787
7203
  const na = parseInt(a.id.replace("EPC-", ""), 10);
6788
7204
  const nb = parseInt(b.id.replace("EPC-", ""), 10);
6789
7205
  return na - nb;
6790
7206
  });
6791
7207
  }
6792
7208
  function getEpic(epicsDir, id) {
6793
- const filePath = path30.join(epicsDir, `${id}.md`);
6794
- if (!fs29.existsSync(filePath)) return null;
7209
+ const filePath = path32.join(epicsDir, `${id}.md`);
7210
+ if (!fs31.existsSync(filePath)) return null;
6795
7211
  return parseEpicFile(filePath);
6796
7212
  }
6797
7213
  function formatEpicCard(epic, stories) {
@@ -6815,14 +7231,14 @@ function formatEpicCard(epic, stories) {
6815
7231
 
6816
7232
  // src/product.ts
6817
7233
  init_js_yaml();
6818
- import fs30 from "node:fs";
6819
- import path31 from "node:path";
7234
+ import fs32 from "node:fs";
7235
+ import path33 from "node:path";
6820
7236
  function slugify2(title) {
6821
7237
  return title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-");
6822
7238
  }
6823
7239
  function nextPrdId(prdsDir) {
6824
- if (!fs30.existsSync(prdsDir)) return "PRD-1";
6825
- const existing = fs30.readdirSync(prdsDir).filter((f) => f.endsWith(".md")).map((f) => parsePrdFile(path31.join(prdsDir, f))).filter((p) => p !== null).map((p) => parseInt(p.id.replace("PRD-", ""), 10)).filter((n) => !isNaN(n));
7240
+ if (!fs32.existsSync(prdsDir)) return "PRD-1";
7241
+ const existing = fs32.readdirSync(prdsDir).filter((f) => f.endsWith(".md")).map((f) => parsePrdFile(path33.join(prdsDir, f))).filter((p) => p !== null).map((p) => parseInt(p.id.replace("PRD-", ""), 10)).filter((n) => !isNaN(n));
6826
7242
  const max = existing.length > 0 ? Math.max(...existing) : 0;
6827
7243
  return `PRD-${max + 1}`;
6828
7244
  }
@@ -6860,7 +7276,7 @@ ${frontmatter}---
6860
7276
  }
6861
7277
  function parsePrdFile(filePath) {
6862
7278
  try {
6863
- const content = fs30.readFileSync(filePath, "utf8");
7279
+ const content = fs32.readFileSync(filePath, "utf8");
6864
7280
  const match = content.match(/^---\n([\s\S]*?)\n---/);
6865
7281
  if (!match) return null;
6866
7282
  const data = jsYaml.load(match[1]);
@@ -6877,37 +7293,37 @@ function parsePrdFile(filePath) {
6877
7293
  }
6878
7294
  }
6879
7295
  function createPrd(prdsDir, title) {
6880
- fs30.mkdirSync(prdsDir, { recursive: true });
7296
+ fs32.mkdirSync(prdsDir, { recursive: true });
6881
7297
  const id = nextPrdId(prdsDir);
6882
7298
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6883
7299
  const slug = slugify2(title);
6884
7300
  const filename = `${today}-${slug}.md`;
6885
- const filepath = path31.join(prdsDir, filename);
7301
+ const filepath = path33.join(prdsDir, filename);
6886
7302
  const prd = { id, title, date: today, status: "draft", filepath };
6887
- fs30.writeFileSync(filepath, prdToMarkdown(prd), "utf8");
7303
+ fs32.writeFileSync(filepath, prdToMarkdown(prd), "utf8");
6888
7304
  return prd;
6889
7305
  }
6890
7306
  function listPrds(prdsDir) {
6891
- if (!fs30.existsSync(prdsDir)) return [];
6892
- return fs30.readdirSync(prdsDir).filter((f) => f.endsWith(".md")).map((f) => parsePrdFile(path31.join(prdsDir, f))).filter((p) => p !== null && /^PRD-\d+$/.test(p.id)).sort((a, b) => {
7307
+ if (!fs32.existsSync(prdsDir)) return [];
7308
+ return fs32.readdirSync(prdsDir).filter((f) => f.endsWith(".md")).map((f) => parsePrdFile(path33.join(prdsDir, f))).filter((p) => p !== null && /^PRD-\d+$/.test(p.id)).sort((a, b) => {
6893
7309
  const na = parseInt(a.id.replace("PRD-", ""), 10);
6894
7310
  const nb = parseInt(b.id.replace("PRD-", ""), 10);
6895
7311
  return na - nb;
6896
7312
  });
6897
7313
  }
6898
7314
  function getPrd(prdsDir, id) {
6899
- if (!fs30.existsSync(prdsDir)) return null;
6900
- const files = fs30.readdirSync(prdsDir).filter((f) => f.endsWith(".md"));
7315
+ if (!fs32.existsSync(prdsDir)) return null;
7316
+ const files = fs32.readdirSync(prdsDir).filter((f) => f.endsWith(".md"));
6901
7317
  for (const f of files) {
6902
- const prd = parsePrdFile(path31.join(prdsDir, f));
7318
+ const prd = parsePrdFile(path33.join(prdsDir, f));
6903
7319
  if (prd && prd.id === id) return prd;
6904
7320
  }
6905
7321
  return null;
6906
7322
  }
6907
7323
  function generateRoadmap(forgehiveDir2) {
6908
- const prdsDir = path31.join(forgehiveDir2, "memory", "prds");
6909
- const epicsDir = path31.join(forgehiveDir2, "memory", "epics");
6910
- const storiesDir = path31.join(forgehiveDir2, "memory", "stories");
7324
+ const prdsDir = path33.join(forgehiveDir2, "memory", "prds");
7325
+ const epicsDir = path33.join(forgehiveDir2, "memory", "epics");
7326
+ const storiesDir = path33.join(forgehiveDir2, "memory", "stories");
6911
7327
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6912
7328
  const prds = listPrds(prdsDir);
6913
7329
  const epics = listEpics(epicsDir);
@@ -6977,24 +7393,24 @@ function generateRoadmap(forgehiveDir2) {
6977
7393
  }
6978
7394
 
6979
7395
  // src/velocity.ts
6980
- import fs31 from "node:fs";
6981
- import path32 from "node:path";
7396
+ import fs33 from "node:fs";
7397
+ import path34 from "node:path";
6982
7398
  var HEADER = "# Sprint Velocity\n\n| Sprint | Datum | Committed | Delivered | Rate |\n|---|---|---|---|---|\n";
6983
7399
  function recordVelocity(velocityFile, sprint, committed, delivered) {
6984
7400
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6985
7401
  const rate = committed > 0 ? Math.round(delivered / committed * 100) : 0;
6986
7402
  const row = `| Sprint ${sprint} | ${date} | ${committed} | ${delivered} | ${rate}% |
6987
7403
  `;
6988
- if (!fs31.existsSync(velocityFile)) {
6989
- fs31.mkdirSync(path32.dirname(velocityFile), { recursive: true });
6990
- fs31.writeFileSync(velocityFile, HEADER + row, "utf8");
7404
+ if (!fs33.existsSync(velocityFile)) {
7405
+ fs33.mkdirSync(path34.dirname(velocityFile), { recursive: true });
7406
+ fs33.writeFileSync(velocityFile, HEADER + row, "utf8");
6991
7407
  } else {
6992
- fs31.appendFileSync(velocityFile, row, "utf8");
7408
+ fs33.appendFileSync(velocityFile, row, "utf8");
6993
7409
  }
6994
7410
  }
6995
7411
  function getVelocityHistory(velocityFile) {
6996
- if (!fs31.existsSync(velocityFile)) return [];
6997
- const content = fs31.readFileSync(velocityFile, "utf8");
7412
+ if (!fs33.existsSync(velocityFile)) return [];
7413
+ const content = fs33.readFileSync(velocityFile, "utf8");
6998
7414
  const rows = content.split("\n").filter((l) => l.startsWith("| Sprint "));
6999
7415
  return rows.map((row) => {
7000
7416
  const cells = row.split("|").map((c) => c.trim()).filter(Boolean);
@@ -7035,8 +7451,8 @@ function formatVelocityReport(history) {
7035
7451
 
7036
7452
  // src/docs.ts
7037
7453
  init_js_yaml();
7038
- import fs32 from "node:fs";
7039
- import path33 from "node:path";
7454
+ import fs34 from "node:fs";
7455
+ import path35 from "node:path";
7040
7456
  import { spawnSync as spawnSync12 } from "node:child_process";
7041
7457
  var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".py", ".go"];
7042
7458
  var IGNORE_DIRS4 = ["node_modules", ".git", "dist", ".forgehive", "coverage", ".next", "build", "test", "__tests__", "spec"];
@@ -7047,10 +7463,10 @@ var EXPORT_PATTERNS2 = [
7047
7463
  /^export\s+default\s+(?:function\s+)?(\w+)?/gm
7048
7464
  ];
7049
7465
  function readCapabilities2(forgehiveDir2) {
7050
- const capPath = path33.join(forgehiveDir2, "capabilities.yaml");
7051
- if (!fs32.existsSync(capPath)) return {};
7466
+ const capPath = path35.join(forgehiveDir2, "capabilities.yaml");
7467
+ if (!fs34.existsSync(capPath)) return {};
7052
7468
  try {
7053
- return jsYaml.load(fs32.readFileSync(capPath, "utf8")) ?? {};
7469
+ return jsYaml.load(fs34.readFileSync(capPath, "utf8")) ?? {};
7054
7470
  } catch {
7055
7471
  return {};
7056
7472
  }
@@ -7077,11 +7493,11 @@ function extractCapabilityInfo(caps) {
7077
7493
  return { language, packageManager };
7078
7494
  }
7079
7495
  function readMemoryFiles2(forgehiveDir2) {
7080
- const memDir = path33.join(forgehiveDir2, "memory");
7081
- if (!fs32.existsSync(memDir)) return {};
7496
+ const memDir = path35.join(forgehiveDir2, "memory");
7497
+ if (!fs34.existsSync(memDir)) return {};
7082
7498
  const result = {};
7083
- for (const f of fs32.readdirSync(memDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md")) {
7084
- result[f] = fs32.readFileSync(path33.join(memDir, f), "utf8");
7499
+ for (const f of fs34.readdirSync(memDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md")) {
7500
+ result[f] = fs34.readFileSync(path35.join(memDir, f), "utf8");
7085
7501
  }
7086
7502
  return result;
7087
7503
  }
@@ -7104,19 +7520,19 @@ function extractExports(content) {
7104
7520
  function walkSourceFiles(dir) {
7105
7521
  const results = [];
7106
7522
  function walk(current) {
7107
- if (!fs32.existsSync(current)) return;
7108
- for (const entry of fs32.readdirSync(current, { withFileTypes: true })) {
7523
+ if (!fs34.existsSync(current)) return;
7524
+ for (const entry of fs34.readdirSync(current, { withFileTypes: true })) {
7109
7525
  if (IGNORE_DIRS4.includes(entry.name)) continue;
7110
- const full = path33.join(current, entry.name);
7526
+ const full = path35.join(current, entry.name);
7111
7527
  if (entry.isDirectory()) walk(full);
7112
- else if (entry.isFile() && SOURCE_EXTS.includes(path33.extname(entry.name))) results.push(full);
7528
+ else if (entry.isFile() && SOURCE_EXTS.includes(path35.extname(entry.name))) results.push(full);
7113
7529
  }
7114
7530
  }
7115
7531
  walk(dir);
7116
7532
  return results;
7117
7533
  }
7118
7534
  function generateUserGuide(projectRoot2, forgehiveDir2) {
7119
- const projectName = path33.basename(projectRoot2);
7535
+ const projectName = path35.basename(projectRoot2);
7120
7536
  const caps = readCapabilities2(forgehiveDir2);
7121
7537
  const { language: lang, packageManager, entryPoints } = extractCapabilityInfo(caps);
7122
7538
  const memFiles = readMemoryFiles2(forgehiveDir2);
@@ -7195,8 +7611,8 @@ function generateUserGuide(projectRoot2, forgehiveDir2) {
7195
7611
  return lines.join("\n");
7196
7612
  }
7197
7613
  function generateApiReference(projectRoot2) {
7198
- const srcDir = path33.join(projectRoot2, "src");
7199
- const searchDir = fs32.existsSync(srcDir) ? srcDir : projectRoot2;
7614
+ const srcDir = path35.join(projectRoot2, "src");
7615
+ const searchDir = fs34.existsSync(srcDir) ? srcDir : projectRoot2;
7200
7616
  const files = walkSourceFiles(searchDir);
7201
7617
  const lines = [];
7202
7618
  lines.push("# API Reference");
@@ -7210,13 +7626,13 @@ function generateApiReference(projectRoot2) {
7210
7626
  for (const filePath of files) {
7211
7627
  let content = "";
7212
7628
  try {
7213
- content = fs32.readFileSync(filePath, "utf8");
7629
+ content = fs34.readFileSync(filePath, "utf8");
7214
7630
  } catch {
7215
7631
  continue;
7216
7632
  }
7217
7633
  const exports = extractExports(content);
7218
7634
  if (exports.length === 0) continue;
7219
- const relPath = path33.relative(projectRoot2, filePath);
7635
+ const relPath = path35.relative(projectRoot2, filePath);
7220
7636
  lines.push(`## \`${relPath}\``);
7221
7637
  lines.push("");
7222
7638
  lines.push("**Exports:**");
@@ -7228,23 +7644,23 @@ function generateApiReference(projectRoot2) {
7228
7644
  }
7229
7645
  function listExistingDocs(projectRoot2) {
7230
7646
  const docs = [];
7231
- const docsDir = path33.join(projectRoot2, "docs");
7232
- if (fs32.existsSync(docsDir)) {
7233
- for (const f of fs32.readdirSync(docsDir)) {
7234
- if (f.endsWith(".md")) docs.push(path33.join(docsDir, f));
7647
+ const docsDir = path35.join(projectRoot2, "docs");
7648
+ if (fs34.existsSync(docsDir)) {
7649
+ for (const f of fs34.readdirSync(docsDir)) {
7650
+ if (f.endsWith(".md")) docs.push(path35.join(docsDir, f));
7235
7651
  }
7236
7652
  }
7237
7653
  const rootDocs = ["README.md", "CHANGELOG.md", "ONBOARDING.md", "CONTRIBUTING.md"];
7238
7654
  for (const f of rootDocs) {
7239
- const full = path33.join(projectRoot2, f);
7240
- if (fs32.existsSync(full)) docs.push(full);
7655
+ const full = path35.join(projectRoot2, f);
7656
+ if (fs34.existsSync(full)) docs.push(full);
7241
7657
  }
7242
7658
  return docs;
7243
7659
  }
7244
7660
 
7245
7661
  // src/router.ts
7246
- import fs33 from "node:fs";
7247
- import path34 from "node:path";
7662
+ import fs35 from "node:fs";
7663
+ import path36 from "node:path";
7248
7664
  var AGENT_KEYWORDS = {
7249
7665
  vera: ["security", "auth", "vulnerability", "owasp", "cve", "gdpr", "compliance", "penetration", "exploit"],
7250
7666
  sam: ["test", "coverage", "qa", "quality", "spec", "regression", "fixture", "assertion", "e2e"],
@@ -7353,9 +7769,9 @@ function routeTask(task) {
7353
7769
  }
7354
7770
  function resolveWorktreePath(forgehiveDir2, agent) {
7355
7771
  if (!agent) return void 0;
7356
- const worktreeFile = path34.join(forgehiveDir2, "agents", agent, "worktree");
7357
- if (fs33.existsSync(worktreeFile)) {
7358
- return fs33.readFileSync(worktreeFile, "utf8").trim();
7772
+ const worktreeFile = path36.join(forgehiveDir2, "agents", agent, "worktree");
7773
+ if (fs35.existsSync(worktreeFile)) {
7774
+ return fs35.readFileSync(worktreeFile, "utf8").trim();
7359
7775
  }
7360
7776
  return void 0;
7361
7777
  }
@@ -7384,17 +7800,18 @@ function formatRoutingResult(result, worktreePath) {
7384
7800
 
7385
7801
  // src/github.ts
7386
7802
  init_js_yaml();
7387
- import fs34 from "node:fs";
7388
- import path35 from "node:path";
7803
+ import fs36 from "node:fs";
7804
+ import path37 from "node:path";
7389
7805
  import https from "node:https";
7806
+ import { spawnSync as spawnSync13 } from "node:child_process";
7390
7807
  function loadGitHubConfig(forgehiveDir2) {
7391
- const configPath = path35.join(forgehiveDir2, "github.yaml");
7392
- if (!fs34.existsSync(configPath)) {
7808
+ const configPath = path37.join(forgehiveDir2, "github.yaml");
7809
+ if (!fs36.existsSync(configPath)) {
7393
7810
  throw new Error("Nicht konfiguriert. F\xFChre zuerst aus: fh github setup");
7394
7811
  }
7395
7812
  let parsed;
7396
7813
  try {
7397
- parsed = jsYaml.load(fs34.readFileSync(configPath, "utf8"));
7814
+ parsed = jsYaml.load(fs36.readFileSync(configPath, "utf8"));
7398
7815
  } catch {
7399
7816
  throw new Error("Ung\xFCltige Konfigurationsdatei: .forgehive/github.yaml ist kein g\xFCltiges YAML.");
7400
7817
  }
@@ -7408,9 +7825,9 @@ function loadGitHubConfig(forgehiveDir2) {
7408
7825
  return { repo: data.repo };
7409
7826
  }
7410
7827
  function saveGitHubConfig(forgehiveDir2, config) {
7411
- fs34.mkdirSync(forgehiveDir2, { recursive: true });
7412
- const configPath = path35.join(forgehiveDir2, "github.yaml");
7413
- fs34.writeFileSync(configPath, jsYaml.dump({ repo: config.repo }), "utf8");
7828
+ fs36.mkdirSync(forgehiveDir2, { recursive: true });
7829
+ const configPath = path37.join(forgehiveDir2, "github.yaml");
7830
+ fs36.writeFileSync(configPath, jsYaml.dump({ repo: config.repo }), "utf8");
7414
7831
  }
7415
7832
  function githubGet(apiPath, token) {
7416
7833
  return new Promise((resolve, reject) => {
@@ -7492,22 +7909,22 @@ async function fetchPRFiles(owner, repo, number, token) {
7492
7909
  return files;
7493
7910
  }
7494
7911
  function issueAlreadySynced(storiesDir, issueNumber) {
7495
- if (!fs34.existsSync(storiesDir)) return false;
7912
+ if (!fs36.existsSync(storiesDir)) return false;
7496
7913
  const pattern = new RegExp(`GitHub: #${issueNumber}(?![\\d\\w])`);
7497
- return fs34.readdirSync(storiesDir).filter((f) => f.endsWith(".md")).some((f) => pattern.test(fs34.readFileSync(path35.join(storiesDir, f), "utf8")));
7914
+ return fs36.readdirSync(storiesDir).filter((f) => f.endsWith(".md")).some((f) => pattern.test(fs36.readFileSync(path37.join(storiesDir, f), "utf8")));
7498
7915
  }
7499
7916
  function appendGitHubRef(storiesDir, storyId, issue, repoFullName) {
7500
- const filePath = path35.join(storiesDir, `${storyId}.md`);
7501
- if (!fs34.existsSync(filePath)) {
7917
+ const filePath = path37.join(storiesDir, `${storyId}.md`);
7918
+ if (!fs36.existsSync(filePath)) {
7502
7919
  throw new Error(`Story-Datei nicht gefunden: ${storyId}.md`);
7503
7920
  }
7504
- const existing = fs34.readFileSync(filePath, "utf8");
7921
+ const existing = fs36.readFileSync(filePath, "utf8");
7505
7922
  const ref = `
7506
7923
  ## GitHub
7507
7924
 
7508
7925
  GitHub: #${issue.number} \u2014 ${issue.html_url}
7509
7926
  `;
7510
- fs34.writeFileSync(filePath, existing.trimEnd() + "\n" + ref, "utf8");
7927
+ fs36.writeFileSync(filePath, existing.trimEnd() + "\n" + ref, "utf8");
7511
7928
  }
7512
7929
  function formatPRContext(pr, files, repoFullName) {
7513
7930
  const body = pr.body?.trim() || "(keine Beschreibung)";
@@ -7543,6 +7960,366 @@ async function fetchOpenPRs(owner, repo, token) {
7543
7960
  }
7544
7961
  return prs.map((pr) => pr.number);
7545
7962
  }
7963
+ function createPR(branch, title, body) {
7964
+ const result = spawnSync13(
7965
+ "gh",
7966
+ ["pr", "create", "--title", title, "--body", body, "--head", branch],
7967
+ { encoding: "utf8" }
7968
+ );
7969
+ if (result.status !== 0) {
7970
+ throw new Error(`gh pr create fehlgeschlagen: ${result.stderr?.trim() ?? "unbekannter Fehler"}`);
7971
+ }
7972
+ return result.stdout.trim();
7973
+ }
7974
+
7975
+ // src/autonomous.ts
7976
+ init_js_yaml();
7977
+ import fs38 from "node:fs";
7978
+ import path39 from "node:path";
7979
+ import { spawnSync as spawnSync14 } from "node:child_process";
7980
+ import { createInterface } from "node:readline";
7981
+
7982
+ // src/prompt-builder.ts
7983
+ import fs37 from "node:fs";
7984
+ import path38 from "node:path";
7985
+ function buildPlanningPrompt(storyContent, capabilitiesPath, forgehiveDir2) {
7986
+ const capabilities = fs37.existsSync(capabilitiesPath) ? fs37.readFileSync(capabilitiesPath, "utf8") : "";
7987
+ const feedbackPath = path38.join(forgehiveDir2, "memory", "feedback.md");
7988
+ const projectPath = path38.join(forgehiveDir2, "memory", "project.md");
7989
+ const memory = [feedbackPath, projectPath].filter((p) => fs37.existsSync(p)).map((p) => fs37.readFileSync(p, "utf8")).join("\n\n");
7990
+ return [
7991
+ "Du bist ein Planungs-Agent. Produziere einen Implementierungsplan als Markdown.",
7992
+ "",
7993
+ "## Story",
7994
+ storyContent,
7995
+ "",
7996
+ "## Stack",
7997
+ capabilities,
7998
+ ...memory ? ["", "## Memory", memory] : [],
7999
+ "",
8000
+ "## Ausgabeformat",
8001
+ "### Files",
8002
+ "(Liste der zu \xE4ndernden/erstellenden Dateipfade, einer pro Zeile)",
8003
+ "### Ansatz",
8004
+ "(2-3 S\xE4tze Beschreibung des Vorgehens)",
8005
+ "### Tests",
8006
+ "(Liste der erwarteten Tests die danach gr\xFCn sein m\xFCssen)"
8007
+ ].join("\n");
8008
+ }
8009
+ function buildImplementationPrompt(storyContent, planContent, capabilitiesPath, forgehiveDir2) {
8010
+ const capabilities = fs37.existsSync(capabilitiesPath) ? fs37.readFileSync(capabilitiesPath, "utf8") : "";
8011
+ const skillsDir = path38.join(forgehiveDir2, "skills", "expert");
8012
+ const skills = fs37.existsSync(skillsDir) ? fs37.readdirSync(skillsDir).filter((f) => f.endsWith(".md")).slice(0, 3).map((f) => fs37.readFileSync(path38.join(skillsDir, f), "utf8")).join("\n\n---\n\n") : "";
8013
+ return [
8014
+ "Du bist Kai, Senior Software Engineer. Implementiere den folgenden Plan.",
8015
+ "Erstelle einen git-Commit wenn du fertig bist.",
8016
+ "\xC4ndere NUR die Dateien die im Plan aufgelistet sind.",
8017
+ "",
8018
+ "## Story",
8019
+ storyContent,
8020
+ "",
8021
+ "## Plan",
8022
+ planContent,
8023
+ "",
8024
+ "## Stack",
8025
+ capabilities,
8026
+ ...skills ? ["", "## Skills", skills] : []
8027
+ ].join("\n");
8028
+ }
8029
+ function buildEscalationPrompt(planContent, diff, testError) {
8030
+ return [
8031
+ "Du bist Sam, QA Architect. Kai hat versucht den Plan umzusetzen aber die Tests schlagen fehl.",
8032
+ "Analysiere was schiefgelaufen ist und produziere einen minimalen Fix.",
8033
+ "Kein neuer Plan \u2014 nur der Fix. Committe danach.",
8034
+ "",
8035
+ "## Originaler Plan",
8036
+ planContent,
8037
+ "",
8038
+ "## Was produziert wurde (git diff)",
8039
+ diff,
8040
+ "",
8041
+ "## Test-Fehler",
8042
+ testError
8043
+ ].join("\n");
8044
+ }
8045
+
8046
+ // src/autonomous.ts
8047
+ function generateRunId() {
8048
+ const now = /* @__PURE__ */ new Date();
8049
+ const pad = (n) => String(n).padStart(2, "0");
8050
+ return `run-${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
8051
+ }
8052
+ function getRunDir(forgehiveDir2, runId) {
8053
+ return path39.join(forgehiveDir2, "runs", runId);
8054
+ }
8055
+ function saveRunState(forgehiveDir2, state) {
8056
+ const runDir = getRunDir(forgehiveDir2, state.runId);
8057
+ fs38.mkdirSync(runDir, { recursive: true });
8058
+ fs38.writeFileSync(path39.join(runDir, "state.yaml"), jsYaml.dump(state), "utf8");
8059
+ }
8060
+ function loadRunState(forgehiveDir2, runId) {
8061
+ const statePath = path39.join(getRunDir(forgehiveDir2, runId), "state.yaml");
8062
+ if (!fs38.existsSync(statePath)) {
8063
+ throw new Error(`Run ${runId} nicht gefunden`);
8064
+ }
8065
+ return jsYaml.load(fs38.readFileSync(statePath, "utf8"));
8066
+ }
8067
+ function listRuns(forgehiveDir2) {
8068
+ const runsDir = path39.join(forgehiveDir2, "runs");
8069
+ if (!fs38.existsSync(runsDir)) return [];
8070
+ return fs38.readdirSync(runsDir).filter((d) => d.startsWith("run-")).sort().reverse().map((d) => {
8071
+ try {
8072
+ return loadRunState(forgehiveDir2, d);
8073
+ } catch {
8074
+ return null;
8075
+ }
8076
+ }).filter((s) => s !== null);
8077
+ }
8078
+ function nextPhase(current, ctx) {
8079
+ switch (current) {
8080
+ case "analyzing":
8081
+ return "planning";
8082
+ case "planning":
8083
+ return "gate-1";
8084
+ case "gate-1":
8085
+ return "implementing";
8086
+ case "implementing":
8087
+ return "testing";
8088
+ case "testing":
8089
+ if (ctx.testsPassed) return "gate-2";
8090
+ return ctx.attempts >= 2 ? "gate-stuck" : "escalating";
8091
+ case "escalating":
8092
+ return "testing";
8093
+ case "gate-2":
8094
+ return "pr-creating";
8095
+ case "pr-creating":
8096
+ return "done";
8097
+ default:
8098
+ return current;
8099
+ }
8100
+ }
8101
+ function generateBranchName(entryPoint) {
8102
+ if (/^US-\d+$/.test(entryPoint)) return `feat/${entryPoint}`;
8103
+ const slug = entryPoint.toLowerCase().replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-").slice(0, 40).replace(/-+$/, "");
8104
+ return `feat/${slug}`;
8105
+ }
8106
+ function shouldSkipGate(phase, auto) {
8107
+ if (phase === "gate-stuck") return false;
8108
+ return auto && (phase === "gate-1" || phase === "gate-2");
8109
+ }
8110
+ function formatRunStatus(state) {
8111
+ const lines = [
8112
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
8113
+ `fh auto \u203A ${state.entryPoint} \u203A ${state.status}`,
8114
+ `Run-ID: ${state.runId}`,
8115
+ `Branch: ${state.branch}`,
8116
+ `Agent: ${state.agent}`,
8117
+ `Start: ${state.startedAt}`,
8118
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
8119
+ ];
8120
+ return lines.join("\n");
8121
+ }
8122
+ async function promptUser(question) {
8123
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
8124
+ return new Promise((resolve) => {
8125
+ rl.question(question, (answer) => {
8126
+ rl.close();
8127
+ resolve(answer.trim());
8128
+ });
8129
+ });
8130
+ }
8131
+ function runClaudeP(prompt) {
8132
+ const result = spawnSync14("claude", ["-p", prompt], { encoding: "utf8", maxBuffer: 10 * 1024 * 1024 });
8133
+ return {
8134
+ stdout: result.stdout ?? "",
8135
+ success: result.status === 0
8136
+ };
8137
+ }
8138
+ function getTestCmd(forgehiveDir2) {
8139
+ const capPath = path39.join(forgehiveDir2, "capabilities.yaml");
8140
+ if (!fs38.existsSync(capPath)) return "npm test";
8141
+ try {
8142
+ const caps = jsYaml.load(fs38.readFileSync(capPath, "utf8"));
8143
+ return caps?.testCmd ?? "npm test";
8144
+ } catch {
8145
+ return "npm test";
8146
+ }
8147
+ }
8148
+ function getDiff(projectRoot2) {
8149
+ const result = spawnSync14("git", ["diff", "HEAD"], { cwd: projectRoot2, encoding: "utf8" });
8150
+ return result.stdout ?? "";
8151
+ }
8152
+ async function startRun(entryPoint, forgehiveDir2, projectRoot2, auto) {
8153
+ const runId = generateRunId();
8154
+ const capPath = path39.join(forgehiveDir2, "capabilities.yaml");
8155
+ let storyContent;
8156
+ if (/^US-\d+$/.test(entryPoint)) {
8157
+ const storiesDir = path39.join(forgehiveDir2, "memory", "stories");
8158
+ const story = getStory(storiesDir, entryPoint);
8159
+ storyContent = story ? `${story.id}: ${story.title}` : entryPoint;
8160
+ } else {
8161
+ const storiesDir = path39.join(forgehiveDir2, "memory", "stories");
8162
+ fs38.mkdirSync(storiesDir, { recursive: true });
8163
+ const story = createStory(storiesDir, entryPoint);
8164
+ storyContent = `${story.id}: ${story.title}`;
8165
+ entryPoint = story.id;
8166
+ }
8167
+ const routing = routeTask(storyContent);
8168
+ const agent = routing.type === "agent" ? routing.agent ?? "kai" : "kai";
8169
+ const branch = generateBranchName(entryPoint);
8170
+ const state = {
8171
+ runId,
8172
+ entryPoint,
8173
+ status: "planning",
8174
+ agent,
8175
+ branch,
8176
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
8177
+ attempts: 0,
8178
+ auto
8179
+ };
8180
+ saveRunState(forgehiveDir2, state);
8181
+ await executeRun(state, storyContent, forgehiveDir2, projectRoot2, capPath);
8182
+ }
8183
+ async function resumeRun(runId, forgehiveDir2, projectRoot2) {
8184
+ const state = loadRunState(forgehiveDir2, runId);
8185
+ const capPath = path39.join(forgehiveDir2, "capabilities.yaml");
8186
+ const storyContent = state.entryPoint;
8187
+ await executeRun(state, storyContent, forgehiveDir2, projectRoot2, capPath);
8188
+ }
8189
+ async function executeRun(state, storyContent, forgehiveDir2, projectRoot2, capPath) {
8190
+ const runDir = path39.join(forgehiveDir2, "runs", state.runId);
8191
+ if (state.status === "planning") {
8192
+ console.log(`
8193
+ [fh auto] Planning...`);
8194
+ const planPrompt = buildPlanningPrompt(storyContent, capPath, forgehiveDir2);
8195
+ const { stdout: planMd } = runClaudeP(planPrompt);
8196
+ fs38.writeFileSync(path39.join(runDir, "plan.md"), planMd, "utf8");
8197
+ state.status = "gate-1";
8198
+ saveRunState(forgehiveDir2, state);
8199
+ }
8200
+ if (state.status === "gate-1") {
8201
+ const planMd = fs38.readFileSync(path39.join(runDir, "plan.md"), "utf8");
8202
+ if (!shouldSkipGate("gate-1", state.auto)) {
8203
+ console.log(`
8204
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
8205
+ console.log(`fh auto \u203A ${state.entryPoint} \u203A Plan bereit`);
8206
+ console.log(`\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
8207
+ console.log(planMd);
8208
+ const answer = await promptUser("Fortfahren? [j / n / anpassen: <freitext>]\n> ");
8209
+ if (answer.toLowerCase() === "n") {
8210
+ console.log("Abgebrochen.");
8211
+ return;
8212
+ }
8213
+ if (answer.toLowerCase() !== "j" && answer.toLowerCase() !== "ja") {
8214
+ const revisedPrompt = buildPlanningPrompt(
8215
+ `${storyContent}
8216
+
8217
+ Feedback: ${answer}`,
8218
+ capPath,
8219
+ forgehiveDir2
8220
+ );
8221
+ const { stdout: revisedPlan } = runClaudeP(revisedPrompt);
8222
+ fs38.writeFileSync(path39.join(runDir, "plan.md"), revisedPlan, "utf8");
8223
+ }
8224
+ }
8225
+ state.status = "implementing";
8226
+ saveRunState(forgehiveDir2, state);
8227
+ }
8228
+ if (state.status === "implementing") {
8229
+ console.log(`
8230
+ [fh auto] Implementing (${state.agent})...`);
8231
+ const planMd = fs38.readFileSync(path39.join(runDir, "plan.md"), "utf8");
8232
+ const implPrompt = buildImplementationPrompt(storyContent, planMd, capPath, forgehiveDir2);
8233
+ const branchResult = spawnSync14("git", ["checkout", "-b", state.branch], { cwd: projectRoot2, encoding: "utf8" });
8234
+ if (branchResult.status !== 0) {
8235
+ spawnSync14("git", ["checkout", state.branch], { cwd: projectRoot2, encoding: "utf8" });
8236
+ }
8237
+ runClaudeP(implPrompt);
8238
+ state.status = "testing";
8239
+ state.attempts += 1;
8240
+ saveRunState(forgehiveDir2, state);
8241
+ }
8242
+ if (state.status === "testing") {
8243
+ console.log(`
8244
+ [fh auto] Running tests...`);
8245
+ const testCmd = getTestCmd(forgehiveDir2);
8246
+ const [cmd, ...args] = testCmd.split(" ");
8247
+ const testResult = spawnSync14(cmd, args, { cwd: projectRoot2, encoding: "utf8" });
8248
+ const testsPassed = testResult.status === 0;
8249
+ const ctx = { testsPassed, attempts: state.attempts };
8250
+ state.status = nextPhase("testing", ctx);
8251
+ saveRunState(forgehiveDir2, state);
8252
+ if (!testsPassed && state.status === "escalating") {
8253
+ console.log(`
8254
+ [fh auto] Tests failed. Escalating to Sam...`);
8255
+ const planMd = fs38.readFileSync(path39.join(runDir, "plan.md"), "utf8");
8256
+ const diff = getDiff(projectRoot2);
8257
+ const escalationPrompt = buildEscalationPrompt(planMd, diff, testResult.stdout + testResult.stderr);
8258
+ runClaudeP(escalationPrompt);
8259
+ state.status = "testing";
8260
+ state.attempts += 1;
8261
+ saveRunState(forgehiveDir2, state);
8262
+ const retestResult = spawnSync14(cmd, args, { cwd: projectRoot2, encoding: "utf8" });
8263
+ const retestPassed = retestResult.status === 0;
8264
+ state.status = nextPhase("testing", { testsPassed: retestPassed, attempts: state.attempts });
8265
+ saveRunState(forgehiveDir2, state);
8266
+ if (!retestPassed && state.status === "gate-stuck") {
8267
+ const diff2 = getDiff(projectRoot2);
8268
+ fs38.writeFileSync(path39.join(runDir, "diff.patch"), diff2, "utf8");
8269
+ console.log(`
8270
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
8271
+ console.log(`fh auto \u203A ${state.entryPoint} \u203A Stuck nach 2 Versuchen`);
8272
+ console.log(`\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
8273
+ console.log(`\u2717 ${retestResult.stderr?.trim().split("\n")[0] ?? "Tests fehlgeschlagen"}`);
8274
+ console.log(`
8275
+ Diff: ${path39.join(forgehiveDir2, "runs", state.runId, "diff.patch")}`);
8276
+ console.log(`Run pausiert. Wenn du bereit bist:
8277
+ fh auto resume ${state.runId}`);
8278
+ return;
8279
+ }
8280
+ }
8281
+ }
8282
+ if (state.status === "gate-2") {
8283
+ const diff = getDiff(projectRoot2);
8284
+ fs38.writeFileSync(path39.join(runDir, "diff.patch"), diff, "utf8");
8285
+ const additions = (diff.match(/^\+[^+]/mg) ?? []).length;
8286
+ const deletions = (diff.match(/^-[^-]/mg) ?? []).length;
8287
+ if (!shouldSkipGate("gate-2", state.auto)) {
8288
+ console.log(`
8289
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
8290
+ console.log(`fh auto \u203A ${state.entryPoint} \u203A Tests gr\xFCn`);
8291
+ console.log(`\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
8292
+ console.log(`\u2713 Tests passing`);
8293
+ console.log(`Branch: ${state.branch}`);
8294
+ console.log(`Diff: +${additions} / -${deletions} Zeilen`);
8295
+ const answer = await promptUser("\nPR erstellen? [j / n]\n> ");
8296
+ if (answer.toLowerCase() === "n") {
8297
+ console.log("Branch behalten. Kein PR erstellt.");
8298
+ return;
8299
+ }
8300
+ }
8301
+ state.status = "pr-creating";
8302
+ saveRunState(forgehiveDir2, state);
8303
+ }
8304
+ if (state.status === "pr-creating") {
8305
+ console.log(`
8306
+ [fh auto] Creating PR...`);
8307
+ const planMd = fs38.readFileSync(path39.join(runDir, "plan.md"), "utf8");
8308
+ try {
8309
+ spawnSync14("git", ["push", "-u", "origin", state.branch], { cwd: projectRoot2, encoding: "utf8" });
8310
+ const prUrl = createPR(state.branch, `${state.entryPoint}: ${storyContent}`, planMd);
8311
+ state.status = "done";
8312
+ saveRunState(forgehiveDir2, state);
8313
+ console.log(`
8314
+ \u2713 PR erstellt: ${prUrl}`);
8315
+ } catch (e) {
8316
+ console.warn(`PR konnte nicht erstellt werden: ${e.message}`);
8317
+ console.warn("Branch ist lokal verf\xFCgbar. Erstelle den PR manuell.");
8318
+ state.status = "done";
8319
+ saveRunState(forgehiveDir2, state);
8320
+ }
8321
+ }
8322
+ }
7546
8323
 
7547
8324
  // src/cli.ts
7548
8325
  import { createRequire } from "node:module";
@@ -7550,7 +8327,7 @@ var require2 = createRequire(import.meta.url);
7550
8327
  var { version } = require2("../package.json");
7551
8328
  var [, , command, subcommand, ...rest] = process.argv;
7552
8329
  var projectRoot = process.cwd();
7553
- var forgehiveDir = path36.join(projectRoot, ".forgehive");
8330
+ var forgehiveDir = path40.join(projectRoot, ".forgehive");
7554
8331
  if (command === "--version" || command === "-v") {
7555
8332
  console.log(version);
7556
8333
  process.exit(0);
@@ -7566,7 +8343,14 @@ SETUP
7566
8343
  fh init [--yes] Set up forgehive in the current project
7567
8344
  fh confirm Activate capabilities (draft \u2192 confirmed)
7568
8345
  fh rollback Remove forgehive from the project
8346
+ fh auto <US-N|text> Autonomer Pipeline-Run (plan \u2192 implement \u2192 test \u2192 PR)
8347
+ fh auto --auto <US-N|text> Ohne Gates (CI/overnight)
8348
+ fh auto resume <run-id> Unterbrochenen Run fortsetzen
8349
+ fh auto status Aktive und letzte Runs
8350
+ fh auto list Alle Runs auflisten
8351
+
7569
8352
  fh status Show current project state
8353
+ fh next Zeigt n\xE4chste empfohlene Aktion (Phase + Befehl)
7570
8354
  fh watch [--smart] Observe project; --smart runs tests on change and suggests agents
7571
8355
  fh scan --update Re-scan project after changes
7572
8356
  fh scan --check Check if scan is still current
@@ -7623,6 +8407,7 @@ TEAM
7623
8407
 
7624
8408
  AGENTS & MCP
7625
8409
  fh party [--set name|run|status|cleanup]
8410
+ fh outcomes Show party/auto run statistics and last 5 runs
7626
8411
  fh wire <service> Configure MCP server
7627
8412
  fh mcp auth add|list|remove Manage credentials
7628
8413
  fh mcp search <query> Search MCP registry
@@ -7638,19 +8423,19 @@ COST
7638
8423
  process.exit(0);
7639
8424
  }
7640
8425
  function loadClaudeMdBlock() {
7641
- const templatePath = path36.join(
7642
- path36.dirname(new URL(import.meta.url).pathname),
8426
+ const templatePath = path40.join(
8427
+ path40.dirname(new URL(import.meta.url).pathname),
7643
8428
  "..",
7644
8429
  "forgehive",
7645
8430
  "templates",
7646
8431
  "claude-md.block.md"
7647
8432
  );
7648
- if (!fs35.existsSync(templatePath)) return "## forgehive\n\nSee .forgehive/ for configuration.";
7649
- return fs35.readFileSync(templatePath, "utf8");
8433
+ if (!fs39.existsSync(templatePath)) return "## forgehive\n\nSee .forgehive/ for configuration.";
8434
+ return fs39.readFileSync(templatePath, "utf8");
7650
8435
  }
7651
8436
  async function promptConfirm(question) {
7652
8437
  if (!process.stdin.isTTY) return false;
7653
- const rl = createInterface({ input: process.stdin, output: process.stdout });
8438
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
7654
8439
  return new Promise((resolve) => {
7655
8440
  rl.question(question, (answer) => {
7656
8441
  rl.close();
@@ -7669,13 +8454,13 @@ function buildCapabilitySummary(ids) {
7669
8454
  }
7670
8455
  if (command === "init") {
7671
8456
  (async () => {
7672
- const gitCheck = spawnSync13("git", ["--version"], { stdio: "ignore" });
8457
+ const gitCheck = spawnSync15("git", ["--version"], { stdio: "ignore" });
7673
8458
  if (gitCheck.error || gitCheck.status !== 0) {
7674
8459
  console.error("Fehler: git nicht gefunden.");
7675
8460
  console.error(" Installation: https://git-scm.com");
7676
8461
  process.exit(1);
7677
8462
  }
7678
- const forgehiveDirExists = fs35.existsSync(forgehiveDir);
8463
+ const forgehiveDirExists = fs39.existsSync(forgehiveDir);
7679
8464
  if (forgehiveDirExists && subcommand !== "--force" && !rest.includes("--force")) {
7680
8465
  console.log(`\u26A0 .forgehive/ existiert bereits in diesem Projekt.`);
7681
8466
  console.log(` Nutze 'fh init --force' um neu zu initialisieren (\xFCberschreibt capabilities.yaml).`);
@@ -7693,9 +8478,9 @@ if (command === "init") {
7693
8478
  const block = loadClaudeMdBlock();
7694
8479
  writeForgehiveDir(projectRoot, scanResult, capMap, block);
7695
8480
  const hash = computeHash(projectRoot);
7696
- fs35.writeFileSync(path36.join(forgehiveDir, ".scan-hash"), hash, "utf8");
7697
- const runtimeDir = path36.join(
7698
- path36.dirname(new URL(import.meta.url).pathname),
8481
+ fs39.writeFileSync(path40.join(forgehiveDir, ".scan-hash"), hash, "utf8");
8482
+ const runtimeDir = path40.join(
8483
+ path40.dirname(new URL(import.meta.url).pathname),
7699
8484
  "..",
7700
8485
  "forgehive"
7701
8486
  );
@@ -7737,7 +8522,7 @@ if (command === "init") {
7737
8522
  process.exit(1);
7738
8523
  }
7739
8524
  } else if (command === "memory") {
7740
- if (!fs35.existsSync(forgehiveDir)) {
8525
+ if (!fs39.existsSync(forgehiveDir)) {
7741
8526
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
7742
8527
  process.exit(1);
7743
8528
  }
@@ -7746,7 +8531,7 @@ if (command === "init") {
7746
8531
  } else if (subcommand === "clean") {
7747
8532
  cleanMemory(forgehiveDir);
7748
8533
  } else if (subcommand === "export") {
7749
- const outputPath = rest[0] ?? path36.join(projectRoot, "forgehive-memory-export.md");
8534
+ const outputPath = rest[0] ?? path40.join(projectRoot, "forgehive-memory-export.md");
7750
8535
  try {
7751
8536
  exportMemory(forgehiveDir, outputPath);
7752
8537
  } catch (err) {
@@ -7794,7 +8579,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
7794
8579
  } else if (subcommand === "snapshot") {
7795
8580
  const snapAction = rest[0];
7796
8581
  if (snapAction === "export") {
7797
- const outPath = rest[1] ?? path36.join(
8582
+ const outPath = rest[1] ?? path40.join(
7798
8583
  projectRoot,
7799
8584
  `forgehive-snapshot-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.json`
7800
8585
  );
@@ -7834,11 +8619,11 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
7834
8619
  process.exit(1);
7835
8620
  }
7836
8621
  } else if (command === "scan" && subcommand === "--update") {
7837
- if (!fs35.existsSync(forgehiveDir)) {
8622
+ if (!fs39.existsSync(forgehiveDir)) {
7838
8623
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
7839
8624
  process.exit(1);
7840
8625
  }
7841
- const savedHash = fs35.existsSync(path36.join(forgehiveDir, ".scan-hash")) ? fs35.readFileSync(path36.join(forgehiveDir, ".scan-hash"), "utf8").trim() : null;
8626
+ const savedHash = fs39.existsSync(path40.join(forgehiveDir, ".scan-hash")) ? fs39.readFileSync(path40.join(forgehiveDir, ".scan-hash"), "utf8").trim() : null;
7842
8627
  const currentHash = computeHash(projectRoot);
7843
8628
  if (savedHash === currentHash) {
7844
8629
  console.log("\u2713 Keine \xC4nderungen erkannt \u2014 capabilities.yaml ist aktuell");
@@ -7846,7 +8631,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
7846
8631
  }
7847
8632
  console.log("\u{1F50D} \xC4nderungen erkannt \u2014 scanne erneut...\n");
7848
8633
  const oldDoc = jsYaml.load(
7849
- fs35.readFileSync(path36.join(forgehiveDir, "capabilities.yaml"), "utf8")
8634
+ fs39.readFileSync(path40.join(forgehiveDir, "capabilities.yaml"), "utf8")
7850
8635
  );
7851
8636
  const oldMap = { confirmed: oldDoc.capabilities.confirmed ?? [], inferred: [] };
7852
8637
  const scanResult = scan(projectRoot);
@@ -7866,16 +8651,16 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
7866
8651
  console.log();
7867
8652
  const block = loadClaudeMdBlock();
7868
8653
  writeForgehiveDir(projectRoot, scanResult, newMap, block);
7869
- fs35.writeFileSync(path36.join(forgehiveDir, ".scan-hash"), currentHash, "utf8");
8654
+ fs39.writeFileSync(path40.join(forgehiveDir, ".scan-hash"), currentHash, "utf8");
7870
8655
  console.log("\u2713 scan-result.yaml und capabilities.yaml aktualisiert");
7871
8656
  console.log(" F\xFChre `fh confirm` aus um die \xC4nderungen zu best\xE4tigen");
7872
8657
  }
7873
8658
  } else if (command === "scan" && subcommand === "--check") {
7874
- if (!fs35.existsSync(path36.join(forgehiveDir, ".scan-hash"))) {
8659
+ if (!fs39.existsSync(path40.join(forgehiveDir, ".scan-hash"))) {
7875
8660
  console.log("Warnung: Kein Scan-Hash gefunden. F\xFChre `fh init` aus.");
7876
8661
  process.exit(1);
7877
8662
  }
7878
- const saved = fs35.readFileSync(path36.join(forgehiveDir, ".scan-hash"), "utf8").trim();
8663
+ const saved = fs39.readFileSync(path40.join(forgehiveDir, ".scan-hash"), "utf8").trim();
7879
8664
  const current = computeHash(projectRoot);
7880
8665
  if (saved !== current) {
7881
8666
  console.log("\u26A0 Codebase hat sich seit letztem Scan ge\xE4ndert.");
@@ -7884,7 +8669,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
7884
8669
  }
7885
8670
  console.log("\u2713 capabilities.yaml ist aktuell");
7886
8671
  } else if (command === "skills") {
7887
- if (!fs35.existsSync(forgehiveDir)) {
8672
+ if (!fs39.existsSync(forgehiveDir)) {
7888
8673
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
7889
8674
  process.exit(1);
7890
8675
  }
@@ -7920,7 +8705,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
7920
8705
  process.exit(1);
7921
8706
  }
7922
8707
  } else if (command === "party") {
7923
- if (!fs35.existsSync(forgehiveDir)) {
8708
+ if (!fs39.existsSync(forgehiveDir)) {
7924
8709
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
7925
8710
  process.exit(1);
7926
8711
  }
@@ -8041,7 +8826,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
8041
8826
  }
8042
8827
  }
8043
8828
  } else if (command === "wire") {
8044
- if (!fs35.existsSync(forgehiveDir)) {
8829
+ if (!fs39.existsSync(forgehiveDir)) {
8045
8830
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
8046
8831
  process.exit(1);
8047
8832
  }
@@ -8073,16 +8858,47 @@ N\xE4chster Schritt: Setze die erforderlichen Umgebungsvariablen und starte Clau
8073
8858
  console.error(`Fehler: ${err.message}`);
8074
8859
  process.exit(1);
8075
8860
  }
8861
+ } else if (command === "auto") {
8862
+ if (subcommand === "status" || subcommand === "list") {
8863
+ const runs = listRuns(forgehiveDir);
8864
+ if (runs.length === 0) {
8865
+ console.log("Keine Runs gefunden.");
8866
+ } else {
8867
+ runs.slice(0, 10).forEach((r) => console.log(formatRunStatus(r)));
8868
+ }
8869
+ process.exit(0);
8870
+ }
8871
+ if (subcommand === "resume") {
8872
+ const runId = rest[0];
8873
+ if (!runId) {
8874
+ console.error("Usage: fh auto resume <run-id>");
8875
+ process.exit(1);
8876
+ }
8877
+ await resumeRun(runId, forgehiveDir, projectRoot);
8878
+ process.exit(0);
8879
+ }
8880
+ const autoFlag = subcommand === "--auto";
8881
+ const entryPoint = autoFlag ? rest[0] : subcommand;
8882
+ if (!entryPoint) {
8883
+ console.error("Usage: fh auto <US-N|text>");
8884
+ process.exit(1);
8885
+ }
8886
+ await startRun(entryPoint, forgehiveDir, projectRoot, autoFlag);
8887
+ process.exit(0);
8076
8888
  } else if (command === "status") {
8077
8889
  console.log(projectStatus(projectRoot, forgehiveDir));
8078
8890
  const dashboard = formatDashboard(projectRoot, forgehiveDir);
8079
8891
  if (dashboard) console.log(dashboard);
8892
+ } else if (command === "next") {
8893
+ const { formatNextFull: formatNextFull2 } = await Promise.resolve().then(() => (init_next(), next_exports));
8894
+ console.log(formatNextFull2(projectRoot, forgehiveDir));
8895
+ process.exit(0);
8080
8896
  } else if (command === "cost") {
8081
8897
  const allArgs = [subcommand, ...rest].filter((a) => Boolean(a));
8082
8898
  const limitIdx = allArgs.indexOf("--limit");
8083
8899
  const alertIdx = allArgs.indexOf("--alert");
8084
8900
  if (limitIdx !== -1) {
8085
- if (!fs35.existsSync(forgehiveDir)) {
8901
+ if (!fs39.existsSync(forgehiveDir)) {
8086
8902
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
8087
8903
  process.exit(1);
8088
8904
  }
@@ -8108,24 +8924,24 @@ N\xE4chster Schritt: Setze die erforderlichen Umgebungsvariablen und starte Clau
8108
8924
  }
8109
8925
  const sessions = parseCostSessions(projectRoot);
8110
8926
  console.log(formatCostReport(sessions, range));
8111
- if (fs35.existsSync(forgehiveDir)) {
8927
+ if (fs39.existsSync(forgehiveDir)) {
8112
8928
  const total = sessions.reduce((s, x) => s + x.estimatedCostUsd, 0);
8113
8929
  const status = checkSpendStatus(forgehiveDir, total);
8114
8930
  if (status.message) console.log("\n" + status.message);
8115
8931
  }
8116
8932
  }
8117
8933
  } else if (command === "watch") {
8118
- if (!fs35.existsSync(forgehiveDir)) {
8934
+ if (!fs39.existsSync(forgehiveDir)) {
8119
8935
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
8120
8936
  process.exit(1);
8121
8937
  }
8122
8938
  const isSmartMode = subcommand === "--smart" || rest.includes("--smart");
8123
8939
  if (isSmartMode) {
8124
8940
  let testCmd = "npm test";
8125
- const capsPath = path36.join(forgehiveDir, "capabilities.yaml");
8126
- if (fs35.existsSync(capsPath)) {
8941
+ const capsPath = path40.join(forgehiveDir, "capabilities.yaml");
8942
+ if (fs39.existsSync(capsPath)) {
8127
8943
  try {
8128
- const caps = jsYaml.load(fs35.readFileSync(capsPath, "utf8"));
8944
+ const caps = jsYaml.load(fs39.readFileSync(capsPath, "utf8"));
8129
8945
  if (caps?.test_command) testCmd = caps.test_command;
8130
8946
  } catch {
8131
8947
  }
@@ -8158,7 +8974,7 @@ N\xE4chster Schritt: Setze die erforderlichen Umgebungsvariablen und starte Clau
8158
8974
  });
8159
8975
  }
8160
8976
  } else if (command === "mcp") {
8161
- if (!fs35.existsSync(forgehiveDir)) {
8977
+ if (!fs39.existsSync(forgehiveDir)) {
8162
8978
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
8163
8979
  process.exit(1);
8164
8980
  }
@@ -8269,7 +9085,7 @@ Setze diese Umgebungsvariablen:`);
8269
9085
  process.exit(1);
8270
9086
  }
8271
9087
  } else if (command === "security") {
8272
- if (!fs35.existsSync(forgehiveDir)) {
9088
+ if (!fs39.existsSync(forgehiveDir)) {
8273
9089
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
8274
9090
  process.exit(1);
8275
9091
  }
@@ -8322,8 +9138,8 @@ Setze diese Umgebungsvariablen:`);
8322
9138
  high: high.length
8323
9139
  }
8324
9140
  });
8325
- fs35.writeFileSync(
8326
- path36.join(forgehiveDir, "security-last-scan.txt"),
9141
+ fs39.writeFileSync(
9142
+ path40.join(forgehiveDir, "security-last-scan.txt"),
8327
9143
  (/* @__PURE__ */ new Date()).toISOString(),
8328
9144
  "utf8"
8329
9145
  );
@@ -8360,8 +9176,8 @@ Setze diese Umgebungsvariablen:`);
8360
9176
  `);
8361
9177
  const report = generateSecurityReport(projectRoot, forgehiveDir, mode);
8362
9178
  const text = formatSecurityReport(report);
8363
- const reportPath = path36.join(forgehiveDir, "security-report.md");
8364
- fs35.writeFileSync(reportPath, text, "utf8");
9179
+ const reportPath = path40.join(forgehiveDir, "security-report.md");
9180
+ fs39.writeFileSync(reportPath, text, "utf8");
8365
9181
  console.log(text);
8366
9182
  console.log(`
8367
9183
  \u2713 Report gespeichert: ${reportPath}`);
@@ -8373,8 +9189,8 @@ Setze diese Umgebungsvariablen:`);
8373
9189
  } else if (subcommand === "permissions") {
8374
9190
  const { writePermissions: writePermissions2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
8375
9191
  writePermissions2(forgehiveDir);
8376
- const permPath = path36.join(forgehiveDir, "harness", "permissions.yaml");
8377
- console.log(fs35.readFileSync(permPath, "utf8"));
9192
+ const permPath = path40.join(forgehiveDir, "harness", "permissions.yaml");
9193
+ console.log(fs39.readFileSync(permPath, "utf8"));
8378
9194
  } else {
8379
9195
  console.error(`Unbekannter security-Subcommand: ${subcommand}`);
8380
9196
  console.error("Verf\xFCgbar: scan | deps | audit | report [gdpr|soc2|hipaa|none] | permissions");
@@ -8386,18 +9202,18 @@ Setze diese Umgebungsvariablen:`);
8386
9202
  const failOnArg = allCiArgs.includes("--fail-on") ? allCiArgs[allCiArgs.indexOf("--fail-on") + 1] : "high";
8387
9203
  const initFlag = allCiArgs.includes("--init");
8388
9204
  if (initFlag) {
8389
- const ghDir = path36.join(projectRoot, ".github", "workflows");
8390
- fs35.mkdirSync(ghDir, { recursive: true });
8391
- const outPath = path36.join(ghDir, "forgehive.yml");
8392
- fs35.writeFileSync(outPath, getGithubActionsTemplate(), "utf8");
9205
+ const ghDir = path40.join(projectRoot, ".github", "workflows");
9206
+ fs39.mkdirSync(ghDir, { recursive: true });
9207
+ const outPath = path40.join(ghDir, "forgehive.yml");
9208
+ fs39.writeFileSync(outPath, getGithubActionsTemplate(), "utf8");
8393
9209
  console.log(`\u2714 GitHub Actions workflow geschrieben: ${outPath}`);
8394
9210
  } else {
8395
9211
  const report = generateCiReport(projectRoot, forgehiveDir, failOnArg);
8396
9212
  const output = formatCiReport(report, format);
8397
9213
  console.log(output);
8398
9214
  if (format === "json") {
8399
- fs35.mkdirSync(forgehiveDir, { recursive: true });
8400
- fs35.writeFileSync(path36.join(forgehiveDir, "ci-report.json"), output, "utf8");
9215
+ fs39.mkdirSync(forgehiveDir, { recursive: true });
9216
+ fs39.writeFileSync(path40.join(forgehiveDir, "ci-report.json"), output, "utf8");
8401
9217
  }
8402
9218
  if (report.status === "fail") process.exit(1);
8403
9219
  }
@@ -8405,29 +9221,29 @@ Setze diese Umgebungsvariablen:`);
8405
9221
  const semanticFlag = rest.includes("--semantic");
8406
9222
  const map2 = generateMap(projectRoot);
8407
9223
  const semantic = buildSemanticMap(projectRoot);
8408
- const mapPath = path36.join(forgehiveDir, "map.md");
8409
- fs35.mkdirSync(forgehiveDir, { recursive: true });
9224
+ const mapPath = path40.join(forgehiveDir, "map.md");
9225
+ fs39.mkdirSync(forgehiveDir, { recursive: true });
8410
9226
  if (semanticFlag) {
8411
9227
  const fullMd = formatMap(map2, semantic);
8412
9228
  const marker = "\n## Semantic Graph\n";
8413
9229
  const markerIdx = fullMd.indexOf(marker);
8414
9230
  const semanticMd = markerIdx >= 0 ? "## Semantic Graph\n" + fullMd.slice(markerIdx + marker.length) : fullMd;
8415
- fs35.writeFileSync(mapPath, fullMd, "utf8");
9231
+ fs39.writeFileSync(mapPath, fullMd, "utf8");
8416
9232
  console.log(semanticMd);
8417
9233
  console.log(`
8418
9234
  \u2714 Codebase-Map gespeichert (Semantic Graph hervorgehoben): ${mapPath}`);
8419
9235
  } else {
8420
9236
  const md = formatMap(map2, semantic);
8421
- fs35.writeFileSync(mapPath, md, "utf8");
9237
+ fs39.writeFileSync(mapPath, md, "utf8");
8422
9238
  console.log(md);
8423
9239
  console.log(`
8424
9240
  \u2714 Codebase-Map gespeichert: ${mapPath}`);
8425
9241
  }
8426
9242
  } else if (command === "onboard") {
8427
9243
  const outputArg = rest.includes("--output") ? rest[rest.indexOf("--output") + 1] : null;
8428
- const outputPath = outputArg ?? path36.join(projectRoot, "ONBOARDING.md");
9244
+ const outputPath = outputArg ?? path40.join(projectRoot, "ONBOARDING.md");
8429
9245
  const doc = generateOnboardingDoc(projectRoot, forgehiveDir);
8430
- fs35.writeFileSync(outputPath, doc, "utf8");
9246
+ fs39.writeFileSync(outputPath, doc, "utf8");
8431
9247
  console.log(`\u2714 Onboarding-Dokument geschrieben: ${outputPath}`);
8432
9248
  } else if (command === "ask") {
8433
9249
  const taskArg = subcommand ? [subcommand, ...rest.filter((r) => !r.startsWith("--"))].join(" ") : rest.filter((r) => !r.startsWith("--")).join(" ");
@@ -8450,28 +9266,28 @@ Task: "${taskArg}"
8450
9266
  const commits = parseGitLog(rawLog);
8451
9267
  let version2 = "unreleased";
8452
9268
  try {
8453
- const pkgPath = path36.join(projectRoot, "package.json");
8454
- if (fs35.existsSync(pkgPath)) {
8455
- const pkg = JSON.parse(fs35.readFileSync(pkgPath, "utf8").replace(/^\s*\/\/.*$/gm, ""));
9269
+ const pkgPath = path40.join(projectRoot, "package.json");
9270
+ if (fs39.existsSync(pkgPath)) {
9271
+ const pkg = JSON.parse(fs39.readFileSync(pkgPath, "utf8").replace(/^\s*\/\/.*$/gm, ""));
8456
9272
  version2 = pkg.version ?? "unreleased";
8457
9273
  }
8458
9274
  } catch {
8459
9275
  }
8460
9276
  const md = formatChangelog(commits, version2);
8461
- const outputPath = outputArg ?? path36.join(projectRoot, "CHANGELOG.md");
9277
+ const outputPath = outputArg ?? path40.join(projectRoot, "CHANGELOG.md");
8462
9278
  let existing = "";
8463
- if (fs35.existsSync(outputPath)) existing = fs35.readFileSync(outputPath, "utf8");
8464
- fs35.writeFileSync(outputPath, md + "\n\n" + existing, "utf8");
9279
+ if (fs39.existsSync(outputPath)) existing = fs39.readFileSync(outputPath, "utf8");
9280
+ fs39.writeFileSync(outputPath, md + "\n\n" + existing, "utf8");
8465
9281
  console.log(`\u2714 CHANGELOG.md aktualisiert: ${outputPath}`);
8466
9282
  console.log(` ${commits.length} Commits verarbeitet`);
8467
9283
  } else if (command === "metrics") {
8468
9284
  const sinceArg = rest.includes("--since") ? rest[rest.indexOf("--since") + 1] : void 0;
8469
9285
  const rawLog = getMetricsGitLog(projectRoot, sinceArg);
8470
9286
  const stats = parseCommitStats(rawLog);
8471
- const md = formatMetrics(stats, path36.basename(projectRoot));
8472
- const metricsPath = path36.join(forgehiveDir, "metrics.md");
8473
- fs35.mkdirSync(forgehiveDir, { recursive: true });
8474
- fs35.writeFileSync(metricsPath, md, "utf8");
9287
+ const md = formatMetrics(stats, path40.basename(projectRoot));
9288
+ const metricsPath = path40.join(forgehiveDir, "metrics.md");
9289
+ fs39.mkdirSync(forgehiveDir, { recursive: true });
9290
+ fs39.writeFileSync(metricsPath, md, "utf8");
8475
9291
  console.log(md);
8476
9292
  console.log(`
8477
9293
  \u2714 Metrics gespeichert: ${metricsPath}`);
@@ -8518,7 +9334,7 @@ Task: "${taskDescription}"
8518
9334
  console.log(` fh run status \u2014 aktive Sessions anzeigen`);
8519
9335
  }
8520
9336
  } else if (command === "story") {
8521
- const storiesDir = path36.join(forgehiveDir, "memory", "stories");
9337
+ const storiesDir = path40.join(forgehiveDir, "memory", "stories");
8522
9338
  if (subcommand === "create") {
8523
9339
  const title = rest.filter((r) => !r.startsWith("--")).join(" ");
8524
9340
  const epicArg = rest.includes("--epic") ? rest[rest.indexOf("--epic") + 1] : void 0;
@@ -8529,7 +9345,7 @@ Task: "${taskDescription}"
8529
9345
  }
8530
9346
  const story = createStory(storiesDir, title, epicArg);
8531
9347
  if (pointsArg) updateStoryPoints(storiesDir, story.id, pointsArg);
8532
- console.log(`\u2714 ${story.id} erstellt: ${path36.join(storiesDir, story.id + ".md")}`);
9348
+ console.log(`\u2714 ${story.id} erstellt: ${path40.join(storiesDir, story.id + ".md")}`);
8533
9349
  console.log(` Bearbeite die Datei um Acceptance Criteria hinzuzuf\xFCgen.`);
8534
9350
  } else if (subcommand === "list") {
8535
9351
  const epicFilter = rest.includes("--epic") ? rest[rest.indexOf("--epic") + 1] : null;
@@ -8578,8 +9394,8 @@ Task: "${taskDescription}"
8578
9394
  console.error("Verf\xFCgbar: fh story create | list | show | done");
8579
9395
  }
8580
9396
  } else if (command === "epic") {
8581
- const epicsDir = path36.join(forgehiveDir, "memory", "epics");
8582
- const storiesDir = path36.join(forgehiveDir, "memory", "stories");
9397
+ const epicsDir = path40.join(forgehiveDir, "memory", "epics");
9398
+ const storiesDir = path40.join(forgehiveDir, "memory", "stories");
8583
9399
  if (subcommand === "create") {
8584
9400
  const goalArg = rest.includes("--goal") ? rest[rest.indexOf("--goal") + 1] : void 0;
8585
9401
  const prdArg = rest.includes("--prd") ? rest[rest.indexOf("--prd") + 1] : void 0;
@@ -8597,12 +9413,12 @@ Task: "${taskDescription}"
8597
9413
  process.exit(1);
8598
9414
  }
8599
9415
  if (prdArg) {
8600
- const prdsDir = path36.join(forgehiveDir, "memory", "prds");
9416
+ const prdsDir = path40.join(forgehiveDir, "memory", "prds");
8601
9417
  const prd = getPrd(prdsDir, prdArg);
8602
9418
  if (!prd) console.warn(`\u26A0 PRD ${prdArg} nicht gefunden \u2014 Epic wird trotzdem erstellt`);
8603
9419
  }
8604
9420
  const epic = createEpic(epicsDir, title, goalArg, prdArg);
8605
- console.log(`\u2714 ${epic.id} erstellt: ${path36.join(epicsDir, epic.id + ".md")}`);
9421
+ console.log(`\u2714 ${epic.id} erstellt: ${path40.join(epicsDir, epic.id + ".md")}`);
8606
9422
  if (prdArg) console.log(` Verkn\xFCpft mit: ${prdArg}`);
8607
9423
  } else if (subcommand === "list") {
8608
9424
  const epics = listEpics(epicsDir);
@@ -8629,7 +9445,7 @@ Task: "${taskDescription}"
8629
9445
  console.error("Verf\xFCgbar: fh epic create | list | show");
8630
9446
  }
8631
9447
  } else if (command === "product") {
8632
- const prdsDir = path36.join(forgehiveDir, "memory", "prds");
9448
+ const prdsDir = path40.join(forgehiveDir, "memory", "prds");
8633
9449
  if (subcommand === "prd") {
8634
9450
  const title = rest.filter((r) => !r.startsWith("--")).join(" ");
8635
9451
  if (!title) {
@@ -8637,7 +9453,7 @@ Task: "${taskDescription}"
8637
9453
  process.exit(1);
8638
9454
  }
8639
9455
  const prd = createPrd(prdsDir, title);
8640
- const relPath = path36.relative(projectRoot, prd.filepath);
9456
+ const relPath = path40.relative(projectRoot, prd.filepath);
8641
9457
  console.log(`\u2714 ${prd.id} erstellt: ${relPath}`);
8642
9458
  console.log(` Bearbeite die Datei um Anforderungen zu definieren.`);
8643
9459
  } else if (subcommand === "list") {
@@ -8662,10 +9478,10 @@ Task: "${taskDescription}"
8662
9478
  console.error(`${id} nicht gefunden`);
8663
9479
  process.exit(1);
8664
9480
  }
8665
- console.log(fs35.readFileSync(prd.filepath, "utf8"));
9481
+ console.log(fs39.readFileSync(prd.filepath, "utf8"));
8666
9482
  } else if (subcommand === "status") {
8667
- const epicsDir = path36.join(forgehiveDir, "memory", "epics");
8668
- const storiesDir = path36.join(forgehiveDir, "memory", "stories");
9483
+ const epicsDir = path40.join(forgehiveDir, "memory", "epics");
9484
+ const storiesDir = path40.join(forgehiveDir, "memory", "stories");
8669
9485
  const prds = listPrds(prdsDir);
8670
9486
  const epics = listEpics(epicsDir);
8671
9487
  const stories = listStories(storiesDir);
@@ -8708,14 +9524,14 @@ Task: "${taskDescription}"
8708
9524
  }
8709
9525
  } else if (subcommand === "roadmap") {
8710
9526
  const content = generateRoadmap(forgehiveDir);
8711
- const roadmapPath = path36.join(projectRoot, "ROADMAP.md");
8712
- fs35.writeFileSync(roadmapPath, content, "utf8");
9527
+ const roadmapPath = path40.join(projectRoot, "ROADMAP.md");
9528
+ fs39.writeFileSync(roadmapPath, content, "utf8");
8713
9529
  console.log(`\u2714 ROADMAP.md generiert: ${roadmapPath}`);
8714
9530
  } else {
8715
9531
  console.error("Verf\xFCgbar: fh product prd | list | show | status | roadmap");
8716
9532
  }
8717
9533
  } else if (command === "velocity") {
8718
- const velocityFile = path36.join(forgehiveDir, "memory", "velocity.md");
9534
+ const velocityFile = path40.join(forgehiveDir, "memory", "velocity.md");
8719
9535
  if (subcommand === "record") {
8720
9536
  const sprintNum = parseInt(rest[0] ?? "0", 10);
8721
9537
  const committed = rest.includes("--committed") ? parseInt(rest[rest.indexOf("--committed") + 1], 10) : NaN;
@@ -8734,7 +9550,7 @@ Task: "${taskDescription}"
8734
9550
  console.error("Verf\xFCgbar: fh velocity show | record <N> --committed N --delivered N");
8735
9551
  }
8736
9552
  } else if (command === "docs") {
8737
- const docsDir = path36.join(projectRoot, "docs");
9553
+ const docsDir = path40.join(projectRoot, "docs");
8738
9554
  if (!subcommand || subcommand === "list") {
8739
9555
  const existing = listExistingDocs(projectRoot);
8740
9556
  if (existing.length === 0) {
@@ -8748,29 +9564,29 @@ Task: "${taskDescription}"
8748
9564
  console.log(" fh docs adr <titel> \u2014 Architecture Decision Record");
8749
9565
  } else {
8750
9566
  console.log(`Vorhandene Dokumentation (${existing.length} Dateien):`);
8751
- for (const d of existing) console.log(` ${path36.relative(projectRoot, d)}`);
9567
+ for (const d of existing) console.log(` ${path40.relative(projectRoot, d)}`);
8752
9568
  console.log("");
8753
9569
  console.log("Aktualisieren: fh docs user | api | onboard | changelog");
8754
9570
  }
8755
9571
  } else if (subcommand === "user") {
8756
- fs35.mkdirSync(docsDir, { recursive: true });
9572
+ fs39.mkdirSync(docsDir, { recursive: true });
8757
9573
  const outputArg = rest.includes("--output") ? rest[rest.indexOf("--output") + 1] : null;
8758
- const outputPath = outputArg ?? path36.join(docsDir, "user-guide.md");
9574
+ const outputPath = outputArg ?? path40.join(docsDir, "user-guide.md");
8759
9575
  const guide = generateUserGuide(projectRoot, forgehiveDir);
8760
- fs35.writeFileSync(outputPath, guide, "utf8");
9576
+ fs39.writeFileSync(outputPath, guide, "utf8");
8761
9577
  console.log(`\u2714 User Guide geschrieben: ${outputPath}`);
8762
9578
  } else if (subcommand === "api") {
8763
- fs35.mkdirSync(docsDir, { recursive: true });
9579
+ fs39.mkdirSync(docsDir, { recursive: true });
8764
9580
  const outputArg = rest.includes("--output") ? rest[rest.indexOf("--output") + 1] : null;
8765
- const outputPath = outputArg ?? path36.join(docsDir, "api.md");
9581
+ const outputPath = outputArg ?? path40.join(docsDir, "api.md");
8766
9582
  const ref = generateApiReference(projectRoot);
8767
- fs35.writeFileSync(outputPath, ref, "utf8");
9583
+ fs39.writeFileSync(outputPath, ref, "utf8");
8768
9584
  console.log(`\u2714 API-Referenz geschrieben: ${outputPath}`);
8769
9585
  } else if (subcommand === "onboard") {
8770
9586
  const outputArg = rest.includes("--output") ? rest[rest.indexOf("--output") + 1] : null;
8771
- const outputPath = outputArg ?? path36.join(projectRoot, "ONBOARDING.md");
9587
+ const outputPath = outputArg ?? path40.join(projectRoot, "ONBOARDING.md");
8772
9588
  const doc = generateOnboardingDoc(projectRoot, forgehiveDir);
8773
- fs35.writeFileSync(outputPath, doc, "utf8");
9589
+ fs39.writeFileSync(outputPath, doc, "utf8");
8774
9590
  console.log(`\u2714 Onboarding-Dokument geschrieben: ${outputPath}`);
8775
9591
  } else if (subcommand === "changelog") {
8776
9592
  const sinceArg = rest.includes("--since") ? rest[rest.indexOf("--since") + 1] : null;
@@ -8780,15 +9596,15 @@ Task: "${taskDescription}"
8780
9596
  const commits = parseGitLog(rawLog);
8781
9597
  let pkg = {};
8782
9598
  try {
8783
- pkg = JSON.parse(fs35.readFileSync(path36.join(projectRoot, "package.json"), "utf8"));
9599
+ pkg = JSON.parse(fs39.readFileSync(path40.join(projectRoot, "package.json"), "utf8"));
8784
9600
  } catch {
8785
9601
  }
8786
9602
  const pkgVersion = pkg.version ?? "unreleased";
8787
9603
  const md = formatChangelog(commits, pkgVersion);
8788
- const outputPath = outputArg ?? path36.join(projectRoot, "CHANGELOG.md");
9604
+ const outputPath = outputArg ?? path40.join(projectRoot, "CHANGELOG.md");
8789
9605
  let existing = "";
8790
- if (fs35.existsSync(outputPath)) existing = fs35.readFileSync(outputPath, "utf8");
8791
- fs35.writeFileSync(outputPath, md + "\n\n" + existing, "utf8");
9606
+ if (fs39.existsSync(outputPath)) existing = fs39.readFileSync(outputPath, "utf8");
9607
+ fs39.writeFileSync(outputPath, md + "\n\n" + existing, "utf8");
8792
9608
  console.log(`\u2714 CHANGELOG.md aktualisiert (${commits.length} Commits)`);
8793
9609
  } else if (subcommand === "adr") {
8794
9610
  const title = rest.join(" ");
@@ -8796,9 +9612,9 @@ Task: "${taskDescription}"
8796
9612
  console.error("Usage: fh docs adr <titel>");
8797
9613
  process.exit(1);
8798
9614
  }
8799
- const adrsDir = path36.join(forgehiveDir, "memory", "adrs");
8800
- fs35.mkdirSync(adrsDir, { recursive: true });
8801
- const existing = fs35.readdirSync(adrsDir).filter((f) => f.endsWith(".md")).length;
9615
+ const adrsDir = path40.join(forgehiveDir, "memory", "adrs");
9616
+ fs39.mkdirSync(adrsDir, { recursive: true });
9617
+ const existing = fs39.readdirSync(adrsDir).filter((f) => f.endsWith(".md")).length;
8802
9618
  const adrId = String(existing + 1).padStart(4, "0");
8803
9619
  const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
8804
9620
  const filename = `${adrId}-${slug}.md`;
@@ -8819,13 +9635,13 @@ Task: "${taskDescription}"
8819
9635
 
8820
9636
  (Beschreibe die Auswirkungen dieser Entscheidung.)
8821
9637
  `;
8822
- fs35.writeFileSync(path36.join(adrsDir, filename), content, "utf8");
9638
+ fs39.writeFileSync(path40.join(adrsDir, filename), content, "utf8");
8823
9639
  console.log(`\u2714 ADR erstellt: .forgehive/memory/adrs/${filename}`);
8824
9640
  } else {
8825
9641
  console.error("Verf\xFCgbar: fh docs [list|user|api|onboard|changelog|adr <titel>]");
8826
9642
  }
8827
9643
  } else if (command === "github") {
8828
- const storiesDir = path36.join(forgehiveDir, "memory", "stories");
9644
+ const storiesDir = path40.join(forgehiveDir, "memory", "stories");
8829
9645
  if (subcommand === "setup") {
8830
9646
  const readline = await import("node:readline/promises");
8831
9647
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -8896,8 +9712,8 @@ Task: "${taskDescription}"
8896
9712
  try {
8897
9713
  const [syncOwner, syncRepo] = config.repo.split("/");
8898
9714
  const openPrNumbers = await fetchOpenPRs(syncOwner, syncRepo, creds.GITHUB_TOKEN);
8899
- fs35.writeFileSync(
8900
- path36.join(forgehiveDir, "github-pr-cache.json"),
9715
+ fs39.writeFileSync(
9716
+ path40.join(forgehiveDir, "github-pr-cache.json"),
8901
9717
  JSON.stringify({ open: openPrNumbers, ts: (/* @__PURE__ */ new Date()).toISOString() }),
8902
9718
  "utf8"
8903
9719
  );
@@ -8935,9 +9751,9 @@ Task: "${taskDescription}"
8935
9751
  process.exit(1);
8936
9752
  }
8937
9753
  const contextMd = formatPRContext(pr, files, config.repo);
8938
- fs35.mkdirSync(forgehiveDir, { recursive: true });
8939
- const outputPath = path36.join(forgehiveDir, `github-pr-${prNumber}.md`);
8940
- fs35.writeFileSync(outputPath, contextMd, "utf8");
9754
+ fs39.mkdirSync(forgehiveDir, { recursive: true });
9755
+ const outputPath = path40.join(forgehiveDir, `github-pr-${prNumber}.md`);
9756
+ fs39.writeFileSync(outputPath, contextMd, "utf8");
8941
9757
  console.log(` \u2713 PR-Kontext gespeichert: .forgehive/github-pr-${prNumber}.md`);
8942
9758
  console.log("");
8943
9759
  console.log(` PR context gespeichert. Starte Review: fh party run review`);
@@ -8945,9 +9761,45 @@ Task: "${taskDescription}"
8945
9761
  console.log("Verf\xFCgbar: fh github setup | fh github sync | fh github pr <number>");
8946
9762
  console.log("Hilfe: fh --help");
8947
9763
  }
9764
+ } else if (command === "outcomes") {
9765
+ if (!fs39.existsSync(forgehiveDir)) {
9766
+ console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
9767
+ process.exit(1);
9768
+ }
9769
+ const { aggregateByAgent: aggregateByAgent2, aggregateByTaskType: aggregateByTaskType2, readOutcomes: readOutcomes2 } = await Promise.resolve().then(() => (init_outcomes(), outcomes_exports));
9770
+ const entries = readOutcomes2(forgehiveDir);
9771
+ if (entries.length === 0) {
9772
+ console.log("Keine Outcome-Daten vorhanden. Starte 'fh auto' oder 'fh party run' um Daten zu sammeln.");
9773
+ process.exit(0);
9774
+ }
9775
+ const byAgent = aggregateByAgent2(forgehiveDir);
9776
+ const byType = aggregateByTaskType2(forgehiveDir);
9777
+ console.log(`
9778
+ Outcome-\xDCbersicht (${entries.length} Runs)
9779
+ `);
9780
+ console.log("\u2500\u2500 Agenten \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
9781
+ for (const [agent, stats] of Object.entries(byAgent)) {
9782
+ const avgMin = (stats.avgDurationMs / 6e4).toFixed(1);
9783
+ const rating = stats.avgRating !== null ? ` \u2605 ${stats.avgRating.toFixed(1)}` : "";
9784
+ const types2 = Object.keys(stats.taskTypes).join(", ");
9785
+ console.log(` ${agent.padEnd(8)} \xD8 ${avgMin} min [${types2}]${rating}`);
9786
+ }
9787
+ console.log("\n\u2500\u2500 Task-Typen \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
9788
+ for (const [type2, agg] of Object.entries(byType)) {
9789
+ console.log(` ${type2.padEnd(16)} ${agg.count}x`);
9790
+ }
9791
+ const last = entries.slice(-5).reverse();
9792
+ console.log("\n\u2500\u2500 Letzte 5 Runs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
9793
+ for (const e of last) {
9794
+ const mins = (e.durationMs / 6e4).toFixed(1);
9795
+ const rating = e.rating !== null ? ` \u2605 ${e.rating}` : "";
9796
+ const agents = e.agents.join("+");
9797
+ console.log(` ${e.runId} ${e.taskType} ${agents} ${mins} min${rating}`);
9798
+ }
9799
+ process.exit(0);
8948
9800
  } else {
8949
9801
  console.error("Unbekannter Befehl: " + command);
8950
- 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> | github [setup|sync|pr] | cost | memory | skills | party | wire | mcp | security");
9802
+ console.error("Verf\xFCgbar: init | confirm | rollback | scan | status | auto | 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> | github [setup|sync|pr] | cost | memory | skills | party | outcomes | wire | mcp | security");
8951
9803
  console.error("Hilfe: fh --help");
8952
9804
  process.exit(1);
8953
9805
  }