Skip to content

feat(gritlint): gritlint lsp and gritlint export biome - #20

Merged
ryanleecode merged 31 commits into
mainfrom
feat/lsp-biome-export
Oct 13, 2026
Merged

ryanleecode merged 31 commits into
mainfrom
feat/lsp-biome-export

Conversation

@systemfsoftware-maker

@systemfsoftware-maker systemfsoftware-maker commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Unit B1 Done 6 (PR7): gritlint lsp and gritlint export biome. Plan: docs/plans/2026-10-12-2059-feat-gritlint-standalone-plan.md (supersedes ...2025..., which superseded ...2312... and the PR6 plan ...1301...), U10, R25, R26, R31 to R38, AE12 to AE18.

#19 (fix/vendor-lockfile-independent) has merged; this branch carries main through 0e2e07b (#17) and takes #19's crane vendoring, so the new LSP crates need no vendor hash.

What changes for a consumer

  • gritlint lsp [--config PATH] [--rules PATH]... [ROOT] serves diagnostics over the Language Server Protocol on stdio. Each open document gets one error diagnostic per finding, computed from its unsaved text, multifile rules included. The server loads what check loads. If that load would refuse, initialize fails with check's text (-32803) and the process exits 2. shutdown then exit exits 0; exit alone, or the end of input, exits 1. Positions are UTF-8 when the client offers it, else UTF-16.
  • A file the engine cannot scan (target-does-not-parse, target-too-large, target-not-scanned), or a closed file below ROOT the server cannot read (target-unreadable: not UTF-8, or an I/O error such as permission denied), does not stop the server. The file gets a diagnostic with its reason code, each open document in a directory batch that read it gets one naming it, and the rest of the scan is still reported. check still refuses such a tree with exit 2 and unchanged text.
  • gritlint export biome [--rule ID] prints a single-file css/json/js rule as a Biome GritQL plugin, or lists every enabled rule as exportable or refused <reasonCode>: <next action>. A refusal exits 2 with one stderr line. The codes, checked in this order, are export-unknown-rule, export-multifile, export-language, export-construct, export-rewrite and export-library. All 15 bundled rules are multifile and list as refused. Plugins are verified in gritlint's engine only (Q1: Biome is not an approved executable).
  • check changes for one rule shape (KTD54). A rule's kind now comes from the engine (Problem::is_multifile), so a rule whose multifile block follows a comment or a pattern definition runs per directory like every multifile rule, instead of over every file of its language at once. No bundled rule has that shape. check's loader moved into workspace::configure / Configured::load (pure) and shell::load_workspace (reads); its output is otherwise unchanged.
  • README sections for both subcommands; minor changeset.

How a keystroke is served

decide::restrict keeps only the single-file tasks for open documents and the multifile directory batches holding one. Open documents overlay disk through Sources::overlaid. The event's sources are read once. A read that fails on a file below ROOT refuses that file (target-unreadable), and a scan that refuses a target drops it (decide::without). Either way the rest is read or scanned again (KTD48, KTD55).

Measured at 66a753d on x86_64-linux with packages.x86_64-linux.gritlint-unwrapped, over the PR3 corpus (Effect-TS/effect@0b17b9e2) and effect.gritlint.json, with packages/effect/package.json open (didOpen, then didChange to publish):

Event Median Samples (ms)
clean tree 34 ms 36 34 34 30 33 34 34 32
malformed tsconfig.broken.json sibling 46 ms 45 45 49 47 51 53 45 46
latin-1 tsconfig.latin1.json sibling (target-unreadable) 47 ms 57 50 43 41 63 41 42 43 53 39 48 37 46 54 55 47
full gritlint check of the corpus 150 ms 153 148 150

The host's load average was 21 to 38 during the last row; repeat sessions showed single spikes up to 296 ms on the clean tree, with no trend across 16 events.

Verification

  • At 66a753d: cargo fmt --check, cargo clippy --workspace --all-targets -- -D warnings and cargo test --workspace (16 binaries, 135 tests) exit 0. The nix build of all 19 checks.x86_64-linux.* plus packages.x86_64-linux.{gritlint,gritlint-unwrapped} exits 0. That includes gritlint-agent-journey, which drives the sandboxed gritlint lsp over real stdio and gritlint export biome.
  • CI run 38307881354 is green on 66a753d, aarch64 included.
  • Earlier scratch-copy red probes were hand-applied mutants; they are withdrawn as evidence and are not cited here. Red evidence:
    • an_unreadable_closed_sibling_is_named_on_the_open_documents_of_its_batch_until_it_reads (lsp_contract.rs:607) is red on the unmodified pre-fix commit 1eedb94. I ran that commit's own gritlint-unwrapped over stdio on the test's scenario: a/x.json holds bytes ff fe, a/package.json is open, and the rules are license and directory-read. It publishes nothing for the open document. Besides its startup announce, it logs only one error: cannot read `<ROOT>/a/x.json`: stream did not contain valid UTF-8. The test's diagnostics assertion (lsp_contract.rs:623-629) expects unlicensed at 1:2 plus target-unreadable at 0:0, which is exactly what 66a753d publishes on the same scenario.
    • R35 order fixtures (each_refused_rule_reports_its_own_reason_code) and the R36 backslash fixture (each_exportable_rule_renders_its_committed_plugin): red not shown. export.rs is byte-identical at 1eedb94 and 66a753d, so these fixtures pin behaviour that already held, and no pre-fix commit exists for them to fail on.
    • Regression tests from rounds 1 to 4 (fixed in 757dd06, e905ecc, 0c45d15 and a290c70): red not shown.
  • Stdio smoke against the debug binary on a tree with a latin-1 sibling: the open document gets unlicensed plus target-unreadable naming a/bad.json, and shutdown then exit exits 0. gritlint check on the same tree exits 2 with cannot read ./a/bad.json, as before.

Review (ce-code-review, report-only, five rounds)

Round 5 ran at 1eedb94 (run 20261012-202737-4018f61a). Lenses: correctness, testing, maintainability, reliability, performance, api-contract, project-standards (all 16 constitution laws) and learnings (41 of 41 pack rule files, 6 apply, all comply, each with a HEAD line quoted and checked). The cross-model adversarial peer was served by openrouter/deepseek/deepseek-v4.1-flash (71 turns) and minimax-code/MiniMax-M3.1-Flash-Preview (38 turns), counted from its session transcript, and read only the diff, the file list and the evidence file. Correctness and performance returned no findings.

Round Finding Ruling
5 P1 (peer) / P2 (reliability) one unreadable closed sibling blanks every open document Fixed in 66a753d: target-unreadable refusal; this reverses the round-1 and round-4 "per KTD48" rejections, and the plan is superseded for it
5 P2 R35 refusal order unpinned Fixed in 66a753d: four two-check fixtures
5 P2 server scan-failure path untested Fixed in 66a753d for unreadable targets. The rest of the log-only path is reachable over real stdio (ROOT a regular file), behaves as KTD48 specifies, and is untested: follow-up F1, ruling R0 under Round-5 residuals
5 P3 backslash escaping untested Fixed in 66a753d
5 P3 evaluated count computed at two sites Fixed in 66a753d: derived where it is read
5 P2 (peer) a brand-new unsaved file publishes clean Rejected: R31, README: a document check would not select gets no diagnostics
5 P2 the review intent said check is unchanged Rejected: KTD54 and the changeset state the change; this body states it above
5 P3 frozen sibling plans cite retired plan paths Rejected: those plans are frozen contracts (1640's says so); the supersedes: chain resolves the paths
5 P3 (CONST-D2) export-construct also covers a body that does not parse Rejected: KTD52, unreachable, because check compiled the body; no public code for an unreachable path
4 P2 an editor scan narrowed a multifile body the line test called single-file Fixed in a290c70: one classifier from the engine (KTD54)
4 P2 refusal loop re-read every source per pass; two-refusal loop untested; sibling parse failure swallowed Fixed in a290c70
4 P2/P3 plan named mechanisms the code does not ship; single-use helper; duplicate message join Fixed in a290c70 (plan superseded)
4 P3 first message after initialize other than initialized exits 1 Rejected: LSP 3.17 requires initialized next
3 P2 (CONST-D1/D2) open-string reason codes; (CONST-B4) core opened stdio; (CONST-B1) loader did its own reads Fixed in 0c45d15: TargetReason/ExportReason, lsp::Files port with shell::Disk, pure workspace
3 P2/P3 target-too-large, jsx and bare js untested; missing ROOT refused; loop rebuilt its error; four test pack loaders Fixed in 0c45d15
3 P2 handshake waits unbounded Rejected: initialize_finish returns on disconnect or any other message
2 P1 a stray message after shutdown wedged the server; P1 two URIs of one file followed URI order Fixed in e905ecc
2 P2 export multifile and sibling-call checks were line heuristics; failed client write kept scanning; missing protocol error tests Fixed in e905ecc
1 export biome CLI untested; didSave, out-of-root, rangeless finding untested; one alias of a file got nothing Fixed in 757dd06

Final-head delta review (1eedb94..66a753d, ce-code-review report-only, depth full)

Round 5 reviewed 1eedb94; its fixes landed in 66a753d. Run 20261012-214525-6e25ba95 reviewed exactly that delta (20 files) and returned Ready to merge at 66a753d (stage log complete).

  • Reviewers: correctness, testing, reliability, performance, api-contract, project-standards (all 16 constitution laws: 16 comply, each with a HEAD line quoted and checked), learnings (41 of 41 pack rule files; 8 apply, all comply, quotes checked). Maintainability was not selected: 111 executable non-test changed lines and no new abstraction.
  • Cross-model peer (mandatory): omp adversarial-reviewer route. The session transcript shows openrouter/deepseek/deepseek-v4.1-flash (51 turns) and minimax-code/MiniMax-M3.1-Flash-Preview (13 turns) served it, with no Grok or xai. It finished in 915 s of a 1210 s deadline and read only the brief, the diff, the evidence file and the reviewed tree. It filed 2 findings.
  • Validator: one independent batch over the 2 selected findings: 0 confirmed, 2 rejected, 0 unresolved.
Finding (reviewer) CE result Ruling
P3 a read failure with no encoding cause is refused as target-unreadable (peer) Rejected by the validator: lsp.rs:430-433 puts the read error's own text in the message, and KTD48 (plan:257) specifies exactly this conversion No change
P2 exported messages carry a literal \n (peer, pre-existing) Rejected by the validator: export.rs has the same md5 at 1eedb94 and 66a753d, and R36 (plan:120) specifies the escape Biome unescapes No change; outside the delta
P2 the read retry rereads the remaining sources once per unreadable file (reliability, anchor 50) Suppressed by the confidence gate No change. KTD55 states this cost; the peer measured 2 ms events with 4 unreadable siblings
P3 the plan's supersedes: names its own former path (api-contract, anchor 50) Suppressed by the confidence gate No change. That line is the supersession record plan-discipline requires

Round-5 fixes on the final head:

  • target-unreadable: correctness filed nothing, and the peer checked the refusal's scope by hand. The regression test is red on the unmodified pre-fix commit 1eedb94 (see Verification).
  • R35 order: four fixtures. Red not shown, because export.rs is unchanged since 1eedb94.
  • R36 backslash: one fixture. Red not shown, for the same reason.
  • The evaluated field is gone; CONST-D4 complies.
  • Plan supersession and solution-doc citations resolve.

No new defect survived. The three gaps the delta report recorded (two unreadable files in one event, an unreadable file seen only by single-file rules, non-encoding read errors) are ruled D1 to D3 under Round-5 residuals, each with a stdio probe.

Report errata: the rendered report.md prose says the peer "confirmed findings are design decisions"; in fact the peer filed them and the validator rejected them. It also names a TargetReason::NotParsed variant (the code has DoesNotParse) and lists 3 of the 5 packs whose rules apply (package-topology and schema-laws also apply). The verdict, counts and verdicts above come from the run's JSON artifacts.

ci-wall-clock: read by hand from systemfsoftware main at 763fd663, because gritlint is not enrolled. .compound-engineering/config.yaml at 66a753d (identical to main) pins the compound packs at 02bf9653 and declares five packs. 02bf9653 predates ci-wall-clock, which systemfsoftware#742 added (merge 8d071180). ce-code-review therefore loaded only the five declared packs (41 rule files), and no receipt above covers ci-wall-clock. I read its 12 rules at 763fd6639979ec955dcb004e6d4d97e1cb49f9eb (#746's merge, systemfsoftware main when the delta review ran) and matched each applies_when against main...66a753d. Enrolment PR: none exists yet. No gritlint PR or branch has changed .compound-engineering/config.yaml since #7 (searched 2026-10-13). Enrolment is the ci-wall-clock repin queued after this PR, which this PR does not start. The precedent at the same pin is systemfsoftware/younna-ai#38, which pins 763fd663 and enrols ci-wall-clock.

Rule (pack line) Applies Verdict and evidence at 66a753d
pr-gate-runs-only-what-the-diff-affects (applies_when line 10: a diff that changes a CI-facing flake output) yes. checks.<system>.gritlint-agent-journey now drives apps/gritlint-e2e/src/lsp.rs:18 (.arg("lsp")), and gritlint-nextest gains lsp_contract/export_contract Runs only what the diff affects: comply. .github/workflows/ci.yml:64 restores (actions/cache/restore) before :78-79 builds, and the diff changes no workflow. Fits in 10 minutes: measured over. x86_64 took 12m05s; see CI wall clock (follow-up F3)
flake-outputs-build-once-on-main (line 5: changing a check a workflow builds) yes, same outputs comply. Pull requests restore read-only (ci.yml:64), and only the default branch exports and saves (ci.yml:96, :107). This PR's job builds the dependency derivations without saving because its diff changes their inputs (Cargo.lock), the input-change case of the two the rule allows (the other is a missed restore of main's key)
no-lane-grades-the-repos-own-docs (line 4: adding a test) yes comply. Each new test's subject is the shipped tool: crates/gritlint_core/tests/lsp_contract.rs:53 runs lsp::serve, and apps/gritlint-e2e/src/export.rs:74 runs export biome --rules rules
setup-under-a-minute, gates-share-one-job, no-dev-shell-on-verdict-lanes, one-stuck-job-backstop, light-checks-run-the-pinned-tool, heavy-suites-judge-every-main-commit, heavy-lanes-state-their-daily-minutes, red-main-reverts-its-culprit-same-day, release-promotes-the-staging-proven-build no git diff --name-only main...66a753d -- .github .compound-engineering nix flake.nix flake.lock scripts .cargo is empty. The diff adds or changes no job, step, trigger, timeout, heavy lane, release, light check or dev shell. For reference, run 38307881354's x86_64 job reached its first verdict step (An unsupported system is refused) 8 s after it started

Round-5 residuals: every item ruled

Round 5 (run 20261012-202737-4018f61a, at 1eedb94) dispatched nine reviewers. All nine returned; none timed out. It then ended at dispatch, superseded by 66a753d, so its residual risks and testing gaps were never merged or ruled. Each one is ruled below. A ruling is one of three kinds:

  • fixed, naming the existing commit;
  • rejected, with the evidence;
  • follow-up, naming an owner and a trigger.

Probes ran at 66a753d against packages.x86_64-linux.gritlint-unwrapped over real stdio, with scratch trees and the fixture rules tests/fixtures/lsp/rules/{license,directory-read}.md. Test gaps are stated by reading the existing tests and the product path they drive. Earlier scratch-copy red probes were hand-applied mutants; they are withdrawn as evidence and are not cited here. No probe, scratch test or fixture is committed. Line numbers are at 66a753d.

Follow-ups:

# Reviewer Residual (abridged) Ruling
R0 testing P2 (round-5 table) The server's log-only scan-failure path is untested follow-up F1. Most of the path is fixed in 66a753d, since unreadable targets are now refused. The body's earlier "reachable only through a Files double" was wrong: with ROOT a regular file, initialize succeeds and each event sends one ERROR log ("path <ROOT> is not a usable relative path: the path is empty") and publishes nothing. check exits 2 with the same text. That matches KTD48 (plan:257). No test observes this path. The lsp_contract harness drops every notification other than publishDiagnostics (lsp_contract.rs:93-118), and no file under crates/gritlint_core/tests or apps/gritlint-e2e/src mentions window/logMessage, LogMessage or MessageType, so nothing reads Server::log's output (lsp.rs:527-532). Red not shown
R1, R14, R33 peer, correctness, standards check changes for a rule whose multifile block follows a comment rejected: the change is intended and disclosed. KTD54, .changeset/gritlint-lsp-biome-export.md:9 and What changes state it, and tests/scan_contract.rs:814 pins it. Each of the 15 bundled rules has multifile as its first code line, so none changes
R2, R23 peer, performance Each event re-walks the whole tree rejected, measured. A clean event on the 2,641-file corpus takes 34 ms against 150 ms for a full check (table above). KTD44 and KTD55 choose a fresh walk
R3, R26 peer, performance One more engine pass per refused target rejected, measured. 46 ms with a malformed sibling, 47 ms with a latin-1 one. The loop stops once a refusal no longer shrinks the needed sources (lsp.rs:556-558)
R4, R25 peer, performance export biome listing is O(n²) in sibling parses rejected, measured. 25, 100 and 200 single-scope rules list in 6, 31 and 109 ms. Bundled rules return before any parse (export.rs:34)
R5, R21, R37 peer, maintainability, testing Callsite completeness rests on text search rejected. The compiler checks the callsites: CI 38307881354 built gritlint-clippy (-D warnings, all targets) and gritlint-nextest green at 66a753d
R6 peer Only forbidden-license.grit is compiled in the engine rejected by probe. All 8 committed expected/*.grit plugins compile in gritlint's engine with R36's register_diagnostic stub (8/8 Ok). Loading them in Biome is R13
R7 api-contract A clean document and an unscanned one look alike rejected. R31 and KTD44 specify this, npm/gritlint/README.md:107-108 documents it, and the server announces the ROOT it serves (lsp.rs:517-525)
R8 api-contract publish drops the event on a non-target error follow-up F1 (same path as R0)
R9 api-contract export-construct also covers a body that does not parse rejected (round-5 table, KTD52). It is unreachable, because check compiled the body
R10 api-contract Consumer reach of the classifier change is unmeasured rejected. Bundled rules are unaffected (R1), and the minor intent tells consumers about their own rules
R11 api-contract End of input after a refused initialize is untested rejected by construction and probe. serve returns Ending::Refused after refuse whatever until_exit returns (lsp.rs:70-74, :120-130). Over stdio, -32803 followed by closing stdin exits 2
R12 api-contract No CLI-level test of the commented-multifile change rejected. scan_contract.rs:814 asserts findings through decide::plan and decide::scan, the calls check makes. The CLI adds only rendering
R13 correctness Plugins are never loaded in Biome follow-up F2, operator-approved for after merge. Until then, this PR verifies plugins in gritlint's engine only (KTD51)
R15 correctness initialize_finish panics if a memory client drops between initialize and initialized rejected. Production uses Connection::stdio() (apps/gritlint/src/main.rs:145). Over stdio, a client that leaves before initialize gives exit 1, and one that closes its read end mid-session gives exit 1 (probes)
R16 correctness Second bitflags major rejected. Run 38307881354's cargo deny printed advisories ok, bans ok, licenses ok, sources ok, and every vendor and dependency derivation built
R17 correctness The e2e journey compares UTF-16 with UTF-8 columns rejected. apps/gritlint-e2e/src contains no non-ASCII byte, so the two encodings give the same columns. lsp_contract.rs:367 pins the encoding mapping (AE14)
R18 correctness The losing URI's positions are unpinned rejected by construction. publish sends each URI the one set computed for its path (lsp.rs:360-366), against the last-edited buffer (:371-376)
R19 maintainability A restricted plan keeps the unrestricted files_selected rejected. lsp.rs reads no selection count, and check never restricts
R20 maintainability domain.rs size rejected. The reviewer records it as following the ParamNameRefusal/RelPathRefusal placement, not as a finding
R22 maintainability lsp is never run end to end with --config or --rules rejected by probe. The nix-built binary over stdio, given --rules and given --config alt.json, loads and publishes unlicensed. The loader is check's (shell::load_workspace, KTD47)
R24 performance Per-event cost grows with open documents rejected, measured on the corpus (median of 8 didChange events, load 32-36 on 32 cores). One open package.json takes 37 ms, 10 take 57 ms, and all 39 packages/*/package.json take 99 ms, under the 150 ms of a full check
R27, R28 performance Nothing pins an event's work or the refusal loop's cost rejected. A call-count Files double would pin wiring, not behaviour. The behaviour is latency, measured in R2, R3 and R24
R29 reliability stdio shutdown wedges if the client closes its read end and keeps stdin open rejected by probe. In exactly that state the server exits 1 on its next publish, within 10 s
R30 reliability A ROOT that cannot be canonicalized publishes empty sets rejected. lsp_contract.rs:648 pins the missing-ROOT case, the same empty sets check gives when it selects nothing. Probe: a ROOT unreadable to the running user makes check exit 2 ("selected no files"), while the server announces that ROOT and publishes an empty set
R31 reliability No test drives stdio shutdown rejected. gritlint-agent-journey drives the real binary over stdio (apps/gritlint-e2e/src/lsp.rs:18), green in CI. In every probe, shutdown then exit with stdin still open exits 0
R32 project-standards Two computations of "the other rules of my pack" rejected. Both take the scope from RuleId::scope (domain.rs:630-632, export.rs:72-76), and export-library refuses any sibling call
R34 project-standards CONST-B4 if "the core" means the crate rejected. KTD43 (plan:252) applies CONST-B4 as "the server never opens stdio itself". main.rs:145 opens it, and lsp.rs imports no shell (CONST-B4 comply in both rounds)
R35, R36 project-standards, testing target-not-scanned is never exercised follow-up F1. The code is reachable: a rule calling log() makes check exit 2 with a/package.json: target-not-scanned: seen, and the server publishes target-not-scanned on the open document. No test constructs it. No file under crates/gritlint_core/tests or apps/gritlint-e2e/src names target-not-scanned, NotScanned or TargetNotScanned. The only producer is an engine log of DiagnosticKind::Other (decide.rs:453-456, engine.rs:104-109), mapped by GritlintError::target (error.rs:191-193). Red not shown
R38 testing Tests assert the order of diagnostics rejected. The order is the server's deterministic output, findings then refusals (lsp.rs:463-473)
R39 testing A client that leaves before initialize is untested rejected by probe. Closing stdin before initialize exits 1 (lsp.rs:60-62)
R40 testing didChange with empty contentChanges is ignored rejected. Sync is full (lsp.rs:105), so an empty array carries no text. Probe: no republish, and shutdown then exit gives 0
R41 testing A plan restricted to zero tasks is never scanned rejected by probe. With only an outside-ROOT document open, the server sends one empty set and no error
D1 delta report Two unreadable files in one event rejected by probe. In one batch, the open document names a/x.json, then a/y.json once x reads, the same one-at-a-time order as check (cannot read ./a/x.json). Across two batches, each open document names its own file in one event
D2 delta report An unreadable file seen only by single-file rules rejected by probe. Single-file tasks keep only open documents (KTD44), so the closed file is never read and the open document gets its finding while check exits 2. A closed file's findings are out of scope in the same way (R31, KTD45)
D3 delta report Non-encoding read errors are untested rejected by probe. Running as uid 65534, a mode-000 sibling gives a/locked.json: target-unreadable: Permission denied (os error 13), and check exits 2 with cannot read. The conversion matches every GritlintError::Io below ROOT with no ErrorKind branch (lsp.rs:427-433), the arm lsp_contract.rs:607 pins

Review receipt detail (from the reviewers' returns)

Both rounds ran on ce-code-review's full path (depth: full). All five declared packs loaded from the 02bf9653 pin: boundary-testing, cell-architecture, mutation-discipline, package-topology and schema-laws, 41 rule files. Each round's learnings reviewer read every rule, and its project-standards reviewer treated all 16 constitution laws. Rules that apply, with the HEAD line each reviewer quoted:

Round Pack rule Verdict Evidence
5 (1eedb94) boundary-testing/no-mocks-on-internal-glue comply crates/gritlint_core/tests/lsp_contract.rs:52, crates/gritlint_core/src/lsp.rs:54
5 (1eedb94) boundary-testing/port-fakes-pass-the-port-contract not-applicable crates/gritlint_core/src/lsp.rs:46
5 (1eedb94) boundary-testing/real-system-oracles comply apps/gritlint-e2e/src/lsp.rs:17, apps/gritlint-e2e/src/lsp.rs:175
5 (1eedb94) boundary-testing/staged-protocol-evidence comply crates/gritlint_core/src/lsp.rs:29, crates/gritlint_core/src/error.rs:122
5 (1eedb94) mutation-discipline/kill-survivors-with-contract-properties comply crates/gritlint_core/tests/export_contract.rs:142, crates/gritlint_core/tests/export_contract.rs:186
5 (1eedb94) mutation-discipline/suppress-by-shape-in-an-ignorer comply .cargo/mutants.toml:3, .cargo/mutants.toml:9
5 (1eedb94) mutation-discipline/unkillable-survivors-are-dead-code not-applicable .cargo/mutants.toml:13
5 (1eedb94) package-topology/surface-changes-are-versioned comply .changeset/gritlint-lsp-biome-export.md:2
delta (66a753d) boundary-testing/no-mocks-on-internal-glue comply crates/gritlint_core/tests/lsp_contract.rs:611
delta (66a753d) boundary-testing/real-system-oracles comply crates/gritlint_core/tests/lsp_contract.rs:637
delta (66a753d) cell-architecture/decode-never-cast comply crates/gritlint_core/src/lsp.rs:427
delta (66a753d) cell-architecture/four-channel-contracts comply crates/gritlint_core/src/error.rs:101
delta (66a753d) mutation-discipline/kill-survivors-with-contract-properties comply crates/gritlint_core/tests/lsp_contract.rs:627
delta (66a753d) package-topology/surface-changes-are-versioned comply .changeset/gritlint-lsp-biome-export.md:2 (minor), :5 (the summary; the reviewer's return cited it as :4)
delta (66a753d) schema-laws/refusals-beside-generated-laws comply crates/gritlint_core/tests/lsp_contract.rs:607
delta (66a753d) schema-laws/tagged-unions-over-state-by-presence comply crates/gritlint_core/src/error.rs:131

Round 5 marked 8 rules as applying, 6 comply and 2 judged not applicable on reading; the delta marked 8, all comply. Every other rule file was judged not applicable, each with a stated reason in the reviewer's return.

Law Round 5 (1eedb94) Delta (66a753d)
CONST-D1 comply: crates/gritlint_core/src/workspace.rs:27, crates/gritlint_core/src/domain.rs:36 comply: crates/gritlint_core/src/error.rs:101, crates/gritlint_core/src/domain.rs:829
CONST-D2 violate (ruled: round-5 table, export-construct row): crates/gritlint_core/src/export.rs:43, crates/gritlint_core/src/domain.rs:12 comply: crates/gritlint_core/src/error.rs:123, crates/gritlint_core/src/error.rs:131
CONST-D4 comply: crates/gritlint_core/src/lsp.rs:219, crates/gritlint_core/src/lsp.rs:208 comply: crates/gritlint_core/src/domain.rs:827, crates/gritlint_core/src/decide.rs:49
CONST-B1 comply: crates/gritlint_core/src/lsp.rs:46, apps/gritlint/src/main.rs:146 comply: crates/gritlint_core/src/lsp.rs:426, crates/gritlint_core/src/lsp.rs:545
CONST-B4 comply: crates/gritlint_core/src/shell.rs:8, crates/gritlint_core/src/shell.rs:119 comply: crates/gritlint_core/src/lsp.rs:2, crates/gritlint_core/src/shell.rs:128
CONST-N1 comply: crates/gritlint_core/src/export.rs:29, crates/gritlint_core/src/workspace.rs:81 comply: crates/gritlint_core/src/lsp.rs:545, crates/gritlint_core/tests/lsp_contract.rs:607
CONST-N3 comply: crates/gritlint_core/src/workspace.rs:81, crates/gritlint_core/src/shell.rs:93 comply: crates/gritlint_core/src/lsp.rs:407, crates/gritlint_core/src/lsp.rs:545
CONST-S1 comply: crates/gritlint_core/src/domain.rs:692, crates/gritlint_core/src/engine.rs:175 comply: crates/gritlint_core/src/lsp.rs:402, crates/gritlint_core/src/lsp.rs:427
CONST-S2 comply: docs/plans/2026-10-12-2025-feat-gritlint-standalone-plan.md:263, crates/gritlint_core/src/lsp.rs:46 comply: crates/gritlint_core/src/error.rs:195, crates/gritlint_core/src/lsp.rs:428
CONST-S4 comply: crates/gritlint_core/src/encode.rs:281, crates/gritlint_core/tests/common/mod.rs:12 comply: crates/gritlint_core/src/decide.rs:48, crates/gritlint_core/src/decide.rs:377
CONST-T8 comply: apps/gritlint-e2e/src/export.rs:111, apps/gritlint-e2e/src/main.rs:142 comply: crates/gritlint_core/tests/lsp_contract.rs:611, crates/gritlint_core/tests/export_contract.rs:65
CONST-T10 comply: apps/gritlint-e2e/src/export.rs:34, crates/gritlint_core/tests/export_contract.rs:186 comply: crates/gritlint_core/tests/lsp_contract.rs:627, crates/gritlint_core/tests/fixtures/export/expected/backslash-separator.grit:10
CONST-T3 comply: crates/gritlint_core/tests/export_contract.rs:82, crates/gritlint_core/tests/lsp_contract.rs:626 comply: crates/gritlint_core/tests/lsp_contract.rs:632, crates/gritlint_core/tests/lsp_contract.rs:637
CONST-T12 comply: crates/gritlint_core/src/domain.rs:691, crates/gritlint_core/src/export.rs:76 comply: crates/gritlint_core/src/lsp.rs:427, crates/gritlint_core/src/lsp.rs:428
CONST-W1 comply: apps/gritlint/src/main.rs:126, apps/gritlint/src/main.rs:129 comply: crates/gritlint_core/tests/export_contract.rs:98, crates/gritlint_core/tests/export_contract.rs:100
CONST-E7 comply: apps/gritlint-e2e/src/main.rs:143, crates/gritlint_core/tests/lsp_contract.rs:552 comply: crates/gritlint_core/tests/scan_contract.rs:181, crates/gritlint_core/tests/fixtures/export/expected/listing.txt:2

Every reviewer in both rounds returned, and none timed out. The delta round's cross-model peer finished in 915 s of its 1,210 s deadline. Served models, counted as assistant turns in each reviewer's session transcript (no Grok or xai in any):

Round Reviewer Served by
5 AdversarialPeer openrouter/deepseek/deepseek-v4.1-flash 71, minimax-code/MiniMax-M3.1-Flash-Preview 38
5 ApiContract openrouter/deepseek/deepseek-v4.1-flash 49, minimax-code/MiniMax-M3.1-Flash-Preview 4
5 Correctness openrouter/deepseek/deepseek-v4.1-flash 44, minimax-code/MiniMax-M3.1-Flash-Preview 4
5 Learnings openrouter/deepseek/deepseek-v4.1-flash 32
5 Maintainability openrouter/deepseek/deepseek-v4.1-flash 63, minimax-code/MiniMax-M3.1-Flash-Preview 4
5 Performance openrouter/deepseek/deepseek-v4.1-flash 77, minimax-code/MiniMax-M3.1-Flash-Preview 4
5 Reliability openrouter/deepseek/deepseek-v4.1-flash 48, minimax-code/MiniMax-M3.1-Flash-Preview 3
5 Standards openrouter/deepseek/deepseek-v4.1-flash 35, minimax-code/MiniMax-M3.1-Flash-Preview 5
5 Testing openrouter/deepseek/deepseek-v4.1-flash 59, minimax-code/MiniMax-M3.1-Flash-Preview 4
delta AdversarialPeer openrouter/deepseek/deepseek-v4.1-flash 51, minimax-code/MiniMax-M3.1-Flash-Preview 13
delta ApiContract openrouter/deepseek/deepseek-v4.1-flash 25, minimax-code/MiniMax-M3.1-Flash-Preview 9
delta Correctness openrouter/deepseek/deepseek-v4.1-flash 34, minimax-code/MiniMax-M3.1-Flash-Preview 7
delta Learnings openrouter/deepseek/deepseek-v4.1-flash 58
delta Performance openrouter/deepseek/deepseek-v4.1-flash 10, minimax-code/MiniMax-M3.1-Flash-Preview 8
delta Reliability openrouter/deepseek/deepseek-v4.1-flash 26, minimax-code/MiniMax-M3.1-Flash-Preview 9
delta Standards openrouter/deepseek/deepseek-v4.1-flash 31, minimax-code/MiniMax-M3.1-Flash-Preview 9
delta Testing openrouter/deepseek/deepseek-v4.1-flash 43, minimax-code/MiniMax-M3.1-Flash-Preview 9
delta Validator minimax-code/MiniMax-M3.1-Flash-Preview 14, openrouter/deepseek/deepseek-v4.1-flash 10

The delta round's merge and report leaves ran on anthropic/claude-haiku-4-5. They synthesize the reviewers' returns and do not review.

Cross-model passes on the 2026-10-13 body updates. Both used an adversarial-reviewer on DeepSeek, worked read-only, and finished without timing out; neither used Grok or xai.

  • Pass 1 (openrouter/deepseek/deepseek-v4.1-flash, 43 turns, 3m53s) checked the CI, ci-wall-clock and residual sections against the jobs API, the job logs, the 66a753d tree and the pack at 763fd663. It filed 2 P3 findings, both applied: flake-outputs-build-once-on-main allows two no-save cases, not one, and a receipt row cited the changeset's blank line 4.
  • Pass 2 (openrouter/deepseek/deepseek-v4.1-flash, 16 turns, 1m59s) checked the whole body for red evidence that is not allowed. It found none. It confirmed the unmodified-1eedb94 red by rerunning both binaries, the export.rs byte-identity, and the reading-based gaps. It filed 1 P3, applied: the red contradicts the test's diagnostics assertion (:623-629), not its first one.

ce-compound

Skipped. The durable reasoning is in the plan (KTD43 to KTD55), the contract tests (lsp_contract.rs, export_contract.rs, scan_contract.rs) and this body. No lesson from this unit passes the counterfactual bar.

Mutants (cargo mutants --workspace --list --in-diff, no mutation run)

186 of the workspace's 627 mutant ids fall in this PR's diff: lsp.rs 84, export.rs 34, decide.rs 13, workspace.rs 13, error.rs 12, domain.rs 9, engine.rs 9, shell.rs 8, encode.rs 4. main's Mutants workflow measures them after merge.

ids
crates/gritlint_core/src/decide.rs:37:5: replace plan -> ScanPlan with Default::default()
crates/gritlint_core/src/decide.rs:84:5: replace restrict -> ScanPlan with Default::default()
crates/gritlint_core/src/decide.rs:93:5: replace without -> ScanPlan with Default::default()
crates/gritlint_core/src/decide.rs:95:21: replace != with == in without
crates/gritlint_core/src/decide.rs:96:53: replace != with == in without
crates/gritlint_core/src/decide.rs:105:5: replace narrow -> ScanPlan with Default::default()
crates/gritlint_core/src/decide.rs:135:5: replace batch_mates -> BTreeSet<RelPath> with BTreeSet::new()
crates/gritlint_core/src/decide.rs:135:5: replace batch_mates -> BTreeSet<RelPath> with BTreeSet::from_iter([Default::default()])
crates/gritlint_core/src/decide.rs:142:73: replace == with != in batch_mates
crates/gritlint_core/src/decide.rs:144:30: replace != with == in batch_mates
crates/gritlint_core/src/decide.rs:364:5: replace tasks_of -> Vec<Task<'_>> with vec![]
crates/gritlint_core/src/decide.rs:364:5: replace tasks_of -> Vec<Task<'_>> with vec![Default::default()]
crates/gritlint_core/src/decide.rs:375:5: replace count_of -> RuleCount with Default::default()
crates/gritlint_core/src/domain.rs:24:9: replace ExportReason::code -> &'static str with ""
crates/gritlint_core/src/domain.rs:24:9: replace ExportReason::code -> &'static str with "xyzzy"
crates/gritlint_core/src/domain.rs:61:9: replace ExportRefusal::reason -> ExportReason with Default::default()
crates/gritlint_core/src/domain.rs:66:9: replace ExportRefusal::next_action -> &'static str with ""
crates/gritlint_core/src/domain.rs:66:9: replace ExportRefusal::next_action -> &'static str with "xyzzy"
crates/gritlint_core/src/domain.rs:72:9: replace <impl fmt::Display for ExportRefusal>::fmt -> fmt::Result with Ok(Default::default())
crates/gritlint_core/src/domain.rs:557:9: replace ParsedRule::assemble -> Self with Default::default()
crates/gritlint_core/src/domain.rs:692:9: replace CompiledRule::kind -> RuleKind with Default::default()
crates/gritlint_core/src/domain.rs:739:9: replace Sources::overlaid -> Self with Default::default()
crates/gritlint_core/src/encode.rs:247:5: replace render_github -> String with String::new()
crates/gritlint_core/src/encode.rs:247:5: replace render_github -> String with "xyzzy".into()
crates/gritlint_core/src/encode.rs:282:5: replace message_and_next_action -> String with String::new()
crates/gritlint_core/src/encode.rs:282:5: replace message_and_next_action -> String with "xyzzy".into()
crates/gritlint_core/src/engine.rs:175:9: replace Compiled::is_multifile -> bool with true
crates/gritlint_core/src/engine.rs:175:9: replace Compiled::is_multifile -> bool with false
crates/gritlint_core/src/engine.rs:236:5: replace build_library -> Result<BTreeMap<String, String>, EngineError> with Ok(BTreeMap::new())
crates/gritlint_core/src/engine.rs:236:5: replace build_library -> Result<BTreeMap<String, String>, EngineError> with Ok(BTreeMap::from_iter([(String::new(), String::new())]))
crates/gritlint_core/src/engine.rs:236:5: replace build_library -> Result<BTreeMap<String, String>, EngineError> with Ok(BTreeMap::from_iter([(String::new(), "xyzzy".into())]))
crates/gritlint_core/src/engine.rs:236:5: replace build_library -> Result<BTreeMap<String, String>, EngineError> with Ok(BTreeMap::from_iter([("xyzzy".into(), String::new())]))
crates/gritlint_core/src/engine.rs:236:5: replace build_library -> Result<BTreeMap<String, String>, EngineError> with Ok(BTreeMap::from_iter([("xyzzy".into(), "xyzzy".into())]))
crates/gritlint_core/src/engine.rs:268:5: replace parameter_definition -> String with String::new()
crates/gritlint_core/src/engine.rs:268:5: replace parameter_definition -> String with "xyzzy".into()
crates/gritlint_core/src/error.rs:137:9: replace TargetReason::code -> &'static str with ""
crates/gritlint_core/src/error.rs:137:9: replace TargetReason::code -> &'static str with "xyzzy"
crates/gritlint_core/src/error.rs:147:9: replace TargetReason::position -> Option<(u32, u32)> with None
crates/gritlint_core/src/error.rs:147:9: replace TargetReason::position -> Option<(u32, u32)> with Some((0, 0))
crates/gritlint_core/src/error.rs:147:9: replace TargetReason::position -> Option<(u32, u32)> with Some((0, 1))
crates/gritlint_core/src/error.rs:147:9: replace TargetReason::position -> Option<(u32, u32)> with Some((1, 0))
crates/gritlint_core/src/error.rs:147:9: replace TargetReason::position -> Option<(u32, u32)> with Some((1, 1))
crates/gritlint_core/src/error.rs:163:9: replace TargetRefusal<'a>::file -> &'a str with ""
crates/gritlint_core/src/error.rs:163:9: replace TargetRefusal<'a>::file -> &'a str with "xyzzy"
crates/gritlint_core/src/error.rs:168:9: replace TargetRefusal<'a>::reason -> TargetReason with Default::default()
crates/gritlint_core/src/error.rs:175:9: replace GritlintError::target -> Option<TargetRefusal<'_>> with None
crates/gritlint_core/src/error.rs:175:9: replace GritlintError::target -> Option<TargetRefusal<'_>> with Some(Default::default())
crates/gritlint_core/src/export.rs:15:5: replace find -> Result<&'a CompiledRule, ExportRefusal> with Ok(Box::leak(Box::new(Default::default())))
crates/gritlint_core/src/export.rs:18:59: replace == with != in find
crates/gritlint_core/src/export.rs:30:5: replace biome -> Result<String, ExportRefusal> with Ok(String::new())
crates/gritlint_core/src/export.rs:30:5: replace biome -> Result<String, ExportRefusal> with Ok("xyzzy".into())
crates/gritlint_core/src/export.rs:34:24: replace == with != in biome
crates/gritlint_core/src/export.rs:76:49: replace && with || in biome
crates/gritlint_core/src/export.rs:76:36: replace != with == in biome
crates/gritlint_core/src/export.rs:76:71: replace == with != in biome
crates/gritlint_core/src/export.rs:110:5: replace listing -> String with String::new()
crates/gritlint_core/src/export.rs:110:5: replace listing -> String with "xyzzy".into()
crates/gritlint_core/src/export.rs:142:9: replace Body::read -> Self with Default::default()
crates/gritlint_core/src/export.rs:152:34: delete ! in Body::read
crates/gritlint_core/src/export.rs:165:9: replace Body::visit with ()
crates/gritlint_core/src/export.rs:166:13: delete match arm "foreignFunctionDefinition" in Body::visit
crates/gritlint_core/src/export.rs:167:13: delete match arm "rewrite" | "predicateRewrite" in Body::visit
crates/gritlint_core/src/export.rs:168:13: delete match arm "nodeLike" | "predicateCall" in Body::visit
crates/gritlint_core/src/export.rs:172:13: delete match arm "patternDefinition" | "predicateDefinition" | "functionDefinition" in Body::visit
crates/gritlint_core/src/export.rs:181:9: replace Body::unsupported_language -> Option<&str> with None
crates/gritlint_core/src/export.rs:181:9: replace Body::unsupported_language -> Option<&str> with Some("")
crates/gritlint_core/src/export.rs:181:9: replace Body::unsupported_language -> Option<&str> with Some("xyzzy")
crates/gritlint_core/src/export.rs:183:13: replace || with && in Body::unsupported_language
crates/gritlint_core/src/export.rs:182:52: replace != with == in Body::unsupported_language
crates/gritlint_core/src/export.rs:187:10: delete ! in Body::unsupported_language
crates/gritlint_core/src/export.rs:187:27: replace && with || in Body::unsupported_language
crates/gritlint_core/src/export.rs:197:5: replace parse -> Result<Tree, String> with Ok(Default::default())
crates/gritlint_core/src/export.rs:203:5: replace pattern_text -> String with String::new()
crates/gritlint_core/src/export.rs:203:5: replace pattern_text -> String with "xyzzy".into()
crates/gritlint_core/src/export.rs:218:5: replace text -> String with String::new()
crates/gritlint_core/src/export.rs:218:5: replace text -> String with "xyzzy".into()
crates/gritlint_core/src/export.rs:224:5: replace render -> String with String::new()
crates/gritlint_core/src/export.rs:224:5: replace render -> String with "xyzzy".into()
crates/gritlint_core/src/export.rs:241:27: delete ! in render
crates/gritlint_core/src/export.rs:249:5: replace escape -> String with String::new()
crates/gritlint_core/src/export.rs:249:5: replace escape -> String with "xyzzy".into()
crates/gritlint_core/src/lsp.rs:23:30: delete -
crates/gritlint_core/src/lsp.rs:24:37: delete -
crates/gritlint_core/src/lsp.rs:25:31: delete -
crates/gritlint_core/src/lsp.rs:26:29: delete -
crates/gritlint_core/src/lsp.rs:38:9: replace Ending::exit_code -> u8 with 0
crates/gritlint_core/src/lsp.rs:38:9: replace Ending::exit_code -> u8 with 1
crates/gritlint_core/src/lsp.rs:60:5: replace serve -> Ending with Default::default()
crates/gritlint_core/src/lsp.rs:99:5: replace initialize_result -> serde_json::Value with Default::default()
crates/gritlint_core/src/lsp.rs:101:13: delete field position_encoding from struct ServerCapabilities expression in initialize_result
crates/gritlint_core/src/lsp.rs:102:13: delete field text_document_sync from struct ServerCapabilities expression in initialize_result
crates/gritlint_core/src/lsp.rs:104:21: delete field open_close from struct TextDocumentSyncOptions expression in initialize_result
crates/gritlint_core/src/lsp.rs:105:21: delete field change from struct TextDocumentSyncOptions expression in initialize_result
crates/gritlint_core/src/lsp.rs:106:21: delete field save from struct TextDocumentSyncOptions expression in initialize_result
crates/gritlint_core/src/lsp.rs:121:5: replace refuse with ()
crates/gritlint_core/src/lsp.rs:133:5: replace until_exit -> bool with true
crates/gritlint_core/src/lsp.rs:133:5: replace until_exit -> bool with false
crates/gritlint_core/src/lsp.rs:141:52: replace match guard notification.method == Exit::METHOD with true in until_exit
crates/gritlint_core/src/lsp.rs:141:52: replace match guard notification.method == Exit::METHOD with false in until_exit
crates/gritlint_core/src/lsp.rs:141:72: replace == with != in until_exit
crates/gritlint_core/src/lsp.rs:151:5: replace send -> bool with true
crates/gritlint_core/src/lsp.rs:151:5: replace send -> bool with false
crates/gritlint_core/src/lsp.rs:162:9: replace Encoding::negotiate -> Self with Default::default()
crates/gritlint_core/src/lsp.rs:175:9: replace Encoding::kind -> PositionEncodingKind with Default::default()
crates/gritlint_core/src/lsp.rs:182:9: replace Encoding::position -> Position with Default::default()
crates/gritlint_core/src/lsp.rs:193:5: replace utf16_column -> u32 with 0
crates/gritlint_core/src/lsp.rs:193:5: replace utf16_column -> u32 with 1
crates/gritlint_core/src/lsp.rs:201:41: replace < with == in utf16_column
crates/gritlint_core/src/lsp.rs:201:41: replace < with > in utf16_column
crates/gritlint_core/src/lsp.rs:201:41: replace < with <= in utf16_column
crates/gritlint_core/src/lsp.rs:238:9: replace Server<'_>::run -> Ending with Default::default()
crates/gritlint_core/src/lsp.rs:260:9: replace Server<'_>::handle -> Handled with Default::default()
crates/gritlint_core/src/lsp.rs:268:9: replace Server<'_>::request -> Handled with Default::default()
crates/gritlint_core/src/lsp.rs:268:27: replace == with != in Server<'_>::request
crates/gritlint_core/src/lsp.rs:293:9: replace Server<'_>::send with ()
crates/gritlint_core/src/lsp.rs:293:12: delete ! in Server<'_>::send
crates/gritlint_core/src/lsp.rs:299:9: replace Server<'_>::notify with ()
crates/gritlint_core/src/lsp.rs:303:9: replace Server<'_>::notification -> Handled with Default::default()
crates/gritlint_core/src/lsp.rs:304:13: delete match arm DidOpenTextDocument::METHOD in Server<'_>::notification
crates/gritlint_core/src/lsp.rs:308:13: delete match arm DidChangeTextDocument::METHOD in Server<'_>::notification
crates/gritlint_core/src/lsp.rs:314:13: delete match arm DidSaveTextDocument::METHOD in Server<'_>::notification
crates/gritlint_core/src/lsp.rs:315:13: delete match arm DidCloseTextDocument::METHOD in Server<'_>::notification
crates/gritlint_core/src/lsp.rs:319:13: delete match arm Exit::METHOD in Server<'_>::notification
crates/gritlint_core/src/lsp.rs:325:9: replace Server<'_>::open -> Handled with Default::default()
crates/gritlint_core/src/lsp.rs:329:20: replace += with -= in Server<'_>::open
crates/gritlint_core/src/lsp.rs:329:20: replace += with *= in Server<'_>::open
crates/gritlint_core/src/lsp.rs:336:9: replace Server<'_>::close -> Handled with Default::default()
crates/gritlint_core/src/lsp.rs:342:9: replace Server<'_>::relative -> Option<RelPath> with None
crates/gritlint_core/src/lsp.rs:342:9: replace Server<'_>::relative -> Option<RelPath> with Some(Default::default())
crates/gritlint_core/src/lsp.rs:342:61: replace != with == in Server<'_>::relative
crates/gritlint_core/src/lsp.rs:353:9: replace Server<'_>::publish with ()
crates/gritlint_core/src/lsp.rs:371:9: replace Server<'_>::diagnose -> Result<BTreeMap<RelPath, Vec<Diagnostic>>, GritlintError> with Ok(BTreeMap::new())
crates/gritlint_core/src/lsp.rs:371:9: replace Server<'_>::diagnose -> Result<BTreeMap<RelPath, Vec<Diagnostic>>, GritlintError> with Ok(BTreeMap::from_iter([(Default::default(), vec![])]))
crates/gritlint_core/src/lsp.rs:371:9: replace Server<'_>::diagnose -> Result<BTreeMap<RelPath, Vec<Diagnostic>>, GritlintError> with Ok(BTreeMap::from_iter([(Default::default(), vec![Default::default()])]))
crates/gritlint_core/src/lsp.rs:400:9: replace Server<'_>::scan -> Result<(Outcome, Vec<Refusal>), GritlintError> with Ok((Default::default(), vec![]))
crates/gritlint_core/src/lsp.rs:400:9: replace Server<'_>::scan -> Result<(Outcome, Vec<Refusal>), GritlintError> with Ok((Default::default(), vec![Default::default()]))
crates/gritlint_core/src/lsp.rs:423:9: replace Server<'_>::sources -> Result<Sources, GritlintError> with Ok(Default::default())
crates/gritlint_core/src/lsp.rs:428:17: replace && with || in Server<'_>::sources
crates/gritlint_core/src/lsp.rs:450:9: replace Server<'_>::diagnostics -> Vec<Diagnostic> with vec![]
crates/gritlint_core/src/lsp.rs:450:9: replace Server<'_>::diagnostics -> Vec<Diagnostic> with vec![Default::default()]
crates/gritlint_core/src/lsp.rs:452:44: replace == with != in Server<'_>::diagnostics
crates/gritlint_core/src/lsp.rs:466:57: replace == with != in Server<'_>::diagnostics
crates/gritlint_core/src/lsp.rs:477:9: replace Server<'_>::finding -> Diagnostic with Default::default()
crates/gritlint_core/src/lsp.rs:496:9: replace Server<'_>::refusal -> Diagnostic with Default::default()
crates/gritlint_core/src/lsp.rs:497:37: replace match guard refusal.file == *path with true in Server<'_>::refusal
crates/gritlint_core/src/lsp.rs:497:37: replace match guard refusal.file == *path with false in Server<'_>::refusal
crates/gritlint_core/src/lsp.rs:497:50: replace == with != in Server<'_>::refusal
crates/gritlint_core/src/lsp.rs:510:9: replace Server<'_>::send_diagnostics with ()
crates/gritlint_core/src/lsp.rs:518:9: replace Server<'_>::announce with ()
crates/gritlint_core/src/lsp.rs:528:9: replace Server<'_>::log with ()
crates/gritlint_core/src/lsp.rs:536:5: replace selected_open -> BTreeSet<RelPath> with BTreeSet::new()
crates/gritlint_core/src/lsp.rs:536:5: replace selected_open -> BTreeSet<RelPath> with BTreeSet::from_iter([Default::default()])
crates/gritlint_core/src/lsp.rs:551:5: replace drop_refused_target -> Result<(crate::domain::ScanPlan, Vec<RelPath>), GritlintError> with Ok((Default::default(), vec![]))
crates/gritlint_core/src/lsp.rs:551:5: replace drop_refused_target -> Result<(crate::domain::ScanPlan, Vec<RelPath>), GritlintError> with Ok((Default::default(), vec![Default::default()]))
crates/gritlint_core/src/lsp.rs:556:24: replace == with != in drop_refused_target
crates/gritlint_core/src/lsp.rs:564:5: replace refusal_of -> Option<Refusal> with None
crates/gritlint_core/src/lsp.rs:564:5: replace refusal_of -> Option<Refusal> with Some(Default::default())
crates/gritlint_core/src/lsp.rs:576:5: replace diagnostic -> Diagnostic with Default::default()
crates/gritlint_core/src/lsp.rs:577:9: delete field range from struct Diagnostic expression in diagnostic
crates/gritlint_core/src/lsp.rs:578:9: delete field severity from struct Diagnostic expression in diagnostic
crates/gritlint_core/src/lsp.rs:579:9: delete field code from struct Diagnostic expression in diagnostic
crates/gritlint_core/src/lsp.rs:580:9: delete field source from struct Diagnostic expression in diagnostic
crates/gritlint_core/src/lsp.rs:581:9: delete field message from struct Diagnostic expression in diagnostic
crates/gritlint_core/src/lsp.rs:587:5: replace params -> Option<P> with None
crates/gritlint_core/src/lsp.rs:587:5: replace params -> Option<P> with Some(Default::default())
crates/gritlint_core/src/shell.rs:87:5: replace absolute -> Result<PathBuf, GritlintError> with Ok(Default::default())
crates/gritlint_core/src/shell.rs:97:5: replace load_workspace -> Result<Workspace, GritlintError> with Ok(Default::default())
crates/gritlint_core/src/shell.rs:98:50: replace || with && in load_workspace
crates/gritlint_core/src/shell.rs:114:5: replace select -> Result<SelectedFiles, GritlintError> with Ok(Default::default())
crates/gritlint_core/src/shell.rs:121:9: replace <impl lsp::Files for Disk>::load -> Result<Workspace, GritlintError> with Ok(Default::default())
crates/gritlint_core/src/shell.rs:125:9: replace <impl lsp::Files for Disk>::select -> Result<SelectedFiles, GritlintError> with Ok(Default::default())
crates/gritlint_core/src/shell.rs:129:9: replace <impl lsp::Files for Disk>::read -> Result<Sources, GritlintError> with Ok(Default::default())
crates/gritlint_core/src/shell.rs:133:9: replace <impl lsp::Files for Disk>::absolute -> Result<PathBuf, GritlintError> with Ok(Default::default())
crates/gritlint_core/src/workspace.rs:19:9: replace WorkspaceInputs::config_path -> PathBuf with Default::default()
crates/gritlint_core/src/workspace.rs:34:9: replace Workspace::root -> &Path with Box::leak(Box::new(Default::default()))
crates/gritlint_core/src/workspace.rs:39:9: replace Workspace::rules -> &NonEmpty<CompiledRule> with Box::leak(Box::new(NonEmpty::new()))
crates/gritlint_core/src/workspace.rs:39:9: replace Workspace::rules -> &NonEmpty<CompiledRule> with Box::leak(Box::new(NonEmpty::from_iter([Default::default()])))
crates/gritlint_core/src/workspace.rs:39:9: replace Workspace::rules -> &NonEmpty<CompiledRule> with Box::leak(Box::new(NonEmpty::new(Default::default())))
crates/gritlint_core/src/workspace.rs:39:9: replace Workspace::rules -> &NonEmpty<CompiledRule> with Box::leak(Box::new(NonEmpty::from(Default::default())))
crates/gritlint_core/src/workspace.rs:44:9: replace Workspace::ignore -> &IgnoreGlobs with Box::leak(Box::new(Default::default()))
crates/gritlint_core/src/workspace.rs:48:9: replace Workspace::select -> Result<SelectedFiles, GritlintError> with Ok(Default::default())
crates/gritlint_core/src/workspace.rs:63:9: replace Configured::rule_paths -> &[PathBuf] with Vec::leak(Vec::new())
crates/gritlint_core/src/workspace.rs:63:9: replace Configured::rule_paths -> &[PathBuf] with Vec::leak(vec![Default::default()])
crates/gritlint_core/src/workspace.rs:71:9: replace Configured::load -> Result<Workspace, GritlintError> with Ok(Default::default())
crates/gritlint_core/src/workspace.rs:85:5: replace configure -> Result<Configured, GritlintError> with Ok(Default::default())
crates/gritlint_core/src/workspace.rs:110:5: replace resolve_against -> PathBuf with Default::default()

CI wall clock (jobs API): x86_64 12m05s against the 10-minute PR target

Target: every pull-request job finishes within 10 minutes (plan R27; ci-wall-clock pr-gate-runs-only-what-the-diff-affects). On run 38307881354 at 66a753d, Nix · x86_64-linux took 12m05s, 2m05s over. Every other job finished under the target. Budgets are measured, not enforced, so timeout-minutes stays at the uniform 60 (KTD53).

Job (run 38307881354, attempt 1) Time
Nix · x86_64-linux (job 115006212507) 12m05s
Nix · aarch64-linux (job 115006212352) 6m33s
A change under crates, apps or packs adds an intent 6s
Tarball and binaries match across systems 6s
Changeset Check (run 38307881969) 2m16s

x86_64, step by step (started_at/completed_at of each step from GET /repos/systemfsoftware/gritlint/actions/runs/38307881354/jobs):

Step Time What happened (job log)
1-3 Set up job, checkout, install Nix 7 s
4 An unsupported system is refused 7 s first verdict step, starting 8 s into the job
5 Every flake output for this system 1m21s evaluates the 31 targets' outPaths
6 Nix store cache 14 s no exact hit for nix-store-x86_64-linux-88683b6d…. The prefix hit was this PR's own entry …f219aea9… (2.30 GB), saved at 20:04:56Z by run 38300038684 at 30af161, before #17 stopped pull-request saves
7 Load the cached store paths 20 s nix copy from the restored bundle
9 Build every flake output for this system 9m45s 31 derivations built, 21 paths fetched
10-17 Benchmark summary, tarball rebuild, upload, cargo deny, gate journey, conventions 8 s export and save skipped (pull request)

Inside step 9, from the derivation log prefixes (UTC):

  • 21:03:05-21:08:10. The dependency derivations rebuild: gritlint-deps 5m05s, gritlint-linux-x64-deps 4m55s, and gritlint-linux-arm64-deps (cross) 3m29s (21:06:25-21:09:54).
  • 21:08:12-21:11:17. The workspace builds: gritlint 2m21s, gritlint-linux-x64 2m21s, gritlint-nextest 1m30s, the gritlint-bench binary 1m11s, and gritlint-linux-arm64 (cross) 1m10s.
  • 21:11:21-21:12:40. checks.x86_64-linux.gritlint-bench, the PR3 corpus benchmark, runs as the last derivation for 1m19s.

Cause: structural, not a cold cache after #17. The five crane dependency derivations take Cargo.lock as input, and this PR changes it: lsp-server and lsp-types add 68 lines against main, and 0c45d15 gave apps/gritlint its own lsp-server edge. No cache entry on any ref holds these derivations:

So every run of this PR since 0c45d15 rebuilds them. Run 38304323655 at 1eedb94 had the same Cargo.lock as 66a753d (no dependency input changed between the two). It rebuilt the same five dependency derivations, identical store hashes, that run 38307881354 rebuilt, and took 10m18s. Two consecutive runs at the same inputs were both cold for dependencies. That pair is the evidence that a re-run cannot be warm. A re-run of 38307881354 restores the same f219aea9 entry and rebuilds the same 31 derivations, so no warm re-run number exists to record. I also could not start one: gh run rerun 38307881354, POST /actions/runs/38307881354/rerun, POST /actions/jobs/115006212507/rerun and a workflow_dispatch of ci.yml each return 403 "Resource not accessible by personal access token".

Measured context (x86_64 and aarch64 Nix jobs, jobs API):

Run Ref Dependency derivations x86_64 aarch64
38307881354 #20 66a753d rebuilt (prefix hit on the PR's own entry) 12m05s 6m33s
38304323655 #20 1eedb94, same Cargo.lock rebuilt, same 5 store hashes 10m18s 6m22s
38300038684 #20 30af161, pre-#17 rule (exported and saved) rebuilt 13m28s 7m02s
38267386762 main push #18 (workspace versions) rebuilt 8m53s 6m47s
38288658641 main push #19 (no cache) rebuilt, 285 derivations 12m04s 6m54s
38262833748 main push #16 cached, 22 derivations built 6m44s 4m09s
38291526750 PR #17 (ci.yml and docs) cached, build step 9 s 2m44s 1m42s
38301803916 main push #17 exact hit, nothing built 2m23s 1m43s

Follow-up F3. Owner: the gritlint ci-wall-clock repin, which enrols the pack and is queued after this PR (not started here). Trigger: that repin's PR. Under pr-gate-runs-only-what-the-diff-affects, it must bring a dependency-changing pull request's x86_64 job under 10 minutes or record the measured exception. Until then, each PR whose Cargo.lock differs from main's newest bundle pays this rebuild. On this run that was about 5 minutes of critical path, plus 1m19s for the corpus benchmark at the tail. After this PR merges, main's push saves a bundle with the new crates, and later PRs that leave Cargo.lock alone restore it.

Readiness

Head 66a753d8e9d5429a107f0b0280445d1cc40ebdc5, unchanged by the 2026-10-13 body update: CI run 38307881354 and Changeset Check 38307881969 green; delta receipt 20261012-214525-6e25ba95 Ready to merge; contains main at 0e2e07b. #19, the layer below, has merged, so this is the only open layer on trunk main. Merging needs the one approving review the main ruleset requires; the operator merges.

Re-issued as a new dated plan (plan-discipline: a started plan is frozen).
Resolves Q2 (static glibc, both targets built on each system; musl measured
80x slower) and Q10 (release wiring through pnpm-release-management at
8cd6e830). Adds R29 (cross-system byte identity) and R30 (tarball binary
within 1.25x of gritlint-unwrapped), KTD32-KTD38, and open questions Q12-Q14.
Applies report-only ce-doc-review findings. Solution docs point at the new
plan path.
Q13 (b): KTD39 adds scripts/tools/intent-gate.sh, a changeset-intent ci.yml
job and the gritlint-intent-gate flake-check case; R24 names the check.
Q14: actions/download-artifact pinned by full SHA joins KTD9 for
tarballs-match. Q12: no fallback approved; a static-glibc link failure or
crash stops U9 with the failure.
Pin pnpm-release-management at b9b4a1d48cc1 (its main; 8cd6e830 lacks
#54) in R24, KTD9, KTD35 and the release Key Decision, with the cited
release.yml, changeset-check.yml and README lines re-read there. Add
KTD40 (timeout-minutes: 60 on every job PR6 adds or changes; jobs that
only call a reusable workflow take the called workflow's), KTD41 (the
dev shell's cargo deny setup time, before and after) and KTD42 (the
first-release probe with the pinned planner). Record the d96dc51 base
merge and refresh the line references against the merged tree.
…s private

npm/gritlint/bin/gritlint.js maps linux x64/arm64 to the binary beside it,
passes arguments, stdio, exit status and terminating signals through, and
exits 2 naming linux-x64 and linux-arm64 on any other platform. The manifest
gains bin, files, repository and an inert publishConfig; the README's
install and release sections describe the tarball and changesets.

Plan: docs/plans/2026-10-10-1945-feat-gritlint-standalone-plan.md (U9, KTD34).
nix/gritlint-static.nix builds gritlint-linux-x64 and gritlint-linux-arm64
with +crt-static, each system building one target natively and the other
through pkgsCross. The tarball comes from pnpm-release-management's
mkPnpmWorkspacePackages at b9b4a1d over a one-member pnpm workspace, with
the binaries laid into npm/gritlint/bin; the flake exports it as
gritlint-tarball beside workspace-tarballs and pnpm-store, and evaluation
throws if a release output would shadow one of gritlint's own packages.
checks.<system>.gritlint-tarball-journey installs the tarball offline and
runs gate-journey.sh against it; checks.<system>.gritlint-intent-gate pins
scripts/tools/intent-gate.sh. The dev shell gains node, pnpm, the sandbox
and SANDBOX_PNPM_STORE for the reusable release workflow.

Plan: docs/plans/2026-10-10-1945-feat-gritlint-standalone-plan.md (U9, KTD32-KTD37, KTD39).
…systems

release.yml and changeset-check.yml call pnpm-release-management's reusable
workflows at b9b4a1d, configured by release.jsonc (changesets, a cargo
surface for Cargo.toml and Cargo.lock, the paths gate). ci.yml uploads each
system's tarball and binaries after a --rebuild, tarballs-match fails on any
file aarch64-linux builds differently from x86_64-linux, and
changeset-intent fails a pull request that changes crates/, apps/ or packs/
without a new .changeset/*.md.

Plan: docs/plans/2026-10-10-1945-feat-gritlint-standalone-plan.md (U9, KTD36, KTD38-KTD40).
cmp -l exits non-zero for files that differ, which under pipefail ended
the step after the first binary, so the second was never compared.
…e glibc

rustc chooses rust-lld or GNU ld by the host it runs on, and the native and
cross glibc are separate derivations whose store paths libc.a compiles in,
so x86_64-linux and aarch64-linux built different bytes for both binaries
(run 38135793197). Both targets now pass -fuse-ld=bfd to the C driver (rust
1.99 stabilises its own linker-selecting flags only for
x86_64-unknown-linux-gnu) and link nixpkgs.legacyPackages.<target>.glibc.static,
which the other host substitutes from cache.nixos.org.

Plan: docs/plans/2026-10-10-1945-feat-gritlint-standalone-plan.md (U9, KTD32; R29 ruling).
…flags

Temporary, reverted by the fix. Each target's build on each system writes
GNU ld's link map, the cc crate's compiler commands, and the target C
compiler's predefined macros and -v driver line under the flags the cc
crate used for tree-sitter's core. tarballs-match prints, per target, the
input sections whose size differs between the two systems' builds, and
the diffs of the macros, driver lines and commands.
Cargo mixes every dependency's metadata into a unit's -C metadata, and
build scripts and proc macros are host units whose hash includes the
host (cargo src/compiler/build_runner/compilation_files.rs:849-856,
929-934). The same target crate therefore got different symbol names on
each system. A RUSTC_WRAPPER replaces -C metadata for target units only
with a hash of crate name, package name and version, crate type, target
and sorted features; host units and every other argument pass through.
Each crane build refuses two target units sharing a normalized hash.

Removes the round-3 diagnostic (link maps, cc commands and macros, and
their comparison step). The plan is superseded by a new dated plan whose
KTD32 records rounds 1 to 4.

Plan: docs/plans/2026-10-11-1301-feat-gritlint-standalone-plan.md (U9, KTD32).
The pinned planner gives phase=release on the first main push, owing
@systemfsoftware/gritlint@0.1.0, which has no tag. Without its changelog
the release refuses after pushing the tag (api-extractor-effect@8e7bd7c).
The file also creates .changeset/changelogs, which version-management
bump needs before it can write the next version's changelog.

Plan: docs/plans/2026-10-11-1301-feat-gritlint-standalone-plan.md (U9, KTD42).
- intent-gate cases move to scripts/tools/intent-gate.test.sh, run by the
  flake check like mutants-plan.test.sh, with per-case ok/not ok lines
- the tarball step builds with --rebuild once instead of twice
- upload-artifact uses the pin mutants.yml already uses
- tarballs-match drops a bash default its one step does not need
- gritlint-static.nix names the C env target once and prints an absent
  passthrough unit with a shell default

Behaviour unchanged: every x86_64-linux check builds; the intent-gate
test fails its two refusal cases against an always-exit-0 gate.
…metadata wrapper

Code review rulings on PR #15 (accepted findings):
- tarballs-match compares the union of both systems' files, fails when
  either artifact is missing, empty or one-sided; upload-artifact sets
  if-no-files-found: error
- the static build fails when the rustc wrapper normalized no target unit
- KTD39 states the :(glob) pathspec the intent gate runs, and the
  test script that is its flake check

docs(solutions): the three host-dependent inputs behind R29 (linker,
cross glibc, cargo -C metadata) and the invariant that removes them.

Plan: docs/plans/2026-10-11-1301-feat-gritlint-standalone-plan.md (U9, R29, KTD38, KTD39).
… PR6 plan

Deepens U10 for Done 6 on feat/lsp-biome-export (stacked on #15 at 0dfe794):
R31-R38, AE12-AE17, KTD43-KTD52, Q1/Q11/Q15 with recommended answers.
Plan only: no product code. Repoints four docs/solutions citations to the
renamed plan.
# Conflicts:
#	docs/plans/2026-10-10-1309-feat-gritlint-standalone-plan.md
#	docs/plans/2026-10-11-1301-feat-gritlint-standalone-plan.md
#	docs/plans/2026-10-11-2312-feat-gritlint-standalone-plan.md
#	docs/solutions/build-errors/cross-compiled-rust-binary-differs-from-native-build.md
#	docs/solutions/logic-errors/flake-sandbox-check-compared-two-builds-of-one-file.md
#	docs/solutions/performance-issues/fetch-cargo-vendor-given-whole-source-rebuilds-crane-deps.md
#	docs/solutions/performance-issues/gritql-target-language-debug-key-embeds-node-table.md
Root plan gate of 2026-10-12: R34/AE15/KTD47 narrow config refusals to what
check does today (one shared loader, no new reason codes); R38/KTD48/AE18
specify a refused file, its batch mates and the rescan; R36/AE16/KTD51 pin
one register_diagnostic call per finding on a committed two-range fixture in
gritlint's engine and state what Biome-side acceptance is not proven (Q1,
engine-only, no new executable); R35 adds export-unknown-rule; KTD44 states
per-directory multifile batching; KTD53 states warm 8m08s and cold 9m52s
against R27's 10 minutes, measured not enforced. Round-2 report-only
ce-doc-review findings folded in.
PR7 (Unit B1 Done 6; plan docs/plans/2026-10-11-2312-feat-gritlint-standalone-plan.md
U10, R25, R26, R31 to R38).

- workspace.rs: one loader shared by check, lsp and export biome (KTD47), so a
  config check refuses is refused identically everywhere.
- lsp.rs: Language Server Protocol over stdio via lsp-server 0.10 and
  lsp-types 0.97. Each event re-walks, plans, and scans only the tasks holding
  an open document (decide::restrict, KTD44); open documents overlay disk
  (Sources::overlaid). A target refusal drops that file and every directory
  batch holding it (decide::without, R38) and is reported on the refused file
  and its open batch mates. UTF-8 positions when offered, else UTF-16.
- export.rs: Biome GritQL plugin for single-file css/json/js rules; refusal
  codes export-unknown-rule, -multifile, -language, -construct, -rewrite,
  -library (R35). Verified in gritlint's engine only (Q1, KTD51).
- gritlint-e2e drives `gritlint lsp` over real stdio against the sandboxed
  binary: loadable root, refused root, and the exit-code contract.
- Simplify pass: plan narrowing by value, per-document line index, one
  definition of the parameter-pattern shape and of each target reason code.
…ions

The version PR (#18) moved the workspace members to 0.2.0 in Cargo.lock,
and rustPlatform.fetchCargoVendor copies the whole lock into its
fixed-output staging derivation, so its hash broke and every flake
output failed on main.

crane's vendorCargoDeps reads only name, version, source and checksum of
third-party lock entries: registry crates come by their lock checksums
and the GritQL engine by builtins.fetchGit at the locked rev. No
fixed-output hash covers the vendor, and a version bump leaves its
drvPath unchanged.

overrideVendorGitCheckout prepares the engine checkout before crane
scans it: it deletes the upstream grammar copies in
resources/language-submodules, so crane keeps the copies the engine's
manifests build, and the nested manifests under
resources/language-metavariables, which made `cargo package --list`
drop tree-sitter-php's and tree-sitter-md's sources.

Docs drop the cargoDeps.hash re-pin steps and the source-git-0 path.
…rsions

Records why #18 broke the vendor hash, why crane's vendorCargoDeps
replaced fetchCargoVendor and not importCargoLock, the two checkout
preparations the GritQL engine needs, and the version-churn gate.
The 2026-10-10 lock-only vendor lesson now points at it and drops its
hash re-pin step. The gritlint.nix header no longer claims a version
bump keeps the dependency derivations: their names carry the version.
Review rulings for PR7 (ce-code-review, report-only):

- publish handed a path's diagnostics to the first open document that
  resolved to it and an empty set to every other URI of the same file (a
  symlinked path beside the real one). Each alias now gets a copy.
  lsp_contract::two_uris_for_one_file_both_get_its_diagnostics fails on the
  previous code (timeout waiting for the second set) and passes now.
- gritlint-e2e drives `gritlint export biome` against the sandboxed binary:
  the listing (exit 0), one exportable consumer rule (stdout equals a
  hand-written plugin), and two refusals (exit 2, empty stdout, one
  `ERROR gritlint: <ruleId>: <reasonCode>: ... Next action: ...` line).
- lsp_contract pins didSave republishing the buffer, empty sets for an
  out-of-root path and an `untitled:` URI, and a rangeless finding at 0:0.
… into feat/lsp-biome-export

# Conflicts:
#	docs/solutions/performance-issues/fetch-cargo-vendor-given-whole-source-rebuilds-crane-deps.md
#	nix/gritlint.nix
@systemfsoftware-maker
systemfsoftware-maker added this pull request to stack #21 October 12, 2026 16:27
@systemfsoftware-maker systemfsoftware-maker changed the title feat/lsp biome export feat(gritlint): gritlint lsp and gritlint export biome Oct 12, 2026
The hashless engine fetch made crane call builtins.fetchGit with every
submodule during evaluation. CI evaluates before it restores the store
cache, so each Nix job's "Every flake output" step went from about 50 s
to 618 s (x86_64) and 625 s (aarch64) on run 38273376142.

The engine now gets one crane outputHashes entry, keyed by its lock
source string (git+<repo>?rev=R#R), so a version PR never moves it and
only an engine re-pin changes engineHash. crane then fetches with
fetchgit at build time, about 60 s cold, served from the store cache.
engineSource is the identical fetchgit, so evaluation no longer
fetches the engine at all.

Two flake checks pin the crane behaviours the vendor relies on, as
boundary-testing/pin-dependency-semantics requires:
- gritlint-vendor-ignores-workspace-versions: vendorFor over the lock
  with every workspace member's version moved keeps the vendor's
  drvPath; given main's fetchCargoVendor vendor it fails evaluation.
- gritlint-vendor-engine-copies: each locked crate marzano-language's
  manifest takes by path is vendored once and byte-identical to that
  copy; a vendor with the upstream tree-sitter-css fails it.

The superseding plan records the pack and constitution comparison.
systemfsoftware-maker added a commit that referenced this pull request Oct 12, 2026
… refresh

Review of PR #17 (cross-model peer) flagged that 'a pull request restore
counts as an access' had no source. The caches API shows it: main's
x86_64 entry's last_accessed_at moved to the minute #17's pull request
job restored it. Symptoms also gain the live recurrence of 2026-10-12,
when #19 and #20, still on the old rule, left no refs/heads/main entry.
Base automatically changed from fix/vendor-lockfile-independent to main October 12, 2026 18:06
Review run 20261012-165733-1e7bdfca (adversarial peer on MiniMax/DeepSeek):
- lsp: after `shutdown` the server answers every request with InvalidRequest
  and waits for `exit` (exit 0) or the end of input (exit 1); a stray message
  no longer wedges it (#1).
- lsp: two URIs of one file follow whichever buffer was edited last, not URI
  sort order (#2).
- lsp: a failed write to the client ends the server instead of scanning on (#12).
- lsp_contract pins MethodNotFound, InvalidRequest after shutdown and
  ServerNotInitialized after a refused initialize (#11).
- export: the multifile refusal and the sibling-call refusal read the parse
  tree, so a commented `multifile` line or a call to a pattern a sibling rule
  defines is decided correctly; a rule's own helper exports (#3, #5, #9).
- README: the `export biome` synopsis names `--rule` (#16).
# Conflicts:
#	docs/plans/2026-10-12-1740-fix-vendor-workspace-version-independent-plan.md
#	docs/solutions/build-errors/fixed-output-cargo-vendor-hash-covers-workspace-versions.md
#	docs/solutions/performance-issues/fetch-cargo-vendor-given-whole-source-rebuilds-crane-deps.md
#	nix/gritlint-vendor-checks.nix
#	nix/gritlint-vendor-engine-copies.py
#	nix/gritlint.nix
ryanleecode pushed a commit that referenced this pull request Oct 12, 2026
* docs(plans): save the Nix store cache on main only

Pull request runs keep restoring by the nix-store-<system>- prefix,
which reaches main, and stop exporting and saving. Plan only; U1 is a
two-line if: change to .github/workflows/ci.yml. Reviewed report-only
(ce-doc-review, 4 reviewers); findings are ruled in the hand-off, not
applied.

* docs(plans): refresh store-cache plan for #15 and #16 on main

#15 (5e29691) and #16 (f552830) merged first. Update the renamed
standalone plan path, the merged-in base, the merge-order questions,
the ci.yml line references of the two conditioned steps, and the
V1/V2/V4 sources, which main pushes made before this PR now supply.

* ci: save the Nix store cache on main only

A pull request's cache is scoped to its merge ref, so no other run
could restore it, yet each one took up to 4 GB and evicted main's
bundle (10.5 GB in 6 PR-only entries on 2026-10-11). #16 then
restored nothing and its x86_64 job took 592 s.

`Export the unsigned store paths` and `Save the Nix store cache` now
also require `github.ref == 'refs/heads/main'` (plan KTD1). Every run
still restores by the `nix-store-<system>-` prefix, which reaches
main's newest bundle; pull requests skip the export and the save.
Nothing else changes: no action, pin, key or timeout-minutes.

* docs(solutions): pull request cache saves evict main's Nix store bundle

Records why only main saves the store bundle: a pull request's Actions
cache lives on refs/pull/N/merge, which no other run can restore, yet it
counts against the 10 GB limit and evicted main's bundle. Includes the
jobs API wall times before and after (#16 592 s, #17 316 s on x86_64),
the alternatives rejected in the plan, and a review gate with the grep
that lists every cache save step.

* docs(plans): supersede the store-cache plan with a default-branch save rule

The full-path review of PR #17 found the shipped `refs/heads/main`
literal against CONST-T12, and the release flow's workflow_dispatch on
changeset-release/main as a second non-default writer (run
38266314981). U1 had executed, so a new dated plan carries the corrected
contract: R8 and KTD1 derive the default branch from the event, KTD3 and
AE4/AE5 account for legacy entries on other refs, V5/V6 take the first
output-changing main push after merge, and Q5 asks the operator to delete
the legacy entries.

* ci: derive the default branch in the store-cache save guard

`Export the unsigned store paths` and `Save the Nix store cache` compare
`github.ref` with `refs/heads/<default_branch>` from the event payload
instead of the literal `refs/heads/main`. The restore fallback already
follows the default branch by what it is; a second, literal copy of that
fact could be left behind by a rename, stopping every save while every
job stays green (CONST-T12). Behaviour on main is unchanged.

The solution doc states the derived condition, names the release flow's
dispatch as a non-pull-request writer, and corrects the claim that the
cache holds default-branch generations alone while legacy entries remain.

* docs(solutions): record #19/#20 evicting main's bundle and the access refresh

Review of PR #17 (cross-model peer) flagged that 'a pull request restore
counts as an access' had no source. The caches API shows it: main's
x86_64 entry's last_accessed_at moved to the minute #17's pull request
job restored it. Symptoms also gain the live recurrence of 2026-10-12,
when #19 and #20, still on the old rule, left no refs/heads/main entry.

* docs(solutions): state the observed restore and access times exactly

The previous wording said last_accessed_at moved 'to the minute' a pull
request job restored main's entry. The job logs put the restores at
16:37:36Z and 16:38:12Z and the caches API reported access at 16:39:05Z
and 16:38:59Z, so the doc now states those times.
…son types

Review run 20261012-194950-133db7d1 (constitution treatment, pack rules,
cross-model peer):
- CONST-B1/B4: `workspace` is pure (`configure` names the next read,
  `Configured::load` takes the read rule sources, `Workspace::select` takes
  the walk). `shell::load_workspace`/`shell::select` perform the reads, and
  `shell::Disk` implements the `lsp::Files` port the server declares. The
  app's composition root opens stdio and wires `shell::Disk`; no core module
  imports `shell`.
- CONST-D1/D2: `TargetReason` (position only on a parse failure) and
  `ExportReason` (the six published codes) replace open `&'static str`
  codes. Rendered text is unchanged.
- lsp: the no-progress guard returns the scan's own error; a ROOT that
  cannot be canonicalized serves from the given path, so a missing ROOT
  initializes and selects nothing, as `check` does.
- Tests: a position-less `target-too-large` sibling refusal at 0:0, the
  missing ROOT, jsx and bare js exports; one shared test pack loader.
…d design

Review run 20261012-200921-5115f566 at 0c45d15:
- correctness P2: `CompiledRule::kind` reads the engine's
  `Problem::is_multifile`, replacing the first-line `rule_kind` test, so a
  `multifile` block after a comment is planned per directory and kept whole
  for an open file; `check`, `lsp` and `export biome` share the classifier
  (changeset names the `check` change).
- performance P2: one source read per event serves every refusal pass.
- reliability P2: a sibling rule that does not parse refuses the export
  (`export-construct`) instead of counting as defining nothing.
- testing P2: two open documents that do not parse each get their own
  refusal in one event.
- standards P3: `encode::message_and_next_action` is the one message join
  for `--format github` and the LSP (R32).
- maintainability P2 / api-contract P3: the started PR7 plan is superseded by
  docs/plans/2026-10-12-2025-feat-gritlint-standalone-plan.md (KTD43, KTD47,
  KTD49, KTD50, KTD52, U10 corrected; KTD54, KTD55 new); four solution docs
  repointed.
…port order

Review run 20261012-202737-4018f61a at 1eedb94:
- adversarial peer P1 / reliability P2: one closed file the server could not
  read (not UTF-8) made `publish` log and send nothing, so every open document
  showed no diagnostics while `check` refused the tree. The server now turns a
  failed read below ROOT into `GritlintError::TargetUnreadable`
  (`target-unreadable`), drops the file with `decide::without` and reads again,
  so the rest of the event is published. `check`'s refusal text is unchanged.
- testing P2: R35's refusal order is pinned by four fixtures, each tripping two
  adjacent checks; testing P3: a backslash in an exported message is pinned.
- maintainability P3: `Evaluation::MultiFile` no longer stores a count derived
  from its own batches.

Plan superseded (plan-discipline): docs/plans/2026-10-12-2059-feat-gritlint-
standalone-plan.md adds `target-unreadable` to R38 and states it in KTD48 and
KTD55; the living solution docs cite the new path (DEL1).
@systemfsoftware-maker
systemfsoftware-maker marked this pull request as ready for review October 12, 2026 22:35

@ryanleecode ryanleecode left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Root verdict: 5/5 green on 66a753d, 0 behind main, 0 open threads. Full-path rounds plus a delta receipt Ready to merge at this head; peers DeepSeek and MiniMax, Grok excluded. Every Round-5 item is fixed, rejected with evidence, or a follow-up with an owner (F1 Mutants, F2 Biome load check, F3 wall-clock via the repin). The 12m05s x86_64 job is diagnosed from step times (structural Cargo.lock dependency rebuild) and owned by F3. Red evidence uses only the pre-fix commit 1eedb94 or 'red not shown'; the earlier scratch-copy probes are withdrawn. Read the added tests: lsp_contract drives the real stdio server, and export_contract compares against hand-written expected plugins and runs them in the engine. The test-file clippy allows follow the repo's existing convention. Diff hunt and privacy greps clean.

@ryanleecode
ryanleecode merged commit 187c25c into main Oct 13, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants