Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
75e5c45
fix: consolidate reviewed false-negative detector fixes
aidenybai Oct 27, 2026
bf5e4b2
fix: preserve guarded find aliases in combined detectors
aidenybai Oct 28, 2026
97c6cd4
fix: recognize canonical Next navigation exits before find reads
aidenybai Oct 28, 2026
2675383
fix: cache find alias owner eval proofs
aidenybai Oct 28, 2026
18d7da5
feat: add opt-in region accessible name checks
aidenybai Oct 28, 2026
f456583
fix(a11y): preserve Unicode ID identity
aidenybai Oct 29, 2026
db45a1e
fix(a11y): preserve region reachability through finalizers
aidenybai Oct 29, 2026
2dabef6
chore: stream large parity artifact validation
aidenybai Oct 29, 2026
27a619d
fix: clarify editable state and button advice
aidenybai Oct 29, 2026
132bdf9
fix: accept forwarded component event props
aidenybai Oct 29, 2026
e109682
perf: reuse diagnostic evidence file derivatives
aidenybai Oct 30, 2026
9be020d
perf: cache forwarded component handler proofs
aidenybai Oct 30, 2026
da13452
perf: cache UTF-8 diagnostic line offsets
aidenybai Oct 30, 2026
c99654b
fix: qualify transition-all animation risk
aidenybai Oct 30, 2026
7d471f3
fix: validate and cache forwarded handler bindings
aidenybai Oct 30, 2026
9dcca93
fix: integrate diagnostic reader and region reachability fixes
aidenybai Oct 30, 2026
53f322c
fix: integrate handler binding repair into FN mega
aidenybai Oct 30, 2026
69af1ac
fix: clarify transition-all advisory in FN aggregate
aidenybai Oct 30, 2026
a58069f
merge: integrate current main repairs into reviewed FN aggregate
aidenybai Nov 1, 2026
36a7f9b
merge: retain upstream package versions in FN integration
aidenybai Nov 1, 2026
694b769
fix: preserve extracted cleanup registry replay ownership
aidenybai Nov 1, 2026
f7a2ed4
fix: recognize proven focus-effect cleanup ownership
aidenybai Nov 1, 2026
efe4c52
fix: constrain focus timer cleanup forwarding proof
aidenybai Nov 1, 2026
33d764a
fix: prove returned factory cleanup before suppressing warnings
aidenybai Nov 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
23 changes: 15 additions & 8 deletions .agents/skills/run-parity/scripts/baseline-cache-provenance.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ const scanBaseline = async ({ baselinePath, expectedProducer }) => {
const decoder = new StringDecoder("utf8");
const repositories = [];
let byteLength = 0;
let bufferedText = "";
const lineFragments = [];
let recordCount = 0;

const consumeLine = (line) => {
Expand Down Expand Up @@ -200,16 +200,23 @@ const scanBaseline = async ({ baselinePath, expectedProducer }) => {
for await (const chunk of createReadStream(baselinePath)) {
artifactHash.update(chunk);
byteLength += chunk.byteLength;
bufferedText += decoder.write(chunk);
let newlineIndex = bufferedText.indexOf("\n");
const decodedChunk = decoder.write(chunk);
let lineStartIndex = 0;
let newlineIndex = decodedChunk.indexOf("\n");
while (newlineIndex !== -1) {
consumeLine(bufferedText.slice(0, newlineIndex));
bufferedText = bufferedText.slice(newlineIndex + 1);
newlineIndex = bufferedText.indexOf("\n");
lineFragments.push(decodedChunk.slice(lineStartIndex, newlineIndex));
consumeLine(lineFragments.join(""));
lineFragments.length = 0;
lineStartIndex = newlineIndex + 1;
newlineIndex = decodedChunk.indexOf("\n", lineStartIndex);
}
if (lineStartIndex < decodedChunk.length) {
lineFragments.push(decodedChunk.slice(lineStartIndex));
}
}
bufferedText += decoder.end();
if (bufferedText.length !== 0) consumeLine(bufferedText);
const decoderTail = decoder.end();
if (decoderTail.length !== 0) lineFragments.push(decoderTail);
if (lineFragments.length !== 0) consumeLine(lineFragments.join(""));
if (recordCount === 0) throw new Error("Baseline must contain at least one record");

const projectSet = buildProjectSet(repositories, "baseline");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
Expand Down Expand Up @@ -185,3 +186,76 @@ test("rejects provenance and corpus tampering independently", async () => {
await assert.rejects(verifyBaselineCacheProvenance(input), /Corpus provenance does not match/);
});
});

test("hashes long UTF-8 records across chunks with CRLF and an unterminated last record", async () => {
const records = [
buildRecord({ report: { text: "é😀".repeat(100_000) } }),
buildRecord({ repository: buildRepository({ name: "second" }) }),
];
await withFixture(async ({ input }) => {
const contents = Buffer.from(
` \r\n${JSON.stringify(records[0])}\r\n\t\n${JSON.stringify(records[1])}`,
);
await writeFile(input.baselinePath, contents);
const created = await createBaselineCacheProvenance(input);
assert.deepEqual(created.artifact, {
byteLength: contents.length,
recordCount: 2,
sha256: createHash("sha256").update(contents).digest("hex"),
});
assert.deepEqual(await verifyBaselineCacheProvenance(input), created);
}, records);
});

test("keeps carriage returns inside records as JSON whitespace without treating them as separators", async () => {
await withFixture(async ({ input }) => {
const record = JSON.stringify(buildRecord()).replace('{"schemaVersion"', '{\r"schemaVersion"');
await writeFile(input.baselinePath, record);
await assert.doesNotReject(createBaselineCacheProvenance(input));
});
const records = [buildRecord(), buildRecord({ repository: buildRepository({ name: "second" }) })];
await withFixture(async ({ input }) => {
await writeFile(input.baselinePath, records.map((record) => JSON.stringify(record)).join("\r"));
await assert.rejects(
createBaselineCacheProvenance(input),
/Baseline record 1 is not valid JSON/,
);
}, records);
});

test("retains UTF-8 replacement decoding and invalid final-record numbering", async () => {
await withFixture(async ({ input }) => {
const prefix = JSON.stringify(buildRecord({ report: { text: "MARKER" } })).split("MARKER");
const contents = Buffer.concat([
Buffer.from(prefix[0]),
Buffer.from([0xf0, 0x9f]),
Buffer.from(prefix[1]),
]);
await writeFile(input.baselinePath, contents);
const created = await createBaselineCacheProvenance(input);
assert.equal(created.artifact.sha256, createHash("sha256").update(contents).digest("hex"));
assert.deepEqual(await verifyBaselineCacheProvenance(input), created);
});
await withFixture(async ({ input }) => {
await writeFile(input.baselinePath, `${JSON.stringify(buildRecord())}\n \n{`);
await assert.rejects(
createBaselineCacheProvenance(input),
/Baseline record 2 is not valid JSON/,
);
});
});

test("rejects an incomplete UTF-8 sequence after an otherwise valid final record", async () => {
const records = [buildRecord(), buildRecord({ repository: buildRepository({ name: "second" }) })];
await withFixture(async ({ input }) => {
const contents = Buffer.concat([
Buffer.from(records.map((record) => JSON.stringify(record)).join("\n")),
Buffer.from([0xf0, 0x9f]),
]);
await writeFile(input.baselinePath, contents);
await assert.rejects(
createBaselineCacheProvenance(input),
/Baseline record 2 is not valid JSON/,
);
}, records);
});
7 changes: 6 additions & 1 deletion .agents/skills/run-parity/scripts/verify-matrix-artifact.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createHash } from "node:crypto";
import { createReadStream } from "node:fs";
import { readFile, stat } from "node:fs/promises";
import { join, resolve } from "node:path";
import { isDeepStrictEqual } from "node:util";
Expand Down Expand Up @@ -60,7 +61,11 @@ const PROJECT_KEYS = ["name", "org", "ref", "rootDir"];

const hashBytes = (contents) => createHash("sha256").update(contents).digest("hex");

const hashFile = async (filePath) => hashBytes(await readFile(filePath));
const hashFile = async (filePath) => {
const hasher = createHash("sha256");
for await (const chunk of createReadStream(filePath)) hasher.update(chunk);
return hasher.digest("hex");
};

const compareCodeUnitStrings = (left, right) => {
if (left === right) return 0;
Expand Down
36 changes: 36 additions & 0 deletions .agents/skills/run-parity/scripts/verify-matrix-artifact.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,39 @@ test("rejects rehashed descriptor and impact schema drift", async () => {
},
);
});

test("verifies complete multi-chunk artifact hashes and rejects same-length trailing tampering", async () => {
await withArtifact(
async ({
artifactDirectory,
repositories,
candidateProducer,
baseProducer,
provenance,
writeProvenance,
}) => {
const padding = " ".repeat(2 * 1024 * 1024);
const candidate = `${padding}${serializeRecords(repositories, candidateProducer)}${padding}`;
const base = `${padding}${serializeRecords(repositories, baseProducer)}${padding}`;
await writeFile(join(artifactDirectory, "candidate.ndjson"), candidate);
await writeFile(join(artifactDirectory, "base.ndjson"), base);
provenance.artifact.sha256 = hash(candidate);
provenance.artifact.byteLength = Buffer.byteLength(candidate);
provenance.baseArtifact.sha256 = hash(base);
provenance.baseArtifact.byteLength = Buffer.byteLength(base);
await writeProvenance();
await assert.doesNotReject(verifyMatrixArtifact(artifactDirectory));
await writeFile(join(artifactDirectory, "candidate.ndjson"), `${candidate.slice(0, -1)}\t`);
await assert.rejects(
verifyMatrixArtifact(artifactDirectory),
/bytes do not match provenance/,
);
await writeFile(join(artifactDirectory, "candidate.ndjson"), candidate);
await writeFile(join(artifactDirectory, "base.ndjson"), `${base.slice(0, -1)}\t`);
await assert.rejects(
verifyMatrixArtifact(artifactDirectory),
/bytes do not match provenance/,
);
},
);
});
6 changes: 6 additions & 0 deletions .changeset/absent-state-record-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
---

Detect property additions to state-owned records retained by a shallow array copy.
6 changes: 6 additions & 0 deletions .changeset/accurate-prop-change-state-message.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"eslint-plugin-react-doctor": patch
"oxlint-plugin-react-doctor": patch
---

Clarify that prop-driven state updates in effects can cause another render or show stale values.
6 changes: 6 additions & 0 deletions .changeset/accurate-state-mutation-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
---

Clarify state mutation guidance for nested records and updates that reuse the same state value.
7 changes: 7 additions & 0 deletions .changeset/adjacent-ref-effect-commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-doctor": patch
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
---

Preserve effect commands that update a closed React ref beside the selected state write or its exact local invocation.
9 changes: 9 additions & 0 deletions .changeset/antd-loading-state-role.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
"react-doctor": patch
---

Recognize a Boolean state setter used by a returned AntD Button loading prop when checking resets after rejected asynchronous work. Preserve existing loading-name detection and exceptional reset analysis.

Keep erased type declarations from hiding runtime loading roles, decline closed unsupported selectors, and cache immutable program and JSX membership proofs across distinct state setters.
7 changes: 7 additions & 0 deletions .changeset/awaited-request-fulfillment-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-doctor": patch
"eslint-plugin-react-doctor": patch
"oxlint-plugin-react-doctor": patch
---

Detect an unguarded async event handler when an awaited mutating request updates local state in its first inline fulfillment statement.
7 changes: 7 additions & 0 deletions .changeset/bright-markdown-hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-doctor": patch
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
---

Recognize closed ReactMarkdown component maps as component render scopes when checking hook calls.
7 changes: 7 additions & 0 deletions .changeset/bright-requests-callback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-doctor": patch
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
---

Detect effect-driven fetches through closed local React useCallback bindings.
7 changes: 7 additions & 0 deletions .changeset/button-type-scan-scope-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-doctor": patch
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
---

Clarify button-has-type advice for curated scans and upstream plugin mode without changing which buttons are reported.
9 changes: 9 additions & 0 deletions .changeset/cache-diagnostic-evidence-file-derivatives.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"react-doctor": patch
---

Reuse file line splits and UTF-8 buffers when reading evidence for multiple diagnostics.

Load diagnostic evidence syntax only for forwarded-handler analysis, and avoid repeated suppression scans in source snapshots without disable directives.

Reuse per-file UTF-8 newline indexes when resolving diagnostic span lines.
7 changes: 7 additions & 0 deletions .changeset/cache-find-alias-owner-proofs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-doctor": patch
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
---

Cache dynamic-code checks for find-result aliases within each function to avoid repeated full-function scans.
7 changes: 7 additions & 0 deletions .changeset/catch-native-storage-user-read-rejections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-doctor": patch
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
---

Detect missing rejection handlers for direct OIDC user reads backed by native browser storage, including one immutable local facade. Cache provider proofs across repeated reads while preserving their filesystem dependencies.
5 changes: 5 additions & 0 deletions .changeset/chatty-parks-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"oxlint-plugin-react-doctor": patch
---

Use Sets for repeated cleanup node membership checks.
7 changes: 7 additions & 0 deletions .changeset/cleanup-return-invocation-membership.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"eslint-plugin-react-doctor": patch
"oxlint-plugin-react-doctor": patch
"react-doctor": patch
---

Use a Set for repeated cleanup return membership checks after effect invocations.
7 changes: 7 additions & 0 deletions .changeset/clear-pascal-case-naming-guidance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-doctor": patch
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
---

Describe the configured JSX component naming convention without claiming that React treats uppercase component names as HTML tags.
7 changes: 7 additions & 0 deletions .changeset/close-react-provider-accessor-capabilities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-doctor": patch
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
---

Retain conservative React provider checks when a namespace exposes accessor or prototype mutation capabilities, including readonly namespace aliases.
7 changes: 7 additions & 0 deletions .changeset/closed-tailwind-template-prefix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
"react-doctor": patch
---

Detect deprecated Tailwind v4 utilities in complete class-name prefixes before one closed literal conditional suffix.
8 changes: 8 additions & 0 deletions .changeset/closed-uppercase-array-find.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"eslint-plugin-react-doctor": patch
"oxlint-plugin-react-doctor": patch
---

Check unguarded find results on closed local arrays whose bindings start with uppercase letters.

Resolve erased TypeScript receiver bindings and retain dynamic provider exclusions when type-only declarations share native names.
7 changes: 7 additions & 0 deletions .changeset/conditional-class-lifecycle-migration-guidance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
"react-doctor": patch
---

Describe legacy class lifecycle concurrency risks without asserting that React 19 removed the APIs, and recommend choosing replacements by the work they perform.
7 changes: 7 additions & 0 deletions .changeset/conditional-pressable-migration-guidance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
"react-doctor": patch
---

Describe Touchable value imports as Pressable migration candidates and require preserving interaction and accessibility behavior before replacement.
7 changes: 7 additions & 0 deletions .changeset/constructor-prop-notification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-doctor": patch
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
---

Report discarded prop callbacks invoked directly in genuine React component constructors. Reuse React provider checks across sibling constructors.
7 changes: 7 additions & 0 deletions .changeset/count-local-native-button-label-controls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
"react-doctor": patch
---

Detect labels containing a native control and a directly imported local button with a proven native host. Keep polymorphic, wrapped and uncertain components conservative, and track local wrapper changes in the lint cache.
7 changes: 7 additions & 0 deletions .changeset/default-callback-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
"react-doctor": patch
---

Report missing callback dependencies when a parameter or destructured prop has a default function that callers can replace.
7 changes: 7 additions & 0 deletions .changeset/detect-native-react-callback-state-forwarding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-doctor": patch
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
---

Recognize live state forwarded to a parent through a genuine React namespace callback wrapper. Preserve returned local getters and unsupported or modified wrappers.
7 changes: 7 additions & 0 deletions .changeset/effect-helper-function-bindings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
"react-doctor": patch
---

Detect effect resources created by called function declarations while preserving synchronous helper cleanup and directly returned disposers.
5 changes: 5 additions & 0 deletions .changeset/effect-needs-cleanup-renamed-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"oxlint-plugin-react-doctor": patch
---

Fix `effect-needs-cleanup` false positive when React Navigation `useNavigation()` result is renamed. The rule now correctly recognizes cleanup for `addListener` calls regardless of the receiver variable name, while preserving support for `navigation` props.
7 changes: 7 additions & 0 deletions .changeset/empty-store-initializer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"oxlint-plugin-react-doctor": patch
"eslint-plugin-react-doctor": patch
"react-doctor": patch
---

Prevent external-store analysis from crashing on useState calls without an initializer.
Loading
Loading