Compare commits

..

3 commits

Author SHA1 Message Date
johba
daf08fe62d chore: gardener housekeeping 2026-03-27
- Update AGENTS.md watermarks to current HEAD across all 10 files
- dev/AGENTS.md: document rebase-before-push and _inject_into_session
- review/AGENTS.md: document formal forge review fallback in review-poll
- gardener/pending-actions.json: close #3 (already implemented in PR #2)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 00:05:07 +00:00
9b91c6a5bd Merge pull request 'fix: Docker stack: edge proxy + staging container from bootstrap (#1)' (#2) from fix/issue-1 into main 2026-03-26 21:22:06 +00:00
johba
723167d2f2 fix: Docker stack: edge proxy + staging container from bootstrap (#1)
- Add edge (Caddy) service to docker-compose.yml as reverse proxy for
  Forgejo (/forgejo/*), Woodpecker (/ci/*), and staging (default)
- Replace placeholder staging service with Caddy-based static file server
- Generate docker/Caddyfile template during disinto init
- Generate default "Nothing shipped yet" staging page in docker/staging-seed/
- Add caddy_data and staging-site volumes
- Staging container seeds default page on first boot; CI overwrites later

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:16:08 +00:00
13 changed files with 126 additions and 50 deletions

View file

@ -1,4 +1,4 @@
<!-- last-reviewed: f32707ba659de278a3af434e3549fb8a8dce9d3a --> <!-- last-reviewed: 9b91c6a5bdd83513ca262dd468ea14db675971c1 -->
# Disinto — Agent Instructions # Disinto — Agent Instructions
## What this repo is ## What this repo is

View file

@ -1,4 +1,4 @@
<!-- last-reviewed: f32707ba659de278a3af434e3549fb8a8dce9d3a --> <!-- last-reviewed: 9b91c6a5bdd83513ca262dd468ea14db675971c1 -->
# Action Agent # Action Agent
**Role**: Execute operational tasks described by action formulas — run scripts, **Role**: Execute operational tasks described by action formulas — run scripts,

View file

@ -260,25 +260,44 @@ services:
networks: networks:
- disinto-net - disinto-net
# Staging deployment slot — activated by Woodpecker staging pipeline (#755). # Edge proxy — reverse proxies Forgejo, Woodpecker, and staging.
# Profile-gated: only starts when explicitly targeted by deploy commands. # IP-only at bootstrap; domain + Let's Encrypt added later via vault.
# Customize image/ports/volumes for your project after init. edge:
staging: image: caddy:alpine
image: alpine:3 restart: unless-stopped
profiles: ["staging"] ports:
security_opt: - "80:80"
- apparmor=unconfined - "443:443"
environment: volumes:
DEPLOY_ENV: staging - ./docker/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
depends_on:
- forgejo
- woodpecker
- staging
networks:
- disinto-net
# Staging container — static file server for project staging artifacts.
# CI pipelines write to the staging-site volume to update content.
# Seeds default page on first boot; CI overwrites volume contents later.
staging:
image: caddy:alpine
restart: unless-stopped
volumes:
- staging-site:/srv/site
- ./docker/staging-seed:/srv/seed:ro
command: ["sh", "-c", "cp -n /srv/seed/* /srv/site/ 2>/dev/null; caddy file-server --root /srv/site --listen :80"]
networks: networks:
- disinto-net - disinto-net
command: ["echo", "staging slot — replace with project image"]
volumes: volumes:
forgejo-data: forgejo-data:
woodpecker-data: woodpecker-data:
agent-data: agent-data:
project-repos: project-repos:
caddy_data:
staging-site:
networks: networks:
disinto-net: disinto-net:
@ -308,6 +327,77 @@ COMPOSEEOF
echo "Created: ${compose_file}" echo "Created: ${compose_file}"
} }
# Generate docker/Caddyfile for the edge proxy.
generate_caddyfile() {
local caddyfile="${FACTORY_ROOT}/docker/Caddyfile"
if [ -f "$caddyfile" ]; then
echo "Caddyfile: ${caddyfile} (already exists, skipping)"
return
fi
cat > "$caddyfile" <<'CADDYEOF'
# Caddyfile — generated by disinto init
# IP-only at bootstrap; domain + Let's Encrypt added later via vault.
:80 {
handle /forgejo/* {
uri strip_prefix /forgejo
reverse_proxy forgejo:3000
}
handle /ci/* {
uri strip_prefix /ci
reverse_proxy woodpecker:8000
}
handle {
reverse_proxy staging:80
}
}
CADDYEOF
echo "Created: ${caddyfile}"
}
# Generate default staging page in the staging-site volume seed directory.
generate_staging_page() {
local staging_dir="${FACTORY_ROOT}/docker/staging-seed"
local index_file="${staging_dir}/index.html"
if [ -f "$index_file" ]; then
echo "Staging: ${index_file} (already exists, skipping)"
return
fi
mkdir -p "$staging_dir"
cat > "$index_file" <<'HTMLEOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Staging</title>
<style>
body { font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #f5f5f5; color: #333; }
.container { text-align: center; }
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
p { color: #666; }
</style>
</head>
<body>
<div class="container">
<h1>Nothing shipped yet</h1>
<p>This staging site will update automatically when CI pushes new artifacts.</p>
</div>
</body>
</html>
HTMLEOF
echo "Created: ${index_file}"
}
# Generate docker/agents/ files if they don't already exist. # Generate docker/agents/ files if they don't already exist.
generate_agent_docker() { generate_agent_docker() {
local docker_dir="${FACTORY_ROOT}/docker/agents" local docker_dir="${FACTORY_ROOT}/docker/agents"
@ -1394,6 +1484,8 @@ p.write_text(text)
forge_port="${forge_port:-3000}" forge_port="${forge_port:-3000}"
generate_compose "$forge_port" generate_compose "$forge_port"
generate_agent_docker generate_agent_docker
generate_caddyfile
generate_staging_page
# Create empty .env so docker compose can parse the agents service # Create empty .env so docker compose can parse the agents service
# env_file reference before setup_forge generates the real tokens (#769) # env_file reference before setup_forge generates the real tokens (#769)
touch "${FACTORY_ROOT}/.env" touch "${FACTORY_ROOT}/.env"

View file

@ -1,4 +1,4 @@
<!-- last-reviewed: f32707ba659de278a3af434e3549fb8a8dce9d3a --> <!-- last-reviewed: 9b91c6a5bdd83513ca262dd468ea14db675971c1 -->
# Dev Agent # Dev Agent
**Role**: Implement issues autonomously — write code, push branches, address **Role**: Implement issues autonomously — write code, push branches, address
@ -14,7 +14,7 @@ in-progress issues are also picked up. The direct-merge scan runs before the loc
check so approved PRs get merged even while a dev-agent session is active. check so approved PRs get merged even while a dev-agent session is active.
**Key files**: **Key files**:
- `dev/dev-poll.sh` — Cron scheduler: finds next ready issue, handles merge/rebase of approved PRs, tracks CI fix attempts. Formula guard skips issues labeled `formula`, `action`, `prediction/dismissed`, or `prediction/unreviewed` (replaced `prediction/backlog` — that label no longer exists) - `dev/dev-poll.sh` — Cron scheduler: finds next ready issue, handles merge/rebase of approved PRs, tracks CI fix attempts. Formula guard skips issues labeled `formula`, `action`, `prediction/dismissed`, or `prediction/unreviewed`. Also injects CI failures and review feedback into active tmux sessions via `_inject_into_session()` (uses `tmux load-buffer` + `paste-buffer` to handle multi-line text safely).
- `dev/dev-agent.sh` — Orchestrator: claims issue, creates worktree + tmux session with interactive `claude`, monitors phase file, injects CI results and review feedback, merges on approval - `dev/dev-agent.sh` — Orchestrator: claims issue, creates worktree + tmux session with interactive `claude`, monitors phase file, injects CI results and review feedback, merges on approval
- `dev/phase-handler.sh` — Phase callback functions: `post_refusal_comment()`, `_on_phase_change()`, `build_phase_protocol_prompt()`. `do_merge()` detects already-merged PRs on HTTP 405 (race with dev-poll's pre-lock scan) and returns success instead of escalating. Sources `lib/mirrors.sh` and calls `mirror_push()` after every successful merge. - `dev/phase-handler.sh` — Phase callback functions: `post_refusal_comment()`, `_on_phase_change()`, `build_phase_protocol_prompt()`. `do_merge()` detects already-merged PRs on HTTP 405 (race with dev-poll's pre-lock scan) and returns success instead of escalating. Sources `lib/mirrors.sh` and calls `mirror_push()` after every successful merge.
- `dev/phase-test.sh` — Integration test for the phase protocol - `dev/phase-test.sh` — Integration test for the phase protocol
@ -33,6 +33,8 @@ check so approved PRs get merged even while a dev-agent session is active.
**Crash recovery**: on `PHASE:crashed` or non-zero exit, the worktree is **preserved** (not destroyed) for debugging. Location logged. Supervisor housekeeping removes stale crashed worktrees older than 24h. **Crash recovery**: on `PHASE:crashed` or non-zero exit, the worktree is **preserved** (not destroyed) for debugging. Location logged. Supervisor housekeeping removes stale crashed worktrees older than 24h.
**Rebase-before-push**: the phase protocol instructs Claude to `git fetch && git rebase` on `$PRIMARY_BRANCH` before every push (initial, CI fix, and review address). This avoids merge conflicts when main has advanced since branch creation. Uses `--force-with-lease` on CI/review fix pushes.
**Lifecycle**: dev-poll.sh (`check_active dev`) → dev-agent.sh → tmux `dev-{project}-{issue}` → phase file **Lifecycle**: dev-poll.sh (`check_active dev`) → dev-agent.sh → tmux `dev-{project}-{issue}` → phase file
drives CI/review loop → merge + `mirror_push()` → close issue. On respawn after drives CI/review loop → merge + `mirror_push()` → close issue. On respawn after
`PHASE:escalate`, the stale phase file is cleared first so the session starts `PHASE:escalate`, the stale phase file is cleared first so the session starts

View file

@ -1,4 +1,4 @@
<!-- last-reviewed: f32707ba659de278a3af434e3549fb8a8dce9d3a --> <!-- last-reviewed: 9b91c6a5bdd83513ca262dd468ea14db675971c1 -->
# Gardener Agent # Gardener Agent
**Role**: Backlog grooming — detect duplicate issues, missing acceptance **Role**: Backlog grooming — detect duplicate issues, missing acceptance

View file

@ -1,32 +1,12 @@
[ [
{ {
"action": "edit_body", "action": "comment",
"issue": 765, "issue": 3,
"body": "Depends on: none\n\n## Goal\n\nThe disinto website becomes a versioned artifact: built by CI, published to Codeberg's generic package registry, deployed to staging automatically. Version visible in footer.\n\n## Files to add/change\n\n### `site/VERSION`\n```\n0.1.0\n```\n\n### `site/build.sh`\n```bash\n#!/bin/bash\nVERSION=$(cat VERSION)\nmkdir -p dist\ncp *.html *.jpg *.webp *.png *.ico *.xml robots.txt dist/\nsed -i \"s|Built from scrap, powered by a single battery.|v${VERSION} · Built from scrap, powered by a single battery.|\" dist/index.html\necho \"$VERSION\" > dist/VERSION\n```\n\n### `site/index.html`\nNo template placeholder needed — `build.sh` does the sed replacement on the existing footer text.\n\n### `.woodpecker/site.yml`\n```yaml\nwhen:\n path: \"site/**\"\n event: push\n branch: main\n\nsteps:\n - name: build\n image: alpine\n commands:\n - cd site && sh build.sh\n - VERSION=$(cat site/VERSION)\n - tar czf site-${VERSION}.tar.gz -C site/dist .\n\n - name: publish\n image: alpine\n commands:\n - apk add curl\n - VERSION=$(cat site/VERSION)\n - >-\n curl -sf --user \"johba:$$FORGE_TOKEN\"\n --upload-file site-${VERSION}.tar.gz\n \"https://codeberg.org/api/packages/johba/generic/disinto-site/${VERSION}/site-${VERSION}.tar.gz\"\n environment:\n FORGE_TOKEN:\n from_secret: forge_token\n\n - name: deploy-staging\n image: alpine\n commands:\n - apk add curl\n - VERSION=$(cat site/VERSION)\n - >-\n curl -sf --user \"johba:$$FORGE_TOKEN\"\n \"https://codeberg.org/api/packages/johba/generic/disinto-site/${VERSION}/site-${VERSION}.tar.gz\"\n -o site.tar.gz\n - rm -rf /srv/staging/*\n - tar xzf site.tar.gz -C /srv/staging/\n environment:\n FORGE_TOKEN:\n from_secret: forge_token\n volumes:\n - /home/debian/staging-site:/srv/staging\n```\n\n## Infra setup (manual, before first run)\n- `mkdir -p /home/debian/staging-site`\n- Add to Caddyfile: `staging.disinto.ai { root * /home/debian/staging-site; file_server }`\n- DNS: `staging.disinto.ai` A record → same IP as `disinto.ai`\n- Reload Caddy: `sudo systemctl reload caddy`\n- Add `forge_token` as Woodpecker repo secret for johba/disinto (if not already set)\n- Add `/home/debian/staging-site` to `WOODPECKER_BACKEND_DOCKER_VOLUMES`\n\n## Verification\n- [ ] Merge PR that touches `site/` → CI runs site pipeline\n- [ ] Package appears at `codeberg.org/johba/-/packages/generic/disinto-site/0.1.0`\n- [ ] `staging.disinto.ai` serves the site with `v0.1.0` in footer\n- [ ] `disinto.ai` (production) unchanged\n\n## Related\n- #764 — docker stack edge proxy + staging (future: this moves inside the stack)\n- #755 — vault-gated production promotion (production deploy comes later)\n\n## Affected files\n- `site/VERSION` — new, holds current version string\n- `site/build.sh` — new, builds dist/ with version injected into footer\n- `.woodpecker/site.yml` — new, CI pipeline for build/publish/deploy-staging" "body": "Closing: this issue was fully implemented by PR #2 (commit `723167d`). All acceptance criteria are met:\n- `edge` (Caddy) service added to docker-compose\n- `staging` Caddy static file server configured\n- `docker/Caddyfile` template generated by `disinto init`\n- Default \"Nothing shipped yet\" staging page in `docker/staging-seed/`\n\nThe dev-agent sessions recorded here crashed after the implementation was already merged into `main`. Closing as implemented."
}, },
{ {
"action": "edit_body", "action": "close",
"issue": 764, "issue": 3,
"body": "Depends on: none (builds on existing docker-compose generation in `bin/disinto`)\n\n## Design\n\n`disinto init` + `disinto up` starts two additional containers as base factory infrastructure:\n\n### Edge proxy (Caddy)\n- Reverse proxies to Forgejo and Woodpecker\n- Serves staging site\n- Runs on ports 80/443\n- At bootstrap: IP-only, self-signed TLS or HTTP\n- Domain + Let's Encrypt added later via vault resource request\n\n### Staging container (Caddy)\n- Static file server for the project's staging artifacts\n- Starts with a default \"Nothing shipped yet\" page\n- CI pipelines write to a shared volume to update staging content\n- No vault approval needed — staging is the factory's sandbox\n\n### docker-compose addition\n```yaml\nservices:\n edge:\n image: caddy:alpine\n ports:\n - \"80:80\"\n - \"443:443\"\n volumes:\n - ./Caddyfile:/etc/caddy/Caddyfile\n - caddy_data:/data\n depends_on:\n - forgejo\n - woodpecker-server\n - staging\n\n staging:\n image: caddy:alpine\n volumes:\n - staging-site:/srv/site\n # Not exposed directly — edge proxies to it\n\nvolumes:\n caddy_data:\n staging-site:\n```\n\n### Caddyfile (generated by `disinto init`)\n```\n# IP-only at bootstrap, domain added later\n:80 {\n handle /forgejo/* {\n reverse_proxy forgejo:3000\n }\n handle /ci/* {\n reverse_proxy woodpecker-server:8000\n }\n handle {\n reverse_proxy staging:80\n }\n}\n```\n\n### Staging update flow\n1. CI builds artifact (site tarball, etc.)\n2. CI step writes to `staging-site` volume\n3. Staging container serves updated content immediately\n4. No restart needed — Caddy serves files directly\n\n### Domain lifecycle\n- Bootstrap: no domain, edge serves on IP\n- Later: factory files vault resource request for domain\n- Human buys domain, sets DNS\n- Caddyfile updated with domain, Let's Encrypt auto-provisions TLS\n\n## Affected files\n- `bin/disinto` — `generate_compose()` adds edge + staging services\n- New: default staging page (\"Nothing shipped yet\")\n- New: Caddyfile template in `docker/`\n\n## Related\n- #755 — vault-gated deployment promotion (production comes later)\n- #757 — ops repo (domain is a resource requested through vault)\n\n## Acceptance criteria\n- [ ] `disinto init` generates a `docker-compose.yml` that includes `edge` (Caddy) and `staging` containers\n- [ ] Edge proxy routes `/forgejo/*` → Forgejo, `/ci/*` → Woodpecker, default → staging container\n- [ ] Staging container serves a default \"Nothing shipped yet\" page on first boot\n- [ ] `docker/` directory contains a Caddyfile template generated by `disinto init`\n- [ ] `disinto up` starts all containers including edge and staging without manual steps" "reason": "already implemented in PR #2 (commit 723167d)"
},
{
"action": "edit_body",
"issue": 761,
"body": "Depends on: #747\n\n## Design\n\nEach agent account on the bundled Forgejo gets a `.profile` repo. This repo holds the agent's formula (copied from disinto at creation time) and its journal.\n\n### Structure\n```\n{agent-bot}/.profile/\n├── formula.toml # snapshot of the formula at agent creation time\n├── journal/ # daily logs of what the agent did\n│ ├── 2026-03-26.md\n│ └── ...\n└── knowledge/ # learned patterns, best-practices (optional, agent can evolve)\n```\n\n### Lifecycle\n1. **Create agent** — `disinto init` or `disinto spawn-agent` creates Forgejo account + `.profile` repo\n2. **Copy formula** — current `formulas/{role}.toml` from disinto repo is copied to `.profile/formula.toml`\n3. **Agent reads its own formula** — at session start, agent reads from its `.profile`, not from the disinto repo\n4. **Agent writes journal** — daily entries pushed to `.profile/journal/`\n5. **Agent can evolve knowledge** — best-practices, heuristics, patterns written to `.profile/knowledge/`\n\n### What this enables\n\n**A/B testing formulas:** Create two agents from different formula versions, run both against the same backlog, compare results (cycle time, CI pass rate, review rejection rate).\n\n**Rollback:** New formula worse? Kill agent, spawn from older formula version.\n\n**Audit:** What formula was this agent running when it produced that PR? Check its `.profile` at that git commit.\n\n**Drift tracking:** Diff what an agent learned (`.profile/knowledge/`) vs what it started with. Measure formula evolution over time.\n\n**Portability:** Move agent to different box — `git clone` its `.profile`.\n\n### Disinto repo becomes the template\n\n```\ndisinto repo:\n formulas/dev-agent.toml ← canonical template, evolves\n formulas/review-agent.toml\n formulas/planner.toml\n ...\n\nRunning agents:\n dev-bot-v2/.profile/formula.toml ← snapshot from formulas/dev-agent.toml@v2\n dev-bot-v3/.profile/formula.toml ← snapshot from formulas/dev-agent.toml@v3\n review-bot/.profile/formula.toml ← snapshot from formulas/review-agent.toml\n```\n\nThe formula in the disinto repo is the template. The `.profile` copy is the instance. They can diverge — that's a feature, not a bug.\n\n## Affected files\n- `bin/disinto` — agent creation copies formula to .profile\n- Agent session scripts — read formula from .profile instead of local formulas/ dir\n- Planner/supervisor — can read other agents' journals from their .profile repos\n\n## Related\n- #747 — per-agent Forgejo accounts (prerequisite)\n- #757 — ops repo (shared concerns stay there: vault, portfolio, resources)\n\n## Acceptance criteria\n- [ ] `disinto spawn-agent` (or `disinto init`) creates a Forgejo account + `.profile` repo for each agent bot\n- [ ] Current `formulas/{role}.toml` is copied to `.profile/formula.toml` at agent creation time\n- [ ] Agent session script reads its formula from `.profile/formula.toml`, not from the repo's `formulas/` directory\n- [ ] Agent writes daily journal entries to `.profile/journal/YYYY-MM-DD.md`"
},
{
"action": "edit_body",
"issue": 742,
"body": "## Problem\n\n`gardener/recipes/*.toml` (4 files: cascade-rebase, chicken-egg-ci, flaky-test, shellcheck-violations) are an older pattern predating `formulas/*.toml`. Two systems for the same thing.\n\n## Fix\n\nMigrate any unique content from recipes to the gardener formula or to new formulas. Delete the recipes directory.\n\n## Affected files\n- `gardener/recipes/*.toml` — delete after migration\n- `formulas/run-gardener.toml` — absorb relevant content\n- Gardener scripts that reference recipes/\n\n## Acceptance criteria\n- [ ] Contents of `gardener/recipes/*.toml` are diff'd against `formulas/run-gardener.toml` — any unique content is migrated\n- [ ] `gardener/recipes/` directory is deleted\n- [ ] No scripts in `gardener/` reference the `recipes/` path after migration\n- [ ] ShellCheck passes on all modified scripts"
},
{
"action": "add_label",
"issue": 742,
"label": "backlog"
},
{
"action": "add_label",
"issue": 741,
"label": "backlog"
} }
] ]

View file

@ -1,4 +1,4 @@
<!-- last-reviewed: f32707ba659de278a3af434e3549fb8a8dce9d3a --> <!-- last-reviewed: 9b91c6a5bdd83513ca262dd468ea14db675971c1 -->
# Shared Helpers (`lib/`) # Shared Helpers (`lib/`)
All agents source `lib/env.sh` as their first action. Additional helpers are All agents source `lib/env.sh` as their first action. Additional helpers are

View file

@ -1,4 +1,4 @@
<!-- last-reviewed: f32707ba659de278a3af434e3549fb8a8dce9d3a --> <!-- last-reviewed: 9b91c6a5bdd83513ca262dd468ea14db675971c1 -->
# Planner Agent # Planner Agent
**Role**: Strategic planning using a Prerequisite Tree (Theory of Constraints), **Role**: Strategic planning using a Prerequisite Tree (Theory of Constraints),

View file

@ -1,4 +1,4 @@
<!-- last-reviewed: f32707ba659de278a3af434e3549fb8a8dce9d3a --> <!-- last-reviewed: 9b91c6a5bdd83513ca262dd468ea14db675971c1 -->
# Predictor Agent # Predictor Agent
**Role**: Abstract adversary (the "goblin"). Runs a 2-step formula **Role**: Abstract adversary (the "goblin"). Runs a 2-step formula

View file

@ -1,4 +1,4 @@
<!-- last-reviewed: f32707ba659de278a3af434e3549fb8a8dce9d3a --> <!-- last-reviewed: 9b91c6a5bdd83513ca262dd468ea14db675971c1 -->
# Review Agent # Review Agent
**Role**: AI-powered PR review — post structured findings and formal **Role**: AI-powered PR review — post structured findings and formal
@ -9,7 +9,7 @@ whose CI has passed and that lack a review for the current HEAD SHA, then
spawns `review-pr.sh <pr-number>`. spawns `review-pr.sh <pr-number>`.
**Key files**: **Key files**:
- `review/review-poll.sh` — Cron scheduler: finds unreviewed PRs with passing CI. Sources `lib/guard.sh` and calls `check_active reviewer` — skips if `$FACTORY_ROOT/state/.reviewer-active` is absent. - `review/review-poll.sh` — Cron scheduler: finds unreviewed PRs with passing CI. Sources `lib/guard.sh` and calls `check_active reviewer` — skips if `$FACTORY_ROOT/state/.reviewer-active` is absent. When injecting review into a dev session, first looks for a bot comment containing `<!-- reviewed: SHA -->`, then falls back to formal Forgejo PR reviews (state `APPROVED` or `REQUEST_CHANGES`) — ensures the dev-agent receives the verdict even when bot comments are absent.
- `review/review-pr.sh` — Creates/reuses a tmux session (`review-{project}-{pr}`), injects PR diff, waits for Claude to write structured JSON output, posts markdown review + formal forge review, auto-creates follow-up issues for pre-existing tech debt. Before starting the session, runs `lib/build-graph.py --changed-files <PR files>` and appends the JSON structural analysis (affected objectives, orphaned prerequisites, thin evidence) to the review prompt. Graph failures are non-fatal — review proceeds without it. - `review/review-pr.sh` — Creates/reuses a tmux session (`review-{project}-{pr}`), injects PR diff, waits for Claude to write structured JSON output, posts markdown review + formal forge review, auto-creates follow-up issues for pre-existing tech debt. Before starting the session, runs `lib/build-graph.py --changed-files <PR files>` and appends the JSON structural analysis (affected objectives, orphaned prerequisites, thin evidence) to the review prompt. Graph failures are non-fatal — review proceeds without it.
**Environment variables consumed**: **Environment variables consumed**:

View file

@ -1,4 +1,4 @@
<!-- last-reviewed: f32707ba659de278a3af434e3549fb8a8dce9d3a --> <!-- last-reviewed: 9b91c6a5bdd83513ca262dd468ea14db675971c1 -->
# Supervisor Agent # Supervisor Agent
**Role**: Health monitoring and auto-remediation, executed as a formula-driven **Role**: Health monitoring and auto-remediation, executed as a formula-driven

View file

@ -26,7 +26,9 @@ pass() { printf 'PASS: %s\n' "$*"; }
cleanup() { cleanup() {
rm -rf "$MOCK_BIN" "$MOCK_STATE" /tmp/smoke-test-repo \ rm -rf "$MOCK_BIN" "$MOCK_STATE" /tmp/smoke-test-repo \
"${FACTORY_ROOT}/projects/smoke-repo.toml" \ "${FACTORY_ROOT}/projects/smoke-repo.toml" \
"${FACTORY_ROOT}/docker-compose.yml" "${FACTORY_ROOT}/docker-compose.yml" \
"${FACTORY_ROOT}/docker/Caddyfile" \
"${FACTORY_ROOT}/docker/staging-seed"
# Restore .env only if we created the backup # Restore .env only if we created the backup
if [ -f "${FACTORY_ROOT}/.env.smoke-backup" ]; then if [ -f "${FACTORY_ROOT}/.env.smoke-backup" ]; then
mv "${FACTORY_ROOT}/.env.smoke-backup" "${FACTORY_ROOT}/.env" mv "${FACTORY_ROOT}/.env.smoke-backup" "${FACTORY_ROOT}/.env"

View file

@ -1,4 +1,4 @@
<!-- last-reviewed: f32707ba659de278a3af434e3549fb8a8dce9d3a --> <!-- last-reviewed: 9b91c6a5bdd83513ca262dd468ea14db675971c1 -->
# Vault Agent # Vault Agent
**Role**: Three-pipeline gate — action safety classification, resource procurement, and human-action drafting. **Role**: Three-pipeline gate — action safety classification, resource procurement, and human-action drafting.