v0.1 wave 3 (steps 9+10): autonomous patch loop + production recipes
Step 9 — autonomous patch loop:
- patcher.py: clawdforge session → unified diff → worktree apply → verify recipe → push branch → open Gitea PR
- migration 007: patch_attempts (UNIQUE per finding+attempt, max 3 attempts)
- runner.py: post-parse hook fires patcher.maybe_draft_for_job when notify.auto_patch=true
- server.py: POST /jobs/{id}/patches, GET /patches, GET /patches/{id}
- digest.py: patch-drafted lines + open-follow-up count via Gitea PR state check
- mcp: crafting_table_draft_patch stub replaced with real implementation
- tests/test_patcher.py + tests/test_patches_api.py: 27 new tests
No auto-merge — patches stop at PR-open. Cobb merges.
Step 10 — production recipes:
- examples/recipes/clawdforge.json: 14 subprojects across all SDKs, audit nightly
- examples/recipes/cauldron.json: single Flask subproject, audit nightly
- examples/recipes/tradecraft.json: nightly audit, auto_patch=false (manual review)
- examples/register-all.sh: bulk-register helper with GITEA_TOKEN substitution
- README "Autonomous patch loop" + "First production recipes" sections
Tests: server 116→143, mcp 65→67. All green.
Spec: memory/spec-crafting-table.md
This commit is contained in:
parent
ecb9d76e6d
commit
4eab869df0
17 changed files with 2752 additions and 78 deletions
19
examples/recipes/cauldron.json
Normal file
19
examples/recipes/cauldron.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "cauldron",
|
||||
"git_url": "http://kayos:REPLACE_WITH_GITEA_TOKEN@192.168.0.5:3001/Sulkta-Coop/cauldron.git",
|
||||
"default_branch": "main",
|
||||
"languages": ["python"],
|
||||
"subprojects": [
|
||||
{
|
||||
"path": ".",
|
||||
"language": "python",
|
||||
"build": "pip install -e .[test]",
|
||||
"test": "pytest tests/",
|
||||
"lint": "ruff check .",
|
||||
"audit": "pip-audit",
|
||||
"timeout_secs": 600
|
||||
}
|
||||
],
|
||||
"schedule": {"audit": "0 2 * * *", "test": "0 */6 * * *"},
|
||||
"notify": {"email": ["cobb@sulkta.com"], "on": ["audit_fail", "test_fail", "cve_found", "patch_drafted"], "auto_patch": true}
|
||||
}
|
||||
24
examples/recipes/clawdforge.json
Normal file
24
examples/recipes/clawdforge.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "clawdforge",
|
||||
"git_url": "http://kayos:REPLACE_WITH_GITEA_TOKEN@192.168.0.5:3001/Sulkta-Coop/clawdforge.git",
|
||||
"default_branch": "main",
|
||||
"languages": ["python", "rust", "go", "ruby", "php", "java", "csharp", "swift", "kotlin", "c", "cpp", "bash", "typescript", "mcp"],
|
||||
"subprojects": [
|
||||
{"path": "clients/python", "language": "python", "build": "pip install -e .[test]", "test": "pytest tests/", "lint": "ruff check . && mypy --strict src/", "audit": "pip-audit", "timeout_secs": 600},
|
||||
{"path": "clients/rust", "language": "rust", "build": "cargo build --release", "test": "cargo test --all", "lint": "cargo clippy --all-targets -- -D warnings && cargo fmt --check", "audit": "cargo audit", "timeout_secs": 1200},
|
||||
{"path": "clients/go", "language": "go", "build": "go build ./...", "test": "go test ./...", "lint": "go vet ./...", "audit": "govulncheck ./...", "timeout_secs": 600},
|
||||
{"path": "clients/typescript", "language": "typescript", "build": "npm install --no-audit", "test": "node --test --import tsx tests/*.test.ts", "lint": "npx tsc --noEmit", "audit": "npm audit", "timeout_secs": 600},
|
||||
{"path": "clients/ruby", "language": "ruby", "build": "bundle install", "test": "bundle exec rake test", "lint": null, "audit": "bundler-audit", "timeout_secs": 600},
|
||||
{"path": "clients/php", "language": "php", "build": "composer install", "test": "vendor/bin/phpunit", "lint": null, "audit": "composer audit", "timeout_secs": 600},
|
||||
{"path": "clients/java", "language": "java", "build": "mvn package -DskipTests", "test": "mvn test", "lint": "mvn javadoc:javadoc -Dquiet=false", "audit": null, "timeout_secs": 1200},
|
||||
{"path": "clients/csharp", "language": "csharp", "build": "dotnet build -c Release", "test": "dotnet test -c Release", "lint": null, "audit": "dotnet list package --vulnerable --include-transitive", "timeout_secs": 900},
|
||||
{"path": "clients/c", "language": "c", "build": "cmake -S . -B build && cmake --build build", "test": "ctest --test-dir build --output-on-failure", "lint": null, "audit": null, "timeout_secs": 900},
|
||||
{"path": "clients/cpp", "language": "cpp", "build": "cmake -S . -B build && cmake --build build", "test": "ctest --test-dir build --output-on-failure", "lint": null, "audit": null, "timeout_secs": 900},
|
||||
{"path": "clients/kotlin", "language": "kotlin", "build": "./gradlew --no-daemon build", "test": "./gradlew --no-daemon test", "lint": null, "audit": null, "timeout_secs": 1800},
|
||||
{"path": "clients/bash", "language": "bash", "build": null, "test": "bash test/run.sh", "lint": "shellcheck cf", "audit": null, "timeout_secs": 300},
|
||||
{"path": "clients/mcp", "language": "python", "build": "pip install -e .", "test": "pytest tests/", "lint": null, "audit": null, "timeout_secs": 300},
|
||||
{"path": ".", "language": "python", "build": "pip install -e .", "test": "pytest tests/", "lint": null, "audit": null, "timeout_secs": 600}
|
||||
],
|
||||
"schedule": {"audit": "0 2 * * *", "test": "0 8 * * *"},
|
||||
"notify": {"email": ["cobb@sulkta.com"], "on": ["audit_fail", "test_fail", "cve_found", "patch_drafted"], "auto_patch": true}
|
||||
}
|
||||
19
examples/recipes/tradecraft.json
Normal file
19
examples/recipes/tradecraft.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "tradecraft",
|
||||
"git_url": "http://kayos:REPLACE_WITH_GITEA_TOKEN@192.168.0.5:3001/TradeCraft/tradecraft.git",
|
||||
"default_branch": "main",
|
||||
"languages": ["python"],
|
||||
"subprojects": [
|
||||
{
|
||||
"path": ".",
|
||||
"language": "python",
|
||||
"build": "pip install -e .",
|
||||
"test": "pytest tests/",
|
||||
"lint": "ruff check .",
|
||||
"audit": "pip-audit",
|
||||
"timeout_secs": 900
|
||||
}
|
||||
],
|
||||
"schedule": {"audit": "0 2 * * *"},
|
||||
"notify": {"email": ["cobb@sulkta.com"], "on": ["audit_fail", "cve_found"], "auto_patch": false}
|
||||
}
|
||||
48
examples/register-all.sh
Executable file
48
examples/register-all.sh
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
#!/bin/bash
|
||||
# Register all example recipes against a running crafting-table instance.
|
||||
#
|
||||
# Reads the bearer token from $CRAFTING_TABLE_TOKEN, falling back to
|
||||
# /data/admin-bearer.txt (the path inside the container) if unset. The
|
||||
# admin bearer file is also bind-mounted at
|
||||
# /mnt/user/appdata/crafting-table/data/admin-bearer.txt on the Lucy host
|
||||
# — that's the recommended source on the host side.
|
||||
#
|
||||
# IMPORTANT: the recipe JSON files in recipes/ ship with a placeholder
|
||||
# git_url containing "REPLACE_WITH_GITEA_TOKEN". This script substitutes
|
||||
# $GITEA_TOKEN into each recipe before posting; commit-time the real
|
||||
# token never lives on disk.
|
||||
set -euo pipefail
|
||||
|
||||
BASE_URL=${CRAFTING_TABLE_URL:-http://192.168.0.5:8810}
|
||||
TOKEN=${CRAFTING_TABLE_TOKEN:-$(cat /data/admin-bearer.txt 2>/dev/null || echo "")}
|
||||
GITEA_TOKEN=${GITEA_TOKEN:-}
|
||||
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo "no crafting-table token (set CRAFTING_TABLE_TOKEN or ensure /data/admin-bearer.txt exists)" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$GITEA_TOKEN" ]; then
|
||||
echo "no Gitea token (set GITEA_TOKEN to substitute into recipe git_url)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DIR="$(dirname "$0")/recipes"
|
||||
for recipe in "$DIR"/*.json; do
|
||||
name="$(basename "$recipe" .json)"
|
||||
echo "registering $name from $recipe..."
|
||||
body="$(sed "s|REPLACE_WITH_GITEA_TOKEN|$GITEA_TOKEN|g" "$recipe")"
|
||||
code=$(printf '%s' "$body" | curl -s -o /tmp/register-resp.json \
|
||||
-w "%{http_code}" \
|
||||
-X POST \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-binary @- \
|
||||
"$BASE_URL/projects" || true)
|
||||
if [ "$code" = "200" ]; then
|
||||
echo " ok"
|
||||
elif [ "$code" = "409" ]; then
|
||||
echo " already exists — use PUT /projects/$name to update"
|
||||
else
|
||||
echo " FAILED ($code): $(cat /tmp/register-resp.json 2>/dev/null || echo no-body)"
|
||||
fi
|
||||
done
|
||||
Loading…
Add table
Add a link
Reference in a new issue