@alizain/doublecheck 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -18
- package/dist/cli.mjs +102 -40
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,23 +4,56 @@ Runs self-authored LLM code-inspectors ("checks") against a project — one
|
|
|
4
4
|
sandboxed agent per check, in parallel — and writes one markdown report per
|
|
5
5
|
check.
|
|
6
6
|
|
|
7
|
-
**A check is a markdown file:**
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
**A check is a markdown file:** a timeless standard — no frontmatter, no
|
|
8
|
+
schema; the body is the inspector's instructions. Checks come from one or
|
|
9
|
+
more `--checks-dir` directories (default: `$TARGET/.agents/checks`), so a
|
|
10
|
+
shared check set can serve a whole folder of repos. Everything run-specific —
|
|
11
|
+
the intent of the work under review, known nuances, sanctioned exceptions,
|
|
12
|
+
scope ("these changed files vs main") — arrives per run via `--context`; the
|
|
10
13
|
harness computes no diffs and knows nothing about git. **A report is a
|
|
11
|
-
markdown file:** whatever the agent writes, no imposed structure
|
|
14
|
+
markdown file:** whatever the agent writes, no imposed structure — and the
|
|
15
|
+
operator's agent reads every line of it, which the prompt tells the inspector.
|
|
16
|
+
|
|
17
|
+
## Why this exists
|
|
18
|
+
|
|
19
|
+
Linters and type checkers catch what can be pattern-matched. The defects that
|
|
20
|
+
survive them are judgment calls: a silent fallback that swallows a config
|
|
21
|
+
error, an abstraction at the wrong layer, a "for now" that will outlive its
|
|
22
|
+
author. doublecheck encodes *your* judgment about those — each check is a
|
|
23
|
+
standard you actually hold, written as prose instructions to an inspector
|
|
24
|
+
agent that can read the whole tree and reason about it.
|
|
25
|
+
|
|
26
|
+
The division of labor is deliberate: everything that requires judgment lives
|
|
27
|
+
in the check body (what to flag, what to leave alone, where to look);
|
|
28
|
+
everything mechanical lives in the harness (sandboxing, parallelism, report
|
|
29
|
+
collection). The harness knows nothing about git, diffs, or languages, so it
|
|
30
|
+
never needs to change when your standards do.
|
|
31
|
+
|
|
32
|
+
## The loop
|
|
33
|
+
|
|
34
|
+
1. **`doublecheck mine`** distills your Claude Code history into durable
|
|
35
|
+
preference observations (`~/.doublecheck/catalog`) — evidence of standards
|
|
36
|
+
you have actually enforced in past conversations.
|
|
37
|
+
2. **A human and/or agent authors checks** from those observations. This step
|
|
38
|
+
is deliberately unproductized: going from "observed preference" to
|
|
39
|
+
"runnable standard" is judgment work.
|
|
40
|
+
3. **`doublecheck check`** runs every check against a project, one sandboxed
|
|
41
|
+
agent per check, and writes one report per check. Read the reports, fix
|
|
42
|
+
what is real, tighten any check that cried wolf.
|
|
12
43
|
|
|
13
44
|
## How it works
|
|
14
45
|
|
|
15
|
-
Per check: a scratch workdir gets `prompt.txt` (environment preamble +
|
|
16
|
-
body + report contract). A [microsandbox](https://github.com/superradcompany/microsandbox)
|
|
46
|
+
Per check: a scratch workdir gets `prompt.txt` (environment preamble +
|
|
47
|
+
operator run context, when given + check body + report contract). A [microsandbox](https://github.com/superradcompany/microsandbox)
|
|
17
48
|
microVM boots from a locally built image with the project bind-mounted
|
|
18
49
|
**read-only at its real host path** and the scratch dir mounted rw as the
|
|
19
50
|
guest cwd. `claude -p` runs inside with the full inspector toolkit (Task,
|
|
20
51
|
Bash, Read, Write, Edit, Glob, Grep, WebSearch, WebFetch) and no permission
|
|
21
52
|
gates — the microVM is the safety boundary, the ro mount is the "don't touch
|
|
22
53
|
my repo" guarantee. The agent writes `report.md`; the host copies it to
|
|
23
|
-
`$OUTPUT/<run-timestamp>/<check>.md
|
|
54
|
+
`$OUTPUT/<run-timestamp>/<check>.md` (with `--save-jsonl`, alongside
|
|
55
|
+
`<check>.stream.jsonl` — the inspector's raw stream, kept so a run can be
|
|
56
|
+
audited after the guest is gone).
|
|
24
57
|
|
|
25
58
|
The project is never copied — every guest shares the live host directory
|
|
26
59
|
(dirty files included) through the ro mount, so per-check cost is one temp dir
|
|
@@ -47,11 +80,14 @@ repo (needs a running Docker daemon; rebuild to pick up a newer claude CLI):
|
|
|
47
80
|
|
|
48
81
|
```bash
|
|
49
82
|
CLAUDE_CODE_OAUTH_TOKEN=... doublecheck check \
|
|
50
|
-
--
|
|
83
|
+
--target DIR # tree under inspection, mounted read-only; default: cwd
|
|
84
|
+
--checks-dir DIR # repeatable; default: $TARGET/.agents/checks
|
|
85
|
+
--context FILE # run brief: intent, nuances, sanctioned exceptions, scope
|
|
51
86
|
--model MODEL # default: haiku
|
|
52
87
|
--parallel N # default: 4 concurrent guests
|
|
53
|
-
--output DIR # default: $
|
|
54
|
-
--check NAME # repeatable; default: every check in
|
|
88
|
+
--output DIR # default: $TARGET/.doublecheck
|
|
89
|
+
--check NAME # repeatable; default: every check in the checks dirs
|
|
90
|
+
--save-jsonl # persist each inspector's raw stream beside its report
|
|
55
91
|
```
|
|
56
92
|
|
|
57
93
|
The token is required and injected into each guest; where it comes from is
|
|
@@ -83,10 +119,35 @@ two legitimate defaults that must not be flagged) for exercising the tool
|
|
|
83
119
|
end-to-end:
|
|
84
120
|
|
|
85
121
|
```bash
|
|
86
|
-
CLAUDE_CODE_OAUTH_TOKEN=... doublecheck check --
|
|
122
|
+
CLAUDE_CODE_OAUTH_TOKEN=... doublecheck check --target fixtures/planted --model haiku
|
|
87
123
|
```
|
|
88
124
|
|
|
89
|
-
##
|
|
125
|
+
## What the inspector sees
|
|
126
|
+
|
|
127
|
+
The facts of the agent's world, so you can decide how to author checks:
|
|
128
|
+
|
|
129
|
+
- **The agent** is one headless `claude -p` per check (model from `--model`,
|
|
130
|
+
default haiku), running inside its own microVM with
|
|
131
|
+
`--dangerously-skip-permissions` — no permission gates; the VM is the
|
|
132
|
+
boundary. Its toolkit: Task (it can spawn subagents), Bash, Read, Write,
|
|
133
|
+
Edit, Glob, Grep, WebSearch, WebFetch. Check agents have unrestricted
|
|
134
|
+
internet egress.
|
|
135
|
+
- **Its only input is the prompt**: a short environment preamble (verbatim in
|
|
136
|
+
`src/contract.ts`) + the operator's run context, when `--context` was given
|
|
137
|
+
+ the check body + the report contract. No session, no conversation history
|
|
138
|
+
— the check body is the standard; the run context is everything about this
|
|
139
|
+
particular run (intent, sanctioned exceptions, scope). The report contract
|
|
140
|
+
also tells the inspector that every line of its report is read in full,
|
|
141
|
+
findings must be verified, and "no findings" is a perfectly good report.
|
|
142
|
+
- **The filesystem**: the project is bind-mounted read-only at its real host
|
|
143
|
+
path — the live working tree, dirty files and `.git` included, so `git
|
|
144
|
+
log`/`git diff`/`git blame` and `rg` work against the real repo; writes to
|
|
145
|
+
it fail. The guest image also has node 24, curl, and wget. The cwd is a
|
|
146
|
+
writable scratch dir private to the check; nothing in it survives except
|
|
147
|
+
`report.md`.
|
|
148
|
+
- **The output**: the prompt tells the agent its chat reply is discarded and
|
|
149
|
+
only `./report.md` is read back. The harness imposes no structure on the
|
|
150
|
+
report — it contains whatever the check body asks for.
|
|
90
151
|
|
|
91
152
|
```bash
|
|
92
153
|
pnpm doublecheck # run the CLI from source (tsx)
|
|
@@ -95,12 +156,47 @@ pnpm typecheck # tsc --noEmit
|
|
|
95
156
|
pnpm check # biome
|
|
96
157
|
```
|
|
97
158
|
|
|
98
|
-
Design
|
|
159
|
+
Design records: `docs/2026-07-05-doublecheck-design.md`, `docs/2026-07-05-run-context-and-decomposed-flags-design.md`. For how a driving agent should use this tool — above all, what a good `--context` brief contains — see `SKILL.md`.
|
|
99
160
|
|
|
100
161
|
## Releasing
|
|
101
162
|
|
|
102
|
-
Manual, via the `release` GitHub Actions workflow
|
|
103
|
-
Conventional Commits
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
163
|
+
Manual, via the `release` GitHub Actions workflow — semantic-release over
|
|
164
|
+
Conventional Commits, ported from pggit. Nothing releases as a side effect of
|
|
165
|
+
pushing to main.
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
gh workflow run release -f dry_run=true # preview next version + notes, publish nothing
|
|
169
|
+
gh workflow run release -f dry_run=false # ship: npm publish + GitHub Release + tag
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
- **Only `feat:` / `fix:` / `BREAKING CHANGE:` commits trigger a release** and
|
|
173
|
+
decide the bump (minor / patch / major). Other commit styles ride along
|
|
174
|
+
unreleased — use `docs:`/`chore:`/plain messages for work that shouldn't ship
|
|
175
|
+
a version.
|
|
176
|
+
- `version` in package.json stays `0.0.0-development` forever — semantic-release
|
|
177
|
+
derives the real version from git tags. Never hand-bump it.
|
|
178
|
+
- The pre-publish gate is biome + tsc + **unit tests only** (`test:unit`
|
|
179
|
+
excludes `*.integration.test.ts`, which boots real microsandbox guests CI
|
|
180
|
+
doesn't have) + tsdown build. Run the full `pnpm test` locally before
|
|
181
|
+
releasing.
|
|
182
|
+
- Needs the `NPM_TOKEN` repo secret: a token that can publish
|
|
183
|
+
`@alizain/doublecheck` without an OTP prompt — classic "Automation" type, or
|
|
184
|
+
a granular token with read/write package access (and 2FA bypass if the
|
|
185
|
+
account requires 2FA for writes).
|
|
186
|
+
|
|
187
|
+
Learned the hard way on the first release (2026-07-05):
|
|
188
|
+
|
|
189
|
+
- **A failed `npm publish` leaves a stale tag.** semantic-release pushes
|
|
190
|
+
`vX.Y.Z` *before* publishing to npm. If the publish step fails, delete the
|
|
191
|
+
tag (`git push origin :refs/tags/vX.Y.Z`) before retrying — otherwise the
|
|
192
|
+
retry sees the tag as the last release, finds no new conventional commits,
|
|
193
|
+
and releases nothing.
|
|
194
|
+
- **The package is scoped because npm forbids unscoped `doublecheck`.** The
|
|
195
|
+
name-similarity rule (`DoubleCheck` and `double-check` exist) rejects it with
|
|
196
|
+
a 403 at publish time only — registry lookups 404 as if the name were free.
|
|
197
|
+
The installed bin is still `doublecheck`; `publishConfig.access: public` is
|
|
198
|
+
what keeps a scoped package from defaulting to private.
|
|
199
|
+
- **npm provenance/OIDC is off on purpose** — the presence of `id-token: write`
|
|
200
|
+
makes npm 11.x prefer trusted publishing and 404 when none is configured
|
|
201
|
+
(npm/cli#8976). Details in the comment block of
|
|
202
|
+
`.github/workflows/release.yml`.
|
package/dist/cli.mjs
CHANGED
|
@@ -2,41 +2,67 @@
|
|
|
2
2
|
import { homedir, tmpdir } from "node:os";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { appendFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
6
6
|
import PQueue from "p-queue";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
|
|
9
9
|
//#region src/contract.ts
|
|
10
10
|
const PROMPT_FILE = "prompt.txt";
|
|
11
11
|
const REPORT_FILE = "report.md";
|
|
12
|
-
function reportContract(activity, deliverable) {
|
|
12
|
+
function reportContract(activity, deliverable, workdir) {
|
|
13
13
|
return `## Report — the only output that counts
|
|
14
14
|
|
|
15
|
-
Your final chat reply is discarded. The only thing read back is the file
|
|
15
|
+
Your final chat reply is discarded. The only thing read back is the file \`${workdir}/${REPORT_FILE}\` — if it does not exist when you exit, this run FAILS. Always use that absolute path: if you \`cd\` elsewhere (into the inspected tree, say), a relative \`./${REPORT_FILE}\` lands in the wrong place, or in a read-only mount where the write fails.
|
|
16
16
|
|
|
17
|
-
So: create
|
|
17
|
+
So: create \`${workdir}/${REPORT_FILE}\` with the Write tool BEFORE you start ${activity} (a title line is enough), and update it as you work. The final state of the file when you finish is ${deliverable}.`;
|
|
18
18
|
}
|
|
19
|
-
function
|
|
19
|
+
function firstActionLine(workdir) {
|
|
20
|
+
return `Your FIRST action, before anything else: create \`${workdir}/${REPORT_FILE}\` with the Write tool (a title line is enough). The full report contract is at the end of this prompt.`;
|
|
21
|
+
}
|
|
22
|
+
function composePrompt(opts) {
|
|
23
|
+
const contextSection = opts.runContext ? `## Run context (from the operator)
|
|
24
|
+
|
|
25
|
+
The operator's brief for this run: the intent behind the work under review, known nuances, sanctioned exceptions, and possibly a Scope naming exactly what is under review. Judge requestedness against it — what the brief explicitly sanctions is not a finding. If it names a Scope, the Scope defines the change under review: findings must concern that change, and the rest of the tree is reference for judgment — with one exception. For concepts the scoped change retires or renames, surviving references anywhere in the tree are reportable findings: the Scope bounds what counts as the change, not where survivors may hide.
|
|
26
|
+
|
|
27
|
+
${opts.runContext}
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
` : "";
|
|
20
32
|
return `## Environment
|
|
21
33
|
|
|
22
34
|
You are a code inspector running inside a sandboxed microVM with full permissions and unrestricted internet access.
|
|
23
35
|
|
|
24
|
-
- The
|
|
36
|
+
- The code under inspection is mounted read-only at \`${opts.target}\`. You cannot modify it — inspect, don't fix.
|
|
25
37
|
- \`git\` and \`rg\` are installed.
|
|
26
|
-
- Your current working directory is a writable scratch workspace.
|
|
38
|
+
- Your current working directory is a writable scratch workspace: \`${opts.workdir}\`.
|
|
39
|
+
|
|
40
|
+
${firstActionLine(opts.workdir)}
|
|
41
|
+
|
|
42
|
+
## Inspection discipline
|
|
43
|
+
|
|
44
|
+
Measured on real runs: inspectors that skip these rules produce reports whose verdicts are right but whose evidence is partly fabricated — which is worse than no report.
|
|
45
|
+
|
|
46
|
+
- Before inspecting, enumerate what is under review (the Run context's Scope list if one is given; otherwise your own enumeration of the target) and keep a ledger as you work: **examined** (content actually opened), **name-only**, **not examined**. End your report with that ledger, with a one-line reason for anything not examined.
|
|
47
|
+
- Use verification verbs — "verified", "confirmed", "checked", "read", "diffed", "grepped", "spot-checked" — ONLY for actions you actually performed in this session on content you actually opened. Everything else must be labeled as inference: "assumed identical by generation — not opened."
|
|
48
|
+
- Never truncate output you will base a claim on — no \`| head\` / \`| tail\` on a diff or search you intend to cite. Examine files one at a time instead.
|
|
27
49
|
|
|
28
50
|
---
|
|
29
51
|
|
|
30
|
-
${checkBody}
|
|
52
|
+
${contextSection}${opts.checkBody}
|
|
31
53
|
|
|
32
54
|
---
|
|
33
55
|
|
|
34
|
-
${reportContract("inspecting", "the check's report")}
|
|
56
|
+
${reportContract("inspecting", "the check's report", opts.workdir)}
|
|
57
|
+
|
|
58
|
+
Every single line of your report will be read in full by the operator's agent — nothing is skimmed, so every line costs attention. Verify each finding against the actual code before writing it down. A report that says "no findings" is a perfectly good report; an unverified finding is not.`;
|
|
35
59
|
}
|
|
36
|
-
function composeMinePrompt(digest) {
|
|
60
|
+
function composeMinePrompt(digest, workdir) {
|
|
37
61
|
return `## Environment
|
|
38
62
|
|
|
39
|
-
You are running inside a sandboxed microVM with no network access. The machine's Claude Code transcripts are mounted read-only at their real paths; your current working directory is a writable scratch workspace
|
|
63
|
+
You are running inside a sandboxed microVM with no network access. The machine's Claude Code transcripts are mounted read-only at their real paths; your current working directory is a writable scratch workspace: \`${workdir}\`.
|
|
64
|
+
|
|
65
|
+
${firstActionLine(workdir)}
|
|
40
66
|
|
|
41
67
|
## Task
|
|
42
68
|
|
|
@@ -80,7 +106,7 @@ If the conversation carries no durable preference signal, the report is exactly
|
|
|
80
106
|
No durable preference signal.
|
|
81
107
|
<one sentence: what the conversation was about instead>
|
|
82
108
|
|
|
83
|
-
${reportContract("mining", "the mined observations")}`;
|
|
109
|
+
${reportContract("mining", "the mined observations", workdir)}`;
|
|
84
110
|
}
|
|
85
111
|
|
|
86
112
|
//#endregion
|
|
@@ -215,31 +241,43 @@ async function runAgent(opts) {
|
|
|
215
241
|
|
|
216
242
|
//#endregion
|
|
217
243
|
//#region src/check.ts
|
|
218
|
-
function selectChecks(all, only,
|
|
219
|
-
if (all.length === 0) throw new Error(`no checks (*.md) in ${
|
|
244
|
+
function selectChecks(all, only, where) {
|
|
245
|
+
if (all.length === 0) throw new Error(`no checks (*.md) in ${where}`);
|
|
220
246
|
if (only.length === 0) return all;
|
|
221
247
|
const byName = new Map(all.map((c) => [c.name, c]));
|
|
222
248
|
return only.map((name) => {
|
|
223
249
|
const check = byName.get(name);
|
|
224
250
|
if (!check) {
|
|
225
251
|
const have = all.map((c) => c.name).join(", ");
|
|
226
|
-
throw new Error(`no check named "${name}" in ${
|
|
252
|
+
throw new Error(`no check named "${name}" in ${where} (have: ${have})`);
|
|
227
253
|
}
|
|
228
254
|
return check;
|
|
229
255
|
});
|
|
230
256
|
}
|
|
231
|
-
function discoverChecks(
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
257
|
+
function discoverChecks(dirs, only) {
|
|
258
|
+
const all = [];
|
|
259
|
+
for (const dir of dirs) {
|
|
260
|
+
let entries;
|
|
261
|
+
try {
|
|
262
|
+
entries = readdirSync(dir);
|
|
263
|
+
} catch {
|
|
264
|
+
throw new Error(`no checks directory at ${dir}`);
|
|
265
|
+
}
|
|
266
|
+
for (const f of entries.filter((e) => e.endsWith(".md")).sort()) {
|
|
267
|
+
const name = f.slice(0, -3);
|
|
268
|
+
const clash = all.find((c) => c.name === name);
|
|
269
|
+
if (clash) throw new Error(`check "${name}" exists in both ${clash.dir} and ${dir} — rename one`);
|
|
270
|
+
all.push({
|
|
271
|
+
name,
|
|
272
|
+
body: readFileSync(join(dir, f), "utf-8"),
|
|
273
|
+
dir
|
|
274
|
+
});
|
|
275
|
+
}
|
|
238
276
|
}
|
|
239
|
-
return selectChecks(
|
|
240
|
-
name
|
|
241
|
-
body
|
|
242
|
-
})), only,
|
|
277
|
+
return selectChecks(all.map(({ name, body }) => ({
|
|
278
|
+
name,
|
|
279
|
+
body
|
|
280
|
+
})).sort((a, b) => a.name.localeCompare(b.name)), only, dirs.join(", "));
|
|
243
281
|
}
|
|
244
282
|
function runTimestamp(d) {
|
|
245
283
|
const p = (n) => String(n).padStart(2, "0");
|
|
@@ -248,20 +286,34 @@ function runTimestamp(d) {
|
|
|
248
286
|
function failureReport(reason, partialReport) {
|
|
249
287
|
return `# CHECK FAILED\n\n${reason}\n${partialReport ? `\n---\n\nPartial ${REPORT_FILE} left by the agent:\n\n${partialReport}` : ""}`;
|
|
250
288
|
}
|
|
251
|
-
async function runOneCheck(check, opts, token, outDir) {
|
|
289
|
+
async function runOneCheck(check, opts, token, runContext, outDir) {
|
|
252
290
|
const workdir = mkdtempSync(join(tmpdir(), `doublecheck-${check.name}-`));
|
|
253
|
-
writeFileSync(join(workdir, PROMPT_FILE), composePrompt(
|
|
291
|
+
writeFileSync(join(workdir, PROMPT_FILE), composePrompt({
|
|
292
|
+
checkBody: check.body,
|
|
293
|
+
target: opts.target,
|
|
294
|
+
workdir,
|
|
295
|
+
runContext
|
|
296
|
+
}));
|
|
254
297
|
const spec = claudeAgent({
|
|
255
298
|
token,
|
|
256
299
|
model: opts.model,
|
|
257
300
|
workdir
|
|
258
301
|
});
|
|
302
|
+
const sink = progressSink(check.name);
|
|
303
|
+
let onLine = sink;
|
|
304
|
+
if (opts.saveJsonl) {
|
|
305
|
+
const streamPath = join(outDir, `${check.name}.stream.jsonl`);
|
|
306
|
+
onLine = (kind, line) => {
|
|
307
|
+
if (kind === "stdout") appendFileSync(streamPath, `${line}\n`);
|
|
308
|
+
sink(kind, line);
|
|
309
|
+
};
|
|
310
|
+
}
|
|
259
311
|
const outcome = await runAgent({
|
|
260
|
-
mount: opts.
|
|
312
|
+
mount: opts.target,
|
|
261
313
|
workdir,
|
|
262
314
|
spec,
|
|
263
315
|
network: "all",
|
|
264
|
-
onLine
|
|
316
|
+
onLine
|
|
265
317
|
});
|
|
266
318
|
const reportPath = join(outDir, `${check.name}.md`);
|
|
267
319
|
if (outcome.ok) {
|
|
@@ -276,12 +328,18 @@ async function runOneCheck(check, opts, token, outDir) {
|
|
|
276
328
|
async function runChecks(opts) {
|
|
277
329
|
const token = process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
278
330
|
if (!token) throw new Error("CLAUDE_CODE_OAUTH_TOKEN is required in the environment (it is injected into each check's sandbox)");
|
|
279
|
-
const checks = discoverChecks(opts.
|
|
331
|
+
const checks = discoverChecks(opts.checksDirs, opts.only);
|
|
332
|
+
let runContext = null;
|
|
333
|
+
if (opts.contextFile) try {
|
|
334
|
+
runContext = readFileSync(opts.contextFile, "utf-8");
|
|
335
|
+
} catch (e) {
|
|
336
|
+
throw new Error(`--context file not readable: ${opts.contextFile} (${String(e)})`);
|
|
337
|
+
}
|
|
280
338
|
const outDir = join(opts.output, runTimestamp(/* @__PURE__ */ new Date()));
|
|
281
339
|
mkdirSync(outDir, { recursive: true });
|
|
282
|
-
process.stderr.write(`${checks.length} check(s) against ${opts.
|
|
340
|
+
process.stderr.write(`${checks.length} check(s) against ${opts.target} (model ${opts.model}, parallel ${opts.parallel})\n`);
|
|
283
341
|
const queue = new PQueue({ concurrency: opts.parallel });
|
|
284
|
-
return (await Promise.all(checks.map((check) => queue.add(() => runOneCheck(check, opts, token, outDir))))).every((ok) => ok === true);
|
|
342
|
+
return (await Promise.all(checks.map((check) => queue.add(() => runOneCheck(check, opts, token, runContext, outDir))))).every((ok) => ok === true);
|
|
285
343
|
}
|
|
286
344
|
|
|
287
345
|
//#endregion
|
|
@@ -421,7 +479,7 @@ function dryRunRow({ unit, status }) {
|
|
|
421
479
|
async function mineOneUnit({ unit, status }, opts, token) {
|
|
422
480
|
const tag = unit.session.slice(0, 8);
|
|
423
481
|
const workdir = mkdtempSync(join(tmpdir(), `doublecheck-mine-${tag}-`));
|
|
424
|
-
writeFileSync(join(workdir, PROMPT_FILE), composeMinePrompt(status.digest));
|
|
482
|
+
writeFileSync(join(workdir, PROMPT_FILE), composeMinePrompt(status.digest, workdir));
|
|
425
483
|
const spec = claudeAgent({
|
|
426
484
|
token,
|
|
427
485
|
model: opts.model,
|
|
@@ -481,15 +539,19 @@ function parsePositiveInt(flag) {
|
|
|
481
539
|
return n;
|
|
482
540
|
};
|
|
483
541
|
}
|
|
484
|
-
const
|
|
485
|
-
program
|
|
486
|
-
|
|
542
|
+
const collect = (v, prev) => [...prev, v];
|
|
543
|
+
const program = new Command().name("doublecheck").description("Run self-authored LLM code-inspectors (checks) against a target tree, one sandboxed agent per check");
|
|
544
|
+
program.command("check").option("--target <dir>", "tree under inspection, mounted read-only", process.cwd()).option("--checks-dir <dir>", "checks directory (repeatable; default: $TARGET/.agents/checks)", collect, []).option("--context <file>", "run-context file spliced into every inspector's prompt (intent, nuances, sanctioned exceptions, scope)").option("--model <model>", "model for the inspector agents", "haiku").option("--parallel <n>", "max concurrent checks", parsePositiveInt("--parallel"), 4).option("--output <dir>", "reports root (default: $TARGET/.doublecheck)").option("--check <name>", "run only this check (repeatable)", collect, []).option("--save-jsonl", "persist each inspector's raw stream-json beside its report (audit trail)").action(async (opts) => {
|
|
545
|
+
const target = resolve(opts.target);
|
|
487
546
|
if (!await runChecks({
|
|
488
|
-
|
|
547
|
+
target,
|
|
548
|
+
checksDirs: opts.checksDir.length > 0 ? opts.checksDir.map((d) => resolve(d)) : [join(target, ".agents", "checks")],
|
|
549
|
+
contextFile: opts.context ? resolve(opts.context) : null,
|
|
489
550
|
model: opts.model,
|
|
490
551
|
parallel: opts.parallel,
|
|
491
|
-
output: opts.output ? resolve(opts.output) : join(
|
|
492
|
-
only: opts.check
|
|
552
|
+
output: opts.output ? resolve(opts.output) : join(target, ".doublecheck"),
|
|
553
|
+
only: opts.check,
|
|
554
|
+
saveJsonl: !!opts.saveJsonl
|
|
493
555
|
})) process.exitCode = 1;
|
|
494
556
|
});
|
|
495
557
|
program.command("mine").option("--projects <dir>", "Claude Code transcripts root", join(homedir(), ".claude", "projects")).option("--catalog <dir>", "observation catalog root", join(homedir(), ".doublecheck", "catalog")).option("--model <model>", "model for the mining agents", "opus").option("--parallel <n>", "max concurrent miners", parsePositiveInt("--parallel"), 4).option("--min-turns <n>", "min genuine human turns for a real conversation", parsePositiveInt("--min-turns"), 2).option("--limit <n>", "mine at most N pending units", parsePositiveInt("--limit")).option("--dry-run", "list what would be mined without booting anything").action(async (opts) => {
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/contract.ts","../src/claude.ts","../src/runner.ts","../src/check.ts","../src/transcript.ts","../src/catalog.ts","../src/mine.ts","../src/cli.ts"],"sourcesContent":["// The agent contract: what the harness stages for the agent and what the\n// agent must leave behind. Any adapter (claude today, codex someday) runs\n// under this same contract, which is what keeps the harness CLI-agnostic.\n\n// Staged into the scratch workdir before boot; the rw bind mount delivers it.\nexport const PROMPT_FILE = \"prompt.txt\"\n// The agent writes its findings here (relative to its cwd); the host reads it\n// back after exit. Missing report = failure.\nexport const REPORT_FILE = \"report.md\"\n\n// Weak models reliably skip a report contract stated once at the prompt's\n// tail unless it leads with the stakes and orders the file created first\n// (measured: haiku went from ~50% to 3/3 with this wording).\nfunction reportContract(activity: string, deliverable: string): string {\n\treturn `## Report — the only output that counts\n\nYour final chat reply is discarded. The only thing read back is the file \\`./${REPORT_FILE}\\` in your working directory — if it does not exist when you exit, this run FAILS.\n\nSo: create \\`./${REPORT_FILE}\\` with the Write tool BEFORE you start ${activity} (a title line is enough), and update it as you work. The final state of the file when you finish is ${deliverable}.`\n}\n\n// Environment preamble + check body + report contract. Scoping (diff vs tree)\n// is the check's own prose — the harness knows nothing about git.\nexport function composePrompt(checkBody: string, project: string): string {\n\treturn `## Environment\n\nYou are a code inspector running inside a sandboxed microVM with full permissions and unrestricted internet access.\n\n- The project under inspection is mounted read-only at \\`${project}\\`. You cannot modify it — inspect, don't fix.\n- \\`git\\` and \\`rg\\` are installed.\n- Your current working directory is a writable scratch workspace.\n\n---\n\n${checkBody}\n\n---\n\n${reportContract(\"inspecting\", \"the check's report\")}`\n}\n\n// The mining prompt: digest of one conversation + what counts as a durable\n// preference observation + the block format the catalog accumulates.\nexport function composeMinePrompt(digest: string): string {\n\treturn `## Environment\n\nYou are running inside a sandboxed microVM with no network access. The machine's Claude Code transcripts are mounted read-only at their real paths; your current working directory is a writable scratch workspace.\n\n## Task\n\nYou are mining ONE Claude Code conversation for the operator's durable preferences — how they want engineering done, in any conversation, not what they wanted built in this one.\n\nBelow is the conversation digest: every genuine human turn, numbered, in order. When a turn is a terse correction or interruption (\"no\", \"stop\", \"don't\", \"why did you…\", \"[Request interrupted]\"), recover its context: the \"# source:\" header names the full session transcript — Grep that turn's text in it to find its line, then Read the assistant turns IMMEDIATELY BEFORE it with offset/limit. The preference lives in the contrast between what the assistant did and what the operator demanded. NEVER read a transcript whole — some are tens of MB.\n\n---\n\n${digest}\n\n---\n\n## What counts as an observation (strict)\n\nA durable preference, backed by a verbatim quote from the digest or transcript:\n\n- **kind: code** — how code should be written/structured/shaped (\"no silent fallbacks\", \"reuse the existing helper instead of hand-rolling\").\n- **kind: workflow** — how work should proceed (\"verify before claiming done\", \"ask before making scope decisions\").\n- **kind: style** — how prose/output/docs should read.\n\nExclude task-of-the-moment directives with no durable signal (\"add a tab to home.tsx\", \"kill the dev server\"). Exclude anything you cannot quote. Be conservative: a weak or speculative observation is worse than none.\n\n## Observation format\n\nThe report is observation blocks and NOTHING else — no title, no preamble, no summary or conclusion sections. One block per observation:\n\n## <kebab-case-name>\n- **observation:** <one sentence: the durable preference>\n- **kind:** code | workflow | style\n- **evidence:** \"<verbatim quote>\" — turn <N>\n\nFor **code** observations that an LLM reviewer could check against a diff or file tree (a concrete change could flip PASS↔FAIL), and ONLY for those — never on workflow or style blocks — ALSO add:\n\n- **pass/fail:** PASS when <…>; FAIL when <…>\n- **why-LLM:** <why a deterministic linter cannot enforce this>\n- **scope:** diff-only | needs-tree\n\nIf the conversation carries no durable preference signal, the report is exactly these two lines and nothing more:\n\nNo durable preference signal.\n<one sentence: what the conversation was about instead>\n\n${reportContract(\"mining\", \"the mined observations\")}`\n}\n","import { PROMPT_FILE } from \"./contract.ts\"\nimport type { AgentSpec } from \"./runner.ts\"\n\nconst GUEST_HOME = \"/root\"\n\n// The claude adapter: produce the AgentSpec that runs `claude -p` in the guest.\nexport function claudeAgent(opts: {\n\ttoken: string\n\tmodel: string\n\tworkdir: string\n}): AgentSpec {\n\treturn {\n\t\t// Headless claude -p cannot answer permission prompts, so in \"default\"\n\t\t// mode every tool is auto-denied and the agent produces nothing. The\n\t\t// microVM is the safety boundary here, so bypass the per-tool gate;\n\t\t// IS_SANDBOX=1 is what lets that run as root without claude's refusal.\n\t\tcommand:\n\t\t\t\"claude -p --no-session-persistence \" +\n\t\t\t\"--dangerously-skip-permissions \" +\n\t\t\t// --verbose is required by claude -p with stream-json output; it only\n\t\t\t// widens what lands on stdout, which describeStreamLine already labels.\n\t\t\t\"--output-format stream-json --verbose \" +\n\t\t\t// Not a permission gate — a contract guard. The default headless tool\n\t\t\t// set omits Glob/Grep and includes harness tools (ReportFindings,\n\t\t\t// TaskCreate, …) that hijack the report: haiku \"reports findings\"\n\t\t\t// through them and never writes report.md (verified live). This pins\n\t\t\t// the inspector's full toolkit and nothing else.\n\t\t\t`--tools Task Bash Read Write Edit Glob Grep WebSearch WebFetch < ${PROMPT_FILE}`,\n\t\tenv: {\n\t\t\tHOME: GUEST_HOME,\n\t\t\tIS_SANDBOX: \"1\",\n\t\t\tGIT_OPTIONAL_LOCKS: \"0\",\n\t\t\tANTHROPIC_MODEL: opts.model,\n\t\t\tCLAUDE_CODE_OAUTH_TOKEN: opts.token,\n\t\t},\n\t\t// Minimal trust-accepted entry keyed to the guest cwd so headless claude\n\t\t// skips the trust prompt. Deliberately NOT a copy of any host .claude.json.\n\t\tfiles: [\n\t\t\t{\n\t\t\t\tpath: `${GUEST_HOME}/.claude.json`,\n\t\t\t\tcontent: `${JSON.stringify(\n\t\t\t\t\t{ projects: { [opts.workdir]: { hasTrustDialogAccepted: true } } },\n\t\t\t\t\tnull,\n\t\t\t\t\t\"\\t\",\n\t\t\t\t)}\\n`,\n\t\t\t},\n\t\t],\n\t}\n}\n\ninterface StreamLine {\n\ttype?: string\n\tsubtype?: string\n\tmessage?: { content?: Array<{ type?: string; text?: string; name?: string }> }\n\tduration_ms?: number\n}\n\n// One human-readable label per stream-json stdout line; null for lines that\n// aren't claude's JSON (dropped from the progress stream).\nexport function describeStreamLine(line: string): string | null {\n\tlet obj: StreamLine\n\ttry {\n\t\tobj = JSON.parse(line) as StreamLine\n\t} catch {\n\t\treturn null\n\t}\n\tconst label = (obj.type ?? \"\") + (obj.subtype ? `:${obj.subtype}` : \"\")\n\t// A once-per-second heartbeat with no content — pure noise in the stream.\n\tif (label === \"system:thinking_tokens\") return null\n\tlet detail = \"\"\n\tif (obj.type === \"assistant\") {\n\t\tconst content = obj.message?.content ?? []\n\t\tconst tools = content\n\t\t\t.filter((b) => b.type === \"tool_use\" && b.name)\n\t\t\t.map((b) => b.name)\n\t\tconst chars = content.reduce((n, b) => n + (b.text?.length ?? 0), 0)\n\t\tdetail = tools.length ? ` ${tools.join(\",\")}` : ` ${chars} chars`\n\t} else if (obj.type === \"result\") {\n\t\tdetail = ` ${obj.subtype}, ${((obj.duration_ms ?? 0) / 1000).toFixed(1)}s`\n\t}\n\treturn `[${label}]${detail}`\n}\n\n// The per-unit progress sink both workflow shells hang on runAgent: guest\n// stderr passes through as `[label] ! line`, stdout is claude's stream-json,\n// labelled by describeStreamLine (non-JSON and heartbeat lines dropped).\nexport function progressSink(\n\tlabel: string,\n): (kind: \"stdout\" | \"stderr\", line: string) => void {\n\treturn (kind, line) => {\n\t\tif (kind === \"stderr\") {\n\t\t\tprocess.stderr.write(`[${label}] ! ${line}\\n`)\n\t\t\treturn\n\t\t}\n\t\tconst described = describeStreamLine(line)\n\t\tif (described) process.stderr.write(`[${label}] ${described}\\n`)\n\t}\n}\n","import { existsSync, readFileSync } from \"node:fs\"\nimport { basename, join } from \"node:path\"\nimport type { ExecEvent } from \"microsandbox\"\nimport { REPORT_FILE } from \"./contract.ts\"\n\n// microsandbox exports its fluent builders as value-only consts; recover their\n// instance types via a type-only import (erased at build) so the native module\n// only loads behind the `await import` in runAgent.\ntype MountBuilder = InstanceType<typeof import(\"microsandbox\").MountBuilder>\ntype PatchBuilder = InstanceType<typeof import(\"microsandbox\").PatchBuilder>\ntype ExecOptionsBuilder = InstanceType<typeof import(\"microsandbox\").ExecOptionsBuilder>\n// The SDK types `.network(cb)` as `(b: any) => any`, and we call the JS-shim\n// `.policy(...)`, absent from the native builder's types.\n// biome-ignore lint/suspicious/noExplicitAny: SDK types this builder callback as any\ntype NetworkBuilder = any\n\n// Locally built (Dockerfile.guest): node 24 slim + git/ripgrep/curl/wget + the\n// claude CLI baked in. Side-loaded into the microsandbox cache by\n// ./scripts/build-guest-image.sh — it exists nowhere else, hence pullPolicy\n// \"never\": a cache miss means \"run the build script\", not \"try Docker Hub\".\nconst GUEST_IMAGE = \"doublecheck-guest:latest\"\nconst GUEST_MEMORY_MIB = 2048\n\n// Staged into the guest before boot (e.g. claude's trust-accepted config).\nexport interface GuestFile {\n\tpath: string\n\tcontent: string\n}\n\n// What it takes to run one agent in a booted guest: a bash command executed in\n// the scratch cwd that must leave REPORT_FILE there, the env it needs, and\n// files staged into the guest. Plain data — adapters produce it, this runner\n// consumes it, tests fake it.\nexport interface AgentSpec {\n\tcommand: string\n\tenv: Record<string, string>\n\tfiles: GuestFile[]\n}\n\nexport type AgentOutcome =\n\t| { ok: true; report: string }\n\t| { ok: false; reason: string; partialReport: string | null }\n\n// Pure line buffering: fold a chunk into the carry, emit complete non-blank\n// lines, keep the unterminated tail.\nexport function feedLines(\n\tcarry: string,\n\tchunk: string,\n): { lines: string[]; carry: string } {\n\tconst parts = (carry + chunk).split(\"\\n\")\n\tconst rest = parts.pop() ?? \"\"\n\treturn { lines: parts.filter((l) => l.trim()), carry: rest }\n}\n\n// Pure outcome decision: what the exec left behind → what it means.\nexport function decideOutcome(\n\texitCode: number | null,\n\treport: string | null,\n\tworkdir: string,\n): AgentOutcome {\n\tif (exitCode !== 0) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\treason: `agent process exited ${exitCode}`,\n\t\t\tpartialReport: report,\n\t\t}\n\t}\n\tif (report === null) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\treason: `agent exited 0 but wrote no ${REPORT_FILE} (work dir: ${workdir})`,\n\t\t\tpartialReport: null,\n\t\t}\n\t}\n\treturn { ok: true, report }\n}\n\nexport interface RunAgentOpts {\n\t// Host dir the agent inspects, bind-mounted READ-ONLY at its real host\n\t// path: the project for `check`, the transcripts corpus for `mine`.\n\tmount: string\n\t// Scratch dir with PROMPT_FILE already inside; bind-mounted rw as the guest\n\t// cwd at its identical host path, so the agent's report lands back on the host.\n\tworkdir: string\n\tspec: AgentSpec\n\t// \"all\" for checks (inspectors may research); \"anthropic-only\" for miners:\n\t// the personal corpus is mounted, so the only reachable destination is the\n\t// API the agent already sends its context to. NetworkPolicy.none() is not\n\t// an option here — it kills DNS entirely and the adapter can't reach its\n\t// own model API (measured: claude retries ~180s then exits 1).\n\tnetwork: \"all\" | \"anthropic-only\"\n\tonLine: (kind: \"stdout\" | \"stderr\", line: string) => void\n}\n\n// Boot a guest (ro mount + scratch rw as cwd), exec the agent command, stream\n// its output line-by-line, read the report back off the scratch dir, tear\n// down. Agent-level failures (exit ≠ 0, no report) return ok:false;\n// harness-level failures (image missing, boot error) throw.\nexport async function runAgent(opts: RunAgentOpts): Promise<AgentOutcome> {\n\tconst microsandbox = await import(\"microsandbox\")\n\tconst name = `doublecheck-${basename(opts.workdir)}`\n\tconst policy =\n\t\topts.network === \"all\"\n\t\t\t? microsandbox.NetworkPolicy.allowAll()\n\t\t\t: microsandbox.NetworkPolicy.builder()\n\t\t\t\t\t.defaultDeny()\n\t\t\t\t\t.egress((rb) => rb.allow((d) => d.domainSuffix(\"anthropic.com\")))\n\t\t\t\t\t.build()\n\tlet sandbox: InstanceType<typeof microsandbox.Sandbox> | null = null\n\ttry {\n\t\ttry {\n\t\t\tsandbox = await microsandbox.Sandbox.builder(name)\n\t\t\t\t.image(GUEST_IMAGE)\n\t\t\t\t.memory(GUEST_MEMORY_MIB)\n\t\t\t\t.pullPolicy(\"never\")\n\t\t\t\t.replace()\n\t\t\t\t.workdir(opts.workdir)\n\t\t\t\t.envs(opts.spec.env)\n\t\t\t\t.volume(opts.mount, (mb: MountBuilder) => mb.bind(opts.mount).readonly())\n\t\t\t\t.volume(opts.workdir, (mb: MountBuilder) => mb.bind(opts.workdir))\n\t\t\t\t.patch((pb: PatchBuilder) => {\n\t\t\t\t\tlet p = pb\n\t\t\t\t\tfor (const f of opts.spec.files) {\n\t\t\t\t\t\tp = p.text(f.path, f.content, { mode: 0o600, replace: true })\n\t\t\t\t\t}\n\t\t\t\t\treturn p\n\t\t\t\t})\n\t\t\t\t.network((nb: NetworkBuilder) => nb.policy(policy))\n\t\t\t\t.create()\n\t\t} catch (e) {\n\t\t\tif (String(e).includes(\"not cached\")) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`guest image ${GUEST_IMAGE} is not in the microsandbox cache — run scripts/build-guest-image.sh (${String(e)})`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tthrow e\n\t\t}\n\n\t\tconst handle = await sandbox.execStreamWith(\"bash\", (e: ExecOptionsBuilder) =>\n\t\t\te.args([\"-c\", opts.spec.command]),\n\t\t)\n\n\t\tconst carry: Record<\"stdout\" | \"stderr\", string> = { stdout: \"\", stderr: \"\" }\n\t\tlet exitCode: number | null = null\n\t\tfor (;;) {\n\t\t\tconst ev: ExecEvent | null = await handle.recv()\n\t\t\tif (ev === null) break\n\t\t\tif (ev.kind === \"stdout\" || ev.kind === \"stderr\") {\n\t\t\t\tconst fed = feedLines(carry[ev.kind], Buffer.from(ev.data).toString())\n\t\t\t\tcarry[ev.kind] = fed.carry\n\t\t\t\tfor (const line of fed.lines) opts.onLine(ev.kind, line)\n\t\t\t} else if (ev.kind === \"exited\") exitCode = ev.code\n\t\t}\n\t\tfor (const kind of [\"stdout\", \"stderr\"] as const) {\n\t\t\tif (carry[kind].trim()) opts.onLine(kind, carry[kind])\n\t\t}\n\n\t\tconst reportPath = join(opts.workdir, REPORT_FILE)\n\t\tconst report = existsSync(reportPath) ? readFileSync(reportPath, \"utf-8\") : null\n\t\treturn decideOutcome(exitCode, report, opts.workdir)\n\t} finally {\n\t\tif (sandbox) {\n\t\t\ttry {\n\t\t\t\tawait sandbox.stop()\n\t\t\t} catch (e) {\n\t\t\t\topts.onLine(\"stderr\", `sandbox stop failed: ${String(e)}`)\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait microsandbox.Sandbox.remove(name)\n\t\t\t} catch (e) {\n\t\t\t\topts.onLine(\"stderr\", `sandbox remove failed: ${String(e)}`)\n\t\t\t}\n\t\t}\n\t}\n}\n","import { mkdirSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from \"node:fs\"\nimport { tmpdir } from \"node:os\"\nimport { join } from \"node:path\"\nimport PQueue from \"p-queue\"\nimport { claudeAgent, progressSink } from \"./claude.ts\"\nimport { composePrompt, PROMPT_FILE, REPORT_FILE } from \"./contract.ts\"\nimport { runAgent } from \"./runner.ts\"\n\n// A check is a plain markdown file in $PROJECT/.agents/checks — no\n// frontmatter, no schema. The body is the inspector's instructions; the name\n// is the filename minus .md.\nexport interface Check {\n\tname: string\n\tbody: string\n}\n\n// Pure selection: `only` (from repeated --check flags) filters, in the order\n// named; naming a check that doesn't exist is an error, not an empty run.\nexport function selectChecks(all: Check[], only: string[], dir: string): Check[] {\n\tif (all.length === 0) throw new Error(`no checks (*.md) in ${dir}`)\n\tif (only.length === 0) return all\n\tconst byName = new Map(all.map((c) => [c.name, c]))\n\treturn only.map((name) => {\n\t\tconst check = byName.get(name)\n\t\tif (!check) {\n\t\t\tconst have = all.map((c) => c.name).join(\", \")\n\t\t\tthrow new Error(`no check named \"${name}\" in ${dir} (have: ${have})`)\n\t\t}\n\t\treturn check\n\t})\n}\n\n// Shell: read every check file, then select purely.\nexport function discoverChecks(project: string, only: string[]): Check[] {\n\tconst dir = join(project, \".agents\", \"checks\")\n\tlet entries: string[]\n\ttry {\n\t\tentries = readdirSync(dir)\n\t} catch {\n\t\tthrow new Error(`no checks directory at ${dir}`)\n\t}\n\tconst all = entries\n\t\t.filter((f) => f.endsWith(\".md\"))\n\t\t.sort()\n\t\t.map((f) => ({\n\t\t\tname: f.slice(0, -\".md\".length),\n\t\t\tbody: readFileSync(join(dir, f), \"utf-8\"),\n\t\t}))\n\treturn selectChecks(all, only, dir)\n}\n\nexport interface CheckWorkflowOpts {\n\tproject: string\n\tmodel: string\n\tparallel: number\n\toutput: string\n\tonly: string[]\n}\n\n// fs-safe local timestamp, one dir per run shared by all its checks:\n// 2026-07-05-143000\nexport function runTimestamp(d: Date): string {\n\tconst p = (n: number) => String(n).padStart(2, \"0\")\n\treturn `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`\n}\n\n// The report written when the agent failed: the failure is the report, plus\n// whatever partial report the agent managed to leave.\nexport function failureReport(reason: string, partialReport: string | null): string {\n\tconst partial = partialReport\n\t\t? `\\n---\\n\\nPartial ${REPORT_FILE} left by the agent:\\n\\n${partialReport}`\n\t\t: \"\"\n\treturn `# CHECK FAILED\\n\\n${reason}\\n${partial}`\n}\n\nasync function runOneCheck(\n\tcheck: Check,\n\topts: CheckWorkflowOpts,\n\ttoken: string,\n\toutDir: string,\n): Promise<boolean> {\n\tconst workdir = mkdtempSync(join(tmpdir(), `doublecheck-${check.name}-`))\n\twriteFileSync(join(workdir, PROMPT_FILE), composePrompt(check.body, opts.project))\n\tconst spec = claudeAgent({ token, model: opts.model, workdir })\n\tconst outcome = await runAgent({\n\t\tmount: opts.project,\n\t\tworkdir,\n\t\tspec,\n\t\tnetwork: \"all\",\n\t\tonLine: progressSink(check.name),\n\t})\n\tconst reportPath = join(outDir, `${check.name}.md`)\n\tif (outcome.ok) {\n\t\twriteFileSync(reportPath, outcome.report)\n\t\tprocess.stderr.write(`[${check.name}] report: ${reportPath}\\n`)\n\t\treturn true\n\t}\n\twriteFileSync(reportPath, failureReport(outcome.reason, outcome.partialReport))\n\tprocess.stderr.write(`[${check.name}] FAILED (${outcome.reason}): ${reportPath}\\n`)\n\treturn false\n}\n\n// The check workflow: one sandboxed agent per check, at most `parallel`\n// guests at once. Returns true only when every check produced a report; agent\n// failures still write a failure-record report and flip the run to false.\nexport async function runChecks(opts: CheckWorkflowOpts): Promise<boolean> {\n\tconst token = process.env.CLAUDE_CODE_OAUTH_TOKEN\n\tif (!token) {\n\t\tthrow new Error(\n\t\t\t\"CLAUDE_CODE_OAUTH_TOKEN is required in the environment (it is injected into each check's sandbox)\",\n\t\t)\n\t}\n\tconst checks = discoverChecks(opts.project, opts.only)\n\tconst outDir = join(opts.output, runTimestamp(new Date()))\n\tmkdirSync(outDir, { recursive: true })\n\tprocess.stderr.write(\n\t\t`${checks.length} check(s) against ${opts.project} (model ${opts.model}, parallel ${opts.parallel})\\n`,\n\t)\n\tconst queue = new PQueue({ concurrency: opts.parallel })\n\tconst results = await Promise.all(\n\t\tchecks.map((check) => queue.add(() => runOneCheck(check, opts, token, outDir))),\n\t)\n\treturn results.every((ok) => ok === true)\n}\n","// Claude Code transcript (.jsonl) → the genuine human turns → a flat digest.\n// Port of the June-2026 jq extraction, verified against the same corpus: a\n// \"genuine human turn\" is type:user, not meta, not sidechain, text content\n// only — which excludes the three things that are also type:user in the JSONL\n// (tool_results, slash-command stdout wrappers, injected system-reminders).\n\n// Per-turn char cap in the digest; the mining agent greps the source jsonl\n// for full text when it needs context around a turn.\nconst TURN_CHAR_CAP = 2000\n\ninterface RawLine {\n\ttype?: string\n\tisMeta?: boolean\n\tisSidechain?: boolean\n\tmessage?: { content?: unknown }\n}\n\nconst NON_HUMAN_TEXT =\n\t/^\\s*<command-name>|<local-command-stdout>|^\\s*<local-command|^\\s*<system-reminder>|Caveat: The messages below/\n\n// Throws on an unparseable line — the caller decides what an unreadable\n// transcript means for its run (mine counts and reports them, visibly).\nexport function humanTurns(jsonl: string): string[] {\n\tconst turns: string[] = []\n\tfor (const line of jsonl.split(\"\\n\")) {\n\t\tif (!line.trim()) continue\n\t\tconst raw = JSON.parse(line) as RawLine\n\t\tif (raw.type !== \"user\" || raw.isMeta || raw.isSidechain) continue\n\t\tconst content = raw.message?.content\n\t\tconst text =\n\t\t\ttypeof content === \"string\"\n\t\t\t\t? content\n\t\t\t\t: Array.isArray(content)\n\t\t\t\t\t? content\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(item): item is { type: \"text\"; text: string } =>\n\t\t\t\t\t\t\t\t\ttypeof item === \"object\" &&\n\t\t\t\t\t\t\t\t\titem !== null &&\n\t\t\t\t\t\t\t\t\t(item as { type?: string }).type === \"text\" &&\n\t\t\t\t\t\t\t\t\ttypeof (item as { text?: unknown }).text === \"string\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.map((item) => item.text)\n\t\t\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t\t: \"\"\n\t\tif (!text) continue\n\t\tif (NON_HUMAN_TEXT.test(text)) continue\n\t\tturns.push(text.replace(/[\\n\\r]+/g, \" ⏎ \").slice(0, TURN_CHAR_CAP))\n\t}\n\treturn turns\n}\n\nexport interface DigestMeta {\n\tsource: string\n\tproject: string\n\tsession: string\n}\n\nexport function renderDigest(meta: DigestMeta, turns: string[]): string {\n\tconst numbered = turns\n\t\t.map((t, i) => `${String(i + 1).padStart(3, \" \")} | ${t}`)\n\t\t.join(\"\\n\")\n\treturn `# source: ${meta.source}\n# project: ${meta.project}\n# session: ${meta.session}\n# human_turns: ${turns.length}\n# To understand WHY a terse turn or interruption happened, grep its text in the\n# source file above and read the assistant turn(s) immediately before it.\n\n${numbered}\n`\n}\n","import { createHash } from \"node:crypto\"\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\"\nimport { basename, join } from \"node:path\"\nimport { humanTurns, renderDigest } from \"./transcript.ts\"\n\n// One minable unit = one top-level transcript $PROJECTS/<project>/<session>.jsonl.\n// Its catalog home mirrors that path: $CATALOG/<project>/<session>/observations.md.\nexport interface Unit {\n\tproject: string\n\tsession: string\n\tjsonlPath: string\n}\n\nexport function enumerateUnits(projectsDir: string): Unit[] {\n\tlet projects: string[]\n\ttry {\n\t\tprojects = readdirSync(projectsDir).sort()\n\t} catch {\n\t\tthrow new Error(`no transcripts directory at ${projectsDir}`)\n\t}\n\tconst units: Unit[] = []\n\tfor (const project of projects) {\n\t\tconst dir = join(projectsDir, project)\n\t\tif (!statSync(dir).isDirectory()) continue\n\t\tfor (const f of readdirSync(dir).sort()) {\n\t\t\tif (!f.endsWith(\".jsonl\")) continue\n\t\t\tunits.push({\n\t\t\t\tproject,\n\t\t\t\tsession: basename(f, \".jsonl\"),\n\t\t\t\tjsonlPath: join(dir, f),\n\t\t\t})\n\t\t}\n\t}\n\treturn units\n}\n\nexport function observationsPath(catalogDir: string, unit: Unit): string {\n\treturn join(catalogDir, unit.project, unit.session, \"observations.md\")\n}\n\nexport type UnitStatus =\n\t// hash matches the recorded source_sha256 — skipped without parsing\n\t| { kind: \"mined\" }\n\t| { kind: \"below-threshold\"; turns: number }\n\t| {\n\t\t\tkind: \"pending\"\n\t\t\treason: \"new\" | \"changed\"\n\t\t\tturns: number\n\t\t\tdigest: string\n\t\t\tsourceSha256: string\n\t }\n\t// transcript has an unparseable line; reported visibly, never mined\n\t| { kind: \"unreadable\"; error: string }\n\n// Pure decision core: everything already read, nothing left but judgment.\nexport function decideUnitStatus(input: {\n\tunit: Unit\n\tjsonl: Buffer\n\trecordedSha256: string | null\n\tminTurns: number\n}): UnitStatus {\n\tconst sourceSha256 = createHash(\"sha256\").update(input.jsonl).digest(\"hex\")\n\tif (input.recordedSha256 === sourceSha256) return { kind: \"mined\" }\n\tlet turns: string[]\n\ttry {\n\t\tturns = humanTurns(input.jsonl.toString(\"utf-8\"))\n\t} catch (e) {\n\t\treturn { kind: \"unreadable\", error: String(e) }\n\t}\n\tif (turns.length < input.minTurns)\n\t\treturn { kind: \"below-threshold\", turns: turns.length }\n\treturn {\n\t\tkind: \"pending\",\n\t\treason: input.recordedSha256 === null ? \"new\" : \"changed\",\n\t\tturns: turns.length,\n\t\tdigest: renderDigest(\n\t\t\t{\n\t\t\t\tsource: input.unit.jsonlPath,\n\t\t\t\tproject: input.unit.project,\n\t\t\t\tsession: input.unit.session,\n\t\t\t},\n\t\t\tturns,\n\t\t),\n\t\tsourceSha256,\n\t}\n}\n\n// Shell: read the unit's inputs, then decide purely.\nexport function unitStatus(unit: Unit, catalogDir: string, minTurns: number): UnitStatus {\n\tconst obsPath = observationsPath(catalogDir, unit)\n\treturn decideUnitStatus({\n\t\tunit,\n\t\tjsonl: readFileSync(unit.jsonlPath),\n\t\trecordedSha256: existsSync(obsPath)\n\t\t\t? readSourceSha256(readFileSync(obsPath, \"utf-8\"))\n\t\t\t: null,\n\t\tminTurns,\n\t})\n}\n\nexport interface ObservationsMeta {\n\tsource: string\n\tsourceSha256: string\n\tminedAt: string\n\tmodel: string\n\thumanTurns: number\n}\n\n// The host composes the final file: frontmatter it alone can vouch for, then\n// the agent's report verbatim.\nexport function composeObservationsFile(meta: ObservationsMeta, body: string): string {\n\treturn `---\nsource: ${meta.source}\nsource_sha256: ${meta.sourceSha256}\nmined_at: ${meta.minedAt}\nmodel: ${meta.model}\nhuman_turns: ${meta.humanTurns}\n---\n\n${body.trimEnd()}\n`\n}\n\nexport function readSourceSha256(observationsMd: string): string | null {\n\treturn observationsMd.match(/^source_sha256: ([0-9a-f]{64})$/m)?.[1] ?? null\n}\n","import { mkdirSync, mkdtempSync, writeFileSync } from \"node:fs\"\nimport { tmpdir } from \"node:os\"\nimport { dirname, join } from \"node:path\"\nimport PQueue from \"p-queue\"\nimport {\n\tcomposeObservationsFile,\n\tenumerateUnits,\n\tobservationsPath,\n\ttype Unit,\n\ttype UnitStatus,\n\tunitStatus,\n} from \"./catalog.ts\"\nimport { claudeAgent, progressSink } from \"./claude.ts\"\nimport { composeMinePrompt, PROMPT_FILE } from \"./contract.ts\"\nimport { runAgent } from \"./runner.ts\"\n\nexport interface MineOpts {\n\tprojects: string\n\tcatalog: string\n\tmodel: string\n\tparallel: number\n\tminTurns: number\n\tlimit?: number\n\tdryRun: boolean\n}\n\nexport interface Pending {\n\tunit: Unit\n\tstatus: Extract<UnitStatus, { kind: \"pending\" }>\n}\n\n// What one mine invocation will and won't do, decided purely from the\n// per-unit statuses.\nexport interface MinePlan {\n\ttodo: Pending[]\n\tpendingTotal: number\n\tmined: number\n\tbelowThreshold: number\n\tunreadable: { unit: Unit; error: string }[]\n}\n\nexport function planMine(\n\tentries: { unit: Unit; status: UnitStatus }[],\n\tlimit?: number,\n): MinePlan {\n\tconst pending = entries.filter((e): e is Pending => e.status.kind === \"pending\")\n\treturn {\n\t\ttodo: limit ? pending.slice(0, limit) : pending,\n\t\tpendingTotal: pending.length,\n\t\tmined: entries.filter((e) => e.status.kind === \"mined\").length,\n\t\tbelowThreshold: entries.filter((e) => e.status.kind === \"below-threshold\").length,\n\t\tunreadable: entries.flatMap((e) =>\n\t\t\te.status.kind === \"unreadable\"\n\t\t\t\t? [{ unit: e.unit, error: e.status.error }]\n\t\t\t\t: [],\n\t\t),\n\t}\n}\n\nexport function summarizeMinePlan(plan: MinePlan, minTurns: number): string {\n\tconst total =\n\t\tplan.pendingTotal + plan.mined + plan.belowThreshold + plan.unreadable.length\n\tconst limited =\n\t\tplan.todo.length !== plan.pendingTotal ? ` (mining ${plan.todo.length})` : \"\"\n\treturn `${total} transcripts: ${plan.pendingTotal} pending${limited}, ${plan.mined} mined, ${plan.belowThreshold} below ${minTurns} turns, ${plan.unreadable.length} unreadable`\n}\n\nexport function dryRunRow({ unit, status }: Pending): string {\n\treturn `${status.reason.padEnd(7)} ${status.turns.toString().padStart(4)} turns ${unit.project}/${unit.session}`\n}\n\nasync function mineOneUnit(\n\t{ unit, status }: Pending,\n\topts: MineOpts,\n\ttoken: string,\n): Promise<boolean> {\n\tconst tag = unit.session.slice(0, 8)\n\tconst workdir = mkdtempSync(join(tmpdir(), `doublecheck-mine-${tag}-`))\n\twriteFileSync(join(workdir, PROMPT_FILE), composeMinePrompt(status.digest))\n\tconst spec = claudeAgent({ token, model: opts.model, workdir })\n\tprocess.stderr.write(\n\t\t`[${tag}] mining ${unit.project}/${unit.session} (${status.turns} turns, ${status.reason})\\n`,\n\t)\n\tconst outcome = await runAgent({\n\t\tmount: opts.projects,\n\t\tworkdir,\n\t\tspec,\n\t\tnetwork: \"anthropic-only\",\n\t\tonLine: progressSink(tag),\n\t})\n\t// A failed mine writes NOTHING — the catalog is a durable asset; the next\n\t// run retries this unit because no hash gets recorded.\n\tif (!outcome.ok) {\n\t\tprocess.stderr.write(`[${tag}] FAILED (${outcome.reason})\\n`)\n\t\treturn false\n\t}\n\tconst obsPath = observationsPath(opts.catalog, unit)\n\tmkdirSync(dirname(obsPath), { recursive: true })\n\twriteFileSync(\n\t\tobsPath,\n\t\tcomposeObservationsFile(\n\t\t\t{\n\t\t\t\tsource: unit.jsonlPath,\n\t\t\t\tsourceSha256: status.sourceSha256,\n\t\t\t\tminedAt: new Date().toISOString(),\n\t\t\t\tmodel: opts.model,\n\t\t\t\thumanTurns: status.turns,\n\t\t\t},\n\t\t\toutcome.report,\n\t\t),\n\t)\n\tprocess.stderr.write(`[${tag}] observations: ${obsPath}\\n`)\n\treturn true\n}\n\n// The mine workflow shell: gather statuses (I/O), plan purely, then either\n// print the plan (dry-run) or run one sandboxed agent per pending unit.\n// Returns true when nothing it attempted failed.\nexport async function runMine(opts: MineOpts): Promise<boolean> {\n\tconst entries = enumerateUnits(opts.projects).map((unit) => ({\n\t\tunit,\n\t\tstatus: unitStatus(unit, opts.catalog, opts.minTurns),\n\t}))\n\tconst plan = planMine(entries, opts.limit)\n\tfor (const { unit, error } of plan.unreadable) {\n\t\tprocess.stderr.write(`UNREADABLE ${unit.project}/${unit.session}: ${error}\\n`)\n\t}\n\tprocess.stderr.write(`${summarizeMinePlan(plan, opts.minTurns)}\\n`)\n\n\tif (opts.dryRun) {\n\t\tfor (const pending of plan.todo) console.log(dryRunRow(pending))\n\t\treturn true\n\t}\n\tif (plan.todo.length === 0) return true\n\n\tconst token = process.env.CLAUDE_CODE_OAUTH_TOKEN\n\tif (!token) {\n\t\tthrow new Error(\n\t\t\t\"CLAUDE_CODE_OAUTH_TOKEN is required in the environment (it is injected into each miner's sandbox)\",\n\t\t)\n\t}\n\tconst queue = new PQueue({ concurrency: opts.parallel })\n\tconst results = await Promise.all(\n\t\tplan.todo.map((p) => queue.add(() => mineOneUnit(p, opts, token))),\n\t)\n\tconst failed = results.filter((ok) => ok !== true).length\n\tprocess.stderr.write(\n\t\t`mined ${results.length - failed}/${results.length}${failed ? `, ${failed} FAILED` : \"\"}\\n`,\n\t)\n\treturn failed === 0\n}\n","#!/usr/bin/env node\nimport { homedir } from \"node:os\"\nimport { join, resolve } from \"node:path\"\nimport { Command } from \"commander\"\nimport { runChecks } from \"./check.ts\"\nimport { runMine } from \"./mine.ts\"\n\nfunction parsePositiveInt(flag: string) {\n\treturn (v: string): number => {\n\t\tconst n = Number.parseInt(v, 10)\n\t\tif (!Number.isInteger(n) || n < 1)\n\t\t\tthrow new Error(`${flag} must be a positive integer, got \"${v}\"`)\n\t\treturn n\n\t}\n}\n\nconst program = new Command()\n\t.name(\"doublecheck\")\n\t.description(\n\t\t\"Run self-authored LLM code-inspectors (.agents/checks/*.md) against a project, one sandboxed agent per check\",\n\t)\n\nprogram\n\t.command(\"check\")\n\t.option(\"--project <dir>\", \"project to inspect\", process.cwd())\n\t.option(\"--model <model>\", \"model for the inspector agents\", \"haiku\")\n\t.option(\"--parallel <n>\", \"max concurrent checks\", parsePositiveInt(\"--parallel\"), 4)\n\t.option(\"--output <dir>\", \"reports root (default: $PROJECT/.doublecheck)\")\n\t.option(\n\t\t\"--check <name>\",\n\t\t\"run only this check (repeatable)\",\n\t\t(v: string, prev: string[]) => [...prev, v],\n\t\t[] as string[],\n\t)\n\t.action(async (opts) => {\n\t\tconst project = resolve(opts.project)\n\t\tconst ok = await runChecks({\n\t\t\tproject,\n\t\t\tmodel: opts.model,\n\t\t\tparallel: opts.parallel,\n\t\t\toutput: opts.output ? resolve(opts.output) : join(project, \".doublecheck\"),\n\t\t\tonly: opts.check,\n\t\t})\n\t\tif (!ok) process.exitCode = 1\n\t})\n\nprogram\n\t.command(\"mine\")\n\t.option(\n\t\t\"--projects <dir>\",\n\t\t\"Claude Code transcripts root\",\n\t\tjoin(homedir(), \".claude\", \"projects\"),\n\t)\n\t.option(\n\t\t\"--catalog <dir>\",\n\t\t\"observation catalog root\",\n\t\tjoin(homedir(), \".doublecheck\", \"catalog\"),\n\t)\n\t.option(\"--model <model>\", \"model for the mining agents\", \"opus\")\n\t.option(\"--parallel <n>\", \"max concurrent miners\", parsePositiveInt(\"--parallel\"), 4)\n\t.option(\n\t\t\"--min-turns <n>\",\n\t\t\"min genuine human turns for a real conversation\",\n\t\tparsePositiveInt(\"--min-turns\"),\n\t\t2,\n\t)\n\t.option(\"--limit <n>\", \"mine at most N pending units\", parsePositiveInt(\"--limit\"))\n\t.option(\"--dry-run\", \"list what would be mined without booting anything\")\n\t.action(async (opts) => {\n\t\tconst ok = await runMine({\n\t\t\tprojects: resolve(opts.projects),\n\t\t\tcatalog: resolve(opts.catalog),\n\t\t\tmodel: opts.model,\n\t\t\tparallel: opts.parallel,\n\t\t\tminTurns: opts.minTurns,\n\t\t\tlimit: opts.limit,\n\t\t\tdryRun: !!opts.dryRun,\n\t\t})\n\t\tif (!ok) process.exitCode = 1\n\t})\n\nprogram.parseAsync().catch((e: unknown) => {\n\tconsole.error(e instanceof Error ? e.message : String(e))\n\tprocess.exit(1)\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/contract.ts","../src/claude.ts","../src/runner.ts","../src/check.ts","../src/transcript.ts","../src/catalog.ts","../src/mine.ts","../src/cli.ts"],"sourcesContent":["// The agent contract: what the harness stages for the agent and what the\n// agent must leave behind. Any adapter (claude today, codex someday) runs\n// under this same contract, which is what keeps the harness CLI-agnostic.\n\n// Staged into the scratch workdir before boot; the rw bind mount delivers it.\nexport const PROMPT_FILE = \"prompt.txt\"\n// The agent writes its findings here (relative to its cwd); the host reads it\n// back after exit. Missing report = failure.\nexport const REPORT_FILE = \"report.md\"\n\n// Weak models reliably skip a report contract stated once at the prompt's\n// tail unless it leads with the stakes and orders the file created first\n// (measured: haiku went from ~50% to 3/3 with this wording). Two hard-won\n// additions (2026-07-05, all three cutover inspectors lost their reports):\n// the path is ABSOLUTE — agents cd into the inspected tree to run git, and a\n// relative ./report.md then lands in the read-only mount — and the\n// create-first order is repeated near the top of the prompt (see\n// firstActionLine), because on long prompts the tail alone loses its grip.\nfunction reportContract(activity: string, deliverable: string, workdir: string): string {\n\treturn `## Report — the only output that counts\n\nYour final chat reply is discarded. The only thing read back is the file \\`${workdir}/${REPORT_FILE}\\` — if it does not exist when you exit, this run FAILS. Always use that absolute path: if you \\`cd\\` elsewhere (into the inspected tree, say), a relative \\`./${REPORT_FILE}\\` lands in the wrong place, or in a read-only mount where the write fails.\n\nSo: create \\`${workdir}/${REPORT_FILE}\\` with the Write tool BEFORE you start ${activity} (a title line is enough), and update it as you work. The final state of the file when you finish is ${deliverable}.`\n}\n\nfunction firstActionLine(workdir: string): string {\n\treturn `Your FIRST action, before anything else: create \\`${workdir}/${REPORT_FILE}\\` with the Write tool (a title line is enough). The full report contract is at the end of this prompt.`\n}\n\n// Environment preamble + optional run context + check body + report\n// contract. Checks are timeless standards; everything run-specific (intent,\n// sanctioned exceptions, scope) arrives in the operator's run context — the\n// harness knows nothing about git.\nexport function composePrompt(opts: {\n\tcheckBody: string\n\ttarget: string\n\tworkdir: string\n\trunContext: string | null\n}): string {\n\tconst contextSection = opts.runContext\n\t\t? `## Run context (from the operator)\n\nThe operator's brief for this run: the intent behind the work under review, known nuances, sanctioned exceptions, and possibly a Scope naming exactly what is under review. Judge requestedness against it — what the brief explicitly sanctions is not a finding. If it names a Scope, the Scope defines the change under review: findings must concern that change, and the rest of the tree is reference for judgment — with one exception. For concepts the scoped change retires or renames, surviving references anywhere in the tree are reportable findings: the Scope bounds what counts as the change, not where survivors may hide.\n\n${opts.runContext}\n\n---\n\n`\n\t\t: \"\"\n\treturn `## Environment\n\nYou are a code inspector running inside a sandboxed microVM with full permissions and unrestricted internet access.\n\n- The code under inspection is mounted read-only at \\`${opts.target}\\`. You cannot modify it — inspect, don't fix.\n- \\`git\\` and \\`rg\\` are installed.\n- Your current working directory is a writable scratch workspace: \\`${opts.workdir}\\`.\n\n${firstActionLine(opts.workdir)}\n\n## Inspection discipline\n\nMeasured on real runs: inspectors that skip these rules produce reports whose verdicts are right but whose evidence is partly fabricated — which is worse than no report.\n\n- Before inspecting, enumerate what is under review (the Run context's Scope list if one is given; otherwise your own enumeration of the target) and keep a ledger as you work: **examined** (content actually opened), **name-only**, **not examined**. End your report with that ledger, with a one-line reason for anything not examined.\n- Use verification verbs — \"verified\", \"confirmed\", \"checked\", \"read\", \"diffed\", \"grepped\", \"spot-checked\" — ONLY for actions you actually performed in this session on content you actually opened. Everything else must be labeled as inference: \"assumed identical by generation — not opened.\"\n- Never truncate output you will base a claim on — no \\`| head\\` / \\`| tail\\` on a diff or search you intend to cite. Examine files one at a time instead.\n\n---\n\n${contextSection}${opts.checkBody}\n\n---\n\n${reportContract(\"inspecting\", \"the check's report\", opts.workdir)}\n\nEvery single line of your report will be read in full by the operator's agent — nothing is skimmed, so every line costs attention. Verify each finding against the actual code before writing it down. A report that says \"no findings\" is a perfectly good report; an unverified finding is not.`\n}\n\n// The mining prompt: digest of one conversation + what counts as a durable\n// preference observation + the block format the catalog accumulates.\nexport function composeMinePrompt(digest: string, workdir: string): string {\n\treturn `## Environment\n\nYou are running inside a sandboxed microVM with no network access. The machine's Claude Code transcripts are mounted read-only at their real paths; your current working directory is a writable scratch workspace: \\`${workdir}\\`.\n\n${firstActionLine(workdir)}\n\n## Task\n\nYou are mining ONE Claude Code conversation for the operator's durable preferences — how they want engineering done, in any conversation, not what they wanted built in this one.\n\nBelow is the conversation digest: every genuine human turn, numbered, in order. When a turn is a terse correction or interruption (\"no\", \"stop\", \"don't\", \"why did you…\", \"[Request interrupted]\"), recover its context: the \"# source:\" header names the full session transcript — Grep that turn's text in it to find its line, then Read the assistant turns IMMEDIATELY BEFORE it with offset/limit. The preference lives in the contrast between what the assistant did and what the operator demanded. NEVER read a transcript whole — some are tens of MB.\n\n---\n\n${digest}\n\n---\n\n## What counts as an observation (strict)\n\nA durable preference, backed by a verbatim quote from the digest or transcript:\n\n- **kind: code** — how code should be written/structured/shaped (\"no silent fallbacks\", \"reuse the existing helper instead of hand-rolling\").\n- **kind: workflow** — how work should proceed (\"verify before claiming done\", \"ask before making scope decisions\").\n- **kind: style** — how prose/output/docs should read.\n\nExclude task-of-the-moment directives with no durable signal (\"add a tab to home.tsx\", \"kill the dev server\"). Exclude anything you cannot quote. Be conservative: a weak or speculative observation is worse than none.\n\n## Observation format\n\nThe report is observation blocks and NOTHING else — no title, no preamble, no summary or conclusion sections. One block per observation:\n\n## <kebab-case-name>\n- **observation:** <one sentence: the durable preference>\n- **kind:** code | workflow | style\n- **evidence:** \"<verbatim quote>\" — turn <N>\n\nFor **code** observations that an LLM reviewer could check against a diff or file tree (a concrete change could flip PASS↔FAIL), and ONLY for those — never on workflow or style blocks — ALSO add:\n\n- **pass/fail:** PASS when <…>; FAIL when <…>\n- **why-LLM:** <why a deterministic linter cannot enforce this>\n- **scope:** diff-only | needs-tree\n\nIf the conversation carries no durable preference signal, the report is exactly these two lines and nothing more:\n\nNo durable preference signal.\n<one sentence: what the conversation was about instead>\n\n${reportContract(\"mining\", \"the mined observations\", workdir)}`\n}\n","import { PROMPT_FILE } from \"./contract.ts\"\nimport type { AgentSpec } from \"./runner.ts\"\n\nconst GUEST_HOME = \"/root\"\n\n// The claude adapter: produce the AgentSpec that runs `claude -p` in the guest.\nexport function claudeAgent(opts: {\n\ttoken: string\n\tmodel: string\n\tworkdir: string\n}): AgentSpec {\n\treturn {\n\t\t// Headless claude -p cannot answer permission prompts, so in \"default\"\n\t\t// mode every tool is auto-denied and the agent produces nothing. The\n\t\t// microVM is the safety boundary here, so bypass the per-tool gate;\n\t\t// IS_SANDBOX=1 is what lets that run as root without claude's refusal.\n\t\tcommand:\n\t\t\t\"claude -p --no-session-persistence \" +\n\t\t\t\"--dangerously-skip-permissions \" +\n\t\t\t// --verbose is required by claude -p with stream-json output; it only\n\t\t\t// widens what lands on stdout, which describeStreamLine already labels.\n\t\t\t\"--output-format stream-json --verbose \" +\n\t\t\t// Not a permission gate — a contract guard. The default headless tool\n\t\t\t// set omits Glob/Grep and includes harness tools (ReportFindings,\n\t\t\t// TaskCreate, …) that hijack the report: haiku \"reports findings\"\n\t\t\t// through them and never writes report.md (verified live). This pins\n\t\t\t// the inspector's full toolkit and nothing else.\n\t\t\t`--tools Task Bash Read Write Edit Glob Grep WebSearch WebFetch < ${PROMPT_FILE}`,\n\t\tenv: {\n\t\t\tHOME: GUEST_HOME,\n\t\t\tIS_SANDBOX: \"1\",\n\t\t\tGIT_OPTIONAL_LOCKS: \"0\",\n\t\t\tANTHROPIC_MODEL: opts.model,\n\t\t\tCLAUDE_CODE_OAUTH_TOKEN: opts.token,\n\t\t},\n\t\t// Minimal trust-accepted entry keyed to the guest cwd so headless claude\n\t\t// skips the trust prompt. Deliberately NOT a copy of any host .claude.json.\n\t\tfiles: [\n\t\t\t{\n\t\t\t\tpath: `${GUEST_HOME}/.claude.json`,\n\t\t\t\tcontent: `${JSON.stringify(\n\t\t\t\t\t{ projects: { [opts.workdir]: { hasTrustDialogAccepted: true } } },\n\t\t\t\t\tnull,\n\t\t\t\t\t\"\\t\",\n\t\t\t\t)}\\n`,\n\t\t\t},\n\t\t],\n\t}\n}\n\ninterface StreamLine {\n\ttype?: string\n\tsubtype?: string\n\tmessage?: { content?: Array<{ type?: string; text?: string; name?: string }> }\n\tduration_ms?: number\n}\n\n// One human-readable label per stream-json stdout line; null for lines that\n// aren't claude's JSON (dropped from the progress stream).\nexport function describeStreamLine(line: string): string | null {\n\tlet obj: StreamLine\n\ttry {\n\t\tobj = JSON.parse(line) as StreamLine\n\t} catch {\n\t\treturn null\n\t}\n\tconst label = (obj.type ?? \"\") + (obj.subtype ? `:${obj.subtype}` : \"\")\n\t// A once-per-second heartbeat with no content — pure noise in the stream.\n\tif (label === \"system:thinking_tokens\") return null\n\tlet detail = \"\"\n\tif (obj.type === \"assistant\") {\n\t\tconst content = obj.message?.content ?? []\n\t\tconst tools = content\n\t\t\t.filter((b) => b.type === \"tool_use\" && b.name)\n\t\t\t.map((b) => b.name)\n\t\tconst chars = content.reduce((n, b) => n + (b.text?.length ?? 0), 0)\n\t\tdetail = tools.length ? ` ${tools.join(\",\")}` : ` ${chars} chars`\n\t} else if (obj.type === \"result\") {\n\t\tdetail = ` ${obj.subtype}, ${((obj.duration_ms ?? 0) / 1000).toFixed(1)}s`\n\t}\n\treturn `[${label}]${detail}`\n}\n\n// The per-unit progress sink both workflow shells hang on runAgent: guest\n// stderr passes through as `[label] ! line`, stdout is claude's stream-json,\n// labelled by describeStreamLine (non-JSON and heartbeat lines dropped).\nexport function progressSink(\n\tlabel: string,\n): (kind: \"stdout\" | \"stderr\", line: string) => void {\n\treturn (kind, line) => {\n\t\tif (kind === \"stderr\") {\n\t\t\tprocess.stderr.write(`[${label}] ! ${line}\\n`)\n\t\t\treturn\n\t\t}\n\t\tconst described = describeStreamLine(line)\n\t\tif (described) process.stderr.write(`[${label}] ${described}\\n`)\n\t}\n}\n","import { existsSync, readFileSync } from \"node:fs\"\nimport { basename, join } from \"node:path\"\nimport type { ExecEvent } from \"microsandbox\"\nimport { REPORT_FILE } from \"./contract.ts\"\n\n// microsandbox exports its fluent builders as value-only consts; recover their\n// instance types via a type-only import (erased at build) so the native module\n// only loads behind the `await import` in runAgent.\ntype MountBuilder = InstanceType<typeof import(\"microsandbox\").MountBuilder>\ntype PatchBuilder = InstanceType<typeof import(\"microsandbox\").PatchBuilder>\ntype ExecOptionsBuilder = InstanceType<typeof import(\"microsandbox\").ExecOptionsBuilder>\n// The SDK types `.network(cb)` as `(b: any) => any`, and we call the JS-shim\n// `.policy(...)`, absent from the native builder's types.\n// biome-ignore lint/suspicious/noExplicitAny: SDK types this builder callback as any\ntype NetworkBuilder = any\n\n// Locally built (Dockerfile.guest): node 24 slim + git/ripgrep/curl/wget + the\n// claude CLI baked in. Side-loaded into the microsandbox cache by\n// ./scripts/build-guest-image.sh — it exists nowhere else, hence pullPolicy\n// \"never\": a cache miss means \"run the build script\", not \"try Docker Hub\".\nconst GUEST_IMAGE = \"doublecheck-guest:latest\"\nconst GUEST_MEMORY_MIB = 2048\n\n// Staged into the guest before boot (e.g. claude's trust-accepted config).\nexport interface GuestFile {\n\tpath: string\n\tcontent: string\n}\n\n// What it takes to run one agent in a booted guest: a bash command executed in\n// the scratch cwd that must leave REPORT_FILE there, the env it needs, and\n// files staged into the guest. Plain data — adapters produce it, this runner\n// consumes it, tests fake it.\nexport interface AgentSpec {\n\tcommand: string\n\tenv: Record<string, string>\n\tfiles: GuestFile[]\n}\n\nexport type AgentOutcome =\n\t| { ok: true; report: string }\n\t| { ok: false; reason: string; partialReport: string | null }\n\n// Pure line buffering: fold a chunk into the carry, emit complete non-blank\n// lines, keep the unterminated tail.\nexport function feedLines(\n\tcarry: string,\n\tchunk: string,\n): { lines: string[]; carry: string } {\n\tconst parts = (carry + chunk).split(\"\\n\")\n\tconst rest = parts.pop() ?? \"\"\n\treturn { lines: parts.filter((l) => l.trim()), carry: rest }\n}\n\n// Pure outcome decision: what the exec left behind → what it means.\nexport function decideOutcome(\n\texitCode: number | null,\n\treport: string | null,\n\tworkdir: string,\n): AgentOutcome {\n\tif (exitCode !== 0) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\treason: `agent process exited ${exitCode}`,\n\t\t\tpartialReport: report,\n\t\t}\n\t}\n\tif (report === null) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\treason: `agent exited 0 but wrote no ${REPORT_FILE} (work dir: ${workdir})`,\n\t\t\tpartialReport: null,\n\t\t}\n\t}\n\treturn { ok: true, report }\n}\n\nexport interface RunAgentOpts {\n\t// Host dir the agent inspects, bind-mounted READ-ONLY at its real host\n\t// path: the target for `check`, the transcripts corpus for `mine`.\n\tmount: string\n\t// Scratch dir with PROMPT_FILE already inside; bind-mounted rw as the guest\n\t// cwd at its identical host path, so the agent's report lands back on the host.\n\tworkdir: string\n\tspec: AgentSpec\n\t// \"all\" for checks (inspectors may research); \"anthropic-only\" for miners:\n\t// the personal corpus is mounted, so the only reachable destination is the\n\t// API the agent already sends its context to. NetworkPolicy.none() is not\n\t// an option here — it kills DNS entirely and the adapter can't reach its\n\t// own model API (measured: claude retries ~180s then exits 1).\n\tnetwork: \"all\" | \"anthropic-only\"\n\tonLine: (kind: \"stdout\" | \"stderr\", line: string) => void\n}\n\n// Boot a guest (ro mount + scratch rw as cwd), exec the agent command, stream\n// its output line-by-line, read the report back off the scratch dir, tear\n// down. Agent-level failures (exit ≠ 0, no report) return ok:false;\n// harness-level failures (image missing, boot error) throw.\nexport async function runAgent(opts: RunAgentOpts): Promise<AgentOutcome> {\n\tconst microsandbox = await import(\"microsandbox\")\n\tconst name = `doublecheck-${basename(opts.workdir)}`\n\tconst policy =\n\t\topts.network === \"all\"\n\t\t\t? microsandbox.NetworkPolicy.allowAll()\n\t\t\t: microsandbox.NetworkPolicy.builder()\n\t\t\t\t\t.defaultDeny()\n\t\t\t\t\t.egress((rb) => rb.allow((d) => d.domainSuffix(\"anthropic.com\")))\n\t\t\t\t\t.build()\n\tlet sandbox: InstanceType<typeof microsandbox.Sandbox> | null = null\n\ttry {\n\t\ttry {\n\t\t\tsandbox = await microsandbox.Sandbox.builder(name)\n\t\t\t\t.image(GUEST_IMAGE)\n\t\t\t\t.memory(GUEST_MEMORY_MIB)\n\t\t\t\t.pullPolicy(\"never\")\n\t\t\t\t.replace()\n\t\t\t\t.workdir(opts.workdir)\n\t\t\t\t.envs(opts.spec.env)\n\t\t\t\t.volume(opts.mount, (mb: MountBuilder) => mb.bind(opts.mount).readonly())\n\t\t\t\t.volume(opts.workdir, (mb: MountBuilder) => mb.bind(opts.workdir))\n\t\t\t\t.patch((pb: PatchBuilder) => {\n\t\t\t\t\tlet p = pb\n\t\t\t\t\tfor (const f of opts.spec.files) {\n\t\t\t\t\t\tp = p.text(f.path, f.content, { mode: 0o600, replace: true })\n\t\t\t\t\t}\n\t\t\t\t\treturn p\n\t\t\t\t})\n\t\t\t\t.network((nb: NetworkBuilder) => nb.policy(policy))\n\t\t\t\t.create()\n\t\t} catch (e) {\n\t\t\tif (String(e).includes(\"not cached\")) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`guest image ${GUEST_IMAGE} is not in the microsandbox cache — run scripts/build-guest-image.sh (${String(e)})`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tthrow e\n\t\t}\n\n\t\tconst handle = await sandbox.execStreamWith(\"bash\", (e: ExecOptionsBuilder) =>\n\t\t\te.args([\"-c\", opts.spec.command]),\n\t\t)\n\n\t\tconst carry: Record<\"stdout\" | \"stderr\", string> = { stdout: \"\", stderr: \"\" }\n\t\tlet exitCode: number | null = null\n\t\tfor (;;) {\n\t\t\tconst ev: ExecEvent | null = await handle.recv()\n\t\t\tif (ev === null) break\n\t\t\tif (ev.kind === \"stdout\" || ev.kind === \"stderr\") {\n\t\t\t\tconst fed = feedLines(carry[ev.kind], Buffer.from(ev.data).toString())\n\t\t\t\tcarry[ev.kind] = fed.carry\n\t\t\t\tfor (const line of fed.lines) opts.onLine(ev.kind, line)\n\t\t\t} else if (ev.kind === \"exited\") exitCode = ev.code\n\t\t}\n\t\tfor (const kind of [\"stdout\", \"stderr\"] as const) {\n\t\t\tif (carry[kind].trim()) opts.onLine(kind, carry[kind])\n\t\t}\n\n\t\tconst reportPath = join(opts.workdir, REPORT_FILE)\n\t\tconst report = existsSync(reportPath) ? readFileSync(reportPath, \"utf-8\") : null\n\t\treturn decideOutcome(exitCode, report, opts.workdir)\n\t} finally {\n\t\tif (sandbox) {\n\t\t\ttry {\n\t\t\t\tawait sandbox.stop()\n\t\t\t} catch (e) {\n\t\t\t\topts.onLine(\"stderr\", `sandbox stop failed: ${String(e)}`)\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait microsandbox.Sandbox.remove(name)\n\t\t\t} catch (e) {\n\t\t\t\topts.onLine(\"stderr\", `sandbox remove failed: ${String(e)}`)\n\t\t\t}\n\t\t}\n\t}\n}\n","import {\n\tappendFileSync,\n\tmkdirSync,\n\tmkdtempSync,\n\treaddirSync,\n\treadFileSync,\n\twriteFileSync,\n} from \"node:fs\"\nimport { tmpdir } from \"node:os\"\nimport { join } from \"node:path\"\nimport PQueue from \"p-queue\"\nimport { claudeAgent, progressSink } from \"./claude.ts\"\nimport { composePrompt, PROMPT_FILE, REPORT_FILE } from \"./contract.ts\"\nimport { runAgent } from \"./runner.ts\"\n\n// A check is a plain markdown file — no frontmatter, no schema. The body is\n// the inspector's instructions; the name is the filename minus .md. Checks\n// come from one or more --checks-dir directories (default:\n// $TARGET/.agents/checks).\nexport interface Check {\n\tname: string\n\tbody: string\n}\n\n// Pure selection: `only` (from repeated --check flags) filters, in the order\n// named; naming a check that doesn't exist is an error, not an empty run.\nexport function selectChecks(all: Check[], only: string[], where: string): Check[] {\n\tif (all.length === 0) throw new Error(`no checks (*.md) in ${where}`)\n\tif (only.length === 0) return all\n\tconst byName = new Map(all.map((c) => [c.name, c]))\n\treturn only.map((name) => {\n\t\tconst check = byName.get(name)\n\t\tif (!check) {\n\t\t\tconst have = all.map((c) => c.name).join(\", \")\n\t\t\tthrow new Error(`no check named \"${name}\" in ${where} (have: ${have})`)\n\t\t}\n\t\treturn check\n\t})\n}\n\n// Shell: read every check file from every checks dir, then select purely.\n// A missing dir is an error; the same check name in two dirs is an error —\n// no precedence rules, rename one.\nexport function discoverChecks(dirs: string[], only: string[]): Check[] {\n\tconst all: Array<Check & { dir: string }> = []\n\tfor (const dir of dirs) {\n\t\tlet entries: string[]\n\t\ttry {\n\t\t\tentries = readdirSync(dir)\n\t\t} catch {\n\t\t\tthrow new Error(`no checks directory at ${dir}`)\n\t\t}\n\t\tfor (const f of entries.filter((e) => e.endsWith(\".md\")).sort()) {\n\t\t\tconst name = f.slice(0, -\".md\".length)\n\t\t\tconst clash = all.find((c) => c.name === name)\n\t\t\tif (clash) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`check \"${name}\" exists in both ${clash.dir} and ${dir} — rename one`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tall.push({ name, body: readFileSync(join(dir, f), \"utf-8\"), dir })\n\t\t}\n\t}\n\tconst checks = all\n\t\t.map(({ name, body }) => ({ name, body }))\n\t\t.sort((a, b) => a.name.localeCompare(b.name))\n\treturn selectChecks(checks, only, dirs.join(\", \"))\n}\n\nexport interface CheckWorkflowOpts {\n\ttarget: string\n\tchecksDirs: string[]\n\tcontextFile: string | null\n\tmodel: string\n\tparallel: number\n\toutput: string\n\tonly: string[]\n\tsaveJsonl: boolean\n}\n\n// fs-safe local timestamp, one dir per run shared by all its checks:\n// 2026-07-05-143000\nexport function runTimestamp(d: Date): string {\n\tconst p = (n: number) => String(n).padStart(2, \"0\")\n\treturn `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`\n}\n\n// The report written when the agent failed: the failure is the report, plus\n// whatever partial report the agent managed to leave.\nexport function failureReport(reason: string, partialReport: string | null): string {\n\tconst partial = partialReport\n\t\t? `\\n---\\n\\nPartial ${REPORT_FILE} left by the agent:\\n\\n${partialReport}`\n\t\t: \"\"\n\treturn `# CHECK FAILED\\n\\n${reason}\\n${partial}`\n}\n\nasync function runOneCheck(\n\tcheck: Check,\n\topts: CheckWorkflowOpts,\n\ttoken: string,\n\trunContext: string | null,\n\toutDir: string,\n): Promise<boolean> {\n\tconst workdir = mkdtempSync(join(tmpdir(), `doublecheck-${check.name}-`))\n\twriteFileSync(\n\t\tjoin(workdir, PROMPT_FILE),\n\t\tcomposePrompt({\n\t\t\tcheckBody: check.body,\n\t\t\ttarget: opts.target,\n\t\t\tworkdir,\n\t\t\trunContext,\n\t\t}),\n\t)\n\tconst spec = claudeAgent({ token, model: opts.model, workdir })\n\t// The report is the verdict; with --save-jsonl the raw stream is kept\n\t// beside it so a run can be audited after the ephemeral guest is gone\n\t// (which files the inspector read, what it skipped, whether report claims\n\t// trace to actual observations).\n\tconst sink = progressSink(check.name)\n\tlet onLine = sink\n\tif (opts.saveJsonl) {\n\t\tconst streamPath = join(outDir, `${check.name}.stream.jsonl`)\n\t\tonLine = (kind, line) => {\n\t\t\tif (kind === \"stdout\") appendFileSync(streamPath, `${line}\\n`)\n\t\t\tsink(kind, line)\n\t\t}\n\t}\n\tconst outcome = await runAgent({\n\t\tmount: opts.target,\n\t\tworkdir,\n\t\tspec,\n\t\tnetwork: \"all\",\n\t\tonLine,\n\t})\n\tconst reportPath = join(outDir, `${check.name}.md`)\n\tif (outcome.ok) {\n\t\twriteFileSync(reportPath, outcome.report)\n\t\tprocess.stderr.write(`[${check.name}] report: ${reportPath}\\n`)\n\t\treturn true\n\t}\n\twriteFileSync(reportPath, failureReport(outcome.reason, outcome.partialReport))\n\tprocess.stderr.write(`[${check.name}] FAILED (${outcome.reason}): ${reportPath}\\n`)\n\treturn false\n}\n\n// The check workflow: one sandboxed agent per check, at most `parallel`\n// guests at once. Returns true only when every check produced a report; agent\n// failures still write a failure-record report and flip the run to false.\nexport async function runChecks(opts: CheckWorkflowOpts): Promise<boolean> {\n\tconst token = process.env.CLAUDE_CODE_OAUTH_TOKEN\n\tif (!token) {\n\t\tthrow new Error(\n\t\t\t\"CLAUDE_CODE_OAUTH_TOKEN is required in the environment (it is injected into each check's sandbox)\",\n\t\t)\n\t}\n\tconst checks = discoverChecks(opts.checksDirs, opts.only)\n\tlet runContext: string | null = null\n\tif (opts.contextFile) {\n\t\ttry {\n\t\t\trunContext = readFileSync(opts.contextFile, \"utf-8\")\n\t\t} catch (e) {\n\t\t\tthrow new Error(\n\t\t\t\t`--context file not readable: ${opts.contextFile} (${String(e)})`,\n\t\t\t)\n\t\t}\n\t}\n\tconst outDir = join(opts.output, runTimestamp(new Date()))\n\tmkdirSync(outDir, { recursive: true })\n\tprocess.stderr.write(\n\t\t`${checks.length} check(s) against ${opts.target} (model ${opts.model}, parallel ${opts.parallel})\\n`,\n\t)\n\tconst queue = new PQueue({ concurrency: opts.parallel })\n\tconst results = await Promise.all(\n\t\tchecks.map((check) =>\n\t\t\tqueue.add(() => runOneCheck(check, opts, token, runContext, outDir)),\n\t\t),\n\t)\n\treturn results.every((ok) => ok === true)\n}\n","// Claude Code transcript (.jsonl) → the genuine human turns → a flat digest.\n// Port of the June-2026 jq extraction, verified against the same corpus: a\n// \"genuine human turn\" is type:user, not meta, not sidechain, text content\n// only — which excludes the three things that are also type:user in the JSONL\n// (tool_results, slash-command stdout wrappers, injected system-reminders).\n\n// Per-turn char cap in the digest; the mining agent greps the source jsonl\n// for full text when it needs context around a turn.\nconst TURN_CHAR_CAP = 2000\n\ninterface RawLine {\n\ttype?: string\n\tisMeta?: boolean\n\tisSidechain?: boolean\n\tmessage?: { content?: unknown }\n}\n\nconst NON_HUMAN_TEXT =\n\t/^\\s*<command-name>|<local-command-stdout>|^\\s*<local-command|^\\s*<system-reminder>|Caveat: The messages below/\n\n// Throws on an unparseable line — the caller decides what an unreadable\n// transcript means for its run (mine counts and reports them, visibly).\nexport function humanTurns(jsonl: string): string[] {\n\tconst turns: string[] = []\n\tfor (const line of jsonl.split(\"\\n\")) {\n\t\tif (!line.trim()) continue\n\t\tconst raw = JSON.parse(line) as RawLine\n\t\tif (raw.type !== \"user\" || raw.isMeta || raw.isSidechain) continue\n\t\tconst content = raw.message?.content\n\t\tconst text =\n\t\t\ttypeof content === \"string\"\n\t\t\t\t? content\n\t\t\t\t: Array.isArray(content)\n\t\t\t\t\t? content\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(item): item is { type: \"text\"; text: string } =>\n\t\t\t\t\t\t\t\t\ttypeof item === \"object\" &&\n\t\t\t\t\t\t\t\t\titem !== null &&\n\t\t\t\t\t\t\t\t\t(item as { type?: string }).type === \"text\" &&\n\t\t\t\t\t\t\t\t\ttypeof (item as { text?: unknown }).text === \"string\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.map((item) => item.text)\n\t\t\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t\t: \"\"\n\t\tif (!text) continue\n\t\tif (NON_HUMAN_TEXT.test(text)) continue\n\t\tturns.push(text.replace(/[\\n\\r]+/g, \" ⏎ \").slice(0, TURN_CHAR_CAP))\n\t}\n\treturn turns\n}\n\nexport interface DigestMeta {\n\tsource: string\n\tproject: string\n\tsession: string\n}\n\nexport function renderDigest(meta: DigestMeta, turns: string[]): string {\n\tconst numbered = turns\n\t\t.map((t, i) => `${String(i + 1).padStart(3, \" \")} | ${t}`)\n\t\t.join(\"\\n\")\n\treturn `# source: ${meta.source}\n# project: ${meta.project}\n# session: ${meta.session}\n# human_turns: ${turns.length}\n# To understand WHY a terse turn or interruption happened, grep its text in the\n# source file above and read the assistant turn(s) immediately before it.\n\n${numbered}\n`\n}\n","import { createHash } from \"node:crypto\"\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\"\nimport { basename, join } from \"node:path\"\nimport { humanTurns, renderDigest } from \"./transcript.ts\"\n\n// One minable unit = one top-level transcript $PROJECTS/<project>/<session>.jsonl.\n// Its catalog home mirrors that path: $CATALOG/<project>/<session>/observations.md.\nexport interface Unit {\n\tproject: string\n\tsession: string\n\tjsonlPath: string\n}\n\nexport function enumerateUnits(projectsDir: string): Unit[] {\n\tlet projects: string[]\n\ttry {\n\t\tprojects = readdirSync(projectsDir).sort()\n\t} catch {\n\t\tthrow new Error(`no transcripts directory at ${projectsDir}`)\n\t}\n\tconst units: Unit[] = []\n\tfor (const project of projects) {\n\t\tconst dir = join(projectsDir, project)\n\t\tif (!statSync(dir).isDirectory()) continue\n\t\tfor (const f of readdirSync(dir).sort()) {\n\t\t\tif (!f.endsWith(\".jsonl\")) continue\n\t\t\tunits.push({\n\t\t\t\tproject,\n\t\t\t\tsession: basename(f, \".jsonl\"),\n\t\t\t\tjsonlPath: join(dir, f),\n\t\t\t})\n\t\t}\n\t}\n\treturn units\n}\n\nexport function observationsPath(catalogDir: string, unit: Unit): string {\n\treturn join(catalogDir, unit.project, unit.session, \"observations.md\")\n}\n\nexport type UnitStatus =\n\t// hash matches the recorded source_sha256 — skipped without parsing\n\t| { kind: \"mined\" }\n\t| { kind: \"below-threshold\"; turns: number }\n\t| {\n\t\t\tkind: \"pending\"\n\t\t\treason: \"new\" | \"changed\"\n\t\t\tturns: number\n\t\t\tdigest: string\n\t\t\tsourceSha256: string\n\t }\n\t// transcript has an unparseable line; reported visibly, never mined\n\t| { kind: \"unreadable\"; error: string }\n\n// Pure decision core: everything already read, nothing left but judgment.\nexport function decideUnitStatus(input: {\n\tunit: Unit\n\tjsonl: Buffer\n\trecordedSha256: string | null\n\tminTurns: number\n}): UnitStatus {\n\tconst sourceSha256 = createHash(\"sha256\").update(input.jsonl).digest(\"hex\")\n\tif (input.recordedSha256 === sourceSha256) return { kind: \"mined\" }\n\tlet turns: string[]\n\ttry {\n\t\tturns = humanTurns(input.jsonl.toString(\"utf-8\"))\n\t} catch (e) {\n\t\treturn { kind: \"unreadable\", error: String(e) }\n\t}\n\tif (turns.length < input.minTurns)\n\t\treturn { kind: \"below-threshold\", turns: turns.length }\n\treturn {\n\t\tkind: \"pending\",\n\t\treason: input.recordedSha256 === null ? \"new\" : \"changed\",\n\t\tturns: turns.length,\n\t\tdigest: renderDigest(\n\t\t\t{\n\t\t\t\tsource: input.unit.jsonlPath,\n\t\t\t\tproject: input.unit.project,\n\t\t\t\tsession: input.unit.session,\n\t\t\t},\n\t\t\tturns,\n\t\t),\n\t\tsourceSha256,\n\t}\n}\n\n// Shell: read the unit's inputs, then decide purely.\nexport function unitStatus(unit: Unit, catalogDir: string, minTurns: number): UnitStatus {\n\tconst obsPath = observationsPath(catalogDir, unit)\n\treturn decideUnitStatus({\n\t\tunit,\n\t\tjsonl: readFileSync(unit.jsonlPath),\n\t\trecordedSha256: existsSync(obsPath)\n\t\t\t? readSourceSha256(readFileSync(obsPath, \"utf-8\"))\n\t\t\t: null,\n\t\tminTurns,\n\t})\n}\n\nexport interface ObservationsMeta {\n\tsource: string\n\tsourceSha256: string\n\tminedAt: string\n\tmodel: string\n\thumanTurns: number\n}\n\n// The host composes the final file: frontmatter it alone can vouch for, then\n// the agent's report verbatim.\nexport function composeObservationsFile(meta: ObservationsMeta, body: string): string {\n\treturn `---\nsource: ${meta.source}\nsource_sha256: ${meta.sourceSha256}\nmined_at: ${meta.minedAt}\nmodel: ${meta.model}\nhuman_turns: ${meta.humanTurns}\n---\n\n${body.trimEnd()}\n`\n}\n\nexport function readSourceSha256(observationsMd: string): string | null {\n\treturn observationsMd.match(/^source_sha256: ([0-9a-f]{64})$/m)?.[1] ?? null\n}\n","import { mkdirSync, mkdtempSync, writeFileSync } from \"node:fs\"\nimport { tmpdir } from \"node:os\"\nimport { dirname, join } from \"node:path\"\nimport PQueue from \"p-queue\"\nimport {\n\tcomposeObservationsFile,\n\tenumerateUnits,\n\tobservationsPath,\n\ttype Unit,\n\ttype UnitStatus,\n\tunitStatus,\n} from \"./catalog.ts\"\nimport { claudeAgent, progressSink } from \"./claude.ts\"\nimport { composeMinePrompt, PROMPT_FILE } from \"./contract.ts\"\nimport { runAgent } from \"./runner.ts\"\n\nexport interface MineOpts {\n\tprojects: string\n\tcatalog: string\n\tmodel: string\n\tparallel: number\n\tminTurns: number\n\tlimit?: number\n\tdryRun: boolean\n}\n\nexport interface Pending {\n\tunit: Unit\n\tstatus: Extract<UnitStatus, { kind: \"pending\" }>\n}\n\n// What one mine invocation will and won't do, decided purely from the\n// per-unit statuses.\nexport interface MinePlan {\n\ttodo: Pending[]\n\tpendingTotal: number\n\tmined: number\n\tbelowThreshold: number\n\tunreadable: { unit: Unit; error: string }[]\n}\n\nexport function planMine(\n\tentries: { unit: Unit; status: UnitStatus }[],\n\tlimit?: number,\n): MinePlan {\n\tconst pending = entries.filter((e): e is Pending => e.status.kind === \"pending\")\n\treturn {\n\t\ttodo: limit ? pending.slice(0, limit) : pending,\n\t\tpendingTotal: pending.length,\n\t\tmined: entries.filter((e) => e.status.kind === \"mined\").length,\n\t\tbelowThreshold: entries.filter((e) => e.status.kind === \"below-threshold\").length,\n\t\tunreadable: entries.flatMap((e) =>\n\t\t\te.status.kind === \"unreadable\"\n\t\t\t\t? [{ unit: e.unit, error: e.status.error }]\n\t\t\t\t: [],\n\t\t),\n\t}\n}\n\nexport function summarizeMinePlan(plan: MinePlan, minTurns: number): string {\n\tconst total =\n\t\tplan.pendingTotal + plan.mined + plan.belowThreshold + plan.unreadable.length\n\tconst limited =\n\t\tplan.todo.length !== plan.pendingTotal ? ` (mining ${plan.todo.length})` : \"\"\n\treturn `${total} transcripts: ${plan.pendingTotal} pending${limited}, ${plan.mined} mined, ${plan.belowThreshold} below ${minTurns} turns, ${plan.unreadable.length} unreadable`\n}\n\nexport function dryRunRow({ unit, status }: Pending): string {\n\treturn `${status.reason.padEnd(7)} ${status.turns.toString().padStart(4)} turns ${unit.project}/${unit.session}`\n}\n\nasync function mineOneUnit(\n\t{ unit, status }: Pending,\n\topts: MineOpts,\n\ttoken: string,\n): Promise<boolean> {\n\tconst tag = unit.session.slice(0, 8)\n\tconst workdir = mkdtempSync(join(tmpdir(), `doublecheck-mine-${tag}-`))\n\twriteFileSync(join(workdir, PROMPT_FILE), composeMinePrompt(status.digest, workdir))\n\tconst spec = claudeAgent({ token, model: opts.model, workdir })\n\tprocess.stderr.write(\n\t\t`[${tag}] mining ${unit.project}/${unit.session} (${status.turns} turns, ${status.reason})\\n`,\n\t)\n\tconst outcome = await runAgent({\n\t\tmount: opts.projects,\n\t\tworkdir,\n\t\tspec,\n\t\tnetwork: \"anthropic-only\",\n\t\tonLine: progressSink(tag),\n\t})\n\t// A failed mine writes NOTHING — the catalog is a durable asset; the next\n\t// run retries this unit because no hash gets recorded.\n\tif (!outcome.ok) {\n\t\tprocess.stderr.write(`[${tag}] FAILED (${outcome.reason})\\n`)\n\t\treturn false\n\t}\n\tconst obsPath = observationsPath(opts.catalog, unit)\n\tmkdirSync(dirname(obsPath), { recursive: true })\n\twriteFileSync(\n\t\tobsPath,\n\t\tcomposeObservationsFile(\n\t\t\t{\n\t\t\t\tsource: unit.jsonlPath,\n\t\t\t\tsourceSha256: status.sourceSha256,\n\t\t\t\tminedAt: new Date().toISOString(),\n\t\t\t\tmodel: opts.model,\n\t\t\t\thumanTurns: status.turns,\n\t\t\t},\n\t\t\toutcome.report,\n\t\t),\n\t)\n\tprocess.stderr.write(`[${tag}] observations: ${obsPath}\\n`)\n\treturn true\n}\n\n// The mine workflow shell: gather statuses (I/O), plan purely, then either\n// print the plan (dry-run) or run one sandboxed agent per pending unit.\n// Returns true when nothing it attempted failed.\nexport async function runMine(opts: MineOpts): Promise<boolean> {\n\tconst entries = enumerateUnits(opts.projects).map((unit) => ({\n\t\tunit,\n\t\tstatus: unitStatus(unit, opts.catalog, opts.minTurns),\n\t}))\n\tconst plan = planMine(entries, opts.limit)\n\tfor (const { unit, error } of plan.unreadable) {\n\t\tprocess.stderr.write(`UNREADABLE ${unit.project}/${unit.session}: ${error}\\n`)\n\t}\n\tprocess.stderr.write(`${summarizeMinePlan(plan, opts.minTurns)}\\n`)\n\n\tif (opts.dryRun) {\n\t\tfor (const pending of plan.todo) console.log(dryRunRow(pending))\n\t\treturn true\n\t}\n\tif (plan.todo.length === 0) return true\n\n\tconst token = process.env.CLAUDE_CODE_OAUTH_TOKEN\n\tif (!token) {\n\t\tthrow new Error(\n\t\t\t\"CLAUDE_CODE_OAUTH_TOKEN is required in the environment (it is injected into each miner's sandbox)\",\n\t\t)\n\t}\n\tconst queue = new PQueue({ concurrency: opts.parallel })\n\tconst results = await Promise.all(\n\t\tplan.todo.map((p) => queue.add(() => mineOneUnit(p, opts, token))),\n\t)\n\tconst failed = results.filter((ok) => ok !== true).length\n\tprocess.stderr.write(\n\t\t`mined ${results.length - failed}/${results.length}${failed ? `, ${failed} FAILED` : \"\"}\\n`,\n\t)\n\treturn failed === 0\n}\n","#!/usr/bin/env node\nimport { homedir } from \"node:os\"\nimport { join, resolve } from \"node:path\"\nimport { Command } from \"commander\"\nimport { runChecks } from \"./check.ts\"\nimport { runMine } from \"./mine.ts\"\n\nfunction parsePositiveInt(flag: string) {\n\treturn (v: string): number => {\n\t\tconst n = Number.parseInt(v, 10)\n\t\tif (!Number.isInteger(n) || n < 1)\n\t\t\tthrow new Error(`${flag} must be a positive integer, got \"${v}\"`)\n\t\treturn n\n\t}\n}\n\nconst collect = (v: string, prev: string[]): string[] => [...prev, v]\n\nconst program = new Command()\n\t.name(\"doublecheck\")\n\t.description(\n\t\t\"Run self-authored LLM code-inspectors (checks) against a target tree, one sandboxed agent per check\",\n\t)\n\nprogram\n\t.command(\"check\")\n\t.option(\"--target <dir>\", \"tree under inspection, mounted read-only\", process.cwd())\n\t.option(\n\t\t\"--checks-dir <dir>\",\n\t\t\"checks directory (repeatable; default: $TARGET/.agents/checks)\",\n\t\tcollect,\n\t\t[] as string[],\n\t)\n\t.option(\n\t\t\"--context <file>\",\n\t\t\"run-context file spliced into every inspector's prompt (intent, nuances, sanctioned exceptions, scope)\",\n\t)\n\t.option(\"--model <model>\", \"model for the inspector agents\", \"haiku\")\n\t.option(\"--parallel <n>\", \"max concurrent checks\", parsePositiveInt(\"--parallel\"), 4)\n\t.option(\"--output <dir>\", \"reports root (default: $TARGET/.doublecheck)\")\n\t.option(\"--check <name>\", \"run only this check (repeatable)\", collect, [] as string[])\n\t.option(\n\t\t\"--save-jsonl\",\n\t\t\"persist each inspector's raw stream-json beside its report (audit trail)\",\n\t)\n\t.action(async (opts) => {\n\t\tconst target = resolve(opts.target)\n\t\tconst checksDirs: string[] =\n\t\t\topts.checksDir.length > 0\n\t\t\t\t? opts.checksDir.map((d: string) => resolve(d))\n\t\t\t\t: [join(target, \".agents\", \"checks\")]\n\t\tconst ok = await runChecks({\n\t\t\ttarget,\n\t\t\tchecksDirs,\n\t\t\tcontextFile: opts.context ? resolve(opts.context) : null,\n\t\t\tmodel: opts.model,\n\t\t\tparallel: opts.parallel,\n\t\t\toutput: opts.output ? resolve(opts.output) : join(target, \".doublecheck\"),\n\t\t\tonly: opts.check,\n\t\t\tsaveJsonl: !!opts.saveJsonl,\n\t\t})\n\t\tif (!ok) process.exitCode = 1\n\t})\n\nprogram\n\t.command(\"mine\")\n\t.option(\n\t\t\"--projects <dir>\",\n\t\t\"Claude Code transcripts root\",\n\t\tjoin(homedir(), \".claude\", \"projects\"),\n\t)\n\t.option(\n\t\t\"--catalog <dir>\",\n\t\t\"observation catalog root\",\n\t\tjoin(homedir(), \".doublecheck\", \"catalog\"),\n\t)\n\t.option(\"--model <model>\", \"model for the mining agents\", \"opus\")\n\t.option(\"--parallel <n>\", \"max concurrent miners\", parsePositiveInt(\"--parallel\"), 4)\n\t.option(\n\t\t\"--min-turns <n>\",\n\t\t\"min genuine human turns for a real conversation\",\n\t\tparsePositiveInt(\"--min-turns\"),\n\t\t2,\n\t)\n\t.option(\"--limit <n>\", \"mine at most N pending units\", parsePositiveInt(\"--limit\"))\n\t.option(\"--dry-run\", \"list what would be mined without booting anything\")\n\t.action(async (opts) => {\n\t\tconst ok = await runMine({\n\t\t\tprojects: resolve(opts.projects),\n\t\t\tcatalog: resolve(opts.catalog),\n\t\t\tmodel: opts.model,\n\t\t\tparallel: opts.parallel,\n\t\t\tminTurns: opts.minTurns,\n\t\t\tlimit: opts.limit,\n\t\t\tdryRun: !!opts.dryRun,\n\t\t})\n\t\tif (!ok) process.exitCode = 1\n\t})\n\nprogram.parseAsync().catch((e: unknown) => {\n\tconsole.error(e instanceof Error ? e.message : String(e))\n\tprocess.exit(1)\n})\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alizain/doublecheck",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Run self-authored LLM code-inspectors against a project — one sandboxed microVM agent per check, one markdown report each",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.5.1",
|