forgehive 0.8.1 → 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.
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 spawnSync12 } 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,21 +5247,146 @@ 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;
5257
+ }
5258
+ function readYamlFrontmatterFiles(dir) {
5259
+ if (!fs17.existsSync(dir)) return [];
5260
+ const results = [];
5261
+ for (const filename of fs17.readdirSync(dir)) {
5262
+ if (!filename.endsWith(".md")) continue;
5263
+ try {
5264
+ const content = fs17.readFileSync(path17.join(dir, filename), "utf8");
5265
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
5266
+ if (!match) continue;
5267
+ const parsed = jsYaml.load(match[1]);
5268
+ if (parsed && typeof parsed === "object") results.push(parsed);
5269
+ } catch {
5270
+ }
5271
+ }
5272
+ return results;
5273
+ }
5274
+ function formatSprintSection(forgehiveDir2) {
5275
+ const storiesDir = path17.join(forgehiveDir2, "memory", "stories");
5276
+ if (!fs17.existsSync(storiesDir)) return "";
5277
+ const stories = readYamlFrontmatterFiles(storiesDir);
5278
+ const open = stories.filter((s) => s.status !== "done");
5279
+ const inSprint = stories.filter((s) => s.status === "in-sprint");
5280
+ const next = inSprint[0] ?? open[0] ?? null;
5281
+ const epicsDir = path17.join(forgehiveDir2, "memory", "epics");
5282
+ const activeEpics = readYamlFrontmatterFiles(epicsDir).filter((e) => e.status === "active" || e.status === "in-progress");
5283
+ const lines = ["SPRINT"];
5284
+ lines.push(` \u25CF ${open.length} offen \u25CF ${inSprint.length} in-sprint`);
5285
+ if (activeEpics.length > 0) {
5286
+ lines.push(` Epic: ${activeEpics[0].id} ${activeEpics[0].title}`);
5287
+ }
5288
+ if (next) {
5289
+ const pts = next.points ? ` [${next.points} pts]` : "";
5290
+ lines.push(` \u2192 n\xE4chstes: ${next.id} "${next.title}"${pts}`);
5291
+ }
5292
+ return lines.join("\n");
5293
+ }
5294
+ function formatCodeHealthSection(projectRoot2, forgehiveDir2) {
5295
+ const lines = ["CODE HEALTH"];
5296
+ const gitResult = spawnSync4("git", ["status", "--porcelain"], {
5297
+ cwd: projectRoot2,
5298
+ encoding: "utf8"
5299
+ });
5300
+ if (gitResult.status === 0) {
5301
+ const changed = gitResult.stdout.trim().split("\n").filter(Boolean).length;
5302
+ lines.push(
5303
+ changed === 0 ? " \u2713 Arbeitsbaum sauber" : ` \u26A0 ${changed} ge\xE4nderte Datei(en) \u2014 nicht committed`
5304
+ );
5305
+ }
5306
+ const secPath = path17.join(forgehiveDir2, "security-last-scan.txt");
5307
+ if (fs17.existsSync(secPath)) {
5308
+ try {
5309
+ const ts = fs17.readFileSync(secPath, "utf8").trim();
5310
+ const days = Math.floor((Date.now() - new Date(ts).getTime()) / 864e5);
5311
+ lines.push(
5312
+ days > 7 ? ` \u26A0 Security-Scan: ${days} Tage alt \u2014 fh security scan` : ` \u2713 Security-Scan: ${days} Tage alt`
5313
+ );
5314
+ } catch {
5315
+ }
5316
+ }
5317
+ const prCachePath = path17.join(forgehiveDir2, "github-pr-cache.json");
5318
+ if (fs17.existsSync(prCachePath)) {
5319
+ try {
5320
+ const cache = JSON.parse(fs17.readFileSync(prCachePath, "utf8"));
5321
+ if (cache.open.length > 0) {
5322
+ lines.push(` \u25CF ${cache.open.length} offene PR(s): #${cache.open.join(", #")}`);
5323
+ }
5324
+ } catch {
5325
+ }
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
+ }
5347
+ return lines.join("\n");
5348
+ }
5349
+ function formatWatchSection(forgehiveDir2) {
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) => {
5353
+ try {
5354
+ return JSON.parse(line);
5355
+ } catch {
5356
+ return null;
5357
+ }
5358
+ }).filter((e) => e !== null).slice(-5).reverse();
5359
+ if (events.length === 0) return "";
5360
+ const lines = ["WATCH (letzte Ereignisse)"];
5361
+ for (const e of events) {
5362
+ const time = new Date(e.ts).toLocaleTimeString("de", { hour: "2-digit", minute: "2-digit" });
5363
+ if (e.type === "stack-update") {
5364
+ lines.push(` [${time}] \u2713 Stack-Update (${e.file ?? "?"}) \u2014 capabilities.yaml aktualisiert`);
5365
+ } else {
5366
+ const verdict = e.accepted === true ? "\u2192 gestartet" : e.accepted === false ? "\u2192 abgelehnt" : "\u2192 vorgeschlagen";
5367
+ lines.push(` [${time}] \u26A0 ${e.file ?? e.type} ${verdict}: ${e.agent ?? "?"}`);
5368
+ }
5369
+ }
5370
+ return lines.join("\n");
5371
+ }
5372
+ function formatDashboard(projectRoot2, forgehiveDir2) {
5373
+ const sections = [
5374
+ formatSprintSection(forgehiveDir2),
5375
+ formatCodeHealthSection(projectRoot2, forgehiveDir2),
5376
+ formatWatchSection(forgehiveDir2),
5377
+ formatNextCompact(projectRoot2, forgehiveDir2)
5378
+ ].filter(Boolean);
5379
+ if (sections.length === 0) return "";
5380
+ return "\n" + sections.join("\n\n");
4862
5381
  }
4863
5382
  function checkDrift(projectRoot2, forgehiveDir2) {
4864
- const scanPath = path15.join(forgehiveDir2, "scan-result.yaml");
4865
- if (!fs15.existsSync(scanPath)) {
5383
+ const scanPath = path17.join(forgehiveDir2, "scan-result.yaml");
5384
+ if (!fs17.existsSync(scanPath)) {
4866
5385
  return { daysSinceScan: null, recentCommits: 0, isDrifted: false };
4867
5386
  }
4868
5387
  let scannedAt = null;
4869
5388
  try {
4870
- const raw = jsYaml.load(fs15.readFileSync(scanPath, "utf8"));
5389
+ const raw = jsYaml.load(fs17.readFileSync(scanPath, "utf8"));
4871
5390
  scannedAt = raw?.scanned_at ?? null;
4872
5391
  } catch {
4873
5392
  }
@@ -4890,14 +5409,14 @@ function checkDrift(projectRoot2, forgehiveDir2) {
4890
5409
  }
4891
5410
  function projectStatus(projectRoot2, forgehiveDir2) {
4892
5411
  const lines = ["ForgeHive Status", ""];
4893
- if (!fs15.existsSync(forgehiveDir2)) {
5412
+ if (!fs17.existsSync(forgehiveDir2)) {
4894
5413
  lines.push("\u2717 Not initialized \u2014 run: fh init");
4895
5414
  return lines.join("\n");
4896
5415
  }
4897
- const capsPath = path15.join(forgehiveDir2, "capabilities.yaml");
4898
- if (fs15.existsSync(capsPath)) {
5416
+ const capsPath = path17.join(forgehiveDir2, "capabilities.yaml");
5417
+ if (fs17.existsSync(capsPath)) {
4899
5418
  try {
4900
- const raw = jsYaml.load(fs15.readFileSync(capsPath, "utf8"));
5419
+ const raw = jsYaml.load(fs17.readFileSync(capsPath, "utf8"));
4901
5420
  const caps = raw && typeof raw === "object" ? raw : {};
4902
5421
  const confirmed = caps.capabilities?.confirmed?.length ?? 0;
4903
5422
  const inferred = caps.capabilities?.inferred?.length ?? 0;
@@ -4908,10 +5427,10 @@ function projectStatus(projectRoot2, forgehiveDir2) {
4908
5427
  lines.push("\u26A0 Capabilities: capabilities.yaml unlesbar");
4909
5428
  }
4910
5429
  }
4911
- const claudeMdPath = path15.join(projectRoot2, "CLAUDE.md");
4912
- if (fs15.existsSync(claudeMdPath)) {
5430
+ const claudeMdPath = path17.join(projectRoot2, "CLAUDE.md");
5431
+ if (fs17.existsSync(claudeMdPath)) {
4913
5432
  try {
4914
- const content = fs15.readFileSync(claudeMdPath, "utf8");
5433
+ const content = fs17.readFileSync(claudeMdPath, "utf8");
4915
5434
  const hasBlock = content.includes("<!-- forgehive:start -->");
4916
5435
  lines.push(hasBlock ? "\u2713 CLAUDE.md block present" : "\u26A0 CLAUDE.md block missing \u2014 run: fh init");
4917
5436
  } catch {
@@ -4920,15 +5439,15 @@ function projectStatus(projectRoot2, forgehiveDir2) {
4920
5439
  } else {
4921
5440
  lines.push("\u26A0 CLAUDE.md not found");
4922
5441
  }
4923
- const skillsDir = path15.join(forgehiveDir2, "skills");
4924
- const generated = countMdFiles(path15.join(skillsDir, "generated"));
4925
- const expert = countMdFiles(path15.join(skillsDir, "expert"));
4926
- 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"));
4927
5446
  lines.push(`\u2713 Skills: ${generated} generated, ${expert} expert, ${workflows} workflows`);
4928
- const mcpPath = path15.join(projectRoot2, ".mcp.json");
4929
- if (fs15.existsSync(mcpPath)) {
5447
+ const mcpPath = path17.join(projectRoot2, ".mcp.json");
5448
+ if (fs17.existsSync(mcpPath)) {
4930
5449
  try {
4931
- const mcp = JSON.parse(fs15.readFileSync(mcpPath, "utf8"));
5450
+ const mcp = JSON.parse(fs17.readFileSync(mcpPath, "utf8"));
4932
5451
  const services = Object.keys(mcp.mcpServers ?? {});
4933
5452
  lines.push(
4934
5453
  services.length > 0 ? `\u2713 MCP: ${services.join(", ")}` : " MCP: none configured"
@@ -4939,17 +5458,17 @@ function projectStatus(projectRoot2, forgehiveDir2) {
4939
5458
  } else {
4940
5459
  lines.push(" MCP: none configured (fh wire <service>)");
4941
5460
  }
4942
- const memoryDir = path15.join(forgehiveDir2, "memory");
4943
- if (fs15.existsSync(memoryDir)) {
4944
- 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"));
4945
5464
  lines.push(`\u2713 Memory: ${files.length} file(s)`);
4946
5465
  } else {
4947
5466
  lines.push(" Memory: not initialized");
4948
5467
  }
4949
- const scanPath = path15.join(forgehiveDir2, "scan-result.yaml");
4950
- if (fs15.existsSync(scanPath)) {
5468
+ const scanPath = path17.join(forgehiveDir2, "scan-result.yaml");
5469
+ if (fs17.existsSync(scanPath)) {
4951
5470
  try {
4952
- const raw = jsYaml.load(fs15.readFileSync(scanPath, "utf8"));
5471
+ const raw = jsYaml.load(fs17.readFileSync(scanPath, "utf8"));
4953
5472
  const scan2 = raw && typeof raw === "object" ? raw : {};
4954
5473
  lines.push(`\u2713 Last scan: ${scan2.scanned_at ?? "unknown"}`);
4955
5474
  } catch {
@@ -4963,23 +5482,24 @@ function projectStatus(projectRoot2, forgehiveDir2) {
4963
5482
  } else if (drift.daysSinceScan !== null) {
4964
5483
  lines.push(`\u2713 Kontext aktuell (${drift.daysSinceScan} Tage, ${drift.recentCommits} Commits seit Scan)`);
4965
5484
  }
4966
- const agentMemDir = path15.join(forgehiveDir2, "agents", "memory");
4967
- if (fs15.existsSync(agentMemDir)) {
4968
- 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"));
4969
5488
  lines.push(`\u2713 Agent memory: ${agents.length} agent(s)`);
4970
5489
  }
4971
5490
  return lines.join("\n");
4972
5491
  }
4973
5492
 
4974
5493
  // src/watch.ts
4975
- import fs16 from "node:fs";
4976
- import path16 from "node:path";
5494
+ import fs18 from "node:fs";
5495
+ import path18 from "node:path";
5496
+ import { spawnSync as spawnSync5 } from "node:child_process";
4977
5497
  function checkProjectHash(projectRoot2, forgehiveDir2, claudeMdBlock) {
4978
- const hashPath = path16.join(forgehiveDir2, ".scan-hash");
4979
- 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;
4980
5500
  const currentHash = computeHash(projectRoot2);
4981
5501
  if (currentHash === savedHash) return false;
4982
- fs16.writeFileSync(hashPath, currentHash, "utf8");
5502
+ fs18.writeFileSync(hashPath, currentHash, "utf8");
4983
5503
  const scanResult = scan(projectRoot2);
4984
5504
  const capMap = mapSignalsToCapabilities(scanResult);
4985
5505
  writeForgehiveDir(projectRoot2, scanResult, capMap, claudeMdBlock);
@@ -4996,7 +5516,7 @@ function watchProject(projectRoot2, forgehiveDir2, claudeMdBlock, onUpdate = def
4996
5516
  "go.mod",
4997
5517
  "pyproject.toml",
4998
5518
  "composer.json"
4999
- ].map((f) => path16.join(projectRoot2, f)).filter((f) => fs16.existsSync(f));
5519
+ ].map((f) => path18.join(projectRoot2, f)).filter((f) => fs18.existsSync(f));
5000
5520
  let debounce = null;
5001
5521
  const watchers = [];
5002
5522
  function schedule() {
@@ -5008,12 +5528,12 @@ function watchProject(projectRoot2, forgehiveDir2, claudeMdBlock, onUpdate = def
5008
5528
  }
5009
5529
  for (const f of candidates) {
5010
5530
  try {
5011
- watchers.push(fs16.watch(f, schedule));
5531
+ watchers.push(fs18.watch(f, schedule));
5012
5532
  } catch {
5013
5533
  }
5014
5534
  }
5015
5535
  try {
5016
- watchers.push(fs16.watch(projectRoot2, schedule));
5536
+ watchers.push(fs18.watch(projectRoot2, schedule));
5017
5537
  } catch {
5018
5538
  }
5019
5539
  return () => {
@@ -5031,10 +5551,95 @@ function defaultOnUpdate(changed) {
5031
5551
  console.log("[forgehive] \u2713 Codebase changed \u2014 capabilities.yaml aktualisiert");
5032
5552
  }
5033
5553
  }
5554
+ function appendWatchEvent(forgehiveDir2, event) {
5555
+ const logPath = path18.join(forgehiveDir2, "watch-events.jsonl");
5556
+ fs18.appendFileSync(logPath, JSON.stringify(event) + "\n", "utf8");
5557
+ try {
5558
+ const stat = fs18.statSync(logPath);
5559
+ if (stat.size > 25e3) {
5560
+ const lines = fs18.readFileSync(logPath, "utf8").trim().split("\n").filter(Boolean);
5561
+ if (lines.length >= 500) {
5562
+ fs18.writeFileSync(logPath, lines.slice(-500).join("\n") + "\n", "utf8");
5563
+ }
5564
+ }
5565
+ } catch {
5566
+ }
5567
+ }
5568
+ function detectErrorType(output) {
5569
+ const lower = output.toLowerCase();
5570
+ if (lower.includes("failing") || lower.includes("assertionerror") || lower.includes("not ok")) {
5571
+ return { agent: "sam", reason: "Test-Fehler erkannt" };
5572
+ }
5573
+ if (lower.includes("error ts") || lower.includes("type error")) {
5574
+ return { agent: "kai", reason: "TypeScript-Fehler erkannt" };
5575
+ }
5576
+ if (lower.includes("secret") || lower.includes("vulnerability") || lower.includes("cve")) {
5577
+ return { agent: "vera", reason: "Sicherheitsproblem erkannt" };
5578
+ }
5579
+ if (lower.includes("error") || lower.includes("failed to compile")) {
5580
+ return { agent: "kai", reason: "Build-Fehler erkannt" };
5581
+ }
5582
+ return null;
5583
+ }
5584
+ function smartWatch(projectRoot2, forgehiveDir2, testCmd, onSuggest) {
5585
+ const srcDir = path18.join(projectRoot2, "src");
5586
+ const testDir = path18.join(projectRoot2, "test");
5587
+ const watchDirs = [srcDir, testDir].filter((d) => fs18.existsSync(d));
5588
+ let debounce = null;
5589
+ const watchers = [];
5590
+ function runAndAnalyze(changedFile) {
5591
+ const result = spawnSync5(testCmd, {
5592
+ cwd: projectRoot2,
5593
+ encoding: "utf8",
5594
+ shell: true
5595
+ });
5596
+ if (result.status === 0) return;
5597
+ const output = (result.stdout ?? "") + (result.stderr ?? "");
5598
+ const detection = detectErrorType(output);
5599
+ if (!detection) return;
5600
+ const relFile = path18.relative(projectRoot2, changedFile);
5601
+ console.log(`
5602
+ \u26A0 ${relFile} \u2014 ${detection.reason}`);
5603
+ console.log(` \u2192 ${detection.agent} ist der richtige Agent.`);
5604
+ void onSuggest(detection.agent, detection.reason, relFile).then((accepted) => {
5605
+ appendWatchEvent(forgehiveDir2, {
5606
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
5607
+ type: "test-failure",
5608
+ file: relFile,
5609
+ agent: detection.agent,
5610
+ accepted
5611
+ });
5612
+ });
5613
+ }
5614
+ function schedule(changedFile) {
5615
+ if (debounce) clearTimeout(debounce);
5616
+ debounce = setTimeout(() => runAndAnalyze(changedFile), 1500);
5617
+ }
5618
+ for (const dir of watchDirs) {
5619
+ try {
5620
+ const watcher = fs18.watch(dir, { recursive: true }, (_evt, filename) => {
5621
+ if (filename && (filename.endsWith(".ts") || filename.endsWith(".js"))) {
5622
+ schedule(path18.join(dir, filename));
5623
+ }
5624
+ });
5625
+ watchers.push(watcher);
5626
+ } catch {
5627
+ }
5628
+ }
5629
+ return () => {
5630
+ if (debounce) clearTimeout(debounce);
5631
+ for (const w of watchers) {
5632
+ try {
5633
+ w.close();
5634
+ } catch {
5635
+ }
5636
+ }
5637
+ };
5638
+ }
5034
5639
 
5035
5640
  // src/cost.ts
5036
- import fs17 from "node:fs";
5037
- import path17 from "node:path";
5641
+ import fs19 from "node:fs";
5642
+ import path19 from "node:path";
5038
5643
  import os2 from "node:os";
5039
5644
  var PRICING = {
5040
5645
  input: 3,
@@ -5045,31 +5650,31 @@ var PRICING = {
5045
5650
  function calcCost(inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens) {
5046
5651
  return inputTokens / 1e6 * PRICING.input + outputTokens / 1e6 * PRICING.output + cacheCreationTokens / 1e6 * PRICING.cacheCreation + cacheReadTokens / 1e6 * PRICING.cacheRead;
5047
5652
  }
5048
- function parseCostSessions(_projectRoot, claudeHome = path17.join(os2.homedir(), ".claude")) {
5049
- const projectsDir = path17.join(claudeHome, "projects");
5050
- if (!fs17.existsSync(projectsDir)) return [];
5051
- 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);
5052
5657
  const sessions = [];
5053
5658
  for (const dir of matchDirs) {
5054
- const dirPath = path17.join(projectsDir, dir);
5659
+ const dirPath = path19.join(projectsDir, dir);
5055
5660
  let stat;
5056
5661
  try {
5057
- stat = fs17.statSync(dirPath);
5662
+ stat = fs19.statSync(dirPath);
5058
5663
  } catch {
5059
5664
  continue;
5060
5665
  }
5061
5666
  if (!stat.isDirectory()) continue;
5062
5667
  let files;
5063
5668
  try {
5064
- files = fs17.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
5669
+ files = fs19.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
5065
5670
  } catch {
5066
5671
  continue;
5067
5672
  }
5068
5673
  for (const jsonlFile of files) {
5069
- const filePath = path17.join(dirPath, jsonlFile);
5674
+ const filePath = path19.join(dirPath, jsonlFile);
5070
5675
  let inputTokens = 0, outputTokens = 0, cacheCreationTokens = 0, cacheReadTokens = 0;
5071
5676
  try {
5072
- const lines = fs17.readFileSync(filePath, "utf8").split("\n").filter(Boolean);
5677
+ const lines = fs19.readFileSync(filePath, "utf8").split("\n").filter(Boolean);
5073
5678
  for (const line of lines) {
5074
5679
  try {
5075
5680
  const entry = JSON.parse(line);
@@ -5082,7 +5687,7 @@ function parseCostSessions(_projectRoot, claudeHome = path17.join(os2.homedir(),
5082
5687
  } catch {
5083
5688
  }
5084
5689
  }
5085
- const mtime = fs17.statSync(filePath).mtime;
5690
+ const mtime = fs19.statSync(filePath).mtime;
5086
5691
  sessions.push({
5087
5692
  sessionFile: jsonlFile,
5088
5693
  date: mtime.toISOString().slice(0, 10),
@@ -5130,28 +5735,28 @@ function formatCostReport(sessions, range) {
5130
5735
  }
5131
5736
 
5132
5737
  // src/mcp-auth.ts
5133
- import fs18 from "node:fs";
5134
- import path18 from "node:path";
5738
+ import fs20 from "node:fs";
5739
+ import path20 from "node:path";
5135
5740
  import os3 from "node:os";
5136
5741
  function getCredsPath() {
5137
- const dir = path18.join(os3.homedir(), ".forgehive");
5138
- fs18.mkdirSync(dir, { recursive: true });
5139
- 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");
5140
5745
  }
5141
5746
  function loadStore() {
5142
5747
  const p = getCredsPath();
5143
- if (!fs18.existsSync(p)) return {};
5748
+ if (!fs20.existsSync(p)) return {};
5144
5749
  try {
5145
- return JSON.parse(fs18.readFileSync(p, "utf8"));
5750
+ return JSON.parse(fs20.readFileSync(p, "utf8"));
5146
5751
  } catch {
5147
5752
  return {};
5148
5753
  }
5149
5754
  }
5150
5755
  function saveStore(store) {
5151
5756
  const p = getCredsPath();
5152
- fs18.writeFileSync(p, JSON.stringify(store, null, 2), "utf8");
5757
+ fs20.writeFileSync(p, JSON.stringify(store, null, 2), "utf8");
5153
5758
  try {
5154
- fs18.chmodSync(p, 384);
5759
+ fs20.chmodSync(p, 384);
5155
5760
  } catch {
5156
5761
  }
5157
5762
  }
@@ -5198,9 +5803,9 @@ function checkMcpTrust(packageName) {
5198
5803
  }
5199
5804
 
5200
5805
  // src/mcp-registry.ts
5201
- import fs19 from "node:fs";
5202
- import path19 from "node:path";
5203
- import { spawnSync as spawnSync5 } from "node:child_process";
5806
+ import fs21 from "node:fs";
5807
+ import path21 from "node:path";
5808
+ import { spawnSync as spawnSync6 } from "node:child_process";
5204
5809
  function parseRegistryResponse(raw) {
5205
5810
  if (!raw || typeof raw !== "object") return [];
5206
5811
  const data = raw;
@@ -5214,7 +5819,7 @@ function parseRegistryResponse(raw) {
5214
5819
  }
5215
5820
  function searchRegistry(query) {
5216
5821
  const url = `https://registry.smithery.ai/servers?q=${encodeURIComponent(query)}&pageSize=10`;
5217
- const result = spawnSync5("curl", [
5822
+ const result = spawnSync6("curl", [
5218
5823
  "-s",
5219
5824
  "--max-time",
5220
5825
  "10",
@@ -5236,11 +5841,11 @@ function searchRegistry(query) {
5236
5841
  }
5237
5842
  function addMcpFromRegistry(projectRoot2, forgehiveDir2, packageName, envKeys) {
5238
5843
  const serverId = packageName.replace(/^@[^/]+\//, "") || packageName;
5239
- const mcpPath = path19.join(projectRoot2, ".mcp.json");
5844
+ const mcpPath = path21.join(projectRoot2, ".mcp.json");
5240
5845
  let mcpConfig = { mcpServers: {} };
5241
- if (fs19.existsSync(mcpPath)) {
5846
+ if (fs21.existsSync(mcpPath)) {
5242
5847
  try {
5243
- mcpConfig = JSON.parse(fs19.readFileSync(mcpPath, "utf8"));
5848
+ mcpConfig = JSON.parse(fs21.readFileSync(mcpPath, "utf8"));
5244
5849
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
5245
5850
  } catch {
5246
5851
  mcpConfig = { mcpServers: {} };
@@ -5255,12 +5860,12 @@ function addMcpFromRegistry(projectRoot2, forgehiveDir2, packageName, envKeys) {
5255
5860
  args: ["-y", packageName],
5256
5861
  ...envKeys.length > 0 ? { env: envObj } : {}
5257
5862
  };
5258
- fs19.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
5259
- const skillDir = path19.join(forgehiveDir2, "skills", "workflows");
5260
- fs19.mkdirSync(skillDir, { recursive: true });
5261
- const skillPath = path19.join(skillDir, `${serverId}.md`);
5262
- if (!fs19.existsSync(skillPath)) {
5263
- 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, [
5264
5869
  `# ${serverId} Workflow Skill`,
5265
5870
  "",
5266
5871
  `## Setup`,
@@ -5275,9 +5880,9 @@ function addMcpFromRegistry(projectRoot2, forgehiveDir2, packageName, envKeys) {
5275
5880
  }
5276
5881
 
5277
5882
  // src/security-scan.ts
5278
- import fs20 from "node:fs";
5279
- import path20 from "node:path";
5280
- import { spawnSync as spawnSync6 } from "node:child_process";
5883
+ import fs22 from "node:fs";
5884
+ import path22 from "node:path";
5885
+ import { spawnSync as spawnSync7 } from "node:child_process";
5281
5886
  var SECRET_PATTERNS = [
5282
5887
  { name: "Anthropic Key", pattern: /sk-ant-[a-zA-Z0-9-]{20,}/g },
5283
5888
  { name: "OpenAI Key", pattern: /sk-(?!ant-)[a-zA-Z0-9]{20,}/g },
@@ -5295,16 +5900,16 @@ function collectScanFiles(dir, exts = SCAN_EXTS) {
5295
5900
  function walk(current) {
5296
5901
  let entries;
5297
5902
  try {
5298
- entries = fs20.readdirSync(current, { withFileTypes: true });
5903
+ entries = fs22.readdirSync(current, { withFileTypes: true });
5299
5904
  } catch {
5300
5905
  return;
5301
5906
  }
5302
5907
  for (const entry of entries) {
5303
5908
  if (IGNORE_DIRS.includes(entry.name)) continue;
5304
- const full = path20.join(current, entry.name);
5909
+ const full = path22.join(current, entry.name);
5305
5910
  if (entry.isDirectory()) {
5306
5911
  walk(full);
5307
- } else if (entry.isFile() && exts.includes(path20.extname(entry.name))) {
5912
+ } else if (entry.isFile() && exts.includes(path22.extname(entry.name))) {
5308
5913
  results.push(full);
5309
5914
  }
5310
5915
  }
@@ -5318,7 +5923,7 @@ function scanSecrets(projectRoot2) {
5318
5923
  for (const file of files) {
5319
5924
  let content;
5320
5925
  try {
5321
- content = fs20.readFileSync(file, "utf8");
5926
+ content = fs22.readFileSync(file, "utf8");
5322
5927
  } catch {
5323
5928
  continue;
5324
5929
  }
@@ -5385,7 +5990,7 @@ function scanSast(projectRoot2) {
5385
5990
  for (const file of files) {
5386
5991
  let content;
5387
5992
  try {
5388
- content = fs20.readFileSync(file, "utf8");
5993
+ content = fs22.readFileSync(file, "utf8");
5389
5994
  } catch {
5390
5995
  continue;
5391
5996
  }
@@ -5410,9 +6015,9 @@ function scanSast(projectRoot2) {
5410
6015
  return findings;
5411
6016
  }
5412
6017
  function scanDeps(projectRoot2) {
5413
- const pkgJsonPath = path20.join(projectRoot2, "package.json");
5414
- if (!fs20.existsSync(pkgJsonPath)) return [];
5415
- const result = spawnSync6("npm", ["audit", "--json"], {
6018
+ const pkgJsonPath = path22.join(projectRoot2, "package.json");
6019
+ if (!fs22.existsSync(pkgJsonPath)) return [];
6020
+ const result = spawnSync7("npm", ["audit", "--json"], {
5416
6021
  cwd: projectRoot2,
5417
6022
  encoding: "utf8",
5418
6023
  timeout: 3e4
@@ -5440,19 +6045,19 @@ function scanDeps(projectRoot2) {
5440
6045
  }
5441
6046
 
5442
6047
  // src/audit.ts
5443
- import fs21 from "node:fs";
5444
- import path21 from "node:path";
6048
+ import fs23 from "node:fs";
6049
+ import path23 from "node:path";
5445
6050
  function getAuditPath(forgehiveDir2) {
5446
- return path21.join(forgehiveDir2, "audit.log");
6051
+ return path23.join(forgehiveDir2, "audit.log");
5447
6052
  }
5448
6053
  function logAuditEvent(forgehiveDir2, event) {
5449
6054
  const line = JSON.stringify(event) + "\n";
5450
- fs21.appendFileSync(getAuditPath(forgehiveDir2), line, "utf8");
6055
+ fs23.appendFileSync(getAuditPath(forgehiveDir2), line, "utf8");
5451
6056
  }
5452
6057
  function getAuditLog(forgehiveDir2, limit = 50) {
5453
6058
  const p = getAuditPath(forgehiveDir2);
5454
- if (!fs21.existsSync(p)) return [];
5455
- 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);
5456
6061
  const recent = lines.slice(-limit);
5457
6062
  return recent.map((l) => {
5458
6063
  try {
@@ -5477,19 +6082,19 @@ function formatAuditReport(events) {
5477
6082
 
5478
6083
  // src/spend-limits.ts
5479
6084
  init_js_yaml();
5480
- import fs22 from "node:fs";
5481
- import path22 from "node:path";
6085
+ import fs24 from "node:fs";
6086
+ import path24 from "node:path";
5482
6087
  function getConfigPath(forgehiveDir2) {
5483
- return path22.join(forgehiveDir2, "cost-config.yaml");
6088
+ return path24.join(forgehiveDir2, "cost-config.yaml");
5484
6089
  }
5485
6090
  function setSpendLimit(forgehiveDir2, config) {
5486
- fs22.writeFileSync(getConfigPath(forgehiveDir2), jsYaml.dump(config), "utf8");
6091
+ fs24.writeFileSync(getConfigPath(forgehiveDir2), jsYaml.dump(config), "utf8");
5487
6092
  }
5488
6093
  function getSpendConfig(forgehiveDir2) {
5489
6094
  const p = getConfigPath(forgehiveDir2);
5490
- if (!fs22.existsSync(p)) return {};
6095
+ if (!fs24.existsSync(p)) return {};
5491
6096
  try {
5492
- return jsYaml.load(fs22.readFileSync(p, "utf8"));
6097
+ return jsYaml.load(fs24.readFileSync(p, "utf8"));
5493
6098
  } catch {
5494
6099
  return {};
5495
6100
  }
@@ -5584,7 +6189,7 @@ function formatSecurityReport(report) {
5584
6189
  }
5585
6190
 
5586
6191
  // src/ci.ts
5587
- import path23 from "node:path";
6192
+ import path25 from "node:path";
5588
6193
  function generateCiReport(projectRoot2, forgehiveDir2, failOn = "high") {
5589
6194
  const secrets = scanSecrets(projectRoot2);
5590
6195
  const sast = scanSast(projectRoot2);
@@ -5601,7 +6206,7 @@ function generateCiReport(projectRoot2, forgehiveDir2, failOn = "high") {
5601
6206
  }
5602
6207
  return {
5603
6208
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5604
- project: path23.basename(projectRoot2),
6209
+ project: path25.basename(projectRoot2),
5605
6210
  status: failedOn ? "fail" : "pass",
5606
6211
  secrets,
5607
6212
  sast,
@@ -5675,8 +6280,8 @@ jobs:
5675
6280
  }
5676
6281
 
5677
6282
  // src/map.ts
5678
- import fs23 from "node:fs";
5679
- import path24 from "node:path";
6283
+ import fs25 from "node:fs";
6284
+ import path26 from "node:path";
5680
6285
  var MAP_EXTS = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"];
5681
6286
  var IGNORE_DIRS2 = ["node_modules", ".git", "dist", ".forgehive", "coverage", ".next", "build"];
5682
6287
  var IMPORT_PATTERNS = [
@@ -5698,11 +6303,11 @@ function extractImports(content) {
5698
6303
  function walkFiles(dir) {
5699
6304
  const results = [];
5700
6305
  function walk(current) {
5701
- for (const entry of fs23.readdirSync(current, { withFileTypes: true })) {
6306
+ for (const entry of fs25.readdirSync(current, { withFileTypes: true })) {
5702
6307
  if (IGNORE_DIRS2.includes(entry.name)) continue;
5703
- const full = path24.join(current, entry.name);
6308
+ const full = path26.join(current, entry.name);
5704
6309
  if (entry.isDirectory()) walk(full);
5705
- else if (entry.isFile() && MAP_EXTS.includes(path24.extname(entry.name)))
6310
+ else if (entry.isFile() && MAP_EXTS.includes(path26.extname(entry.name)))
5706
6311
  results.push(full);
5707
6312
  }
5708
6313
  }
@@ -5714,7 +6319,7 @@ function generateMap(projectRoot2) {
5714
6319
  const files = filePaths.map((filePath) => {
5715
6320
  let content = "";
5716
6321
  try {
5717
- content = fs23.readFileSync(filePath, "utf8");
6322
+ content = fs25.readFileSync(filePath, "utf8");
5718
6323
  } catch {
5719
6324
  }
5720
6325
  const rawLines = content.split("\n");
@@ -5722,7 +6327,7 @@ function generateMap(projectRoot2) {
5722
6327
  const imports = extractImports(content);
5723
6328
  return {
5724
6329
  path: filePath,
5725
- relativePath: path24.relative(projectRoot2, filePath),
6330
+ relativePath: path26.relative(projectRoot2, filePath),
5726
6331
  lines,
5727
6332
  imports
5728
6333
  };
@@ -5811,8 +6416,8 @@ function formatMap(map2, semantic) {
5811
6416
  }
5812
6417
 
5813
6418
  // src/semantic-map.ts
5814
- import fs24 from "node:fs";
5815
- import path25 from "node:path";
6419
+ import fs26 from "node:fs";
6420
+ import path27 from "node:path";
5816
6421
  var IGNORE_DIRS3 = ["node_modules", ".git", "dist", ".forgehive", "coverage", ".next", "build"];
5817
6422
  var MAP_EXTS2 = [".ts", ".tsx", ".js", ".jsx", ".py", ".go"];
5818
6423
  var IMPORT_PATTERNS2 = {
@@ -5863,15 +6468,15 @@ function walkFiles2(dir) {
5863
6468
  function walk(current) {
5864
6469
  let entries;
5865
6470
  try {
5866
- entries = fs24.readdirSync(current, { withFileTypes: true });
6471
+ entries = fs26.readdirSync(current, { withFileTypes: true });
5867
6472
  } catch {
5868
6473
  return;
5869
6474
  }
5870
6475
  for (const entry of entries) {
5871
6476
  if (IGNORE_DIRS3.includes(entry.name)) continue;
5872
- const full = path25.join(current, entry.name);
6477
+ const full = path27.join(current, entry.name);
5873
6478
  if (entry.isDirectory()) walk(full);
5874
- else if (entry.isFile() && MAP_EXTS2.includes(path25.extname(entry.name))) {
6479
+ else if (entry.isFile() && MAP_EXTS2.includes(path27.extname(entry.name))) {
5875
6480
  results.push(full);
5876
6481
  }
5877
6482
  }
@@ -5917,15 +6522,15 @@ function _extractExports(content, lang) {
5917
6522
  }
5918
6523
  function _resolveImport(fromFile, importPath, allFiles) {
5919
6524
  if (!importPath.startsWith(".")) return null;
5920
- const fromDir = path25.dirname(fromFile);
5921
- const base = path25.resolve(fromDir, importPath);
6525
+ const fromDir = path27.dirname(fromFile);
6526
+ const base = path27.resolve(fromDir, importPath);
5922
6527
  if (allFiles.includes(base)) return base;
5923
6528
  for (const ext of [".ts", ".tsx", ".js", ".jsx", ".py", ".go"]) {
5924
6529
  const candidate = base + ext;
5925
6530
  if (allFiles.includes(candidate)) return candidate;
5926
6531
  }
5927
6532
  for (const ext of [".ts", ".tsx", ".js", ".jsx"]) {
5928
- const candidate = path25.join(base, "index" + ext);
6533
+ const candidate = path27.join(base, "index" + ext);
5929
6534
  if (allFiles.includes(candidate)) return candidate;
5930
6535
  }
5931
6536
  return null;
@@ -5973,10 +6578,10 @@ function buildSemanticMap(projectRoot2) {
5973
6578
  for (const filePath of allFiles) {
5974
6579
  let content = "";
5975
6580
  try {
5976
- content = fs24.readFileSync(filePath, "utf8");
6581
+ content = fs26.readFileSync(filePath, "utf8");
5977
6582
  } catch {
5978
6583
  }
5979
- const ext = path25.extname(filePath);
6584
+ const ext = path27.extname(filePath);
5980
6585
  const lang = langOf(ext);
5981
6586
  const rawImps = _extractImports(content, lang);
5982
6587
  const resolvedImps = [];
@@ -5994,11 +6599,11 @@ function buildSemanticMap(projectRoot2) {
5994
6599
 
5995
6600
  // src/onboard.ts
5996
6601
  init_js_yaml();
5997
- import fs25 from "node:fs";
5998
- import path26 from "node:path";
5999
- import { spawnSync as spawnSync7 } from "node:child_process";
6602
+ import fs27 from "node:fs";
6603
+ import path28 from "node:path";
6604
+ import { spawnSync as spawnSync8 } from "node:child_process";
6000
6605
  function getRecentCommits(projectRoot2, n = 20) {
6001
- const result = spawnSync7("git", ["log", `--oneline`, `-${n}`], {
6606
+ const result = spawnSync8("git", ["log", `--oneline`, `-${n}`], {
6002
6607
  cwd: projectRoot2,
6003
6608
  encoding: "utf8"
6004
6609
  });
@@ -6006,30 +6611,30 @@ function getRecentCommits(projectRoot2, n = 20) {
6006
6611
  return result.stdout.trim().split("\n").filter(Boolean);
6007
6612
  }
6008
6613
  function readCapabilities(forgehiveDir2) {
6009
- const capPath = path26.join(forgehiveDir2, "capabilities.yaml");
6010
- if (!fs25.existsSync(capPath)) return {};
6614
+ const capPath = path28.join(forgehiveDir2, "capabilities.yaml");
6615
+ if (!fs27.existsSync(capPath)) return {};
6011
6616
  try {
6012
- return jsYaml.load(fs25.readFileSync(capPath, "utf8")) ?? {};
6617
+ return jsYaml.load(fs27.readFileSync(capPath, "utf8")) ?? {};
6013
6618
  } catch {
6014
6619
  return {};
6015
6620
  }
6016
6621
  }
6017
6622
  function readMemoryFiles(forgehiveDir2) {
6018
- const memDir = path26.join(forgehiveDir2, "memory");
6019
- if (!fs25.existsSync(memDir)) return {};
6623
+ const memDir = path28.join(forgehiveDir2, "memory");
6624
+ if (!fs27.existsSync(memDir)) return {};
6020
6625
  const result = {};
6021
- for (const f of fs25.readdirSync(memDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md")) {
6022
- 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");
6023
6628
  }
6024
6629
  return result;
6025
6630
  }
6026
6631
  function listAdrs2(forgehiveDir2) {
6027
- const adrsDir = path26.join(forgehiveDir2, "memory", "adrs");
6028
- if (!fs25.existsSync(adrsDir)) return [];
6029
- 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"));
6030
6635
  }
6031
6636
  function generateOnboardingDoc(projectRoot2, forgehiveDir2) {
6032
- const projectName = path26.basename(projectRoot2);
6637
+ const projectName = path28.basename(projectRoot2);
6033
6638
  const caps = readCapabilities(forgehiveDir2);
6034
6639
  const memFiles = readMemoryFiles(forgehiveDir2);
6035
6640
  const commits = getRecentCommits(projectRoot2);
@@ -6103,7 +6708,7 @@ function generateOnboardingDoc(projectRoot2, forgehiveDir2) {
6103
6708
  }
6104
6709
 
6105
6710
  // src/changelog.ts
6106
- import { spawnSync as spawnSync8 } from "node:child_process";
6711
+ import { spawnSync as spawnSync9 } from "node:child_process";
6107
6712
  var TYPE_LABELS = {
6108
6713
  feat: "Added",
6109
6714
  fix: "Fixed",
@@ -6167,12 +6772,12 @@ function formatChangelog(commits, version2) {
6167
6772
  function getGitLogSince(projectRoot2, since) {
6168
6773
  const args = ["log", "--oneline", "--no-merges"];
6169
6774
  if (since) args.push(`${since}..HEAD`);
6170
- const result = spawnSync8("git", args, { cwd: projectRoot2, encoding: "utf8" });
6775
+ const result = spawnSync9("git", args, { cwd: projectRoot2, encoding: "utf8" });
6171
6776
  if (result.status !== 0) return "";
6172
6777
  return result.stdout.trim();
6173
6778
  }
6174
6779
  function getLatestTag(projectRoot2) {
6175
- const result = spawnSync8("git", ["describe", "--tags", "--abbrev=0"], {
6780
+ const result = spawnSync9("git", ["describe", "--tags", "--abbrev=0"], {
6176
6781
  cwd: projectRoot2,
6177
6782
  encoding: "utf8"
6178
6783
  });
@@ -6181,7 +6786,7 @@ function getLatestTag(projectRoot2) {
6181
6786
  }
6182
6787
 
6183
6788
  // src/metrics.ts
6184
- import { spawnSync as spawnSync9 } from "node:child_process";
6789
+ import { spawnSync as spawnSync10 } from "node:child_process";
6185
6790
  function parseCommitStats(rawLog) {
6186
6791
  const lines = rawLog.split("\n").map((l) => l.trim()).filter(Boolean);
6187
6792
  const byAuthor = {};
@@ -6238,26 +6843,26 @@ function formatMetrics(stats, projectName) {
6238
6843
  function getMetricsGitLog(projectRoot2, since) {
6239
6844
  const args = ["log", "--no-merges", "--format=%as %an %s"];
6240
6845
  if (since) args.push(`--since=${since}`);
6241
- const result = spawnSync9("git", args, { cwd: projectRoot2, encoding: "utf8" });
6846
+ const result = spawnSync10("git", args, { cwd: projectRoot2, encoding: "utf8" });
6242
6847
  if (result.status !== 0) return "";
6243
6848
  return result.stdout.trim();
6244
6849
  }
6245
6850
 
6246
6851
  // src/sync.ts
6247
- import fs26 from "node:fs";
6248
- import path27 from "node:path";
6249
- import { spawnSync as spawnSync10 } from "node:child_process";
6852
+ import fs28 from "node:fs";
6853
+ import path29 from "node:path";
6854
+ import { spawnSync as spawnSync11 } from "node:child_process";
6250
6855
  function getSyncStatus(forgehiveDir2) {
6251
- const memDir = path27.join(forgehiveDir2, "memory");
6252
- const files = fs26.existsSync(memDir) ? fs26.readdirSync(memDir).filter((f) => f.endsWith(".md")).length : 0;
6253
- const projectRoot2 = path27.dirname(forgehiveDir2);
6254
- const configResult = spawnSync10(
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);
6859
+ const configResult = spawnSync11(
6255
6860
  "git",
6256
6861
  ["config", "--get", "forgehive.sync-remote"],
6257
6862
  { cwd: projectRoot2, encoding: "utf8" }
6258
6863
  );
6259
6864
  const remote = configResult.status === 0 ? configResult.stdout.trim() : null;
6260
- const branchResult = spawnSync10(
6865
+ const branchResult = spawnSync11(
6261
6866
  "git",
6262
6867
  ["config", "--get", "forgehive.sync-branch"],
6263
6868
  { cwd: projectRoot2, encoding: "utf8" }
@@ -6266,24 +6871,24 @@ function getSyncStatus(forgehiveDir2) {
6266
6871
  return { files, hasRemote: remote !== null, remote, branch };
6267
6872
  }
6268
6873
  function pushSync(forgehiveDir2, remote = "origin", branch = "forgehive-memory") {
6269
- const projectRoot2 = path27.dirname(forgehiveDir2);
6270
- const memDir = path27.join(forgehiveDir2, "memory");
6271
- if (!fs26.existsSync(memDir)) {
6874
+ const projectRoot2 = path29.dirname(forgehiveDir2);
6875
+ const memDir = path29.join(forgehiveDir2, "memory");
6876
+ if (!fs28.existsSync(memDir)) {
6272
6877
  return { success: false, message: "Kein Memory-Verzeichnis gefunden.", filesCommitted: 0 };
6273
6878
  }
6274
- const files = fs26.readdirSync(memDir).filter((f) => f.endsWith(".md"));
6879
+ const files = fs28.readdirSync(memDir).filter((f) => f.endsWith(".md"));
6275
6880
  if (files.length === 0) {
6276
6881
  return { success: false, message: "Keine Memory-Dateien gefunden.", filesCommitted: 0 };
6277
6882
  }
6278
- const addResult = spawnSync10(
6883
+ const addResult = spawnSync11(
6279
6884
  "git",
6280
- ["add", path27.join(".forgehive", "memory")],
6885
+ ["add", path29.join(".forgehive", "memory")],
6281
6886
  { cwd: projectRoot2, encoding: "utf8" }
6282
6887
  );
6283
6888
  if (addResult.status !== 0) {
6284
6889
  return { success: false, message: `git add failed: ${addResult.stderr}`, filesCommitted: 0 };
6285
6890
  }
6286
- const commitResult = spawnSync10(
6891
+ const commitResult = spawnSync11(
6287
6892
  "git",
6288
6893
  ["commit", "-m", `chore: sync forgehive memory [${(/* @__PURE__ */ new Date()).toISOString()}]`],
6289
6894
  { cwd: projectRoot2, encoding: "utf8" }
@@ -6292,7 +6897,7 @@ function pushSync(forgehiveDir2, remote = "origin", branch = "forgehive-memory")
6292
6897
  if (!commitOk) {
6293
6898
  return { success: false, message: `git commit failed: ${commitResult.stderr}`, filesCommitted: 0 };
6294
6899
  }
6295
- const pushResult = spawnSync10(
6900
+ const pushResult = spawnSync11(
6296
6901
  "git",
6297
6902
  ["push", remote, `HEAD:${branch}`],
6298
6903
  { cwd: projectRoot2, encoding: "utf8" }
@@ -6300,15 +6905,15 @@ function pushSync(forgehiveDir2, remote = "origin", branch = "forgehive-memory")
6300
6905
  if (pushResult.status !== 0) {
6301
6906
  return { success: false, message: `git push failed: ${pushResult.stderr}`, filesCommitted: 0 };
6302
6907
  }
6303
- spawnSync10("git", ["config", "forgehive.sync-remote", remote], { cwd: projectRoot2 });
6304
- spawnSync10("git", ["config", "forgehive.sync-branch", branch], { cwd: projectRoot2 });
6908
+ spawnSync11("git", ["config", "forgehive.sync-remote", remote], { cwd: projectRoot2 });
6909
+ spawnSync11("git", ["config", "forgehive.sync-branch", branch], { cwd: projectRoot2 });
6305
6910
  return { success: true, message: `Memory gepusht nach ${remote}/${branch}`, filesCommitted: files.length };
6306
6911
  }
6307
6912
  function pullSync(forgehiveDir2, remote = "origin", branch = "forgehive-memory") {
6308
- const projectRoot2 = path27.dirname(forgehiveDir2);
6309
- const memDir = path27.join(forgehiveDir2, "memory");
6310
- fs26.mkdirSync(memDir, { recursive: true });
6311
- const fetchResult = spawnSync10(
6913
+ const projectRoot2 = path29.dirname(forgehiveDir2);
6914
+ const memDir = path29.join(forgehiveDir2, "memory");
6915
+ fs28.mkdirSync(memDir, { recursive: true });
6916
+ const fetchResult = spawnSync11(
6312
6917
  "git",
6313
6918
  ["fetch", remote, branch],
6314
6919
  { cwd: projectRoot2, encoding: "utf8" }
@@ -6316,7 +6921,7 @@ function pullSync(forgehiveDir2, remote = "origin", branch = "forgehive-memory")
6316
6921
  if (fetchResult.status !== 0) {
6317
6922
  return { success: false, message: `git fetch failed: ${fetchResult.stderr}`, filesImported: [] };
6318
6923
  }
6319
- const listResult = spawnSync10(
6924
+ const listResult = spawnSync11(
6320
6925
  "git",
6321
6926
  ["ls-tree", "--name-only", `${remote}/${branch}`, ".forgehive/memory/"],
6322
6927
  { cwd: projectRoot2, encoding: "utf8" }
@@ -6327,16 +6932,16 @@ function pullSync(forgehiveDir2, remote = "origin", branch = "forgehive-memory")
6327
6932
  const remoteFiles = listResult.stdout.trim().split("\n").filter(Boolean);
6328
6933
  const imported = [];
6329
6934
  for (const remotePath of remoteFiles) {
6330
- const filename = path27.basename(remotePath);
6331
- const localPath = path27.join(memDir, filename);
6332
- if (!fs26.existsSync(localPath)) {
6333
- const contentResult = spawnSync10(
6935
+ const filename = path29.basename(remotePath);
6936
+ const localPath = path29.join(memDir, filename);
6937
+ if (!fs28.existsSync(localPath)) {
6938
+ const contentResult = spawnSync11(
6334
6939
  "git",
6335
6940
  ["show", `${remote}/${branch}:${remotePath}`],
6336
6941
  { cwd: projectRoot2, encoding: "utf8" }
6337
6942
  );
6338
6943
  if (contentResult.status === 0) {
6339
- fs26.writeFileSync(localPath, contentResult.stdout, "utf8");
6944
+ fs28.writeFileSync(localPath, contentResult.stdout, "utf8");
6340
6945
  imported.push(filename);
6341
6946
  }
6342
6947
  }
@@ -6349,8 +6954,8 @@ function pullSync(forgehiveDir2, remote = "origin", branch = "forgehive-memory")
6349
6954
  }
6350
6955
 
6351
6956
  // src/background.ts
6352
- import fs27 from "node:fs";
6353
- import path28 from "node:path";
6957
+ import fs29 from "node:fs";
6958
+ import path30 from "node:path";
6354
6959
  import { spawn } from "node:child_process";
6355
6960
  var AGENT_ROLES = {
6356
6961
  kai: "Senior Engineer \u2014 implements features and fixes bugs",
@@ -6397,23 +7002,23 @@ Instructions:
6397
7002
  Work autonomously. Do not ask for clarification \u2014 use your best judgment based on the issue description and codebase context.`;
6398
7003
  }
6399
7004
  function runBackgroundAgent(forgehiveDir2, issueUrl, agentId) {
6400
- const logsDir = path28.join(forgehiveDir2, "background-runs");
6401
- fs27.mkdirSync(logsDir, { recursive: true });
7005
+ const logsDir = path30.join(forgehiveDir2, "background-runs");
7006
+ fs29.mkdirSync(logsDir, { recursive: true });
6402
7007
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
6403
- const logFile = path28.join(logsDir, `${agentId}-${timestamp2}.log`);
7008
+ const logFile = path30.join(logsDir, `${agentId}-${timestamp2}.log`);
6404
7009
  const prompt = buildAgentPrompt(issueUrl, agentId);
6405
- const logStream = fs27.openSync(logFile, "w");
7010
+ const logStream = fs29.openSync(logFile, "w");
6406
7011
  const child = spawn(
6407
7012
  "claude",
6408
7013
  ["-p", prompt, "--output-format", "text"],
6409
7014
  {
6410
- cwd: path28.dirname(forgehiveDir2),
7015
+ cwd: path30.dirname(forgehiveDir2),
6411
7016
  detached: true,
6412
7017
  stdio: ["ignore", logStream, logStream]
6413
7018
  }
6414
7019
  );
6415
7020
  child.unref();
6416
- fs27.closeSync(logStream);
7021
+ fs29.closeSync(logStream);
6417
7022
  return {
6418
7023
  pid: child.pid,
6419
7024
  logFile,
@@ -6423,11 +7028,11 @@ function runBackgroundAgent(forgehiveDir2, issueUrl, agentId) {
6423
7028
 
6424
7029
  // src/stories.ts
6425
7030
  init_js_yaml();
6426
- import fs28 from "node:fs";
6427
- import path29 from "node:path";
7031
+ import fs30 from "node:fs";
7032
+ import path31 from "node:path";
6428
7033
  function nextStoryId(storiesDir) {
6429
- if (!fs28.existsSync(storiesDir)) return "US-1";
6430
- 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));
6431
7036
  const max = existing.length > 0 ? Math.max(...existing) : 0;
6432
7037
  return `US-${max + 1}`;
6433
7038
  }
@@ -6459,7 +7064,7 @@ ${acLines || "- [ ] (noch nicht definiert)"}
6459
7064
  }
6460
7065
  function parseStoryFile(filePath) {
6461
7066
  try {
6462
- const content = fs28.readFileSync(filePath, "utf8");
7067
+ const content = fs30.readFileSync(filePath, "utf8");
6463
7068
  const match = content.match(/^---\n([\s\S]*?)\n---/);
6464
7069
  if (!match) return null;
6465
7070
  const data = jsYaml.load(match[1]);
@@ -6479,7 +7084,7 @@ function parseStoryFile(filePath) {
6479
7084
  }
6480
7085
  }
6481
7086
  function createStory(storiesDir, title, epicId) {
6482
- fs28.mkdirSync(storiesDir, { recursive: true });
7087
+ fs30.mkdirSync(storiesDir, { recursive: true });
6483
7088
  const id = nextStoryId(storiesDir);
6484
7089
  const story = {
6485
7090
  id,
@@ -6492,33 +7097,33 @@ function createStory(storiesDir, title, epicId) {
6492
7097
  epicId: epicId ?? null,
6493
7098
  status: "backlog"
6494
7099
  };
6495
- fs28.writeFileSync(path29.join(storiesDir, `${id}.md`), storyToMarkdown(story), "utf8");
7100
+ fs30.writeFileSync(path31.join(storiesDir, `${id}.md`), storyToMarkdown(story), "utf8");
6496
7101
  return story;
6497
7102
  }
6498
7103
  function listStories(storiesDir) {
6499
- if (!fs28.existsSync(storiesDir)) return [];
6500
- 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) => {
6501
7106
  const na = parseInt(a.id.replace("US-", ""), 10);
6502
7107
  const nb = parseInt(b.id.replace("US-", ""), 10);
6503
7108
  return na - nb;
6504
7109
  });
6505
7110
  }
6506
7111
  function getStory(storiesDir, id) {
6507
- const filePath = path29.join(storiesDir, `${id}.md`);
6508
- if (!fs28.existsSync(filePath)) return null;
7112
+ const filePath = path31.join(storiesDir, `${id}.md`);
7113
+ if (!fs30.existsSync(filePath)) return null;
6509
7114
  return parseStoryFile(filePath);
6510
7115
  }
6511
7116
  function updateStoryPoints(storiesDir, id, points) {
6512
7117
  const story = getStory(storiesDir, id);
6513
7118
  if (!story) throw new Error(`Story ${id} nicht gefunden`);
6514
7119
  story.points = points;
6515
- fs28.writeFileSync(path29.join(storiesDir, `${id}.md`), storyToMarkdown(story), "utf8");
7120
+ fs30.writeFileSync(path31.join(storiesDir, `${id}.md`), storyToMarkdown(story), "utf8");
6516
7121
  }
6517
7122
  function updateStoryStatus(storiesDir, id, status) {
6518
7123
  const story = getStory(storiesDir, id);
6519
7124
  if (!story) throw new Error(`Story ${id} nicht gefunden`);
6520
7125
  story.status = status;
6521
- fs28.writeFileSync(path29.join(storiesDir, `${id}.md`), storyToMarkdown(story), "utf8");
7126
+ fs30.writeFileSync(path31.join(storiesDir, `${id}.md`), storyToMarkdown(story), "utf8");
6522
7127
  }
6523
7128
  function formatStoryCard(story) {
6524
7129
  const points = story.points !== null ? ` \xB7 ${story.points} Punkte` : "";
@@ -6537,11 +7142,11 @@ function formatStoryCard(story) {
6537
7142
 
6538
7143
  // src/epics.ts
6539
7144
  init_js_yaml();
6540
- import fs29 from "node:fs";
6541
- import path30 from "node:path";
7145
+ import fs31 from "node:fs";
7146
+ import path32 from "node:path";
6542
7147
  function nextEpicId(epicsDir) {
6543
- if (!fs29.existsSync(epicsDir)) return "EPC-1";
6544
- 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));
6545
7150
  const max = existing.length > 0 ? Math.max(...existing) : 0;
6546
7151
  return `EPC-${max + 1}`;
6547
7152
  }
@@ -6569,7 +7174,7 @@ ${storyLines || "(noch keine Stories)"}
6569
7174
  }
6570
7175
  function parseEpicFile(filePath) {
6571
7176
  try {
6572
- const content = fs29.readFileSync(filePath, "utf8");
7177
+ const content = fs31.readFileSync(filePath, "utf8");
6573
7178
  const match = content.match(/^---\n([\s\S]*?)\n---/);
6574
7179
  if (!match) return null;
6575
7180
  const data = jsYaml.load(match[1]);
@@ -6586,23 +7191,23 @@ function parseEpicFile(filePath) {
6586
7191
  }
6587
7192
  }
6588
7193
  function createEpic(epicsDir, title, goal, prdId) {
6589
- fs29.mkdirSync(epicsDir, { recursive: true });
7194
+ fs31.mkdirSync(epicsDir, { recursive: true });
6590
7195
  const id = nextEpicId(epicsDir);
6591
7196
  const epic = { id, title, goal: goal ?? "", stories: [], status: "active", prdId: prdId ?? null };
6592
- fs29.writeFileSync(path30.join(epicsDir, `${id}.md`), epicToMarkdown(epic), "utf8");
7197
+ fs31.writeFileSync(path32.join(epicsDir, `${id}.md`), epicToMarkdown(epic), "utf8");
6593
7198
  return epic;
6594
7199
  }
6595
7200
  function listEpics(epicsDir) {
6596
- if (!fs29.existsSync(epicsDir)) return [];
6597
- 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) => {
6598
7203
  const na = parseInt(a.id.replace("EPC-", ""), 10);
6599
7204
  const nb = parseInt(b.id.replace("EPC-", ""), 10);
6600
7205
  return na - nb;
6601
7206
  });
6602
7207
  }
6603
7208
  function getEpic(epicsDir, id) {
6604
- const filePath = path30.join(epicsDir, `${id}.md`);
6605
- if (!fs29.existsSync(filePath)) return null;
7209
+ const filePath = path32.join(epicsDir, `${id}.md`);
7210
+ if (!fs31.existsSync(filePath)) return null;
6606
7211
  return parseEpicFile(filePath);
6607
7212
  }
6608
7213
  function formatEpicCard(epic, stories) {
@@ -6626,14 +7231,14 @@ function formatEpicCard(epic, stories) {
6626
7231
 
6627
7232
  // src/product.ts
6628
7233
  init_js_yaml();
6629
- import fs30 from "node:fs";
6630
- import path31 from "node:path";
7234
+ import fs32 from "node:fs";
7235
+ import path33 from "node:path";
6631
7236
  function slugify2(title) {
6632
7237
  return title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-");
6633
7238
  }
6634
7239
  function nextPrdId(prdsDir) {
6635
- if (!fs30.existsSync(prdsDir)) return "PRD-1";
6636
- 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));
6637
7242
  const max = existing.length > 0 ? Math.max(...existing) : 0;
6638
7243
  return `PRD-${max + 1}`;
6639
7244
  }
@@ -6671,7 +7276,7 @@ ${frontmatter}---
6671
7276
  }
6672
7277
  function parsePrdFile(filePath) {
6673
7278
  try {
6674
- const content = fs30.readFileSync(filePath, "utf8");
7279
+ const content = fs32.readFileSync(filePath, "utf8");
6675
7280
  const match = content.match(/^---\n([\s\S]*?)\n---/);
6676
7281
  if (!match) return null;
6677
7282
  const data = jsYaml.load(match[1]);
@@ -6688,37 +7293,37 @@ function parsePrdFile(filePath) {
6688
7293
  }
6689
7294
  }
6690
7295
  function createPrd(prdsDir, title) {
6691
- fs30.mkdirSync(prdsDir, { recursive: true });
7296
+ fs32.mkdirSync(prdsDir, { recursive: true });
6692
7297
  const id = nextPrdId(prdsDir);
6693
7298
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6694
7299
  const slug = slugify2(title);
6695
7300
  const filename = `${today}-${slug}.md`;
6696
- const filepath = path31.join(prdsDir, filename);
7301
+ const filepath = path33.join(prdsDir, filename);
6697
7302
  const prd = { id, title, date: today, status: "draft", filepath };
6698
- fs30.writeFileSync(filepath, prdToMarkdown(prd), "utf8");
7303
+ fs32.writeFileSync(filepath, prdToMarkdown(prd), "utf8");
6699
7304
  return prd;
6700
7305
  }
6701
7306
  function listPrds(prdsDir) {
6702
- if (!fs30.existsSync(prdsDir)) return [];
6703
- 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) => {
6704
7309
  const na = parseInt(a.id.replace("PRD-", ""), 10);
6705
7310
  const nb = parseInt(b.id.replace("PRD-", ""), 10);
6706
7311
  return na - nb;
6707
7312
  });
6708
7313
  }
6709
7314
  function getPrd(prdsDir, id) {
6710
- if (!fs30.existsSync(prdsDir)) return null;
6711
- 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"));
6712
7317
  for (const f of files) {
6713
- const prd = parsePrdFile(path31.join(prdsDir, f));
7318
+ const prd = parsePrdFile(path33.join(prdsDir, f));
6714
7319
  if (prd && prd.id === id) return prd;
6715
7320
  }
6716
7321
  return null;
6717
7322
  }
6718
7323
  function generateRoadmap(forgehiveDir2) {
6719
- const prdsDir = path31.join(forgehiveDir2, "memory", "prds");
6720
- const epicsDir = path31.join(forgehiveDir2, "memory", "epics");
6721
- 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");
6722
7327
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6723
7328
  const prds = listPrds(prdsDir);
6724
7329
  const epics = listEpics(epicsDir);
@@ -6788,24 +7393,24 @@ function generateRoadmap(forgehiveDir2) {
6788
7393
  }
6789
7394
 
6790
7395
  // src/velocity.ts
6791
- import fs31 from "node:fs";
6792
- import path32 from "node:path";
7396
+ import fs33 from "node:fs";
7397
+ import path34 from "node:path";
6793
7398
  var HEADER = "# Sprint Velocity\n\n| Sprint | Datum | Committed | Delivered | Rate |\n|---|---|---|---|---|\n";
6794
7399
  function recordVelocity(velocityFile, sprint, committed, delivered) {
6795
7400
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6796
7401
  const rate = committed > 0 ? Math.round(delivered / committed * 100) : 0;
6797
7402
  const row = `| Sprint ${sprint} | ${date} | ${committed} | ${delivered} | ${rate}% |
6798
7403
  `;
6799
- if (!fs31.existsSync(velocityFile)) {
6800
- fs31.mkdirSync(path32.dirname(velocityFile), { recursive: true });
6801
- 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");
6802
7407
  } else {
6803
- fs31.appendFileSync(velocityFile, row, "utf8");
7408
+ fs33.appendFileSync(velocityFile, row, "utf8");
6804
7409
  }
6805
7410
  }
6806
7411
  function getVelocityHistory(velocityFile) {
6807
- if (!fs31.existsSync(velocityFile)) return [];
6808
- const content = fs31.readFileSync(velocityFile, "utf8");
7412
+ if (!fs33.existsSync(velocityFile)) return [];
7413
+ const content = fs33.readFileSync(velocityFile, "utf8");
6809
7414
  const rows = content.split("\n").filter((l) => l.startsWith("| Sprint "));
6810
7415
  return rows.map((row) => {
6811
7416
  const cells = row.split("|").map((c) => c.trim()).filter(Boolean);
@@ -6846,9 +7451,9 @@ function formatVelocityReport(history) {
6846
7451
 
6847
7452
  // src/docs.ts
6848
7453
  init_js_yaml();
6849
- import fs32 from "node:fs";
6850
- import path33 from "node:path";
6851
- import { spawnSync as spawnSync11 } from "node:child_process";
7454
+ import fs34 from "node:fs";
7455
+ import path35 from "node:path";
7456
+ import { spawnSync as spawnSync12 } from "node:child_process";
6852
7457
  var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".py", ".go"];
6853
7458
  var IGNORE_DIRS4 = ["node_modules", ".git", "dist", ".forgehive", "coverage", ".next", "build", "test", "__tests__", "spec"];
6854
7459
  var EXPORT_PATTERNS2 = [
@@ -6858,10 +7463,10 @@ var EXPORT_PATTERNS2 = [
6858
7463
  /^export\s+default\s+(?:function\s+)?(\w+)?/gm
6859
7464
  ];
6860
7465
  function readCapabilities2(forgehiveDir2) {
6861
- const capPath = path33.join(forgehiveDir2, "capabilities.yaml");
6862
- if (!fs32.existsSync(capPath)) return {};
7466
+ const capPath = path35.join(forgehiveDir2, "capabilities.yaml");
7467
+ if (!fs34.existsSync(capPath)) return {};
6863
7468
  try {
6864
- return jsYaml.load(fs32.readFileSync(capPath, "utf8")) ?? {};
7469
+ return jsYaml.load(fs34.readFileSync(capPath, "utf8")) ?? {};
6865
7470
  } catch {
6866
7471
  return {};
6867
7472
  }
@@ -6888,16 +7493,16 @@ function extractCapabilityInfo(caps) {
6888
7493
  return { language, packageManager };
6889
7494
  }
6890
7495
  function readMemoryFiles2(forgehiveDir2) {
6891
- const memDir = path33.join(forgehiveDir2, "memory");
6892
- if (!fs32.existsSync(memDir)) return {};
7496
+ const memDir = path35.join(forgehiveDir2, "memory");
7497
+ if (!fs34.existsSync(memDir)) return {};
6893
7498
  const result = {};
6894
- for (const f of fs32.readdirSync(memDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md")) {
6895
- 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");
6896
7501
  }
6897
7502
  return result;
6898
7503
  }
6899
7504
  function getRecentCommits2(projectRoot2, n = 10) {
6900
- const result = spawnSync11("git", ["log", "--oneline", `-${n}`], { cwd: projectRoot2, encoding: "utf8" });
7505
+ const result = spawnSync12("git", ["log", "--oneline", `-${n}`], { cwd: projectRoot2, encoding: "utf8" });
6901
7506
  if (result.status !== 0) return [];
6902
7507
  return result.stdout.trim().split("\n").filter(Boolean);
6903
7508
  }
@@ -6915,19 +7520,19 @@ function extractExports(content) {
6915
7520
  function walkSourceFiles(dir) {
6916
7521
  const results = [];
6917
7522
  function walk(current) {
6918
- if (!fs32.existsSync(current)) return;
6919
- 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 })) {
6920
7525
  if (IGNORE_DIRS4.includes(entry.name)) continue;
6921
- const full = path33.join(current, entry.name);
7526
+ const full = path35.join(current, entry.name);
6922
7527
  if (entry.isDirectory()) walk(full);
6923
- 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);
6924
7529
  }
6925
7530
  }
6926
7531
  walk(dir);
6927
7532
  return results;
6928
7533
  }
6929
7534
  function generateUserGuide(projectRoot2, forgehiveDir2) {
6930
- const projectName = path33.basename(projectRoot2);
7535
+ const projectName = path35.basename(projectRoot2);
6931
7536
  const caps = readCapabilities2(forgehiveDir2);
6932
7537
  const { language: lang, packageManager, entryPoints } = extractCapabilityInfo(caps);
6933
7538
  const memFiles = readMemoryFiles2(forgehiveDir2);
@@ -7006,8 +7611,8 @@ function generateUserGuide(projectRoot2, forgehiveDir2) {
7006
7611
  return lines.join("\n");
7007
7612
  }
7008
7613
  function generateApiReference(projectRoot2) {
7009
- const srcDir = path33.join(projectRoot2, "src");
7010
- const searchDir = fs32.existsSync(srcDir) ? srcDir : projectRoot2;
7614
+ const srcDir = path35.join(projectRoot2, "src");
7615
+ const searchDir = fs34.existsSync(srcDir) ? srcDir : projectRoot2;
7011
7616
  const files = walkSourceFiles(searchDir);
7012
7617
  const lines = [];
7013
7618
  lines.push("# API Reference");
@@ -7021,13 +7626,13 @@ function generateApiReference(projectRoot2) {
7021
7626
  for (const filePath of files) {
7022
7627
  let content = "";
7023
7628
  try {
7024
- content = fs32.readFileSync(filePath, "utf8");
7629
+ content = fs34.readFileSync(filePath, "utf8");
7025
7630
  } catch {
7026
7631
  continue;
7027
7632
  }
7028
7633
  const exports = extractExports(content);
7029
7634
  if (exports.length === 0) continue;
7030
- const relPath = path33.relative(projectRoot2, filePath);
7635
+ const relPath = path35.relative(projectRoot2, filePath);
7031
7636
  lines.push(`## \`${relPath}\``);
7032
7637
  lines.push("");
7033
7638
  lines.push("**Exports:**");
@@ -7039,23 +7644,23 @@ function generateApiReference(projectRoot2) {
7039
7644
  }
7040
7645
  function listExistingDocs(projectRoot2) {
7041
7646
  const docs = [];
7042
- const docsDir = path33.join(projectRoot2, "docs");
7043
- if (fs32.existsSync(docsDir)) {
7044
- for (const f of fs32.readdirSync(docsDir)) {
7045
- 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));
7046
7651
  }
7047
7652
  }
7048
7653
  const rootDocs = ["README.md", "CHANGELOG.md", "ONBOARDING.md", "CONTRIBUTING.md"];
7049
7654
  for (const f of rootDocs) {
7050
- const full = path33.join(projectRoot2, f);
7051
- if (fs32.existsSync(full)) docs.push(full);
7655
+ const full = path35.join(projectRoot2, f);
7656
+ if (fs34.existsSync(full)) docs.push(full);
7052
7657
  }
7053
7658
  return docs;
7054
7659
  }
7055
7660
 
7056
7661
  // src/router.ts
7057
- import fs33 from "node:fs";
7058
- import path34 from "node:path";
7662
+ import fs35 from "node:fs";
7663
+ import path36 from "node:path";
7059
7664
  var AGENT_KEYWORDS = {
7060
7665
  vera: ["security", "auth", "vulnerability", "owasp", "cve", "gdpr", "compliance", "penetration", "exploit"],
7061
7666
  sam: ["test", "coverage", "qa", "quality", "spec", "regression", "fixture", "assertion", "e2e"],
@@ -7164,9 +7769,9 @@ function routeTask(task) {
7164
7769
  }
7165
7770
  function resolveWorktreePath(forgehiveDir2, agent) {
7166
7771
  if (!agent) return void 0;
7167
- const worktreeFile = path34.join(forgehiveDir2, "agents", agent, "worktree");
7168
- if (fs33.existsSync(worktreeFile)) {
7169
- 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();
7170
7775
  }
7171
7776
  return void 0;
7172
7777
  }
@@ -7195,17 +7800,18 @@ function formatRoutingResult(result, worktreePath) {
7195
7800
 
7196
7801
  // src/github.ts
7197
7802
  init_js_yaml();
7198
- import fs34 from "node:fs";
7199
- import path35 from "node:path";
7803
+ import fs36 from "node:fs";
7804
+ import path37 from "node:path";
7200
7805
  import https from "node:https";
7806
+ import { spawnSync as spawnSync13 } from "node:child_process";
7201
7807
  function loadGitHubConfig(forgehiveDir2) {
7202
- const configPath = path35.join(forgehiveDir2, "github.yaml");
7203
- if (!fs34.existsSync(configPath)) {
7808
+ const configPath = path37.join(forgehiveDir2, "github.yaml");
7809
+ if (!fs36.existsSync(configPath)) {
7204
7810
  throw new Error("Nicht konfiguriert. F\xFChre zuerst aus: fh github setup");
7205
7811
  }
7206
7812
  let parsed;
7207
7813
  try {
7208
- parsed = jsYaml.load(fs34.readFileSync(configPath, "utf8"));
7814
+ parsed = jsYaml.load(fs36.readFileSync(configPath, "utf8"));
7209
7815
  } catch {
7210
7816
  throw new Error("Ung\xFCltige Konfigurationsdatei: .forgehive/github.yaml ist kein g\xFCltiges YAML.");
7211
7817
  }
@@ -7219,9 +7825,9 @@ function loadGitHubConfig(forgehiveDir2) {
7219
7825
  return { repo: data.repo };
7220
7826
  }
7221
7827
  function saveGitHubConfig(forgehiveDir2, config) {
7222
- fs34.mkdirSync(forgehiveDir2, { recursive: true });
7223
- const configPath = path35.join(forgehiveDir2, "github.yaml");
7224
- 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");
7225
7831
  }
7226
7832
  function githubGet(apiPath, token) {
7227
7833
  return new Promise((resolve, reject) => {
@@ -7303,22 +7909,22 @@ async function fetchPRFiles(owner, repo, number, token) {
7303
7909
  return files;
7304
7910
  }
7305
7911
  function issueAlreadySynced(storiesDir, issueNumber) {
7306
- if (!fs34.existsSync(storiesDir)) return false;
7912
+ if (!fs36.existsSync(storiesDir)) return false;
7307
7913
  const pattern = new RegExp(`GitHub: #${issueNumber}(?![\\d\\w])`);
7308
- 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")));
7309
7915
  }
7310
7916
  function appendGitHubRef(storiesDir, storyId, issue, repoFullName) {
7311
- const filePath = path35.join(storiesDir, `${storyId}.md`);
7312
- if (!fs34.existsSync(filePath)) {
7917
+ const filePath = path37.join(storiesDir, `${storyId}.md`);
7918
+ if (!fs36.existsSync(filePath)) {
7313
7919
  throw new Error(`Story-Datei nicht gefunden: ${storyId}.md`);
7314
7920
  }
7315
- const existing = fs34.readFileSync(filePath, "utf8");
7921
+ const existing = fs36.readFileSync(filePath, "utf8");
7316
7922
  const ref = `
7317
7923
  ## GitHub
7318
7924
 
7319
7925
  GitHub: #${issue.number} \u2014 ${issue.html_url}
7320
7926
  `;
7321
- fs34.writeFileSync(filePath, existing.trimEnd() + "\n" + ref, "utf8");
7927
+ fs36.writeFileSync(filePath, existing.trimEnd() + "\n" + ref, "utf8");
7322
7928
  }
7323
7929
  function formatPRContext(pr, files, repoFullName) {
7324
7930
  const body = pr.body?.trim() || "(keine Beschreibung)";
@@ -7339,6 +7945,381 @@ function formatPRContext(pr, files, repoFullName) {
7339
7945
  fileLines || "(keine Dateien)"
7340
7946
  ].join("\n");
7341
7947
  }
7948
+ async function fetchOpenPRs(owner, repo, token) {
7949
+ const prs = [];
7950
+ let page = 1;
7951
+ while (true) {
7952
+ const batch = await githubGet(
7953
+ `/repos/${owner}/${repo}/pulls?state=open&per_page=100&page=${page}`,
7954
+ token
7955
+ );
7956
+ if (batch.length === 0) break;
7957
+ prs.push(...batch);
7958
+ if (batch.length < 100) break;
7959
+ page++;
7960
+ }
7961
+ return prs.map((pr) => pr.number);
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
+ }
7342
8323
 
7343
8324
  // src/cli.ts
7344
8325
  import { createRequire } from "node:module";
@@ -7346,7 +8327,7 @@ var require2 = createRequire(import.meta.url);
7346
8327
  var { version } = require2("../package.json");
7347
8328
  var [, , command, subcommand, ...rest] = process.argv;
7348
8329
  var projectRoot = process.cwd();
7349
- var forgehiveDir = path36.join(projectRoot, ".forgehive");
8330
+ var forgehiveDir = path40.join(projectRoot, ".forgehive");
7350
8331
  if (command === "--version" || command === "-v") {
7351
8332
  console.log(version);
7352
8333
  process.exit(0);
@@ -7362,7 +8343,15 @@ SETUP
7362
8343
  fh init [--yes] Set up forgehive in the current project
7363
8344
  fh confirm Activate capabilities (draft \u2192 confirmed)
7364
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
+
7365
8352
  fh status Show current project state
8353
+ fh next Zeigt n\xE4chste empfohlene Aktion (Phase + Befehl)
8354
+ fh watch [--smart] Observe project; --smart runs tests on change and suggests agents
7366
8355
  fh scan --update Re-scan project after changes
7367
8356
  fh scan --check Check if scan is still current
7368
8357
 
@@ -7418,6 +8407,7 @@ TEAM
7418
8407
 
7419
8408
  AGENTS & MCP
7420
8409
  fh party [--set name|run|status|cleanup]
8410
+ fh outcomes Show party/auto run statistics and last 5 runs
7421
8411
  fh wire <service> Configure MCP server
7422
8412
  fh mcp auth add|list|remove Manage credentials
7423
8413
  fh mcp search <query> Search MCP registry
@@ -7433,24 +8423,24 @@ COST
7433
8423
  process.exit(0);
7434
8424
  }
7435
8425
  function loadClaudeMdBlock() {
7436
- const templatePath = path36.join(
7437
- path36.dirname(new URL(import.meta.url).pathname),
8426
+ const templatePath = path40.join(
8427
+ path40.dirname(new URL(import.meta.url).pathname),
7438
8428
  "..",
7439
8429
  "forgehive",
7440
8430
  "templates",
7441
8431
  "claude-md.block.md"
7442
8432
  );
7443
- if (!fs35.existsSync(templatePath)) return "## forgehive\n\nSee .forgehive/ for configuration.";
7444
- return fs35.readFileSync(templatePath, "utf8");
8433
+ if (!fs39.existsSync(templatePath)) return "## forgehive\n\nSee .forgehive/ for configuration.";
8434
+ return fs39.readFileSync(templatePath, "utf8");
7445
8435
  }
7446
8436
  async function promptConfirm(question) {
7447
8437
  if (!process.stdin.isTTY) return false;
7448
- const rl = createInterface({ input: process.stdin, output: process.stdout });
8438
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
7449
8439
  return new Promise((resolve) => {
7450
8440
  rl.question(question, (answer) => {
7451
8441
  rl.close();
7452
8442
  const a = answer.trim().toLowerCase();
7453
- resolve(a === "y" || a === "yes" || a === "");
8443
+ resolve(a === "y" || a === "yes" || a === "j" || a === "ja" || a === "");
7454
8444
  });
7455
8445
  });
7456
8446
  }
@@ -7464,13 +8454,13 @@ function buildCapabilitySummary(ids) {
7464
8454
  }
7465
8455
  if (command === "init") {
7466
8456
  (async () => {
7467
- const gitCheck = spawnSync12("git", ["--version"], { stdio: "ignore" });
8457
+ const gitCheck = spawnSync15("git", ["--version"], { stdio: "ignore" });
7468
8458
  if (gitCheck.error || gitCheck.status !== 0) {
7469
8459
  console.error("Fehler: git nicht gefunden.");
7470
8460
  console.error(" Installation: https://git-scm.com");
7471
8461
  process.exit(1);
7472
8462
  }
7473
- const forgehiveDirExists = fs35.existsSync(forgehiveDir);
8463
+ const forgehiveDirExists = fs39.existsSync(forgehiveDir);
7474
8464
  if (forgehiveDirExists && subcommand !== "--force" && !rest.includes("--force")) {
7475
8465
  console.log(`\u26A0 .forgehive/ existiert bereits in diesem Projekt.`);
7476
8466
  console.log(` Nutze 'fh init --force' um neu zu initialisieren (\xFCberschreibt capabilities.yaml).`);
@@ -7488,9 +8478,9 @@ if (command === "init") {
7488
8478
  const block = loadClaudeMdBlock();
7489
8479
  writeForgehiveDir(projectRoot, scanResult, capMap, block);
7490
8480
  const hash = computeHash(projectRoot);
7491
- fs35.writeFileSync(path36.join(forgehiveDir, ".scan-hash"), hash, "utf8");
7492
- const runtimeDir = path36.join(
7493
- 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),
7494
8484
  "..",
7495
8485
  "forgehive"
7496
8486
  );
@@ -7532,7 +8522,7 @@ if (command === "init") {
7532
8522
  process.exit(1);
7533
8523
  }
7534
8524
  } else if (command === "memory") {
7535
- if (!fs35.existsSync(forgehiveDir)) {
8525
+ if (!fs39.existsSync(forgehiveDir)) {
7536
8526
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
7537
8527
  process.exit(1);
7538
8528
  }
@@ -7541,7 +8531,7 @@ if (command === "init") {
7541
8531
  } else if (subcommand === "clean") {
7542
8532
  cleanMemory(forgehiveDir);
7543
8533
  } else if (subcommand === "export") {
7544
- const outputPath = rest[0] ?? path36.join(projectRoot, "forgehive-memory-export.md");
8534
+ const outputPath = rest[0] ?? path40.join(projectRoot, "forgehive-memory-export.md");
7545
8535
  try {
7546
8536
  exportMemory(forgehiveDir, outputPath);
7547
8537
  } catch (err) {
@@ -7589,7 +8579,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
7589
8579
  } else if (subcommand === "snapshot") {
7590
8580
  const snapAction = rest[0];
7591
8581
  if (snapAction === "export") {
7592
- const outPath = rest[1] ?? path36.join(
8582
+ const outPath = rest[1] ?? path40.join(
7593
8583
  projectRoot,
7594
8584
  `forgehive-snapshot-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.json`
7595
8585
  );
@@ -7629,11 +8619,11 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
7629
8619
  process.exit(1);
7630
8620
  }
7631
8621
  } else if (command === "scan" && subcommand === "--update") {
7632
- if (!fs35.existsSync(forgehiveDir)) {
8622
+ if (!fs39.existsSync(forgehiveDir)) {
7633
8623
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
7634
8624
  process.exit(1);
7635
8625
  }
7636
- 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;
7637
8627
  const currentHash = computeHash(projectRoot);
7638
8628
  if (savedHash === currentHash) {
7639
8629
  console.log("\u2713 Keine \xC4nderungen erkannt \u2014 capabilities.yaml ist aktuell");
@@ -7641,7 +8631,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
7641
8631
  }
7642
8632
  console.log("\u{1F50D} \xC4nderungen erkannt \u2014 scanne erneut...\n");
7643
8633
  const oldDoc = jsYaml.load(
7644
- fs35.readFileSync(path36.join(forgehiveDir, "capabilities.yaml"), "utf8")
8634
+ fs39.readFileSync(path40.join(forgehiveDir, "capabilities.yaml"), "utf8")
7645
8635
  );
7646
8636
  const oldMap = { confirmed: oldDoc.capabilities.confirmed ?? [], inferred: [] };
7647
8637
  const scanResult = scan(projectRoot);
@@ -7661,16 +8651,16 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
7661
8651
  console.log();
7662
8652
  const block = loadClaudeMdBlock();
7663
8653
  writeForgehiveDir(projectRoot, scanResult, newMap, block);
7664
- fs35.writeFileSync(path36.join(forgehiveDir, ".scan-hash"), currentHash, "utf8");
8654
+ fs39.writeFileSync(path40.join(forgehiveDir, ".scan-hash"), currentHash, "utf8");
7665
8655
  console.log("\u2713 scan-result.yaml und capabilities.yaml aktualisiert");
7666
8656
  console.log(" F\xFChre `fh confirm` aus um die \xC4nderungen zu best\xE4tigen");
7667
8657
  }
7668
8658
  } else if (command === "scan" && subcommand === "--check") {
7669
- if (!fs35.existsSync(path36.join(forgehiveDir, ".scan-hash"))) {
8659
+ if (!fs39.existsSync(path40.join(forgehiveDir, ".scan-hash"))) {
7670
8660
  console.log("Warnung: Kein Scan-Hash gefunden. F\xFChre `fh init` aus.");
7671
8661
  process.exit(1);
7672
8662
  }
7673
- const saved = fs35.readFileSync(path36.join(forgehiveDir, ".scan-hash"), "utf8").trim();
8663
+ const saved = fs39.readFileSync(path40.join(forgehiveDir, ".scan-hash"), "utf8").trim();
7674
8664
  const current = computeHash(projectRoot);
7675
8665
  if (saved !== current) {
7676
8666
  console.log("\u26A0 Codebase hat sich seit letztem Scan ge\xE4ndert.");
@@ -7679,7 +8669,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
7679
8669
  }
7680
8670
  console.log("\u2713 capabilities.yaml ist aktuell");
7681
8671
  } else if (command === "skills") {
7682
- if (!fs35.existsSync(forgehiveDir)) {
8672
+ if (!fs39.existsSync(forgehiveDir)) {
7683
8673
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
7684
8674
  process.exit(1);
7685
8675
  }
@@ -7715,7 +8705,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
7715
8705
  process.exit(1);
7716
8706
  }
7717
8707
  } else if (command === "party") {
7718
- if (!fs35.existsSync(forgehiveDir)) {
8708
+ if (!fs39.existsSync(forgehiveDir)) {
7719
8709
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
7720
8710
  process.exit(1);
7721
8711
  }
@@ -7836,7 +8826,7 @@ Ausf\xFChren mit --remove um zu l\xF6schen: fh memory prune ${days} --remove`);
7836
8826
  }
7837
8827
  }
7838
8828
  } else if (command === "wire") {
7839
- if (!fs35.existsSync(forgehiveDir)) {
8829
+ if (!fs39.existsSync(forgehiveDir)) {
7840
8830
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
7841
8831
  process.exit(1);
7842
8832
  }
@@ -7868,14 +8858,47 @@ N\xE4chster Schritt: Setze die erforderlichen Umgebungsvariablen und starte Clau
7868
8858
  console.error(`Fehler: ${err.message}`);
7869
8859
  process.exit(1);
7870
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);
7871
8888
  } else if (command === "status") {
7872
8889
  console.log(projectStatus(projectRoot, forgehiveDir));
8890
+ const dashboard = formatDashboard(projectRoot, forgehiveDir);
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);
7873
8896
  } else if (command === "cost") {
7874
8897
  const allArgs = [subcommand, ...rest].filter((a) => Boolean(a));
7875
8898
  const limitIdx = allArgs.indexOf("--limit");
7876
8899
  const alertIdx = allArgs.indexOf("--alert");
7877
8900
  if (limitIdx !== -1) {
7878
- if (!fs35.existsSync(forgehiveDir)) {
8901
+ if (!fs39.existsSync(forgehiveDir)) {
7879
8902
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
7880
8903
  process.exit(1);
7881
8904
  }
@@ -7901,30 +8924,57 @@ N\xE4chster Schritt: Setze die erforderlichen Umgebungsvariablen und starte Clau
7901
8924
  }
7902
8925
  const sessions = parseCostSessions(projectRoot);
7903
8926
  console.log(formatCostReport(sessions, range));
7904
- if (fs35.existsSync(forgehiveDir)) {
8927
+ if (fs39.existsSync(forgehiveDir)) {
7905
8928
  const total = sessions.reduce((s, x) => s + x.estimatedCostUsd, 0);
7906
8929
  const status = checkSpendStatus(forgehiveDir, total);
7907
8930
  if (status.message) console.log("\n" + status.message);
7908
8931
  }
7909
8932
  }
7910
8933
  } else if (command === "watch") {
7911
- if (!fs35.existsSync(forgehiveDir)) {
8934
+ if (!fs39.existsSync(forgehiveDir)) {
7912
8935
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
7913
8936
  process.exit(1);
7914
8937
  }
7915
- const block = loadClaudeMdBlock();
7916
- console.log("\u{1F441} ForgeHive watch gestartet \u2014 beobachte Projekt-Dateien (Ctrl+C zum Beenden)\n");
7917
- const stop = watchProject(projectRoot, forgehiveDir, block);
7918
- process.on("SIGINT", () => {
7919
- stop();
7920
- process.exit(0);
7921
- });
7922
- process.on("SIGTERM", () => {
7923
- stop();
7924
- process.exit(0);
7925
- });
8938
+ const isSmartMode = subcommand === "--smart" || rest.includes("--smart");
8939
+ if (isSmartMode) {
8940
+ let testCmd = "npm test";
8941
+ const capsPath = path40.join(forgehiveDir, "capabilities.yaml");
8942
+ if (fs39.existsSync(capsPath)) {
8943
+ try {
8944
+ const caps = jsYaml.load(fs39.readFileSync(capsPath, "utf8"));
8945
+ if (caps?.test_command) testCmd = caps.test_command;
8946
+ } catch {
8947
+ }
8948
+ }
8949
+ console.log(`\u{1F441} ForgeHive Smart Watch \u2014 Testbefehl: ${testCmd}`);
8950
+ console.log(" \xDCberwache src/ und test/ auf \xC4nderungen (Ctrl+C zum Beenden)\n");
8951
+ const stop = smartWatch(projectRoot, forgehiveDir, testCmd, async (agent, _reason, file) => {
8952
+ console.log(` Soll ich ${agent} starten f\xFCr ${file}?`);
8953
+ return promptConfirm(" [j/n] ");
8954
+ });
8955
+ process.on("SIGINT", () => {
8956
+ stop();
8957
+ process.exit(0);
8958
+ });
8959
+ process.on("SIGTERM", () => {
8960
+ stop();
8961
+ process.exit(0);
8962
+ });
8963
+ } else {
8964
+ const block = loadClaudeMdBlock();
8965
+ console.log("\u{1F441} ForgeHive watch gestartet \u2014 beobachte Projekt-Dateien (Ctrl+C zum Beenden)\n");
8966
+ const stop = watchProject(projectRoot, forgehiveDir, block);
8967
+ process.on("SIGINT", () => {
8968
+ stop();
8969
+ process.exit(0);
8970
+ });
8971
+ process.on("SIGTERM", () => {
8972
+ stop();
8973
+ process.exit(0);
8974
+ });
8975
+ }
7926
8976
  } else if (command === "mcp") {
7927
- if (!fs35.existsSync(forgehiveDir)) {
8977
+ if (!fs39.existsSync(forgehiveDir)) {
7928
8978
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
7929
8979
  process.exit(1);
7930
8980
  }
@@ -8035,7 +9085,7 @@ Setze diese Umgebungsvariablen:`);
8035
9085
  process.exit(1);
8036
9086
  }
8037
9087
  } else if (command === "security") {
8038
- if (!fs35.existsSync(forgehiveDir)) {
9088
+ if (!fs39.existsSync(forgehiveDir)) {
8039
9089
  console.error("Fehler: .forgehive/ nicht gefunden \u2014 f\xFChre zuerst `fh init` aus");
8040
9090
  process.exit(1);
8041
9091
  }
@@ -8088,6 +9138,11 @@ Setze diese Umgebungsvariablen:`);
8088
9138
  high: high.length
8089
9139
  }
8090
9140
  });
9141
+ fs39.writeFileSync(
9142
+ path40.join(forgehiveDir, "security-last-scan.txt"),
9143
+ (/* @__PURE__ */ new Date()).toISOString(),
9144
+ "utf8"
9145
+ );
8091
9146
  if (secrets.length > 0 || critical.length > 0 || high.length > 0) {
8092
9147
  process.exit(1);
8093
9148
  }
@@ -8121,8 +9176,8 @@ Setze diese Umgebungsvariablen:`);
8121
9176
  `);
8122
9177
  const report = generateSecurityReport(projectRoot, forgehiveDir, mode);
8123
9178
  const text = formatSecurityReport(report);
8124
- const reportPath = path36.join(forgehiveDir, "security-report.md");
8125
- fs35.writeFileSync(reportPath, text, "utf8");
9179
+ const reportPath = path40.join(forgehiveDir, "security-report.md");
9180
+ fs39.writeFileSync(reportPath, text, "utf8");
8126
9181
  console.log(text);
8127
9182
  console.log(`
8128
9183
  \u2713 Report gespeichert: ${reportPath}`);
@@ -8134,8 +9189,8 @@ Setze diese Umgebungsvariablen:`);
8134
9189
  } else if (subcommand === "permissions") {
8135
9190
  const { writePermissions: writePermissions2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
8136
9191
  writePermissions2(forgehiveDir);
8137
- const permPath = path36.join(forgehiveDir, "harness", "permissions.yaml");
8138
- console.log(fs35.readFileSync(permPath, "utf8"));
9192
+ const permPath = path40.join(forgehiveDir, "harness", "permissions.yaml");
9193
+ console.log(fs39.readFileSync(permPath, "utf8"));
8139
9194
  } else {
8140
9195
  console.error(`Unbekannter security-Subcommand: ${subcommand}`);
8141
9196
  console.error("Verf\xFCgbar: scan | deps | audit | report [gdpr|soc2|hipaa|none] | permissions");
@@ -8147,18 +9202,18 @@ Setze diese Umgebungsvariablen:`);
8147
9202
  const failOnArg = allCiArgs.includes("--fail-on") ? allCiArgs[allCiArgs.indexOf("--fail-on") + 1] : "high";
8148
9203
  const initFlag = allCiArgs.includes("--init");
8149
9204
  if (initFlag) {
8150
- const ghDir = path36.join(projectRoot, ".github", "workflows");
8151
- fs35.mkdirSync(ghDir, { recursive: true });
8152
- const outPath = path36.join(ghDir, "forgehive.yml");
8153
- 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");
8154
9209
  console.log(`\u2714 GitHub Actions workflow geschrieben: ${outPath}`);
8155
9210
  } else {
8156
9211
  const report = generateCiReport(projectRoot, forgehiveDir, failOnArg);
8157
9212
  const output = formatCiReport(report, format);
8158
9213
  console.log(output);
8159
9214
  if (format === "json") {
8160
- fs35.mkdirSync(forgehiveDir, { recursive: true });
8161
- 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");
8162
9217
  }
8163
9218
  if (report.status === "fail") process.exit(1);
8164
9219
  }
@@ -8166,29 +9221,29 @@ Setze diese Umgebungsvariablen:`);
8166
9221
  const semanticFlag = rest.includes("--semantic");
8167
9222
  const map2 = generateMap(projectRoot);
8168
9223
  const semantic = buildSemanticMap(projectRoot);
8169
- const mapPath = path36.join(forgehiveDir, "map.md");
8170
- fs35.mkdirSync(forgehiveDir, { recursive: true });
9224
+ const mapPath = path40.join(forgehiveDir, "map.md");
9225
+ fs39.mkdirSync(forgehiveDir, { recursive: true });
8171
9226
  if (semanticFlag) {
8172
9227
  const fullMd = formatMap(map2, semantic);
8173
9228
  const marker = "\n## Semantic Graph\n";
8174
9229
  const markerIdx = fullMd.indexOf(marker);
8175
9230
  const semanticMd = markerIdx >= 0 ? "## Semantic Graph\n" + fullMd.slice(markerIdx + marker.length) : fullMd;
8176
- fs35.writeFileSync(mapPath, fullMd, "utf8");
9231
+ fs39.writeFileSync(mapPath, fullMd, "utf8");
8177
9232
  console.log(semanticMd);
8178
9233
  console.log(`
8179
9234
  \u2714 Codebase-Map gespeichert (Semantic Graph hervorgehoben): ${mapPath}`);
8180
9235
  } else {
8181
9236
  const md = formatMap(map2, semantic);
8182
- fs35.writeFileSync(mapPath, md, "utf8");
9237
+ fs39.writeFileSync(mapPath, md, "utf8");
8183
9238
  console.log(md);
8184
9239
  console.log(`
8185
9240
  \u2714 Codebase-Map gespeichert: ${mapPath}`);
8186
9241
  }
8187
9242
  } else if (command === "onboard") {
8188
9243
  const outputArg = rest.includes("--output") ? rest[rest.indexOf("--output") + 1] : null;
8189
- const outputPath = outputArg ?? path36.join(projectRoot, "ONBOARDING.md");
9244
+ const outputPath = outputArg ?? path40.join(projectRoot, "ONBOARDING.md");
8190
9245
  const doc = generateOnboardingDoc(projectRoot, forgehiveDir);
8191
- fs35.writeFileSync(outputPath, doc, "utf8");
9246
+ fs39.writeFileSync(outputPath, doc, "utf8");
8192
9247
  console.log(`\u2714 Onboarding-Dokument geschrieben: ${outputPath}`);
8193
9248
  } else if (command === "ask") {
8194
9249
  const taskArg = subcommand ? [subcommand, ...rest.filter((r) => !r.startsWith("--"))].join(" ") : rest.filter((r) => !r.startsWith("--")).join(" ");
@@ -8211,28 +9266,28 @@ Task: "${taskArg}"
8211
9266
  const commits = parseGitLog(rawLog);
8212
9267
  let version2 = "unreleased";
8213
9268
  try {
8214
- const pkgPath = path36.join(projectRoot, "package.json");
8215
- if (fs35.existsSync(pkgPath)) {
8216
- 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, ""));
8217
9272
  version2 = pkg.version ?? "unreleased";
8218
9273
  }
8219
9274
  } catch {
8220
9275
  }
8221
9276
  const md = formatChangelog(commits, version2);
8222
- const outputPath = outputArg ?? path36.join(projectRoot, "CHANGELOG.md");
9277
+ const outputPath = outputArg ?? path40.join(projectRoot, "CHANGELOG.md");
8223
9278
  let existing = "";
8224
- if (fs35.existsSync(outputPath)) existing = fs35.readFileSync(outputPath, "utf8");
8225
- 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");
8226
9281
  console.log(`\u2714 CHANGELOG.md aktualisiert: ${outputPath}`);
8227
9282
  console.log(` ${commits.length} Commits verarbeitet`);
8228
9283
  } else if (command === "metrics") {
8229
9284
  const sinceArg = rest.includes("--since") ? rest[rest.indexOf("--since") + 1] : void 0;
8230
9285
  const rawLog = getMetricsGitLog(projectRoot, sinceArg);
8231
9286
  const stats = parseCommitStats(rawLog);
8232
- const md = formatMetrics(stats, path36.basename(projectRoot));
8233
- const metricsPath = path36.join(forgehiveDir, "metrics.md");
8234
- fs35.mkdirSync(forgehiveDir, { recursive: true });
8235
- 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");
8236
9291
  console.log(md);
8237
9292
  console.log(`
8238
9293
  \u2714 Metrics gespeichert: ${metricsPath}`);
@@ -8279,7 +9334,7 @@ Task: "${taskDescription}"
8279
9334
  console.log(` fh run status \u2014 aktive Sessions anzeigen`);
8280
9335
  }
8281
9336
  } else if (command === "story") {
8282
- const storiesDir = path36.join(forgehiveDir, "memory", "stories");
9337
+ const storiesDir = path40.join(forgehiveDir, "memory", "stories");
8283
9338
  if (subcommand === "create") {
8284
9339
  const title = rest.filter((r) => !r.startsWith("--")).join(" ");
8285
9340
  const epicArg = rest.includes("--epic") ? rest[rest.indexOf("--epic") + 1] : void 0;
@@ -8290,7 +9345,7 @@ Task: "${taskDescription}"
8290
9345
  }
8291
9346
  const story = createStory(storiesDir, title, epicArg);
8292
9347
  if (pointsArg) updateStoryPoints(storiesDir, story.id, pointsArg);
8293
- 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")}`);
8294
9349
  console.log(` Bearbeite die Datei um Acceptance Criteria hinzuzuf\xFCgen.`);
8295
9350
  } else if (subcommand === "list") {
8296
9351
  const epicFilter = rest.includes("--epic") ? rest[rest.indexOf("--epic") + 1] : null;
@@ -8339,8 +9394,8 @@ Task: "${taskDescription}"
8339
9394
  console.error("Verf\xFCgbar: fh story create | list | show | done");
8340
9395
  }
8341
9396
  } else if (command === "epic") {
8342
- const epicsDir = path36.join(forgehiveDir, "memory", "epics");
8343
- const storiesDir = path36.join(forgehiveDir, "memory", "stories");
9397
+ const epicsDir = path40.join(forgehiveDir, "memory", "epics");
9398
+ const storiesDir = path40.join(forgehiveDir, "memory", "stories");
8344
9399
  if (subcommand === "create") {
8345
9400
  const goalArg = rest.includes("--goal") ? rest[rest.indexOf("--goal") + 1] : void 0;
8346
9401
  const prdArg = rest.includes("--prd") ? rest[rest.indexOf("--prd") + 1] : void 0;
@@ -8358,12 +9413,12 @@ Task: "${taskDescription}"
8358
9413
  process.exit(1);
8359
9414
  }
8360
9415
  if (prdArg) {
8361
- const prdsDir = path36.join(forgehiveDir, "memory", "prds");
9416
+ const prdsDir = path40.join(forgehiveDir, "memory", "prds");
8362
9417
  const prd = getPrd(prdsDir, prdArg);
8363
9418
  if (!prd) console.warn(`\u26A0 PRD ${prdArg} nicht gefunden \u2014 Epic wird trotzdem erstellt`);
8364
9419
  }
8365
9420
  const epic = createEpic(epicsDir, title, goalArg, prdArg);
8366
- 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")}`);
8367
9422
  if (prdArg) console.log(` Verkn\xFCpft mit: ${prdArg}`);
8368
9423
  } else if (subcommand === "list") {
8369
9424
  const epics = listEpics(epicsDir);
@@ -8390,7 +9445,7 @@ Task: "${taskDescription}"
8390
9445
  console.error("Verf\xFCgbar: fh epic create | list | show");
8391
9446
  }
8392
9447
  } else if (command === "product") {
8393
- const prdsDir = path36.join(forgehiveDir, "memory", "prds");
9448
+ const prdsDir = path40.join(forgehiveDir, "memory", "prds");
8394
9449
  if (subcommand === "prd") {
8395
9450
  const title = rest.filter((r) => !r.startsWith("--")).join(" ");
8396
9451
  if (!title) {
@@ -8398,7 +9453,7 @@ Task: "${taskDescription}"
8398
9453
  process.exit(1);
8399
9454
  }
8400
9455
  const prd = createPrd(prdsDir, title);
8401
- const relPath = path36.relative(projectRoot, prd.filepath);
9456
+ const relPath = path40.relative(projectRoot, prd.filepath);
8402
9457
  console.log(`\u2714 ${prd.id} erstellt: ${relPath}`);
8403
9458
  console.log(` Bearbeite die Datei um Anforderungen zu definieren.`);
8404
9459
  } else if (subcommand === "list") {
@@ -8423,10 +9478,10 @@ Task: "${taskDescription}"
8423
9478
  console.error(`${id} nicht gefunden`);
8424
9479
  process.exit(1);
8425
9480
  }
8426
- console.log(fs35.readFileSync(prd.filepath, "utf8"));
9481
+ console.log(fs39.readFileSync(prd.filepath, "utf8"));
8427
9482
  } else if (subcommand === "status") {
8428
- const epicsDir = path36.join(forgehiveDir, "memory", "epics");
8429
- const storiesDir = path36.join(forgehiveDir, "memory", "stories");
9483
+ const epicsDir = path40.join(forgehiveDir, "memory", "epics");
9484
+ const storiesDir = path40.join(forgehiveDir, "memory", "stories");
8430
9485
  const prds = listPrds(prdsDir);
8431
9486
  const epics = listEpics(epicsDir);
8432
9487
  const stories = listStories(storiesDir);
@@ -8469,14 +9524,14 @@ Task: "${taskDescription}"
8469
9524
  }
8470
9525
  } else if (subcommand === "roadmap") {
8471
9526
  const content = generateRoadmap(forgehiveDir);
8472
- const roadmapPath = path36.join(projectRoot, "ROADMAP.md");
8473
- fs35.writeFileSync(roadmapPath, content, "utf8");
9527
+ const roadmapPath = path40.join(projectRoot, "ROADMAP.md");
9528
+ fs39.writeFileSync(roadmapPath, content, "utf8");
8474
9529
  console.log(`\u2714 ROADMAP.md generiert: ${roadmapPath}`);
8475
9530
  } else {
8476
9531
  console.error("Verf\xFCgbar: fh product prd | list | show | status | roadmap");
8477
9532
  }
8478
9533
  } else if (command === "velocity") {
8479
- const velocityFile = path36.join(forgehiveDir, "memory", "velocity.md");
9534
+ const velocityFile = path40.join(forgehiveDir, "memory", "velocity.md");
8480
9535
  if (subcommand === "record") {
8481
9536
  const sprintNum = parseInt(rest[0] ?? "0", 10);
8482
9537
  const committed = rest.includes("--committed") ? parseInt(rest[rest.indexOf("--committed") + 1], 10) : NaN;
@@ -8495,7 +9550,7 @@ Task: "${taskDescription}"
8495
9550
  console.error("Verf\xFCgbar: fh velocity show | record <N> --committed N --delivered N");
8496
9551
  }
8497
9552
  } else if (command === "docs") {
8498
- const docsDir = path36.join(projectRoot, "docs");
9553
+ const docsDir = path40.join(projectRoot, "docs");
8499
9554
  if (!subcommand || subcommand === "list") {
8500
9555
  const existing = listExistingDocs(projectRoot);
8501
9556
  if (existing.length === 0) {
@@ -8509,29 +9564,29 @@ Task: "${taskDescription}"
8509
9564
  console.log(" fh docs adr <titel> \u2014 Architecture Decision Record");
8510
9565
  } else {
8511
9566
  console.log(`Vorhandene Dokumentation (${existing.length} Dateien):`);
8512
- for (const d of existing) console.log(` ${path36.relative(projectRoot, d)}`);
9567
+ for (const d of existing) console.log(` ${path40.relative(projectRoot, d)}`);
8513
9568
  console.log("");
8514
9569
  console.log("Aktualisieren: fh docs user | api | onboard | changelog");
8515
9570
  }
8516
9571
  } else if (subcommand === "user") {
8517
- fs35.mkdirSync(docsDir, { recursive: true });
9572
+ fs39.mkdirSync(docsDir, { recursive: true });
8518
9573
  const outputArg = rest.includes("--output") ? rest[rest.indexOf("--output") + 1] : null;
8519
- const outputPath = outputArg ?? path36.join(docsDir, "user-guide.md");
9574
+ const outputPath = outputArg ?? path40.join(docsDir, "user-guide.md");
8520
9575
  const guide = generateUserGuide(projectRoot, forgehiveDir);
8521
- fs35.writeFileSync(outputPath, guide, "utf8");
9576
+ fs39.writeFileSync(outputPath, guide, "utf8");
8522
9577
  console.log(`\u2714 User Guide geschrieben: ${outputPath}`);
8523
9578
  } else if (subcommand === "api") {
8524
- fs35.mkdirSync(docsDir, { recursive: true });
9579
+ fs39.mkdirSync(docsDir, { recursive: true });
8525
9580
  const outputArg = rest.includes("--output") ? rest[rest.indexOf("--output") + 1] : null;
8526
- const outputPath = outputArg ?? path36.join(docsDir, "api.md");
9581
+ const outputPath = outputArg ?? path40.join(docsDir, "api.md");
8527
9582
  const ref = generateApiReference(projectRoot);
8528
- fs35.writeFileSync(outputPath, ref, "utf8");
9583
+ fs39.writeFileSync(outputPath, ref, "utf8");
8529
9584
  console.log(`\u2714 API-Referenz geschrieben: ${outputPath}`);
8530
9585
  } else if (subcommand === "onboard") {
8531
9586
  const outputArg = rest.includes("--output") ? rest[rest.indexOf("--output") + 1] : null;
8532
- const outputPath = outputArg ?? path36.join(projectRoot, "ONBOARDING.md");
9587
+ const outputPath = outputArg ?? path40.join(projectRoot, "ONBOARDING.md");
8533
9588
  const doc = generateOnboardingDoc(projectRoot, forgehiveDir);
8534
- fs35.writeFileSync(outputPath, doc, "utf8");
9589
+ fs39.writeFileSync(outputPath, doc, "utf8");
8535
9590
  console.log(`\u2714 Onboarding-Dokument geschrieben: ${outputPath}`);
8536
9591
  } else if (subcommand === "changelog") {
8537
9592
  const sinceArg = rest.includes("--since") ? rest[rest.indexOf("--since") + 1] : null;
@@ -8541,15 +9596,15 @@ Task: "${taskDescription}"
8541
9596
  const commits = parseGitLog(rawLog);
8542
9597
  let pkg = {};
8543
9598
  try {
8544
- pkg = JSON.parse(fs35.readFileSync(path36.join(projectRoot, "package.json"), "utf8"));
9599
+ pkg = JSON.parse(fs39.readFileSync(path40.join(projectRoot, "package.json"), "utf8"));
8545
9600
  } catch {
8546
9601
  }
8547
9602
  const pkgVersion = pkg.version ?? "unreleased";
8548
9603
  const md = formatChangelog(commits, pkgVersion);
8549
- const outputPath = outputArg ?? path36.join(projectRoot, "CHANGELOG.md");
9604
+ const outputPath = outputArg ?? path40.join(projectRoot, "CHANGELOG.md");
8550
9605
  let existing = "";
8551
- if (fs35.existsSync(outputPath)) existing = fs35.readFileSync(outputPath, "utf8");
8552
- 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");
8553
9608
  console.log(`\u2714 CHANGELOG.md aktualisiert (${commits.length} Commits)`);
8554
9609
  } else if (subcommand === "adr") {
8555
9610
  const title = rest.join(" ");
@@ -8557,9 +9612,9 @@ Task: "${taskDescription}"
8557
9612
  console.error("Usage: fh docs adr <titel>");
8558
9613
  process.exit(1);
8559
9614
  }
8560
- const adrsDir = path36.join(forgehiveDir, "memory", "adrs");
8561
- fs35.mkdirSync(adrsDir, { recursive: true });
8562
- 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;
8563
9618
  const adrId = String(existing + 1).padStart(4, "0");
8564
9619
  const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
8565
9620
  const filename = `${adrId}-${slug}.md`;
@@ -8580,13 +9635,13 @@ Task: "${taskDescription}"
8580
9635
 
8581
9636
  (Beschreibe die Auswirkungen dieser Entscheidung.)
8582
9637
  `;
8583
- fs35.writeFileSync(path36.join(adrsDir, filename), content, "utf8");
9638
+ fs39.writeFileSync(path40.join(adrsDir, filename), content, "utf8");
8584
9639
  console.log(`\u2714 ADR erstellt: .forgehive/memory/adrs/${filename}`);
8585
9640
  } else {
8586
9641
  console.error("Verf\xFCgbar: fh docs [list|user|api|onboard|changelog|adr <titel>]");
8587
9642
  }
8588
9643
  } else if (command === "github") {
8589
- const storiesDir = path36.join(forgehiveDir, "memory", "stories");
9644
+ const storiesDir = path40.join(forgehiveDir, "memory", "stories");
8590
9645
  if (subcommand === "setup") {
8591
9646
  const readline = await import("node:readline/promises");
8592
9647
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -8654,6 +9709,16 @@ Task: "${taskDescription}"
8654
9709
  }
8655
9710
  console.log(`
8656
9711
  ${created} ${created === 1 ? "Story" : "Stories"} erstellt, ${skipped} \xFCbersprungen.`);
9712
+ try {
9713
+ const [syncOwner, syncRepo] = config.repo.split("/");
9714
+ const openPrNumbers = await fetchOpenPRs(syncOwner, syncRepo, creds.GITHUB_TOKEN);
9715
+ fs39.writeFileSync(
9716
+ path40.join(forgehiveDir, "github-pr-cache.json"),
9717
+ JSON.stringify({ open: openPrNumbers, ts: (/* @__PURE__ */ new Date()).toISOString() }),
9718
+ "utf8"
9719
+ );
9720
+ } catch {
9721
+ }
8657
9722
  } else if (subcommand === "pr") {
8658
9723
  const prNumberRaw = rest[0];
8659
9724
  if (!prNumberRaw || isNaN(parseInt(prNumberRaw, 10))) {
@@ -8686,9 +9751,9 @@ Task: "${taskDescription}"
8686
9751
  process.exit(1);
8687
9752
  }
8688
9753
  const contextMd = formatPRContext(pr, files, config.repo);
8689
- fs35.mkdirSync(forgehiveDir, { recursive: true });
8690
- const outputPath = path36.join(forgehiveDir, `github-pr-${prNumber}.md`);
8691
- 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");
8692
9757
  console.log(` \u2713 PR-Kontext gespeichert: .forgehive/github-pr-${prNumber}.md`);
8693
9758
  console.log("");
8694
9759
  console.log(` PR context gespeichert. Starte Review: fh party run review`);
@@ -8696,9 +9761,45 @@ Task: "${taskDescription}"
8696
9761
  console.log("Verf\xFCgbar: fh github setup | fh github sync | fh github pr <number>");
8697
9762
  console.log("Hilfe: fh --help");
8698
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);
8699
9800
  } else {
8700
9801
  console.error("Unbekannter Befehl: " + command);
8701
- 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");
8702
9803
  console.error("Hilfe: fh --help");
8703
9804
  process.exit(1);
8704
9805
  }