Commit graph

21 commits

Author SHA1 Message Date
327b072545 Dockerfile: use python3 -m pipx (PATH-free invocation) for pipx layer 2026-04-29 14:43:30 -07:00
569691a555 Dockerfile: skip cargo-audit/cargo-deny install (both flaking); keep mypy types-requests fix 2026-04-29 14:42:40 -07:00
44535acdf8 Dockerfile: cargo-deny via prebuilt github release binary (cargo install too flaky) 2026-04-29 14:41:48 -07:00
64415348ce Dockerfile: drop cargo-audit (libgit2-sys broken); cargo-deny supersedes 2026-04-29 14:40:27 -07:00
e268986f87 Dockerfile: cargo-audit/cargo-deny to /usr/local + mypy type stubs
Two recipe-shape gaps caught by the all-SDK lint+audit dogfood:

1. `cargo install --root /caches/cargo cargo-audit cargo-deny` lost its
   binaries at runtime because /caches/cargo is volume-shadowed by the
   host bind mount. Fix: install with `--root /usr/local` so the bins
   land in /usr/local/bin (root-owned, not volume-shadowed). Required
   USER root briefly to write to /usr/local; reverts to crafter after.

2. `mypy --strict` against any project that imports requests/PyYAML/
   setuptools fails with "Library stubs not installed" exit 1 because
   pipx-installed mypy lives in its own venv and doesn't see the
   stubs. Fix: `pipx inject mypy types-requests types-PyYAML
   types-setuptools` so the stubs land in mypy's venv.
2026-04-29 14:20:53 -07:00
510915d3ec Dockerfile: clean final PATH at end (single source of truth)
The agent-generated Dockerfile accumulated PATH via 6+ layered ENV
PATH= statements, and my own GOPATH-fix edit (commit 6cd5990) wrote
a literal-expanded PATH that clobbered the swift/kotlin/gradle/bun/
cargo entries. Result: cargo unreachable from crafter user (caught
by the 14-SDK queue dogfood — exit 127 'Permission denied' on cargo
build).

Fix: a final ENV PATH= line right before the CMD that sets PATH to
a clean, comprehensive list of every toolchain bin. Overrides any
drift above. Includes:
- /home/crafter/.local/bin (pipx tools: ruff, mypy, pytest, pip-audit, uv, semgrep)
- /home/crafter/.composer/vendor/bin (phpstan, phpunit)
- /home/crafter/.local/share/gem/ruby/3.1.0/bin (bundler-audit, rubocop)
- /home/crafter/.bun/bin (bun)
- /home/crafter/go/bin (govulncheck, staticcheck)
- /home/crafter/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin (cargo, rustc, clippy, rustfmt)
- /caches/cargo/bin (cargo install artifacts; volume-mounted)
- /opt/swift/usr/bin (swift)
- /opt/kotlin/bin (kotlinc)
- /opt/gradle/bin (gradle)
- /usr/local/go/bin (go)
- system bins

Once this rebuild lands, the rust recipes can drop the per-recipe
PATH= prefix the workaround used.
2026-04-29 14:09:32 -07:00
3578c9321b workspace: explicit fetch right after bare clone (populates remote-tracking refs that --bare doesn't) 2026-04-29 13:58:28 -07:00
129d630391 workspace: per-project lock + fetch into remote-tracking refs
Two bugs caught by the first real concurrent dogfood (15 SDK build jobs
queued at once for clawdforge):

1. Concurrent fetch race — multiple jobs from same project all called
   `git fetch +refs/heads/*:refs/heads/*` simultaneously. Git refuses
   to fetch into a local branch ref that another worktree has checked
   out, so jobs after the first failed fast with:
       fatal: refusing to fetch into branch 'refs/heads/main' checked
       out at '/workspace/clawdforge/<other-job-id>'

2. Worktree-from-local-branch — `worktree add ... main` reserved the
   local branch ref to the worktree, blocking subsequent fetches even
   when the lock above wasn't held.

Fix:
- Per-project asyncio.Lock around the materialize() body (clone +
  fetch + worktree-add). Different projects still parallelize; same
  project serializes through the cache dir.
- Fetch into remote-tracking refs only:
    +refs/heads/*:refs/remotes/origin/*
  Local refs/heads/* are never written, so no worktree can hold them.
- worktree add uses `--detach origin/<branch>` so each worktree is at
  the remote-tracking ref. Multiple detached worktrees share the same
  remote ref without conflict.

The recipe itself runs outside the lock, so concurrency=4 still
parallelizes the actual build/test work — only the brief
materialize step (clone or fetch + worktree create) serializes.

Tests still 6/6 green on test_runner.py (uses _StubWorkspace, not the
real WorkspaceManager, so it's unaffected — the real exercise is the
live re-queue against fixed code).
2026-04-29 13:56:44 -07:00
61a9814b67 patcher: pin model='opus' on clawdforge sessions; CRAFTING_PATCHER_MODEL env override
Code-work prompts (read CVE/lint context, draft a unified diff that
verifies cleanly against the failing recipe) reward Opus's longer
context + careful reasoning. Cauldron-style high-frequency Sonnet calls
are unaffected — this only changes what crafting-table's patcher asks
clawdforge to use for its drafted-patch sessions.

Pairs with clawdforge dbbead2 which adds the optional `model` field on
POST /sessions and propagates ANTHROPIC_MODEL into the acpx subprocess
env on both create + turn.

Override knob: CRAFTING_PATCHER_MODEL env (e.g. "sonnet" if cost > quality).

Tests: 16/16 patcher tests still green.
2026-04-29 12:37:54 -07:00
a1b3c72c8f Dockerfile: retry go install up to 5x with backoff — DNS to 192.168.0.1:53 flakes intermittently for proxy.golang.org 2026-04-29 18:41:59 +00:00
6cd599079b Dockerfile: set GOPATH=/home/crafter/go BEFORE go install layer (was inheriting /root/go) 2026-04-29 18:06:02 +00:00
6c8b0528ab Dockerfile: pin shfmt URL to /releases/download/v3.10.0/ — /latest/download/ broke when shfmt latest moved past v3.10.0 2026-04-29 17:26:48 +00:00
101c8ec2e7 Dockerfile: SHELL [/bin/bash] before Swift layer — fixes ${var//pattern} bash-ism that dash chokes on 2026-04-29 17:16:43 +00:00
4eab869df0 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
2026-04-29 09:04:48 -07:00
ecb9d76e6d v0.1 wave 2B (step 7): MCP server — stdio JSON-RPC, 8 tools
- mcp/ subpackage: crafting-table-mcp (separate pip install)
- Self-contained requests-based HTTP client (mirrors clawdforge_mcp pattern)
- 8 tools: list_projects / register_project / run_audit / run_build / run_test / get_job / get_findings / draft_patch (stub)
- draft_patch is stubbed — full impl lands in wave 3 / step 9
- tests/: client + tool coverage, 401/404 surfacing
- Tools designed for LLM consumption; descriptions tuned for "when to use" guidance

Spec: memory/spec-crafting-table.md
2026-04-29 08:38:29 -07:00
d467b2f5be v0.1 wave 2A (steps 5+6): per-language parsers + findings extraction
- parsers/ package: rust / python / go / typescript / generic
- parser registry with language+recipe -> fallback resolution
- fingerprint hash (kind+file+line+code) for cross-run dedup
- runner.py post-exec hook: parse log, persist findings, count on job row
  (extraction runs before mark_job_finished so callers polling on terminal
  status see findings_count populated atomically)
- db.insert_finding / list_findings / increment_findings_count DAOs already
  shipped in wave 1; wired here
- GET /jobs/{id}/findings now returns real data (server route already
  shipped; was returning empty list because nothing populated the table)
- tests/test_parsers/: 6 modules + 11 fixtures (rust/python/go/typescript)
- tests/test_runner_findings.py: 3 integration tests
- README: tick steps 2-6, add Findings section

Suite: 108 passing (62 wave-1 + 46 new).
Spec: memory/spec-crafting-table.md
2026-04-29 08:36:16 -07:00
98306ca2e0 v0.1 wave 2C (step 8): email digest scheduler
- digest.py: DigestScheduler with daily 06:00 PT loop
- SmtpConfig env-driven (CRAFTING_SMTP_*)
- notify.on event filter respected per project
- GET /digests/{date} + POST /admin/digest/run-now (dry_run flag)
- migration 006: digest_runs (idempotency via UNIQUE(date, project_name))
- text + HTML email bodies; matches spec's worked example
- Server lifespan integration; gracefully disables if SMTP not configured
- tests/test_digest.py: 8 tests (aggregation / filter / smtp mock / idempotency / endpoint)

Patch-drafted line is a placeholder until wave 3 / step 9 ships.

Spec: memory/spec-crafting-table.md
2026-04-29 08:33:37 -07:00
2e16ec886d wave 1 wiring: Dockerfile API stage + compose API command + README quickstart
- Dockerfile: pip-install requirements.txt and copy crafting_table/ into
  /app, switch CMD from /bin/bash to uvicorn server (port 8810). pip lands
  in /usr/local/bin so the crafter user runs uvicorn without elevation.
- compose.yml: replace smoke.sh entrypoint with the API server command;
  bind 192.168.0.5:8810:8810 (LAN-only); switch named volumes to real
  Lucy appdata paths so /data + /workspace + /caches survive recreate.
  env_file marked optional so a fresh checkout boots without copying
  .env.example.
- README.md: tick steps 1-4 done, document API surface table, add
  curl-based quickstart (mint token → register project → kick off job →
  poll → stream log), and an architecture-notes section covering the
  recipe-immutability snapshot, process-group SIGTERM/SIGKILL escalation,
  WAL+single-writer trade-off, and the recipe-security stance.

Smoke remains runnable on demand:
  docker compose run --rm crafting-table /usr/local/bin/smoke.sh
2026-04-29 08:28:51 -07:00
0ec3a04676 v0.1 wave 1 (steps 2+3+4): SQLite ledger + FastAPI skeleton + async job runner
- db.py: migrations + DAOs for tokens / projects / jobs / findings (SQLite WAL)
- auth.py: SHA-256 bearer hashing + LAN-CIDR allowlist + admin/app token tiers
- models.py: Pydantic shapes (Project, Subproject, Schedule, Notify, Job, CreateJobRequest)
- server.py: FastAPI on port 8810; /healthz, /admin/tokens/*, /projects/*, /jobs, /jobs/{id}, /jobs/{id}/log, /jobs/{id}/findings
- runner.py: bounded asyncio pool, per-job timeout with process-group SIGTERM→SIGKILL escalation, orphaned-job recovery on boot
- workspace.py: bare-clone + worktree materialization, gc
- config.py: env-driven
- 62 tests across db / auth / projects / jobs / runner / e2e — all green

Cross-token project access returns 404 (not 403) — existence-leak guard.
Bearer tokens hashed at rest; admin token bootstrapped on first boot.
Recipe subprocess uses start_new_session=True so killpg targets the
whole process tree on timeout — child processes can't escape SIGKILL.
Pump task guarded with wait_for(2s) + cancel fallback against any
orphan that survives the group kill.

Wave 2 (parsers + findings extraction + MCP + email digest) pending.

Spec: memory/spec-crafting-table.md
2026-04-29 08:17:41 -07:00
4e668a79e1 v0.1 step 1: Dockerfile + per-language toolchain smoke
Monolith image with every toolchain in the spec:
- Python 3.12 + uv/ruff/mypy/pytest/pip-audit/semgrep
- Node 22 LTS + bun
- Go 1.22 + govulncheck/staticcheck
- Rust stable + cargo-audit/cargo-deny
- Ruby 3.x + bundler-audit
- PHP 8.x + composer/phpstan
- JDK 17 + 21 + Maven + Gradle
- .NET 8 SDK
- Swift 5.9.2
- Kotlin 1.9.25
- clang + cmake + valgrind + ASan/UBSan/TSan
- bash + shellcheck

smoke.sh proves each toolchain compiles + runs a hello-world.
compose.yml uses the existing 'sulkta' bridge network.

No API yet (steps 2-3); no MCP yet (step 7); no runner yet (step 4).
This is the foundation.

NOTE: docker build + smoke verification not yet run — sandbox doesn't
have docker. Needs `docker compose build && docker compose up` on Lucy
or any real Docker host before we trust the Dockerfile.

Spec: memory/spec-crafting-table.md
2026-04-29 07:29:53 -07:00
5bd1b1de7e Initial commit 2026-04-29 07:22:04 -07:00