Batches, auto-merge, standalone td, one-line install, self-update, server Ghidra

- Batches: claim every open function of a unit or address range (td claim --unit/--range),
  submit the file once; one compile, per-function routing, merged as a whole. A failing
  function returns the batch; peer verdicts carry over to functions whose compiled code is
  unchanged (code fingerprints). Submissions may not change functions they do not hold.
- Byte-identical functions are approved and merged without human review
  (matchPolicy.autoMergeIdentical); peer-confirmed ones need one click per batch.
- Remote check: td check compiles on the server when there is no local toolchain.
- /api/original: assembly and pseudocode of the original from the server's Ghidra export
  (objdump before an export exists); td disasm/decompile fall back to it.
- td as a standalone executable (Node single executable application; tools/build-td),
  one-line installers (install.ps1 / install.sh) with checksum verification, td connect for
  any MCP client, td mcp --workspace, self-update (td update, hourly check).
- Server-side Ghidra project: built once per version, refreshed after merges, downloadable
  for contributors (td ghidra download); JDK 21 and the pinned Ghidra in deploy/install.sh.
- Agent instructions: work continuously, batches first, peer reviews in between.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNwtXPYqFu3oKaaXwYwNWW
This commit is contained in:
Clank Together 2026-10-18 02:13:09 +02:00
commit 125f5c4e7d
27 changed files with 1751 additions and 241 deletions

View file

@ -142,6 +142,25 @@ CREATE TABLE IF NOT EXISTS func_status (
confirmed INTEGER NOT NULL DEFAULT 0
);
-- A batch is one submission: the files once, and one attempt per task (function) it covers.
-- It is verified with one compile and merged as a whole once every attempt is approved; if one
-- attempt fails, the batch goes back to its author (peer verdicts carry over to unchanged code).
CREATE TABLE IF NOT EXISTS batches (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
base_commit TEXT NOT NULL,
files_json TEXT NOT NULL,
notes TEXT,
agent_meta TEXT,
env_digest TEXT,
state TEXT NOT NULL, -- queued, verifying, open, merging, merged, returned, superseded
result_json TEXT,
merged_commit TEXT,
created_at INTEGER NOT NULL,
finished_at INTEGER
);
CREATE INDEX IF NOT EXISTS batches_state ON batches(state);
-- Peer review of attempts that are not byte-identical: other agents read the original's
-- assembly and pseudocode and the submitted code, and judge whether the logic is the same.
CREATE TABLE IF NOT EXISTS peer_reviews (
@ -171,6 +190,15 @@ CREATE TABLE IF NOT EXISTS unit_deps (
);
CREATE INDEX IF NOT EXISTS unit_deps_header ON unit_deps(header);
-- The original functions as text (Ghidra export): assembly with symbol names, and pseudocode.
CREATE TABLE IF NOT EXISTS original_code (
version_id TEXT NOT NULL,
addr INTEGER NOT NULL,
asm TEXT,
c TEXT,
PRIMARY KEY (version_id, addr)
);
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY,
at INTEGER NOT NULL,
@ -189,6 +217,19 @@ export class Store {
this.migrateUsers();
this.db.exec(SCHEMA);
this.addColumn('func_status', 'confirmed', 'INTEGER NOT NULL DEFAULT 0');
// Fingerprint of main's compiled code per function (submissions may only change their own tasks).
this.addColumn('func_status', 'code_hash', 'TEXT');
this.addColumn('attempts', 'batch_id', 'INTEGER REFERENCES batches(id)');
this.db.exec('CREATE INDEX IF NOT EXISTS attempts_batch ON attempts(batch_id)');
}
/** Submitted files of an attempt (kept once per batch). */
attemptFiles(attempt) {
if (attempt.batch_id) {
const b = this.get('SELECT files_json FROM batches WHERE id = ?', attempt.batch_id);
if (b) return JSON.parse(b.files_json);
}
return JSON.parse(attempt.files_json || '{}');
}
/** Adds a column to a table created by an older schema. */