@chrono-meta/fh-gate 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.claude/agents/challenger.md +169 -0
  2. package/AGENTS.md +160 -0
  3. package/CATALOG.md +256 -0
  4. package/CHEATSHEET.md +367 -0
  5. package/CLAUDE.md +331 -0
  6. package/CONTRIBUTING.md +198 -0
  7. package/LICENSE +21 -0
  8. package/README.md +61 -8
  9. package/bin/fh-goal.js +9 -0
  10. package/bin/fh-run.js +9 -0
  11. package/docs/codex-compat.md +123 -0
  12. package/docs/pillars.svg +70 -0
  13. package/knowledge/shared/harness-core/fh_integration_contract.md +45 -28
  14. package/package.json +30 -6
  15. package/plugins/fh-commons/README.md +37 -0
  16. package/plugins/fh-commons/agents/quench-challenger.md +373 -0
  17. package/plugins/fh-commons/skills/convergence-loop/SKILL.md +155 -0
  18. package/plugins/fh-commons/skills/deliberation/SKILL.md +288 -0
  19. package/plugins/fh-commons/skills/mcp-circuit-breaker/SKILL.md +196 -0
  20. package/plugins/fh-commons/skills/token-budget-gate/SKILL.md +175 -0
  21. package/plugins/fh-meta/agents/fact-checker.md +121 -0
  22. package/plugins/fh-meta/agents/hub-persona-auditor.md +109 -0
  23. package/plugins/fh-meta/agents/persona-innovator.md +195 -0
  24. package/plugins/fh-meta/skills/agent-composer/SKILL.md +461 -0
  25. package/plugins/fh-meta/skills/agent-composer/SKILL_detail.md +464 -0
  26. package/plugins/fh-meta/skills/apex-review/SKILL.md +185 -0
  27. package/plugins/fh-meta/skills/asset-placement-gate/SKILL.md +135 -0
  28. package/plugins/fh-meta/skills/contention-layer/SKILL.md +127 -0
  29. package/plugins/fh-meta/skills/context-bridge-dispatch/SKILL.md +30 -0
  30. package/plugins/fh-meta/skills/context-bridge-dispatch/SKILL_detail.md +144 -0
  31. package/plugins/fh-meta/skills/context-doctor/SKILL.md +341 -0
  32. package/plugins/fh-meta/skills/cross-ecosystem-synergy-detection/SKILL.md +202 -0
  33. package/plugins/fh-meta/skills/deep-clarify/SKILL.md +144 -0
  34. package/plugins/fh-meta/skills/edit-manifest/SKILL.md +210 -0
  35. package/plugins/fh-meta/skills/field-harvest/SKILL.md +384 -0
  36. package/plugins/fh-meta/skills/frontier-digest/SKILL.md +272 -0
  37. package/plugins/fh-meta/skills/goal-quench/SKILL.md +509 -0
  38. package/plugins/fh-meta/skills/harness-doctor/SKILL.md +277 -0
  39. package/plugins/fh-meta/skills/harness-doctor/SKILL_detail.md +484 -0
  40. package/plugins/fh-meta/skills/harvest-loop/SKILL.md +231 -0
  41. package/plugins/fh-meta/skills/harvest-loop/SKILL_detail.md +201 -0
  42. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +129 -0
  43. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL_detail.md +158 -0
  44. package/plugins/fh-meta/skills/install-doctor/SKILL.md +207 -0
  45. package/plugins/fh-meta/skills/install-wizard/SKILL.md +613 -0
  46. package/plugins/fh-meta/skills/marketplace-gate/SKILL.md +193 -0
  47. package/plugins/fh-meta/skills/memory-hygiene/SKILL.md +143 -0
  48. package/plugins/fh-meta/skills/meta-prompt-builder/SKILL.md +167 -0
  49. package/plugins/fh-meta/skills/meta-prompt-builder/SKILL_detail.md +37 -0
  50. package/plugins/fh-meta/skills/pipeline-conductor/SKILL.md +430 -0
  51. package/plugins/fh-meta/skills/plugin-recommender/SKILL.md +221 -0
  52. package/plugins/fh-meta/skills/plugin-recommender/SKILL_detail.md +220 -0
  53. package/plugins/fh-meta/skills/prompt-regression/SKILL.md +178 -0
  54. package/plugins/fh-meta/skills/public-surface-audit/SKILL.md +224 -0
  55. package/plugins/fh-meta/skills/return-path-gate/SKILL.md +257 -0
  56. package/plugins/fh-meta/skills/self-marketing-lint/SKILL.md +129 -0
  57. package/plugins/fh-meta/skills/sim-conductor/SKILL.md +364 -0
  58. package/plugins/fh-meta/skills/sim-conductor/SKILL_detail.md +337 -0
  59. package/plugins/fh-meta/skills/skill-splitter/SKILL.md +126 -0
  60. package/plugins/fh-meta/skills/skill-splitter/SKILL_detail.md +185 -0
  61. package/plugins/fh-meta/skills/source-grounding-audit/SKILL.md +230 -0
  62. package/plugins/fh-meta/skills/source-grounding-audit/SKILL_detail.md +182 -0
  63. package/plugins/fh-meta/skills/steel-quench/SKILL.md +226 -0
  64. package/plugins/fh-meta/skills/steel-quench/SKILL_detail.md +453 -0
  65. package/plugins/fh-meta/skills/verify-bidirectional/SKILL.md +238 -0
  66. package/scripts/fh-gate.sh +175 -40
  67. package/scripts/fh-goal.sh +182 -0
  68. package/scripts/fh-run.sh +269 -0
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env bash
2
+ # fh-goal.sh — stop-hook-free goal runner for Codex/Claude primary workflows
3
+ #
4
+ # Runs a backend prompt, detects changed files, then runs fh-gate.
5
+ # This is not Claude Code /goal parity; it is the portable quality-gated
6
+ # execution path for environments without Stop hooks.
7
+
8
+ set -euo pipefail
9
+
10
+ VERSION="0.1.0"
11
+ FH_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
12
+ _TMPDIR="${TMPDIR:-/tmp}"
13
+
14
+ FH_BACKEND="${FH_BACKEND:-codex}"
15
+ FH_TIMEOUT="${FH_TIMEOUT:-600}"
16
+ FH_GATE_LEVEL="${FH_GATE_LEVEL:-quick}"
17
+ FH_CALLER="${FH_CALLER:-fh-goal}"
18
+ FH_DRY_RUN="${FH_DRY_RUN:-0}"
19
+ FH_VERBOSE="${FH_VERBOSE:-0}"
20
+ GOAL_PROMPT=""
21
+ TARGET_FILES=""
22
+
23
+ usage() {
24
+ cat <<'USAGE'
25
+ Usage:
26
+ fh-goal --prompt <task> [--files "path path"] [--gate quick|full]
27
+ fh-goal "task prompt"
28
+
29
+ Environment:
30
+ FH_BACKEND=codex|claude Backend to run the task (default: codex)
31
+ FH_MODEL=<model> Backend model override
32
+ FH_TIMEOUT=600 Backend timeout seconds
33
+ FH_GATE_LEVEL=quick|full Post-run fh-gate level
34
+ FH_DRY_RUN=1 Print backend prompt and planned gate only
35
+
36
+ Examples:
37
+ FH_BACKEND=codex fh-goal --prompt "Implement X and update tests"
38
+ FH_BACKEND=codex fh-goal --prompt "Review docs" --files "README.md docs/codex-compat.md"
39
+ USAGE
40
+ }
41
+
42
+ while [[ $# -gt 0 ]]; do
43
+ case "$1" in
44
+ --prompt)
45
+ GOAL_PROMPT="${2:-}"
46
+ shift 2
47
+ ;;
48
+ --files|--file|--target)
49
+ TARGET_FILES="${2:-}"
50
+ shift 2
51
+ ;;
52
+ --gate)
53
+ FH_GATE_LEVEL="${2:-}"
54
+ shift 2
55
+ ;;
56
+ --backend)
57
+ FH_BACKEND="${2:-}"
58
+ shift 2
59
+ ;;
60
+ --model)
61
+ FH_MODEL="${2:-}"
62
+ shift 2
63
+ ;;
64
+ --help|-h)
65
+ usage
66
+ exit 0
67
+ ;;
68
+ *)
69
+ if [[ -z "$GOAL_PROMPT" ]]; then
70
+ GOAL_PROMPT="$1"
71
+ else
72
+ GOAL_PROMPT="${GOAL_PROMPT} $1"
73
+ fi
74
+ shift
75
+ ;;
76
+ esac
77
+ done
78
+
79
+ case "$FH_BACKEND" in
80
+ codex|claude) ;;
81
+ *)
82
+ echo "ERROR: FH_BACKEND must be codex or claude (got: $FH_BACKEND)" >&2
83
+ exit 11
84
+ ;;
85
+ esac
86
+
87
+ case "$FH_GATE_LEVEL" in
88
+ quick|full) ;;
89
+ *)
90
+ echo "ERROR: FH_GATE_LEVEL must be quick or full (got: $FH_GATE_LEVEL)" >&2
91
+ exit 11
92
+ ;;
93
+ esac
94
+
95
+ if [[ -z "$GOAL_PROMPT" ]]; then
96
+ echo "ERROR: missing goal prompt" >&2
97
+ usage >&2
98
+ exit 11
99
+ fi
100
+
101
+ if [[ -z "${FH_MODEL:-}" ]]; then
102
+ case "$FH_BACKEND" in
103
+ codex) FH_MODEL="gpt-5.5" ;;
104
+ claude) FH_MODEL="claude-sonnet-4-6" ;;
105
+ esac
106
+ fi
107
+
108
+ if ! command -v "$FH_BACKEND" &>/dev/null; then
109
+ echo "ERROR: '$FH_BACKEND' CLI not found." >&2
110
+ exit 10
111
+ fi
112
+
113
+ START_COMMIT="$(git -C "$FH_ROOT" rev-parse HEAD 2>/dev/null || true)"
114
+ PROMPT_FILE=$(mktemp "${_TMPDIR}/fh_goal_prompt_XXXXXX")
115
+ OUTPUT_FILE=$(mktemp "${_TMPDIR}/fh_goal_output_XXXXXX")
116
+ ERR_FILE=$(mktemp "${_TMPDIR}/fh_goal_err_XXXXXX")
117
+ cleanup() { rm -f "$PROMPT_FILE" "$OUTPUT_FILE" "$ERR_FILE"; }
118
+ trap cleanup EXIT
119
+
120
+ cat > "$PROMPT_FILE" <<PROMPT
121
+ FH GOAL RUNNER v${VERSION}
122
+ Backend: ${FH_BACKEND} | Model: ${FH_MODEL}
123
+
124
+ Task:
125
+ ${GOAL_PROMPT}
126
+
127
+ Execution rules:
128
+ 1. Work in the current repository.
129
+ 2. Make the requested changes if implementation is required.
130
+ 3. Prefer small, scoped edits.
131
+ 4. When finished, summarize changed files and verification performed.
132
+ 5. Do not claim FH governance passed; fh-goal runs fh-gate after this backend run.
133
+ PROMPT
134
+
135
+ if [[ "$FH_DRY_RUN" == "1" ]]; then
136
+ cat "$PROMPT_FILE"
137
+ echo
138
+ echo "Planned post-run gate: FH_BACKEND=${FH_BACKEND} scripts/fh-gate.sh \"${TARGET_FILES:-<changed files>}\" ${FH_GATE_LEVEL} ${FH_CALLER}"
139
+ exit 0
140
+ fi
141
+
142
+ echo "→ fh-goal v${VERSION} backend=${FH_BACKEND} model=${FH_MODEL} gate=${FH_GATE_LEVEL}" >&2
143
+
144
+ _TIMEOUT_CMD=""
145
+ if command -v gtimeout &>/dev/null; then
146
+ _TIMEOUT_CMD="gtimeout ${FH_TIMEOUT}"
147
+ elif command -v timeout &>/dev/null; then
148
+ _TIMEOUT_CMD="timeout ${FH_TIMEOUT}"
149
+ fi
150
+
151
+ run_backend() {
152
+ case "$FH_BACKEND" in
153
+ claude) ${_TIMEOUT_CMD} claude --print --model "$FH_MODEL" ;;
154
+ codex) ${_TIMEOUT_CMD} codex exec -m "$FH_MODEL" - ;;
155
+ esac
156
+ }
157
+
158
+ if ! run_backend < "$PROMPT_FILE" > "$OUTPUT_FILE" 2>"$ERR_FILE"; then
159
+ echo "ERROR: ${FH_BACKEND} backend failed or timed out (${FH_TIMEOUT}s)" >&2
160
+ cat "$ERR_FILE" >&2
161
+ exit 10
162
+ fi
163
+
164
+ [[ "$FH_VERBOSE" == "1" ]] && cat "$ERR_FILE" >&2
165
+ cat "$OUTPUT_FILE"
166
+
167
+ if [[ -z "$TARGET_FILES" ]]; then
168
+ if [[ -n "$START_COMMIT" ]]; then
169
+ TARGET_FILES=$(git -C "$FH_ROOT" diff "$START_COMMIT"..HEAD --name-only 2>/dev/null | tr '\n' ' ' | xargs || true)
170
+ fi
171
+ if [[ -z "$TARGET_FILES" ]]; then
172
+ TARGET_FILES=$(git -C "$FH_ROOT" status --short 2>/dev/null | awk '{print $2}' | tr '\n' ' ' | xargs || true)
173
+ fi
174
+ fi
175
+
176
+ if [[ -z "$TARGET_FILES" ]]; then
177
+ echo "→ fh-goal: no changed files detected; skipping fh-gate" >&2
178
+ exit 0
179
+ fi
180
+
181
+ echo "→ fh-goal: running fh-gate on ${TARGET_FILES}" >&2
182
+ FH_BACKEND="$FH_BACKEND" "$FH_ROOT/scripts/fh-gate.sh" "$TARGET_FILES" "$FH_GATE_LEVEL" "$FH_CALLER"
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env bash
2
+ # fh-run.sh — FH runtime adapter for skills and agents
3
+ #
4
+ # Runs a FH skill or agent document through a selectable backend.
5
+ # This is the Codex/Claude bridge for steps that previously assumed
6
+ # Claude Code Agent(...) dispatch or slash-command invocation.
7
+
8
+ set -euo pipefail
9
+
10
+ VERSION="0.1.0"
11
+ FH_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
12
+ _TMPDIR="${TMPDIR:-/tmp}"
13
+
14
+ FH_BACKEND="${FH_BACKEND:-auto}"
15
+ FH_TIMEOUT="${FH_TIMEOUT:-180}"
16
+ FH_DRY_RUN="${FH_DRY_RUN:-0}"
17
+ FH_VERBOSE="${FH_VERBOSE:-0}"
18
+ FH_RUN_PROMPT="${FH_RUN_PROMPT:-}"
19
+ FH_RUN_MODE=""
20
+ FH_RUN_NAME=""
21
+ FH_RUN_UNIT=""
22
+ TARGETS=()
23
+
24
+ usage() {
25
+ cat <<'USAGE'
26
+ Usage:
27
+ fh-run --skill <name> [--file <path> ...] [--prompt <text>]
28
+ fh-run --agent <name> [--file <path> ...] [--prompt <text>]
29
+ fh-run --unit <path> [--file <path> ...] [--prompt <text>]
30
+
31
+ Environment:
32
+ FH_BACKEND=auto|codex|claude Runtime backend (default: auto)
33
+ FH_MODEL=<model> Backend model override
34
+ FH_TIMEOUT=180 Backend timeout seconds
35
+ FH_DRY_RUN=1 Print assembled prompt only
36
+
37
+ Examples:
38
+ FH_BACKEND=codex fh-run --skill source-grounding-audit --file docs/foo.md
39
+ FH_BACKEND=codex fh-run --agent fh-commons:quench-challenger --file plugins/fh-meta/skills/foo/SKILL.md
40
+ USAGE
41
+ }
42
+
43
+ while [[ $# -gt 0 ]]; do
44
+ case "$1" in
45
+ --skill)
46
+ FH_RUN_MODE="skill"
47
+ FH_RUN_NAME="${2:-}"
48
+ shift 2
49
+ ;;
50
+ --agent)
51
+ FH_RUN_MODE="agent"
52
+ FH_RUN_NAME="${2:-}"
53
+ shift 2
54
+ ;;
55
+ --unit)
56
+ FH_RUN_MODE="unit"
57
+ FH_RUN_UNIT="${2:-}"
58
+ shift 2
59
+ ;;
60
+ --file|--target)
61
+ TARGETS+=("${2:-}")
62
+ shift 2
63
+ ;;
64
+ --prompt)
65
+ FH_RUN_PROMPT="${2:-}"
66
+ shift 2
67
+ ;;
68
+ --backend)
69
+ FH_BACKEND="${2:-}"
70
+ shift 2
71
+ ;;
72
+ --model)
73
+ FH_MODEL="${2:-}"
74
+ shift 2
75
+ ;;
76
+ --help|-h)
77
+ usage
78
+ exit 0
79
+ ;;
80
+ --)
81
+ shift
82
+ while [[ $# -gt 0 ]]; do
83
+ TARGETS+=("$1")
84
+ shift
85
+ done
86
+ ;;
87
+ *)
88
+ TARGETS+=("$1")
89
+ shift
90
+ ;;
91
+ esac
92
+ done
93
+
94
+ case "$FH_BACKEND" in
95
+ auto|codex|claude) ;;
96
+ *)
97
+ echo "ERROR: FH_BACKEND must be auto, codex, or claude (got: $FH_BACKEND)" >&2
98
+ exit 11
99
+ ;;
100
+ esac
101
+
102
+ if [[ "$FH_BACKEND" == "auto" ]]; then
103
+ if command -v codex &>/dev/null; then
104
+ FH_BACKEND="codex"
105
+ elif command -v claude &>/dev/null; then
106
+ FH_BACKEND="claude"
107
+ else
108
+ echo "ERROR: no supported backend found. Install 'codex' or 'claude'." >&2
109
+ exit 10
110
+ fi
111
+ fi
112
+
113
+ if [[ -z "${FH_MODEL:-}" ]]; then
114
+ case "$FH_BACKEND" in
115
+ codex) FH_MODEL="gpt-5.5" ;;
116
+ claude) FH_MODEL="claude-sonnet-4-6" ;;
117
+ esac
118
+ fi
119
+
120
+ resolve_unit() {
121
+ local mode="$1"
122
+ local name="$2"
123
+ local plugin=""
124
+ local bare="$name"
125
+
126
+ if [[ "$name" == *:* ]]; then
127
+ plugin="${name%%:*}"
128
+ bare="${name#*:}"
129
+ fi
130
+
131
+ if [[ "$mode" == "unit" ]]; then
132
+ [[ -f "$FH_RUN_UNIT" ]] && printf "%s\n" "$FH_RUN_UNIT" && return 0
133
+ [[ -f "$FH_ROOT/$FH_RUN_UNIT" ]] && printf "%s\n" "$FH_ROOT/$FH_RUN_UNIT" && return 0
134
+ return 1
135
+ fi
136
+
137
+ if [[ "$mode" == "skill" ]]; then
138
+ local candidates=()
139
+ if [[ -n "$plugin" ]]; then
140
+ candidates+=("$FH_ROOT/plugins/$plugin/skills/$bare/SKILL.md")
141
+ fi
142
+ candidates+=(
143
+ "$FH_ROOT/plugins/fh-meta/skills/$bare/SKILL.md"
144
+ "$FH_ROOT/plugins/fh-commons/skills/$bare/SKILL.md"
145
+ )
146
+ for f in "${candidates[@]}"; do
147
+ [[ -f "$f" ]] && printf "%s\n" "$f" && return 0
148
+ done
149
+ return 1
150
+ fi
151
+
152
+ if [[ "$mode" == "agent" ]]; then
153
+ local candidates=()
154
+ if [[ -n "$plugin" ]]; then
155
+ candidates+=("$FH_ROOT/plugins/$plugin/agents/$bare.md")
156
+ fi
157
+ candidates+=(
158
+ "$FH_ROOT/.claude/agents/$bare.md"
159
+ "$FH_ROOT/plugins/fh-meta/agents/$bare.md"
160
+ "$FH_ROOT/plugins/fh-commons/agents/$bare.md"
161
+ )
162
+ for f in "${candidates[@]}"; do
163
+ [[ -f "$f" ]] && printf "%s\n" "$f" && return 0
164
+ done
165
+ return 1
166
+ fi
167
+
168
+ return 1
169
+ }
170
+
171
+ if [[ -z "$FH_RUN_MODE" ]]; then
172
+ echo "ERROR: choose --skill, --agent, or --unit" >&2
173
+ usage >&2
174
+ exit 11
175
+ fi
176
+
177
+ if [[ "$FH_RUN_MODE" != "unit" && -z "$FH_RUN_NAME" ]]; then
178
+ echo "ERROR: missing $FH_RUN_MODE name" >&2
179
+ exit 11
180
+ fi
181
+
182
+ UNIT_FILE="$(resolve_unit "$FH_RUN_MODE" "$FH_RUN_NAME" || true)"
183
+ if [[ -z "$UNIT_FILE" ]]; then
184
+ echo "ERROR: unable to resolve FH $FH_RUN_MODE '${FH_RUN_NAME:-$FH_RUN_UNIT}'" >&2
185
+ exit 11
186
+ fi
187
+
188
+ PROMPT_FILE=$(mktemp "${_TMPDIR}/fh_run_prompt_XXXXXX")
189
+ OUTPUT_FILE=$(mktemp "${_TMPDIR}/fh_run_output_XXXXXX")
190
+ ERR_FILE=$(mktemp "${_TMPDIR}/fh_run_err_XXXXXX")
191
+ cleanup() { rm -f "$PROMPT_FILE" "$OUTPUT_FILE" "$ERR_FILE"; }
192
+ trap cleanup EXIT
193
+
194
+ {
195
+ printf "FH RUNTIME ADAPTER v%s\n" "$VERSION"
196
+ printf "Backend: %s | Model: %s\n" "$FH_BACKEND" "$FH_MODEL"
197
+ printf "Unit: %s\n" "$UNIT_FILE"
198
+ printf "\n"
199
+ printf "You are executing the FH %s below. Follow its workflow and output contract.\n" "$FH_RUN_MODE"
200
+ printf "If the unit references Claude Code Agent(...) dispatch, treat this invocation as the isolated agent run.\n"
201
+ printf "If the unit references a slash command, execute the documented workflow directly.\n"
202
+ printf "\n"
203
+ if [[ -n "$FH_RUN_PROMPT" ]]; then
204
+ printf "User task:\n%s\n\n" "$FH_RUN_PROMPT"
205
+ fi
206
+ if [[ "${#TARGETS[@]}" -gt 0 ]]; then
207
+ printf "Target paths:\n"
208
+ for t in "${TARGETS[@]}"; do
209
+ printf " - %s\n" "$t"
210
+ done
211
+ printf "\n"
212
+ fi
213
+ printf "===== FH UNIT DOCUMENT START =====\n"
214
+ cat "$UNIT_FILE"
215
+ printf "\n===== FH UNIT DOCUMENT END =====\n"
216
+
217
+ if [[ "${#TARGETS[@]}" -gt 0 ]]; then
218
+ for t in "${TARGETS[@]}"; do
219
+ local_path="$t"
220
+ [[ -f "$local_path" ]] || local_path="$FH_ROOT/$t"
221
+ if [[ -f "$local_path" ]]; then
222
+ printf "\n===== TARGET FILE: %s =====\n" "$t"
223
+ sed -n '1,2000p' "$local_path"
224
+ printf "\n===== END TARGET FILE: %s =====\n" "$t"
225
+ elif [[ -d "$local_path" ]]; then
226
+ printf "\n===== TARGET DIRECTORY: %s =====\n" "$t"
227
+ find "$local_path" -maxdepth 2 -type f | sort | sed "s#^$FH_ROOT/##"
228
+ printf "===== END TARGET DIRECTORY: %s =====\n" "$t"
229
+ else
230
+ printf "\nWARNING: target path not found: %s\n" "$t"
231
+ fi
232
+ done
233
+ fi
234
+ } > "$PROMPT_FILE"
235
+
236
+ if [[ "$FH_DRY_RUN" == "1" ]]; then
237
+ cat "$PROMPT_FILE"
238
+ exit 0
239
+ fi
240
+
241
+ if ! command -v "$FH_BACKEND" &>/dev/null; then
242
+ echo "ERROR: '$FH_BACKEND' CLI not found." >&2
243
+ exit 10
244
+ fi
245
+
246
+ echo "→ fh-run v${VERSION} backend=${FH_BACKEND} model=${FH_MODEL} unit=${FH_RUN_MODE}:${FH_RUN_NAME:-$FH_RUN_UNIT}" >&2
247
+
248
+ _TIMEOUT_CMD=""
249
+ if command -v gtimeout &>/dev/null; then
250
+ _TIMEOUT_CMD="gtimeout ${FH_TIMEOUT}"
251
+ elif command -v timeout &>/dev/null; then
252
+ _TIMEOUT_CMD="timeout ${FH_TIMEOUT}"
253
+ fi
254
+
255
+ run_backend() {
256
+ case "$FH_BACKEND" in
257
+ claude) ${_TIMEOUT_CMD} claude --print --model "$FH_MODEL" ;;
258
+ codex) ${_TIMEOUT_CMD} codex exec -m "$FH_MODEL" - ;;
259
+ esac
260
+ }
261
+
262
+ if ! run_backend < "$PROMPT_FILE" > "$OUTPUT_FILE" 2>"$ERR_FILE"; then
263
+ echo "ERROR: ${FH_BACKEND} backend failed or timed out (${FH_TIMEOUT}s)" >&2
264
+ cat "$ERR_FILE" >&2
265
+ exit 10
266
+ fi
267
+
268
+ [[ "$FH_VERBOSE" == "1" ]] && cat "$ERR_FILE" >&2
269
+ cat "$OUTPUT_FILE"