scripts: forge write-back — close the git→gitea→agentic loop
The forge already DRIVES sessions (briefing → tmux launch, sx-fix-up.sh).
This records what comes BACK, making the forge a true system of record:
- sx-fix-writeback.sh <forge-agent> [kind] [base-ref]: reads new commits on
loops/sx-<slug>, appends a record per commit to writeback.sxsrc (idempotent,
matched by sha), then rebuilds the forge + replays them as agentic-sx
commit!s on agents/<forge-agent> and re-dumps forge.sxdata.
- forge-build.sxsrc: fb-writeback-records / fb-replay-writeback / fb-do-writeback
— each real-git commit becomes an agentic-sx commit whose tree is a small
commit.sx pointer (sha/branch/message/files); real git holds the code, the
forge holds the index, so the CID stays small.
- writeback.sxsrc: the append-only record log (source of truth for what's
been recorded); replayed chronologically so agent branch heads advance right.
Verified live: the sx-gate loop's first real commit (f09368e1, "pin K18
expt-overflow float-promotion") is now recorded as a test-kind agentic-sx
commit on agents/ws-W14 (session log: spawn → finding → writeback), its
commit.sx pointing back at the real-git sha.
Loop closed: forge → tmux (drive) and tmux → real-git → forge (record).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -103,6 +103,27 @@
|
||||
"(starting-point :real-git \"8651cefe\" :tree \"plan+rulings+forge+quick-wins\" :suite \"5762p-274f-baseline\" :next \"W14 test-gate first, then W1 K01 guard-hang\" :rule \"no semantic fix before its pinning test + gate\")")
|
||||
{:message "START — fix work begins from 8651cefe; W14 gate first, then W1/K01"}))
|
||||
|
||||
; --- WRITE-BACK: real-git commits recorded into the forge as agentic-sx commits ---
|
||||
; Closes the loop: the forge drives the tmux session (briefing -> launch) AND
|
||||
; records the work that comes back. fb-writeback-records is overridden by loading
|
||||
; plans/sx-review/writeback.sxsrc (maintained by scripts/sx-fix-writeback.sh);
|
||||
; each record routes a real-git commit onto its forge agent branch. The commit's
|
||||
; tree is a single commit.sx pointer (sha+message+files) — real git holds the code,
|
||||
; the forge holds the index, so the CID stays small.
|
||||
(define fb-writeback-records (list))
|
||||
(define fb-replay-writeback
|
||||
(fn (rec)
|
||||
(agentic/commit! fb-sp (get rec :agent) (get rec :kind)
|
||||
(assoc {} "commit.sx"
|
||||
(str "(git-commit :sha \"" (get rec :sha) "\" :branch \"" (get rec :branch)
|
||||
"\" :message \"" (get rec :message) "\" :files \"" (get rec :files) "\")"))
|
||||
{:message (str "writeback " (get rec :sha) ": " (get rec :message))})))
|
||||
(define fb-writeback-cids (list))
|
||||
(define fb-do-writeback
|
||||
(fn () (begin
|
||||
(set! fb-writeback-cids (map fb-replay-writeback fb-writeback-records))
|
||||
fb-writeback-cids)))
|
||||
|
||||
; --- durable dump: kv + streams of the shared backend, serialized ---
|
||||
(define fb-forge-serialize
|
||||
(fn ()
|
||||
@@ -110,5 +131,6 @@
|
||||
{:review "sx-review" :baseline fb-baseline :done fb-done :start fb-start
|
||||
:agents (agentic/agents fb-sp)
|
||||
:workstream-cids fb-ws-cids
|
||||
:writeback fb-writeback-cids
|
||||
:kv (map (fn (k) (list k ((get fb-db :kv-get) k))) ((get fb-db :kv-keys)))
|
||||
:streams (map (fn (s) (list s ((get fb-db :read) s))) ((get fb-db :streams)))})))
|
||||
|
||||
File diff suppressed because one or more lines are too long
11
plans/sx-review/writeback.sxsrc
Normal file
11
plans/sx-review/writeback.sxsrc
Normal file
@@ -0,0 +1,11 @@
|
||||
; writeback.sxsrc — real-git commits recorded into the forge as agentic-sx commits.
|
||||
; APPENDED by scripts/sx-fix-writeback.sh; OVERRIDES fb-writeback-records defined
|
||||
; (empty) in forge-build.sxsrc, then replayed by (fb-do-writeback). Order is
|
||||
; chronological (oldest first) so each agent branch head advances correctly.
|
||||
;
|
||||
; Each record: {:agent "<forge-agent>" :kind "<agentic-kind>" :sha "<short-sha>"
|
||||
; :branch "<git-branch>" :message "<subject>" :files "<space-sep paths>"}
|
||||
(define fb-writeback-records (list
|
||||
{:agent "ws-W14" :kind "test" :sha "f09368e1" :branch "loops/sx-ws-w14" :message "W14: pin K18 expt-overflow float-promotion (test-only) + bootstrap gate briefing " :files "plans/agent-briefings/sx-gate-loop.md spec/tests/test-gate-pins.sx"}
|
||||
;;; END-RECORDS (scripts/sx-fix-writeback.sh inserts new records before this line)
|
||||
))
|
||||
89
scripts/sx-fix-writeback.sh
Executable file
89
scripts/sx-fix-writeback.sh
Executable file
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env bash
|
||||
# sx-fix-writeback.sh — close the loop: record new real-git commits from a
|
||||
# fix branch back into the rose-ash/sx-review forge as agentic-sx commits.
|
||||
#
|
||||
# The forge already DRIVES the tmux session (briefing -> launch, sx-fix-up.sh).
|
||||
# This records what comes BACK, so the forge is a true system of record: each
|
||||
# commit on loops/sx-<slug> becomes an agentic-sx commit on agents/<forge-agent>,
|
||||
# with a small commit.sx pointer (sha/message/files) — real git holds the code.
|
||||
#
|
||||
# Usage: ./scripts/sx-fix-writeback.sh <forge-agent> [kind] [base-ref]
|
||||
# forge-agent agentic-sx agent, e.g. ws-W14 (branch = loops/sx-<slug of agent>)
|
||||
# kind agentic commit kind: test|refactor|finding|decision (default: test)
|
||||
# base-ref commits are <base-ref>..<branch> (default: architecture)
|
||||
#
|
||||
# Idempotent: commits already recorded in writeback.sxsrc (matched by sha) are skipped.
|
||||
# Re-runs forge-build + replay and re-dumps forge.sxdata.
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
|
||||
AGENT="${1:?usage: sx-fix-writeback.sh <forge-agent> [kind] [base-ref]}"
|
||||
KIND="${2:-test}"
|
||||
BASE="${3:-architecture}"
|
||||
SLUG="$(echo "$AGENT" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | sed 's/-*$//')"
|
||||
BRANCH="loops/sx-${SLUG}"
|
||||
WB="plans/sx-review/writeback.sxsrc"
|
||||
DUMP="plans/sx-review/forge.sxdata"
|
||||
BIN="hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||
|
||||
git show-ref --verify --quiet "refs/heads/$BRANCH" || { echo "no branch $BRANCH"; exit 1; }
|
||||
[ -f "$WB" ] || { echo "missing $WB"; exit 1; }
|
||||
|
||||
# sanitize a commit subject for embedding in an SX string literal
|
||||
sanitize() { echo "$1" | tr -d '"\\' | tr '\n' ' ' | cut -c1-120; }
|
||||
|
||||
added=0
|
||||
# oldest-first so replay advances the branch head in order
|
||||
while IFS=$'\x1f' read -r sha subj; do
|
||||
[ -n "$sha" ] || continue
|
||||
if grep -q ":sha \"$sha\"" "$WB"; then continue; fi
|
||||
files="$(git diff-tree --no-commit-id --name-only -r "$sha" | tr '\n' ' ' | sed 's/ *$//')"
|
||||
msg="$(sanitize "$subj")"
|
||||
rec=" {:agent \"$AGENT\" :kind \"$KIND\" :sha \"$sha\" :branch \"$BRANCH\" :message \"$msg\" :files \"$files\"}"
|
||||
# insert before the END-RECORDS marker (keeps chronological order)
|
||||
awk -v r="$rec" '/;;; END-RECORDS/ && !done {print r; done=1} {print}' "$WB" > "$WB.tmp" && mv "$WB.tmp" "$WB"
|
||||
echo "recorded $sha $msg"
|
||||
added=$((added+1))
|
||||
done < <(git log --reverse --format='%h%x1f%s' "$BASE..$BRANCH")
|
||||
|
||||
if [ "$added" -eq 0 ]; then
|
||||
echo "no new commits on $BRANCH to write back (all recorded)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "rebuilding forge with $added new write-back record(s) + re-dumping $DUMP ..."
|
||||
RAW="$(mktemp)"
|
||||
{
|
||||
echo '(epoch 1)'
|
||||
for f in spec/stdlib.sx lib/r7rs.sx lib/persist/event.sx lib/persist/backend.sx \
|
||||
lib/persist/log.sx lib/persist/kv.sx lib/artdag/dag.sx \
|
||||
lib/datalog/tokenizer.sx lib/datalog/parser.sx lib/datalog/unify.sx \
|
||||
lib/datalog/db.sx lib/datalog/builtins.sx lib/datalog/aggregates.sx \
|
||||
lib/datalog/strata.sx lib/datalog/eval.sx lib/datalog/api.sx lib/datalog/magic.sx \
|
||||
lib/git/object.sx lib/git/ref.sx lib/git/dag.sx lib/git/worktree.sx \
|
||||
lib/git/diff.sx lib/git/merge.sx lib/git/porcelain.sx \
|
||||
lib/relations/schema.sx lib/relations/engine.sx lib/relations/api.sx \
|
||||
lib/relations/explain.sx lib/relations/federation.sx lib/relations/tree.sx \
|
||||
lib/agentic/schema.sx lib/agentic/branch.sx lib/gitea/repo.sx; do
|
||||
echo "(load \"$f\")"
|
||||
done
|
||||
echo '(epoch 2)'
|
||||
echo '(load "plans/sx-review/forge-build.sxsrc")'
|
||||
echo '(load "plans/sx-review/writeback.sxsrc")' # overrides fb-writeback-records
|
||||
echo '(epoch 3)'
|
||||
echo '(eval "(len (fb-do-writeback))")' # replay -> agentic commits
|
||||
echo '(epoch 4)'
|
||||
echo '(eval "(fb-forge-serialize)")'
|
||||
} | timeout 300 "$BIN" > "$RAW" 2>&1
|
||||
|
||||
REPLAYED="$(grep -oE '\(ok 3 [0-9]+\)|\(ok-len 3 [0-9]+\)' "$RAW" | head -1)"
|
||||
python3 - "$RAW" > "$DUMP" <<'PY'
|
||||
import sys, re
|
||||
data = open(sys.argv[1],'rb').read()
|
||||
m = re.search(rb'\(ok-len 4 (\d+)\)\n', data)
|
||||
if not m:
|
||||
sys.stderr.write("FAILED: no dump in forge output\n"); sys.exit(1)
|
||||
n = int(m.group(1)); sys.stdout.buffer.write(data[m.end():m.end()+n])
|
||||
PY
|
||||
echo "replay result: $REPLAYED ; forge.sxdata now $(wc -c < "$DUMP") bytes"
|
||||
rm -f "$RAW"
|
||||
echo "done. review: git diff plans/sx-review/{writeback.sxsrc,forge.sxdata}"
|
||||
Reference in New Issue
Block a user