@giamme/forge 0.0.0-stage → 0.8.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/CHANGELOG.md +181 -0
- package/LICENSE +21 -0
- package/README.md +1854 -2
- package/SKILL.md +122 -0
- package/VERSION +1 -0
- package/forge +74 -0
- package/package.json +26 -4
- package/references/dataset.md +329 -0
- package/references/decompose.md +667 -0
- package/references/fractal-dashboard.md +21 -0
- package/references/fractal.md +307 -0
- package/references/harnesses.md +353 -0
- package/references/jev.md +865 -0
- package/references/memory.md +228 -0
- package/references/planning.md +42 -0
- package/references/ripwire.md +47 -0
- package/references/runtime.md +64 -0
- package/references/solo.md +113 -0
- package/references/ui.md +112 -0
- package/registry.tsv +81 -0
- package/scripts/forge-artifact.sh +78 -0
- package/scripts/forge-contract.py +430 -0
- package/scripts/forge-dataset.py +6 -0
- package/scripts/forge-dispatch.sh +665 -0
- package/scripts/forge-fail-first.py +195 -0
- package/scripts/forge-fractal-dispatch.sh +4 -0
- package/scripts/forge-fractal-options.sh +54 -0
- package/scripts/forge-fractal.py +6 -0
- package/scripts/forge-guard.py +266 -0
- package/scripts/forge-install-fractal.sh +4 -0
- package/scripts/forge-install-ripwire.sh +52 -0
- package/scripts/forge-install.sh +135 -0
- package/scripts/forge-jev-options.sh +259 -0
- package/scripts/forge-jev.py +6 -0
- package/scripts/forge-lib-plans.sh +785 -0
- package/scripts/forge-lib-review.sh +494 -0
- package/scripts/forge-lib-task.sh +302 -0
- package/scripts/forge-lib-verify.sh +30 -0
- package/scripts/forge-memory.sh +575 -0
- package/scripts/forge-metrics.sh +39 -0
- package/scripts/forge-parallel-options.sh +76 -0
- package/scripts/forge-parallel.sh +1717 -0
- package/scripts/forge-plan-check.py +118 -0
- package/scripts/forge-prompt.py +45 -0
- package/scripts/forge-ripwire.py +136 -0
- package/scripts/forge-route.py +344 -0
- package/scripts/forge-runtime.py +533 -0
- package/scripts/forge-schedule.py +312 -0
- package/scripts/forge-shadow.py +305 -0
- package/scripts/forge-solo.sh +347 -0
- package/scripts/forge-ui-options.sh +42 -0
- package/scripts/forge-ui.py +435 -0
- package/scripts/forge-update.sh +71 -0
- package/scripts/forge-verify-coverage.py +956 -0
- package/scripts/forge-version.py +198 -0
- package/scripts/forge_dataset/__init__.py +439 -0
- package/scripts/forge_dataset/capture.py +330 -0
- package/scripts/forge_dataset/checks.py +109 -0
- package/scripts/forge_dataset/cli.py +233 -0
- package/scripts/forge_dataset/export.py +1009 -0
- package/scripts/forge_dataset/harvest.py +716 -0
- package/scripts/forge_dataset/importer.py +412 -0
- package/scripts/forge_dataset/outcomes.py +275 -0
- package/scripts/forge_dataset/routing.py +278 -0
- package/scripts/forge_fractal/__init__.py +83 -0
- package/scripts/forge_fractal/bridge.py +46 -0
- package/scripts/forge_fractal/cli.py +202 -0
- package/scripts/forge_fractal/dashboard.css +138 -0
- package/scripts/forge_fractal/dashboard.html +41 -0
- package/scripts/forge_fractal/dashboard.js +687 -0
- package/scripts/forge_fractal/decomposition.py +262 -0
- package/scripts/forge_fractal/execution.py +659 -0
- package/scripts/forge_fractal/inspection.py +261 -0
- package/scripts/forge_fractal/install.py +158 -0
- package/scripts/forge_fractal/invocation.py +94 -0
- package/scripts/forge_fractal/pipeline.py +124 -0
- package/scripts/forge_fractal/selection.py +179 -0
- package/scripts/forge_jev/__init__.py +376 -0
- package/scripts/forge_jev/backtest.py +322 -0
- package/scripts/forge_jev/cli.py +980 -0
- package/scripts/forge_jev/client.py +418 -0
- package/scripts/forge_jev/corpus.py +156 -0
- package/scripts/forge_jev/coupling.py +244 -0
- package/scripts/forge_jev/features.py +24 -0
- package/scripts/forge_jev/gates.py +100 -0
- package/scripts/forge_jev/memory.py +163 -0
- package/scripts/forge_jev/outcome.py +49 -0
- package/scripts/forge_jev/predict.py +204 -0
- package/scripts/forge_jev/questions.py +661 -0
- package/scripts/forge_jev/review.py +73 -0
- package/scripts/forge_jev/routing.py +271 -0
- package/scripts/forge_jev/subset.py +123 -0
- package/scripts/forge_jev/verify.py +453 -0
- package/scripts/forge_registry.py +140 -0
- package/ui/README.md +287 -0
- package/ui/THIRD_PARTY_NOTICES.md +69 -0
- package/ui/docs/api.md +888 -0
- package/ui/forge_ui/__init__.py +20 -0
- package/ui/forge_ui/__main__.py +135 -0
- package/ui/forge_ui/diffs.py +454 -0
- package/ui/forge_ui/executions.py +436 -0
- package/ui/forge_ui/export.py +204 -0
- package/ui/forge_ui/findings.py +132 -0
- package/ui/forge_ui/fractal.py +583 -0
- package/ui/forge_ui/models.py +216 -0
- package/ui/forge_ui/reader.py +1336 -0
- package/ui/forge_ui/server.py +303 -0
- package/ui/forge_ui/static/app.js +924 -0
- package/ui/forge_ui/static/cloud3d.js +646 -0
- package/ui/forge_ui/static/cloudmodel.js +375 -0
- package/ui/forge_ui/static/cloudui.js +394 -0
- package/ui/forge_ui/static/diffview.js +216 -0
- package/ui/forge_ui/static/favicon.svg +1 -0
- package/ui/forge_ui/static/files.js +776 -0
- package/ui/forge_ui/static/gantt.js +97 -0
- package/ui/forge_ui/static/graph.js +266 -0
- package/ui/forge_ui/static/index.html +55 -0
- package/ui/forge_ui/static/iso.js +765 -0
- package/ui/forge_ui/static/leaderboard.js +315 -0
- package/ui/forge_ui/static/map3d.js +45 -0
- package/ui/forge_ui/static/modelstats.js +249 -0
- package/ui/forge_ui/static/notify.js +179 -0
- package/ui/forge_ui/static/panel.js +526 -0
- package/ui/forge_ui/static/runmodel.js +651 -0
- package/ui/forge_ui/static/scene3d.js +996 -0
- package/ui/forge_ui/static/search.js +119 -0
- package/ui/forge_ui/static/style.css +618 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# forge-install.sh — make /forge reachable from codex, claude, openclaude and opencode.
|
|
3
|
+
#
|
|
4
|
+
# This skill directory stays the single source of truth.
|
|
5
|
+
#
|
|
6
|
+
# claude / openclaude / codex symlink into their skills dir — edits are live
|
|
7
|
+
# opencode nothing to do; it scans ~/.claude/ for skills itself
|
|
8
|
+
# antigravity plugin install, which COPIES — see below
|
|
9
|
+
#
|
|
10
|
+
# Codex reads ~/.codex/skills, not ~/.codex/prompts: a prompts entry gives you a
|
|
11
|
+
# slash command, but not the skill, and it is not where any other skill here lives.
|
|
12
|
+
#
|
|
13
|
+
# forge-install.sh [--dry-run]
|
|
14
|
+
set -uo pipefail
|
|
15
|
+
|
|
16
|
+
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
17
|
+
RESULT=0
|
|
18
|
+
DRY=0
|
|
19
|
+
# `forge install --help` must never install; nor may a mistyped --dry-run.
|
|
20
|
+
case "${1:-}" in
|
|
21
|
+
'') ;;
|
|
22
|
+
--dry-run) DRY=1 ;;
|
|
23
|
+
-h|--help) echo 'usage: forge install [--dry-run]'; exit 0 ;;
|
|
24
|
+
*) echo "forge: unknown install option: $1" >&2; exit 2 ;;
|
|
25
|
+
esac
|
|
26
|
+
|
|
27
|
+
say() { printf '%s\n' "$*"; }
|
|
28
|
+
do_() { if [ "$DRY" = 1 ]; then say " would: $*"; else "$@"; fi; }
|
|
29
|
+
|
|
30
|
+
link_dir() { # link_dir <target-parent> <label>
|
|
31
|
+
local parent="$1" label="$2" dest="$1/forge"
|
|
32
|
+
if [ ! -d "$parent" ]; then say "$label: skipped ($parent does not exist)"; return; fi
|
|
33
|
+
if [ "$dest" = "$SKILL_DIR" ]; then say "$label: is the source directory"; return; fi
|
|
34
|
+
if [ -L "$dest" ] && [ "$(readlink "$dest")" = "$SKILL_DIR" ]; then
|
|
35
|
+
say "$label: already linked"; return
|
|
36
|
+
fi
|
|
37
|
+
if [ -e "$dest" ] && [ ! -L "$dest" ]; then
|
|
38
|
+
# Never clobber a real directory the user may have edited in place.
|
|
39
|
+
say "$label: SKIPPED — $dest exists and is not a symlink; move it aside first"; return 1
|
|
40
|
+
fi
|
|
41
|
+
do_ ln -sfn "$SKILL_DIR" "$dest" && say "$label: linked -> $dest"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# opencode needs no install at all: it scans ~/.claude/ for skills by default
|
|
45
|
+
# (disabled only by OPENCODE_DISABLE_CLAUDE_CODE_SKILLS=1), so it picks this
|
|
46
|
+
# directory up directly. Verify with `opencode debug skill`.
|
|
47
|
+
report_opencode() {
|
|
48
|
+
if ! command -v opencode >/dev/null 2>&1; then
|
|
49
|
+
say "opencode : skipped (not on PATH)"; return
|
|
50
|
+
fi
|
|
51
|
+
say "opencode : auto-detected from ~/.claude/skills (no install needed)"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Antigravity loads skills only as part of a plugin — a loose directory under
|
|
55
|
+
# ~/.gemini/skills is ignored (agy cannot see skills placed there at all). So forge
|
|
56
|
+
# is wrapped in a minimal plugin whose skills/ entry points back at this directory.
|
|
57
|
+
#
|
|
58
|
+
# Note that `agy plugin install` COPIES the files into ~/.gemini/config/plugins
|
|
59
|
+
# rather than referencing them. Antigravity is therefore the one harness that does
|
|
60
|
+
# not track edits to this directory automatically: re-run this installer after
|
|
61
|
+
# changing the skill, or agy keeps running the version it copied.
|
|
62
|
+
install_antigravity() {
|
|
63
|
+
local label="antigravity" wrapper="$HOME/.gemini/forge-plugin"
|
|
64
|
+
if ! command -v agy >/dev/null 2>&1; then
|
|
65
|
+
say "$label: skipped (agy not on PATH)"; return
|
|
66
|
+
fi
|
|
67
|
+
if [ "$DRY" = 1 ]; then
|
|
68
|
+
say " would: write plugin wrapper $wrapper"
|
|
69
|
+
say " would: agy plugin install $wrapper"
|
|
70
|
+
say "$label: plugin -> $wrapper"; return
|
|
71
|
+
fi
|
|
72
|
+
mkdir -p "$wrapper/skills" || return 1
|
|
73
|
+
cat > "$wrapper/plugin.json" <<EOF
|
|
74
|
+
{
|
|
75
|
+
"name": "forge",
|
|
76
|
+
"description": "Dispatch a coding task to a dwarf model to implement, then an independent qa model to review its actual diff, across codex/claude/openclaude/opencode/antigravity.",
|
|
77
|
+
"version": "1.0.0",
|
|
78
|
+
"author": { "name": "forge" }
|
|
79
|
+
}
|
|
80
|
+
EOF
|
|
81
|
+
[ "$?" = 0 ] || return 1
|
|
82
|
+
ln -sfn "$SKILL_DIR" "$wrapper/skills/forge" || return 1
|
|
83
|
+
if agy plugin install "$wrapper"; then
|
|
84
|
+
say "$label: plugin installed (copied — re-run this script after editing the skill)"
|
|
85
|
+
else
|
|
86
|
+
say "$label: FAILED — try: agy plugin install $wrapper"
|
|
87
|
+
return 1
|
|
88
|
+
fi
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
say "forge source: $SKILL_DIR"
|
|
92
|
+
[ "$DRY" = 1 ] && say "(dry run)"
|
|
93
|
+
|
|
94
|
+
link_dir "$HOME/.claude/skills" "claude " || RESULT=1
|
|
95
|
+
link_dir "$HOME/.openclaude/skills" "openclaude" || RESULT=1
|
|
96
|
+
link_dir "$HOME/.codex/skills" "codex " || RESULT=1
|
|
97
|
+
install_antigravity || RESULT=1
|
|
98
|
+
report_opencode
|
|
99
|
+
|
|
100
|
+
say
|
|
101
|
+
say "Verify with: bash $SKILL_DIR/scripts/forge-dispatch.sh doctor"
|
|
102
|
+
|
|
103
|
+
# Optional context engine; failure never prevents Forge setup.
|
|
104
|
+
if [ "$DRY" = 1 ]; then
|
|
105
|
+
/bin/bash "$SKILL_DIR/scripts/forge-install-ripwire.sh" --dry-run
|
|
106
|
+
else
|
|
107
|
+
/bin/bash "$SKILL_DIR/scripts/forge-install-ripwire.sh" || true
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# Optional runtime; installing never activates it for a run.
|
|
111
|
+
if [ "$DRY" = 1 ]; then
|
|
112
|
+
/bin/bash "$SKILL_DIR/scripts/forge-install-fractal.sh" --dry-run
|
|
113
|
+
elif [ -t 0 ]; then
|
|
114
|
+
printf 'Install optional Fractal execution support? [y/N] '
|
|
115
|
+
read -r fractal_answer
|
|
116
|
+
if [ "$fractal_answer" = y ] || [ "$fractal_answer" = Y ]; then
|
|
117
|
+
/bin/bash "$SKILL_DIR/scripts/forge-install-fractal.sh" --with-prerequisites || true
|
|
118
|
+
fi
|
|
119
|
+
fi
|
|
120
|
+
say "Fractal CLI: $SKILL_DIR/forge fractal --help (repository-local; existing forge commands are preserved)"
|
|
121
|
+
|
|
122
|
+
# Optional advisory judgments; sends task/diff/test/memory excerpts to an external
|
|
123
|
+
# API. Off unless configured and enabled. Failure never prevents Forge setup.
|
|
124
|
+
if [ "$DRY" = 1 ]; then
|
|
125
|
+
say " would: offer optional Jev setup (sends data to an external API; declined by default)"
|
|
126
|
+
elif [ -t 0 ]; then
|
|
127
|
+
printf 'Set up optional Jev advisory judgments? Sends task/diff excerpts to an external API. [y/N] '
|
|
128
|
+
read -r jev_answer
|
|
129
|
+
if [ "$jev_answer" = y ] || [ "$jev_answer" = Y ]; then
|
|
130
|
+
python3 "$SKILL_DIR/scripts/forge-jev.py" setup || true
|
|
131
|
+
fi
|
|
132
|
+
fi
|
|
133
|
+
say "Jev CLI: $SKILL_DIR/forge jev --help (optional; disabled until configured)"
|
|
134
|
+
|
|
135
|
+
exit "$RESULT"
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Source in runners (forge-solo.sh / forge-parallel.sh, a later phase). Arrays and
|
|
3
|
+
# syntax remain compatible with macOS Bash 3.2: no associative arrays, no
|
|
4
|
+
# ${var,,}, and possibly-empty arrays are always expanded with the
|
|
5
|
+
# ${arr[@]+"${arr[@]}"} idiom.
|
|
6
|
+
#
|
|
7
|
+
# Precedence the Python side (scripts/forge_jev/) implements — keep these two in
|
|
8
|
+
# sync when either changes:
|
|
9
|
+
# 1. FORGE_JEV=off wins over everything: everything off, full stop.
|
|
10
|
+
# 2. Any FORGE_JEV_<CAP>=on acts as an allowlist — only those capabilities run
|
|
11
|
+
# (so `--jev-tests` alone works without `--jev`).
|
|
12
|
+
# 3. Otherwise FORGE_JEV=on (from --jev) or the stored config's `enabled` flag
|
|
13
|
+
# turns it on, and a capability explicitly off is subtracted.
|
|
14
|
+
# 4. Default: off.
|
|
15
|
+
#
|
|
16
|
+
# Phase 0 wires only the flags, env vars and freeze/restore files below. Nothing
|
|
17
|
+
# dispatches differently yet.
|
|
18
|
+
|
|
19
|
+
# What the environment said before any flag was parsed. Captured once, and exported so
|
|
20
|
+
# a second source of this file (there are several per run) sees the original answer
|
|
21
|
+
# rather than whatever forge_jev_export has since written into FORGE_JEV.
|
|
22
|
+
#
|
|
23
|
+
# This exists because wiring the flags up made a documented guarantee reachable for the
|
|
24
|
+
# first time: FORGE_JEV=off is a hard kill switch that wins over everything, and until
|
|
25
|
+
# forge_jev_export was actually called by a runner, it won by default -- nothing was
|
|
26
|
+
# there to override it. The first real run with --jev clobbered it.
|
|
27
|
+
if [ -z "${FORGE_JEV_ENV_CAPTURED:-}" ]; then
|
|
28
|
+
FORGE_JEV_ENV_CAPTURED=1
|
|
29
|
+
FORGE_JEV_ENV_KILL=0
|
|
30
|
+
[ "${FORGE_JEV:-}" = off ] && FORGE_JEV_ENV_KILL=1
|
|
31
|
+
FORGE_JEV_ENV_ROUTING="${FORGE_JEV_ROUTING:-}"
|
|
32
|
+
FORGE_JEV_ENV_TESTS="${FORGE_JEV_TESTS:-}"
|
|
33
|
+
FORGE_JEV_ENV_GATES="${FORGE_JEV_GATES:-}"
|
|
34
|
+
FORGE_JEV_ENV_MEMORY="${FORGE_JEV_MEMORY:-}"
|
|
35
|
+
export FORGE_JEV_ENV_CAPTURED FORGE_JEV_ENV_KILL FORGE_JEV_ENV_ROUTING \
|
|
36
|
+
FORGE_JEV_ENV_TESTS FORGE_JEV_ENV_GATES FORGE_JEV_ENV_MEMORY
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
JEV_CHOICE="" # "" | on | off
|
|
40
|
+
JEV_ACT=0 # --jev-act
|
|
41
|
+
JEV_SHADOW=0 # --jev-shadow
|
|
42
|
+
JEV_CAPS=() # entries like "tests=on" / "routing=off"
|
|
43
|
+
|
|
44
|
+
forge_jev_help() {
|
|
45
|
+
cat <<'HELP'
|
|
46
|
+
Optional Jev (TypeSafe System One) advisory judgments (solo or parallel run):
|
|
47
|
+
--jev | --no-jev Explicit choice; unattended defaults off
|
|
48
|
+
--jev-act Kept for old commands: only marks judgments as acting
|
|
49
|
+
--jev-shadow Record judgments only; changes nothing
|
|
50
|
+
--jev-<cap> | --no-jev-<cap> Per-capability on/off: routing tests gates memory
|
|
51
|
+
FORGE_JEV=off is a hard kill switch and wins over every other setting.
|
|
52
|
+
Setup, status, doctor and enable/disable live under ./forge jev --help.
|
|
53
|
+
HELP
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# forge_jev_flag "$@" — parse ONE option from the front of the argument list.
|
|
57
|
+
# Echoes nothing. Returns 0 and sets JEV_SHIFT to the number of argv items
|
|
58
|
+
# consumed (always 1 for our flags) when the option is ours; returns 1 and sets
|
|
59
|
+
# JEV_SHIFT=0 when the first argument is not one of ours; returns 2 on a usage
|
|
60
|
+
# error (message on stderr), also with JEV_SHIFT unset/irrelevant.
|
|
61
|
+
forge_jev_flag() {
|
|
62
|
+
local opt="$1"
|
|
63
|
+
JEV_SHIFT=0
|
|
64
|
+
case "$opt" in
|
|
65
|
+
--jev)
|
|
66
|
+
[ -z "$JEV_CHOICE" ] || [ "$JEV_CHOICE" = on ] || { echo '--jev and --no-jev are mutually exclusive' >&2; return 2; }
|
|
67
|
+
JEV_CHOICE=on; JEV_SHIFT=1; return 0
|
|
68
|
+
;;
|
|
69
|
+
--no-jev)
|
|
70
|
+
[ -z "$JEV_CHOICE" ] || [ "$JEV_CHOICE" = off ] || { echo '--jev and --no-jev are mutually exclusive' >&2; return 2; }
|
|
71
|
+
JEV_CHOICE=off; JEV_SHIFT=1; return 0
|
|
72
|
+
;;
|
|
73
|
+
--jev-act)
|
|
74
|
+
JEV_ACT=1; JEV_SHIFT=1; return 0
|
|
75
|
+
;;
|
|
76
|
+
--jev-shadow)
|
|
77
|
+
JEV_SHADOW=1; JEV_SHIFT=1; return 0
|
|
78
|
+
;;
|
|
79
|
+
--jev-routing|--jev-tests|--jev-gates|--jev-memory)
|
|
80
|
+
JEV_CAPS+=("${opt#--jev-}=on"); JEV_SHIFT=1; return 0
|
|
81
|
+
;;
|
|
82
|
+
--no-jev-routing|--no-jev-tests|--no-jev-gates|--no-jev-memory)
|
|
83
|
+
JEV_CAPS+=("${opt#--no-jev-}=off"); JEV_SHIFT=1; return 0
|
|
84
|
+
;;
|
|
85
|
+
*)
|
|
86
|
+
return 1
|
|
87
|
+
;;
|
|
88
|
+
esac
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# forge_jev_export — translate the parsed state into the environment the
|
|
92
|
+
# Python side reads. Call after all flags are parsed (and after forge_jev_restore,
|
|
93
|
+
# if resuming, since restore re-parses the frozen selection through this path).
|
|
94
|
+
forge_jev_export() {
|
|
95
|
+
if [ "${FORGE_JEV_ENV_KILL:-0}" = 1 ]; then
|
|
96
|
+
# Rule 1 of the precedence above: it wins over everything, and "everything" has to
|
|
97
|
+
# include this function. A flag must not be able to switch Jev back on.
|
|
98
|
+
export FORGE_JEV=off
|
|
99
|
+
elif [ "$JEV_CHOICE" = off ]; then
|
|
100
|
+
export FORGE_JEV=off
|
|
101
|
+
elif [ "$JEV_CHOICE" = on ]; then
|
|
102
|
+
export FORGE_JEV=on
|
|
103
|
+
else
|
|
104
|
+
unset FORGE_JEV 2>/dev/null || true
|
|
105
|
+
fi
|
|
106
|
+
# Restore what the environment said, rather than clearing it: a per-capability
|
|
107
|
+
# variable set by the caller is rule 2's allowlist, and wiping it here would have
|
|
108
|
+
# made `FORGE_JEV_TESTS=on forge ...` silently do nothing. A flag for the same
|
|
109
|
+
# capability still wins, because it is the more specific instruction.
|
|
110
|
+
local cap_env
|
|
111
|
+
for cap_env in ROUTING TESTS GATES MEMORY; do
|
|
112
|
+
eval "local inherited=\"\${FORGE_JEV_ENV_$cap_env:-}\""
|
|
113
|
+
if [ -n "$inherited" ]; then
|
|
114
|
+
eval "export FORGE_JEV_$cap_env=\"\$inherited\""
|
|
115
|
+
else
|
|
116
|
+
unset "FORGE_JEV_$cap_env" 2>/dev/null || true
|
|
117
|
+
fi
|
|
118
|
+
done
|
|
119
|
+
local entry cap val
|
|
120
|
+
for entry in ${JEV_CAPS[@]+"${JEV_CAPS[@]}"}; do
|
|
121
|
+
cap="${entry%%=*}"; val="${entry#*=}"
|
|
122
|
+
case "$cap" in
|
|
123
|
+
routing) export FORGE_JEV_ROUTING="$val" ;;
|
|
124
|
+
tests) export FORGE_JEV_TESTS="$val" ;;
|
|
125
|
+
gates) export FORGE_JEV_GATES="$val" ;;
|
|
126
|
+
memory) export FORGE_JEV_MEMORY="$val" ;;
|
|
127
|
+
esac
|
|
128
|
+
done
|
|
129
|
+
if [ "$JEV_ACT" = 1 ]; then export FORGE_JEV_ACT=on; else unset FORGE_JEV_ACT 2>/dev/null || true; fi
|
|
130
|
+
if [ "$JEV_SHADOW" = 1 ]; then export FORGE_JEV_SHADOW=on; else unset FORGE_JEV_SHADOW 2>/dev/null || true; fi
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# forge_jev_record <run-dir> — freeze the resolved selection into
|
|
134
|
+
# <run-dir>/jev-selection.json. A resumed or retried run must make the same
|
|
135
|
+
# decisions as the original run, the same reason fractal-selection.json is
|
|
136
|
+
# frozen for Fractal: settings must not silently drift between the first
|
|
137
|
+
# attempt and a later resume/retry of the same run directory.
|
|
138
|
+
forge_jev_record() {
|
|
139
|
+
local run="$1" caps_json="{}" entry cap val first=1
|
|
140
|
+
for entry in ${JEV_CAPS[@]+"${JEV_CAPS[@]}"}; do
|
|
141
|
+
cap="${entry%%=*}"; val="${entry#*=}"
|
|
142
|
+
if [ "$first" = 1 ]; then caps_json="{\"$cap\": \"$val\""; first=0
|
|
143
|
+
else caps_json="$caps_json, \"$cap\": \"$val\""; fi
|
|
144
|
+
done
|
|
145
|
+
[ "$first" = 1 ] || caps_json="$caps_json}"
|
|
146
|
+
python3 -c '
|
|
147
|
+
import json, sys
|
|
148
|
+
run, choice, act, shadow, caps_json = sys.argv[1:6]
|
|
149
|
+
data = {
|
|
150
|
+
"choice": choice if choice else None,
|
|
151
|
+
"act": act == "1",
|
|
152
|
+
"shadow": shadow == "1",
|
|
153
|
+
"capabilities": json.loads(caps_json),
|
|
154
|
+
}
|
|
155
|
+
with open(run + "/jev-selection.json", "w") as fh:
|
|
156
|
+
json.dump(data, fh, indent=2, sort_keys=True)
|
|
157
|
+
fh.write("\n")
|
|
158
|
+
' "$run" "$JEV_CHOICE" "$JEV_ACT" "$JEV_SHADOW" "$caps_json"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# forge_jev_restore <run-dir> — re-export a previously frozen selection, for
|
|
162
|
+
# resume/retry of an existing run directory. Reads jev-selection.json (written
|
|
163
|
+
# by forge_jev_record) back into JEV_CHOICE/JEV_ACT/JEV_SHADOW/JEV_CAPS and
|
|
164
|
+
# calls forge_jev_export so the environment matches the original run exactly.
|
|
165
|
+
forge_jev_restore() {
|
|
166
|
+
# Separate statements on purpose: bash expands every argument of a single `local`
|
|
167
|
+
# before assigning any of them, so `selection="$run/..."` on this line would see an
|
|
168
|
+
# empty run and silently resolve to /jev-selection.json.
|
|
169
|
+
local run="$1" line selection
|
|
170
|
+
selection="$run/jev-selection.json"
|
|
171
|
+
[ -f "$selection" ] || return 0
|
|
172
|
+
JEV_CAPS=()
|
|
173
|
+
local parsed
|
|
174
|
+
parsed="$(python3 -c '
|
|
175
|
+
import json, sys
|
|
176
|
+
with open(sys.argv[1]) as fh:
|
|
177
|
+
data = json.load(fh)
|
|
178
|
+
choice = data.get("choice") or ""
|
|
179
|
+
print(choice)
|
|
180
|
+
print("1" if data.get("act") else "0")
|
|
181
|
+
print("1" if data.get("shadow") else "0")
|
|
182
|
+
for cap, val in sorted((data.get("capabilities") or {}).items()):
|
|
183
|
+
print(cap + "=" + val)
|
|
184
|
+
' "$selection")" || return $?
|
|
185
|
+
local i=0
|
|
186
|
+
while IFS= read -r line; do
|
|
187
|
+
case "$i" in
|
|
188
|
+
0) JEV_CHOICE="$line" ;;
|
|
189
|
+
1) JEV_ACT="$line" ;;
|
|
190
|
+
2) JEV_SHADOW="$line" ;;
|
|
191
|
+
*) [ -z "$line" ] || JEV_CAPS+=("$line") ;;
|
|
192
|
+
esac
|
|
193
|
+
i=$((i + 1))
|
|
194
|
+
done <<EOF
|
|
195
|
+
$parsed
|
|
196
|
+
EOF
|
|
197
|
+
forge_jev_export
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
# forge_jev_settle <plan-dir> — what `run` and `retry` do about Jev.
|
|
201
|
+
#
|
|
202
|
+
# `plan` is the command that makes the choice and freezes it. A later run or retry of
|
|
203
|
+
# the same plan must make the same decisions, for the reason fractal-selection.json is
|
|
204
|
+
# frozen: a resumed run that quietly changed its mind about whether a judgment may act
|
|
205
|
+
# is a run nobody can reason about afterwards.
|
|
206
|
+
#
|
|
207
|
+
# So a frozen selection wins over a flag passed here, and saying so out loud beats
|
|
208
|
+
# ignoring the flag in silence. When nothing was frozen — a plan dir written before
|
|
209
|
+
# selections existed — the flags parsed on this command apply and are then frozen, so
|
|
210
|
+
# the next retry of it is reproducible too.
|
|
211
|
+
forge_jev_settle() {
|
|
212
|
+
local plan="$1"
|
|
213
|
+
if [ -f "$plan/jev-selection.json" ]; then
|
|
214
|
+
if [ -n "$JEV_CHOICE" ] || [ "$JEV_ACT" = 1 ] || [ "$JEV_SHADOW" = 1 ] \
|
|
215
|
+
|| [ "${#JEV_CAPS[@]}" -gt 0 ]; then
|
|
216
|
+
printf 'forge: jev: using the selection frozen at plan time; ignoring the flags given here\n' >&2
|
|
217
|
+
fi
|
|
218
|
+
forge_jev_restore "$plan"
|
|
219
|
+
else
|
|
220
|
+
forge_jev_export
|
|
221
|
+
forge_jev_record "$plan"
|
|
222
|
+
fi
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# forge_jev_active <capability> — succeeds (0) when Jev is enabled for that
|
|
226
|
+
# capability AND a key is configured; fails (non-zero) otherwise, including when
|
|
227
|
+
# the Python package is missing, the config is unreadable, or anything else goes
|
|
228
|
+
# wrong. Silent (no stdout/stderr) and never blocks: it only reads local config
|
|
229
|
+
# through `forge-jev.py status --json`, never the network.
|
|
230
|
+
#
|
|
231
|
+
# The status JSON is fetched at most once per shell (cached in
|
|
232
|
+
# FORGE_JEV_STATUS_JSON/FORGE_JEV_STATUS_FETCHED) so a caller checking several
|
|
233
|
+
# capabilities, or the same capability more than once in one verify_result call,
|
|
234
|
+
# pays for one subprocess instead of one per check.
|
|
235
|
+
FORGE_JEV_STATUS_JSON=""
|
|
236
|
+
FORGE_JEV_STATUS_FETCHED=0
|
|
237
|
+
forge_jev_active() {
|
|
238
|
+
local cap="$1"
|
|
239
|
+
if [ "$FORGE_JEV_STATUS_FETCHED" != 1 ]; then
|
|
240
|
+
FORGE_JEV_STATUS_FETCHED=1
|
|
241
|
+
FORGE_JEV_STATUS_JSON="$(python3 "$SKILL_DIR/scripts/forge-jev.py" status --json 2>/dev/null)" \
|
|
242
|
+
|| FORGE_JEV_STATUS_JSON=""
|
|
243
|
+
fi
|
|
244
|
+
[ -n "$FORGE_JEV_STATUS_JSON" ] || return 1
|
|
245
|
+
printf '%s' "$FORGE_JEV_STATUS_JSON" | python3 -c '
|
|
246
|
+
import json, sys
|
|
247
|
+
cap = sys.argv[1]
|
|
248
|
+
try:
|
|
249
|
+
data = json.load(sys.stdin)
|
|
250
|
+
except ValueError:
|
|
251
|
+
sys.exit(1)
|
|
252
|
+
# capability_ready: some site of this capability has credentials for the provider it
|
|
253
|
+
# uses (a Cloudflare-only setup has no TypeSafe key). Older status output lacks it.
|
|
254
|
+
ready = data.get("capability_ready")
|
|
255
|
+
if isinstance(ready, dict):
|
|
256
|
+
sys.exit(0 if ready.get(cap) else 1)
|
|
257
|
+
sys.exit(0 if data.get("key_present") and data.get("capabilities", {}).get(cap) else 1)
|
|
258
|
+
' "$cap" 2>/dev/null
|
|
259
|
+
}
|