AGENTS.md@workflows · diff
git:20260929.41c6067 to git:20261006.0c6a8b0
6 added, 121 removed. Audit A to A.
# workflows/
- The v3 review pipeline. `pipeline.js` is invoked by the skill through the `Workflow` tool
- (name + args); it has no disk, shell, or `process.env`.
-
- ## Runtime
-
- - **Node 24 is the runtime.** JS tests run under `node --test`. No npm, no `package.json`, no
- `node_modules` — Node builtins and language globals only. Stable `Array.prototype.sort` is relied on.
- - **Only JSON-safe language globals exist in the sandbox.** `structuredClone`, `Buffer`,
- `TextEncoder`/`TextDecoder`, `URL`, `setTimeout`/`queueMicrotask`, `process` and `console` are
- provided by `node --test` but **not** by the sandbox: a reference throws on the first live
- dispatch while every test stays green. Deep-clone with `deepClone`, never `structuredClone`.
- `pipeline_run.test.js` and `stages_persist.test.js` run with all of these except `URL`,
- `process` and `console` deleted from `globalThis`; a reference to one of those three is caught
- only on a live dispatch.
- - **No wall-clock.** Timestamps arrive through the args waist (`generatedAt`); environment values
- are read by the skill and passed in `policy`.
-
- ## JS lint
-
- CI and contributors run the same `python3 workflows/test/tools/biome_check.py`; the operational
- version and per-asset SHA-256 pins live only in `biome-pin.json`.
- To bump, regenerate every row with one command: `gh api repos/biomejs/biome/releases/tags/@biomejs/biome@<new> --jq '.assets[] | {name, digest}'`; update every entry in `biome-pin.json` together and never add a package manifest for auto-bumps.
- Formatter is **off**: default `lineWidth: 80` would wrap long `import … from` lines and break
- `build.js`'s single-line import `strip()` regex, failing `tests/test_bundle_fresh.py`.
- `noRestrictedGlobals` applies to `src/` only; `test/**` and `build.js` override it (they
- legitimately use `process` / `console`).
-
- Deferred rules (measured 2026-07-30, HEAD `ebf399d`) — hit counts are why they
- stay off so a later re-evaluation need not re-derive them:
-
- | Rule | Hits | Why deferred |
- | --- | --- | --- |
- | `useOptionalChain` | 51 | Style churn; no reliability delta |
- | `noUnusedFunctionParameters` | 55 | Dominated by positional mock callback params in tests |
- | `noAssignInExpressions` | 7 | Six are intentional `args.js` pattern |
- | `noControlCharactersInRegex` | 2 | Intentional U+0000/U+001F sanitization in `args.js` |
- | `organizeImports` | 24 | Assist/format-adjacent; out of scope with formatter off |
-
- ## The bundle
-
- - `pipeline.js` is **generated** — never hand-edit it. Source is `src/*.js`, which may use ESM
- `import`/`export` for tests only; `build.js` strips those, drops full-line `//` comments and
- blank lines that V8 proves inert (a `//` line inside a template literal survives), and
- concatenates. It fails the build when the bundle exceeds `WORKFLOW_SCRIPT_CAP -
- BUNDLE_HEADROOM`; the constants and rationale live in `build.js`.
- - Only a single-line `import … from './sibling.js'` is strippable; `build.js` (`unsafeImports`)
- fails on anything else. A `node:`/bare specifier inlines nothing, so stripping it ships an
- undefined reference; a side-effect or multi-line import `strip()` never matches ships verbatim.
- Neither lint nor the bundle-fresh check sees either — inline the value into `src/`.
- - Rebuild after any source change: `node workflows/build.js`. `tests/test_bundle_fresh.py` requires
- the committed bundle to be byte-identical to a fresh build, and import-free.
- - The five transform families under `tests/fixtures/parity/` are JavaScript-owned and change only
- through `UPDATE_GOLDENS=1` in a reviewed diff. The three verify-wire families stay Python-recorded.
- - **`meta.name` must never equal the skill's name.** Both are registered by this plugin; an
- identical name makes `/code-gauntlet:code-gauntlet` resolve to the workflow, which then receives a
- raw user string instead of the args waist.
-
- ## Persistence
-
- **Artifacts reach disk through the workflow's own return value, not through an agent.** A language
- model asked to reproduce large escape-dense JSON verbatim does not: measured across every recorded
- run, 26 of 73 writes failed their own content proof, ranging from escape mangling to silently
- rewriting long prose shorter, and nothing about a document predicts which fails. So the Persist
- stage returns the three primaries at `persistReturn` and the *harness* serializes them —
- byte-exact at every size probed to 4 MB, against ~66 KB of unique content in the largest recorded
- run. **The content proof survives the move and now grades the harness-written copy; do not remove
- it. Respelling JSON backslash escapes on the persist wire was measured and did not fix
- transcription.**
-
- - The two budgets are unrelated and must not re-merge. `VERIFY_INLINE_CHAR_BUDGET` (50k) sizes the
- percent-encoded verify token handed to an executor; `RETURN_CHAR_BUDGET` (1M) sizes what the
- harness serializes. Grading resume state or the returned primaries against the verify budget
- throws away recoverable runs.
- - `fnv1a32` is defined over UTF-16 code units and **must agree between runtimes** — JS uses
- `charCodeAt` + `Math.imul`; Python reads `utf-16-le` pairs. `tests/fixtures/cross_runtime/fnv1a32.json`, read by
- both suites, pins the parity over surrogates and control characters.
- - The checkpoint-skeleton guards must stay in lockstep: `persistPlan` empties
- `phases.challenge.findings` only when it holds an array, and `gauntlet.artifacts` refills it
- under the identical predicate. A looser Python predicate fabricates an array.
- - Structural failures (missing file, unparseable JSON, duplicate id, bad plan checksum) hard-fail.
- A *primary* content mismatch does not — it still derives from on-disk truth and raises a gap.
- - Both writer paths stay live and are not dead code: the derived one is the automatic fallback when
- the primaries exceed `RETURN_CHAR_BUDGET`, the legacy by-value one is the safety net for
- pathological input. Do not delete either.
-
- Verify is the exception to the persist-wire encoding rule: its inline token uses a restricted
- percent-encoded alphabet with no escape semantics left for the model to normalize, measured exact
- in both of the two recorded executor copies at 43,636 characters and again in both copies at
- 51,744 characters; larger payloads are unmeasured. The input content proof remains the belt if
- the inline channel is ever transcribed incorrectly.
-
- ## The verify boundary
-
- The executor echoes a per-id delta, never findings.
-
- Verify hands each projected slice to the executor as one quoted `--input-inline` token. The token
- is planned under `VERIFY_INLINE_CHAR_BUDGET`; the executor reproduces it exactly, and the script
- decodes it and writes the destination path before verification.
-
- - `DELTA_VALUE_FIELDS` (generated Python) and `DELTA_VALUE_KEYS` (JS — `DELTA_KEYS` minus
- `id`/`verified`) are walked in order. The generator projects the Python tuple.
- - `result.deltas` must stay the **first** key of `result` — the reading executor's `Read` is
- length-capped with no truncation notice, so what it echoes must be a prefix.
- - The input proof has **two halves**, both mandatory in `trustSlice`: `inline_checksum` over the
- token as received, and `input_checksum` over the decoded document. Neither sorts keys (#172);
- `sliceInputChecksum` (JS) and `_input_checksum` (Python) are the twins to change together.
- - The slice input is a projection, not a full finding copy: `VERIFY_SLICE_FIELDS` (JS)
- generates `gauntlet.registry.VERIFY_SLICE_FIELDS` (Python) in the same order. Every field
- the script consults on dispatched slices must be listed there. The generator freshness test
- pins the projection, and the read-site scan in `tests/test_verify_findings.py` enforces it
- against the script, exempting its own writes
- (`_SCRIPT_WRITTEN_FIELDS`), legacy-CLI-only reads (`_LEGACY_CLI_FIELDS`), and the `line`/`end_line`
- numeric-coercion loop (`_NUMERIC_FIELDS`).
-
- ## Reading the shared context file
-
- A `Read` returns only part of a large file and gives **no truncation notice**. `contextReadPlan`
- computes the exact offsets from `contextLines`/`contextChars` stamped by the skill — this is
- arithmetic, not an instruction to paginate. Stages receive a prebuilt sentence, never the context
- path, so they cannot construct a read instruction of their own. Do not re-thread `contextPath` into
- a stage input, and never replace that structural property with a phrase check.
+ - `pipeline.js` is generated from `src/*.js` by `build.js`. Never hand-edit it; rebuild and commit
+ it with the source change.
+ - The Workflow sandbox provides language globals plus the host-injected `agent`, `parallel`,
+ `pipeline`, and `args`. It has no disk, shell, wall-clock, or Node and web globals. `node --test`
+ supplies all of those, so a stray reference keeps every test green and throws on the first live
+ dispatch. The lint gate is what catches it; `biome.json` lists the denied globals.