feat: extract reforge engine into a standalone consumable flake
The forgejo-sandbox / reforge harness, lifted out of the machine config into a host-agnostic, generic engine anyone can consume with Nix. Two layers: - engine (this repo) — nixosModules.reforge stands up the sandbox forge, provisions role accounts + tokens, enforces branch protection, and puts the reforge-* CLI + forgejo-mcp on PATH. Carries no project specifics. - run config — per-project manifest/charter/agenda/issues an adopter fills in; scaffold one with the `reforge` flake template. Portability fixes vs the in-config version: - forgejo-mcp resolved from $REFORGE_MCP_BIN or PATH, never a named host (kills the nixosConfigurations.omni hardcode). - all instance data + paths parameterized via REFORGE_* env, baked into the reforge-scripts wrappers from module options (configDir, agentsDir, refsDir, org, port, tokenOwner, ...). - option namespace neutral (reforge.* not omni.packs.*); settings policies carry no absolute /etc/nixos paths. - role briefs + orchestrator playbook genericized: all project specifics point at the charter; refs corpus optional. Validated: nix flake check (eval) + builds of forgejo-mcp, reforge-scripts, and a module-eval check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
df0fd9a9ba
32 changed files with 2698 additions and 0 deletions
80
scripts/reforge-kickoff.sh
Normal file
80
scripts/reforge-kickoff.sh
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env bash
|
||||
# Reforge lifecycle step 3 (docs/reforge.md): file the run agenda as issues
|
||||
# in the relevant sandbox repos. The issue rows live in
|
||||
# $REFORGE_CONFIG_DIR/issues.tsv (one per line):
|
||||
#
|
||||
# repo|title|body
|
||||
#
|
||||
# ('#' and blank lines are ignored; body is single-line markdown.) Keep
|
||||
# this file as the machine-readable twin of your agenda.md — titles should
|
||||
# carry an agenda item id (e.g. "[A1] ...") for traceability.
|
||||
#
|
||||
# Idempotent: an issue whose exact title already exists in the repo (any
|
||||
# state) is skipped, so re-running converges.
|
||||
#
|
||||
# reforge-kickoff
|
||||
set -euo pipefail
|
||||
|
||||
FORGE_URL=${REFORGE_FORGE_URL:-http://localhost:3030}
|
||||
ORG=${REFORGE_ORG:-sandbox-team}
|
||||
ADMIN_USER=${REFORGE_ADMIN_USER:-sandbox-admin}
|
||||
TOKENS_DIR=${REFORGE_TOKENS_DIR:-/var/lib/forgejo-sandbox/tokens}
|
||||
CONFIG_DIR=${REFORGE_CONFIG_DIR:?set REFORGE_CONFIG_DIR to your run config dir}
|
||||
TOKEN_FILE=${REFORGE_ADMIN_TOKEN_FILE:-$TOKENS_DIR/${ADMIN_USER}.token}
|
||||
|
||||
API="$FORGE_URL/api/v1"
|
||||
TOKEN=$(cat "$TOKEN_FILE")
|
||||
ISSUES_FILE="$CONFIG_DIR/issues.tsv"
|
||||
[ -r "$ISSUES_FILE" ] || { echo "kickoff: no readable issues file at $ISSUES_FILE" >&2; exit 1; }
|
||||
|
||||
WORK=$(mktemp -d)
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
RESP="$WORK/resp"
|
||||
|
||||
api() { # method path [json-body] -> echoes HTTP code, body in $RESP
|
||||
local method=$1 path=$2 data=${3:-}
|
||||
local args=(
|
||||
-sS -o "$RESP" -w '%{http_code}' -X "$method"
|
||||
-H "Authorization: token $TOKEN"
|
||||
-H 'Content-Type: application/json'
|
||||
)
|
||||
if [ -n "$data" ]; then args+=(--data "$data"); fi
|
||||
curl "${args[@]}" "$API$path"
|
||||
}
|
||||
|
||||
filed=0 skipped=0 failed=0
|
||||
mapfile -t ROWS < <(grep -Ev '^[[:space:]]*(#|$)' "$ISSUES_FILE")
|
||||
current_repo=
|
||||
for row in "${ROWS[@]}"; do
|
||||
IFS='|' read -r repo title body <<<"$row"
|
||||
|
||||
# one issue-list fetch per repo (titles for the idempotency check)
|
||||
if [ "$repo" != "$current_repo" ]; then
|
||||
current_repo=$repo
|
||||
if [ "$(api GET "/repos/$ORG/$repo/issues?state=all&type=issues&limit=50")" = 200 ]; then
|
||||
jq -r '.[].title' <"$RESP" >"$WORK/titles" || : >"$WORK/titles"
|
||||
else
|
||||
: >"$WORK/titles"
|
||||
fi
|
||||
fi
|
||||
|
||||
if grep -qxF "$title" "$WORK/titles"; then
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
payload=$(jq -n --arg t "$title" --arg b "$body" '{title: $t, body: $b}')
|
||||
code=$(api POST "/repos/$ORG/$repo/issues" "$payload")
|
||||
if [ "$code" = 201 ]; then
|
||||
echo " filed $repo: $title"
|
||||
filed=$((filed + 1))
|
||||
else
|
||||
echo " FAILED $repo: $title (HTTP $code)" >&2
|
||||
cat "$RESP" >&2
|
||||
failed=$((failed + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "kickoff: $filed filed, $skipped already present, $failed failed."
|
||||
[ "$failed" = 0 ]
|
||||
Loading…
Add table
Add a link
Reference in a new issue