From 42194a091bb0b16da722bc27912e4467868d0c9f Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 10:31:01 +0200 Subject: [PATCH 01/11] chore(lint): add anti-slop type-pattern ratchet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three AST checks over production sources — no-chained-type-assertions, no-unknown-type-aliases, no-object-parameters — enforced as a per-rule, per-file baseline that may only shrink, following the existing cwd-relative-test-reads ratchet pattern. Test files are exempt: partial doubles legitimately assert through unknown. --- scripts/lint/anti-slop-baseline.json | 100 ++++++ scripts/lint/audit-anti-slop.test.ts | 200 +++++++++++ scripts/lint/audit-anti-slop.ts | 517 +++++++++++++++++++++++++++ 3 files changed, 817 insertions(+) create mode 100644 scripts/lint/anti-slop-baseline.json create mode 100644 scripts/lint/audit-anti-slop.test.ts create mode 100644 scripts/lint/audit-anti-slop.ts diff --git a/scripts/lint/anti-slop-baseline.json b/scripts/lint/anti-slop-baseline.json new file mode 100644 index 0000000000..a9d3647932 --- /dev/null +++ b/scripts/lint/anti-slop-baseline.json @@ -0,0 +1,100 @@ +{ + "no-chained-type-assertions": { + "extensions/ext-bundler-esbuild/src/esbuild-bundler.ts": 1, + "extensions/ext-redis/src/rate-limit-store.ts": 1, + "extensions/ext-redis/src/redis-client-manager.ts": 1, + "extensions/ext-redis/src/redis-runtime-provider.ts": 1, + "extensions/ext-redis/src/routing-invalidation-bus.ts": 1, + "extensions/ext-schema-zod/src/adapter.ts": 1, + "extensions/ext-schema-zod/src/json-schema.ts": 1, + "src/extensions/distributed/redis-runtime-provider.ts": 1, + "src/platform/adapters/fs/veryfront/adapter.test-helpers.ts": 1 + }, + "no-object-parameters": { + "extensions/ext-css-lightning/src/index.ts": 1, + "extensions/ext-css-purgecss/src/index.ts": 1, + "extensions/ext-image-sharp/src/sharp-runtime.ts": 22, + "extensions/ext-llm-anthropic/src/anthropic-request-builder.ts": 10, + "extensions/ext-llm-google/src/google-thought-signatures.ts": 1, + "extensions/ext-redis/src/event-publisher.ts": 1, + "extensions/ext-redis/src/redis-client-manager.ts": 1, + "extensions/ext-redis/src/redis-runtime-provider.ts": 2, + "extensions/ext-schema-zod/src/adapter.ts": 1, + "src/agent/conversation/private-run-event.ts": 1, + "src/agent/hosted/child-run-event-writer-token.ts": 3, + "src/agent/hosted/child-status.ts": 1, + "src/agent/runtime/error-utils.ts": 5, + "src/agent/runtime/index.ts": 3, + "src/agent/runtime/skill-prompt.ts": 1, + "src/build/asset-pipeline/css-optimizer/data-snapshot.ts": 1, + "src/build/asset-pipeline/css-optimizer/optimization-engine.ts": 1, + "src/build/asset-pipeline/image-optimizer/optimization-engine.ts": 2, + "src/cache/capabilities.ts": 2, + "src/chat/json-value.ts": 3, + "src/client/spa/path-utils.ts": 1, + "src/config/declarative-evaluator-worker-protocol.ts": 4, + "src/config/declarative-evaluator.ts": 7, + "src/config/loader.ts": 9, + "src/config/snapshot.ts": 8, + "src/errors/request-instance.ts": 1, + "src/errors/tenant-classification.ts": 1, + "src/errors/veryfront-error.ts": 2, + "src/extensions/distributed/redis-runtime-provider.ts": 3, + "src/extensions/entrypoint-identity.ts": 2, + "src/extensions/property-inspection.ts": 2, + "src/extensions/websocket/node-websocket-server-provider.ts": 3, + "src/html/client-head-manager.ts": 2, + "src/html/managed-head-protocol.ts": 1, + "src/html/styles-builder/css-hash-cache.ts": 1, + "src/html/styles-builder/prepared-project-css-cache.ts": 1, + "src/html/styles-builder/tailwind-compiler-utils.ts": 1, + "src/modules/import-map/preloader.ts": 2, + "src/oauth/state-utils.ts": 1, + "src/oauth/token-utils.ts": 2, + "src/observability/telemetry-error.ts": 2, + "src/observability/tracing/api-shim.ts": 1, + "src/observability/tracing/service-tracer.ts": 1, + "src/platform/adapters/base.ts": 1, + "src/platform/adapters/file-system-capabilities.ts": 4, + "src/platform/adapters/native-file-system-provenance.ts": 1, + "src/platform/adapters/runtime/node/http-server.ts": 3, + "src/platform/compat/error-introspection.ts": 3, + "src/platform/compat/fs.ts": 1, + "src/platform/compat/native-brand-checks.ts": 1, + "src/platform/compat/not-found-error.ts": 6, + "src/provider/runtime-loader/json-snapshot.ts": 3, + "src/proxy/retry.ts": 1, + "src/react/components/chat/chat/persistence/conversation-codec.ts": 5, + "src/react/components/ui/command.tsx": 4, + "src/react/components/ui/select.tsx": 16, + "src/registry/project-scoped-registry-manager.ts": 1, + "src/release-assets/manifest-schema.ts": 1, + "src/rendering/cache/cache-payload.ts": 2, + "src/rendering/element-validator/element-inspector.ts": 1, + "src/routing/api/response-normalization.ts": 1, + "src/runtime/runtime-bridge.ts": 2, + "src/schemas/json-value.ts": 1, + "src/schemas/lazy.ts": 2, + "src/security/sandbox/project-worker.ts": 1, + "src/security/sandbox/worker-script.ts": 1, + "src/security/secure-fs.ts": 2, + "src/server/project-env/snapshot.ts": 5, + "src/server/services/rsc/endpoints/action-authorization-snapshot.ts": 8, + "src/server/services/rsc/endpoints/action-handler.ts": 1, + "src/server/services/rsc/endpoints/action-parser.ts": 1, + "src/server/shared/browser-module-bundler.ts": 1, + "src/skill/document-parser.ts": 2, + "src/skill/executor.ts": 1, + "src/skill/id-admission.ts": 1, + "src/skill/tools.ts": 1, + "src/tool/data-properties.ts": 5, + "src/transforms/pipeline/cache-identity.ts": 2, + "src/trigger/target.ts": 2, + "src/utils/css-candidate-admission.ts": 1, + "src/utils/import-lockfile.ts": 3, + "src/utils/logger/redact.ts": 3, + "src/utils/logger/serialization.ts": 1, + "src/workflow/executor/workflow-definition-snapshot.ts": 5, + "templates/integrations/_base/files/lib/encrypted-token-store.ts": 2 + } +} diff --git a/scripts/lint/audit-anti-slop.test.ts b/scripts/lint/audit-anti-slop.test.ts new file mode 100644 index 0000000000..308926fe00 --- /dev/null +++ b/scripts/lint/audit-anti-slop.test.ts @@ -0,0 +1,200 @@ +import { assertEquals, assertThrows } from "#veryfront/testing/assert.ts"; +import { describe, it } from "#veryfront/testing/bdd.ts"; +import { + compareBaseline, + countsByRuleAndFile, + findAntiSlop, + parseBaseline, + ParseFailure, + toRepoRelative, +} from "./audit-anti-slop.ts"; + +const rulesOf = (source: string, file = "a.ts") => + findAntiSlop(source, file).map((finding) => finding.rule); + +describe("no-chained-type-assertions", () => { + it("reports `as unknown as` once, at the outermost link", () => { + const findings = findAntiSlop( + `const value = input as unknown as { id: string };`, + "a.ts", + ); + + assertEquals(findings.length, 1); + assertEquals(findings[0]?.rule, "no-chained-type-assertions"); + assertEquals(findings[0]?.line, 1); + }); + + it("reports a parenthesized chain", () => { + assertEquals(rulesOf(`const value = (input as never) as string;`), [ + "no-chained-type-assertions", + ]); + }); + + it("reports a three-link chain once", () => { + assertEquals( + rulesOf(`const value = input as unknown as object as string;`).length, + 1, + ); + }); + + it("allows a single assertion", () => { + assertEquals(rulesOf(`const value = input as string;`), []); + }); + + it("allows an all-const chain", () => { + assertEquals(rulesOf(`const value = ([1, 2] as const) as const;`), []); + }); + + it("reports a chain that mixes const with a real assertion", () => { + assertEquals( + rulesOf(`const value = [1, 2] as const as readonly unknown[];`), + ["no-chained-type-assertions"], + ); + }); + + it("reports angle-bracket chains in .ts sources", () => { + assertEquals(rulesOf(`const value = input;`), [ + "no-chained-type-assertions", + ]); + }); +}); + +describe("no-unknown-type-aliases", () => { + it("reports a direct alias to unknown", () => { + const findings = findAntiSlop(`type Payload = unknown;`, "a.ts"); + + assertEquals(findings.length, 1); + assertEquals(findings[0]?.rule, "no-unknown-type-aliases"); + assertEquals(findings[0]?.detail, "Payload"); + }); + + it("reports an exported alias chain that resolves to unknown", () => { + assertEquals( + rulesOf(`type Inner = unknown;\nexport type Outer = Inner;`).length, + 2, + ); + }); + + it("allows aliases to concrete types and self-referential cycles", () => { + assertEquals(rulesOf(`type Payload = { id: string };`), []); + assertEquals(rulesOf(`type Loop = Loop;`), []); + }); + + it("allows generic aliases and references with type arguments", () => { + assertEquals(rulesOf(`type Wrap = T;\ntype Value = Wrap;`), []); + }); +}); + +describe("no-object-parameters", () => { + it("reports a parameter annotated with the broad object type", () => { + const findings = findAntiSlop( + `function inspect(value: object): void {}`, + "a.ts", + ); + + assertEquals(findings.length, 1); + assertEquals(findings[0]?.rule, "no-object-parameters"); + assertEquals(findings[0]?.detail, "value"); + }); + + it("reports object inside a union and on arrow/method/type signatures", () => { + assertEquals(rulesOf(`const f = (value: object | null) => value;`), [ + "no-object-parameters", + ]); + assertEquals(rulesOf(`interface I { handle(value: object): void; }`), [ + "no-object-parameters", + ]); + assertEquals(rulesOf(`type Fn = (value: object) => void;`), [ + "no-object-parameters", + ]); + }); + + it("reports rest and defaulted parameters", () => { + assertEquals(rulesOf(`function f(...values: object[]): void {}`), []); + assertEquals(rulesOf(`function f(value: object = {}): void {}`), [ + "no-object-parameters", + ]); + }); + + it("allows named types, Record, and unannotated parameters", () => { + assertEquals( + rulesOf( + `function f(a: { id: string }, b: Record, c) {}`, + ), + [], + ); + }); +}); + +describe("findAntiSlop parsing", () => { + it("parses JSX in .tsx sources", () => { + assertEquals( + rulesOf(`export const El = () =>
{x as unknown as string}
;`, "a.tsx"), + ["no-chained-type-assertions"], + ); + }); + + it("fails closed on unparsable sources", () => { + assertThrows(() => findAntiSlop(`const = ;`, "a.ts"), ParseFailure); + }); + + it("ignores violations spelled inside comments", () => { + assertEquals(rulesOf(`// const v = x as unknown as string;`), []); + }); +}); + +describe("baseline mechanics", () => { + it("counts findings per rule per file, sorted", () => { + const counts = countsByRuleAndFile([ + { rule: "no-object-parameters", file: "b.ts", line: 1, detail: "v" }, + { rule: "no-chained-type-assertions", file: "b.ts", line: 2, detail: "2" }, + { rule: "no-chained-type-assertions", file: "a.ts", line: 3, detail: "2" }, + { rule: "no-chained-type-assertions", file: "a.ts", line: 9, detail: "2" }, + ]); + + assertEquals(counts, { + "no-chained-type-assertions": { "a.ts": 2, "b.ts": 1 }, + "no-object-parameters": { "b.ts": 1 }, + }); + }); + + it("flags per-file growth as a regression even for a known file", () => { + const { regressions, improvements } = compareBaseline( + { "no-chained-type-assertions": { "a.ts": 3 } }, + { "no-chained-type-assertions": { "a.ts": 2 } }, + ); + + assertEquals(regressions, ["no-chained-type-assertions a.ts: 2 -> 3"]); + assertEquals(improvements, []); + }); + + it("flags shrinkage and disappearance as improvements", () => { + const { regressions, improvements } = compareBaseline( + {}, + { "no-object-parameters": { "a.ts": 1 } }, + ); + + assertEquals(regressions, []); + assertEquals(improvements, ["no-object-parameters a.ts: 1 -> 0"]); + }); + + it("flags a new rule entry for an unlisted file as a regression", () => { + const { regressions } = compareBaseline( + { "no-unknown-type-aliases": { "new.ts": 1 } }, + {}, + ); + + assertEquals(regressions, ["no-unknown-type-aliases new.ts: 0 -> 1"]); + }); + + it("rejects malformed baselines", () => { + assertThrows(() => parseBaseline([], "p")); + assertThrows(() => parseBaseline({ rule: 1 }, "p")); + assertThrows(() => parseBaseline({ rule: { "a.ts": 0 } }, "p")); + assertThrows(() => parseBaseline({ rule: { "a.ts": 1.5 } }, "p")); + }); + + it("normalises baseline keys to posix repo-relative paths", () => { + assertEquals(toRepoRelative("/repo/src\\x.ts", "/repo/"), "src/x.ts"); + }); +}); diff --git a/scripts/lint/audit-anti-slop.ts b/scripts/lint/audit-anti-slop.ts new file mode 100644 index 0000000000..1fbcd3cc1c --- /dev/null +++ b/scripts/lint/audit-anti-slop.ts @@ -0,0 +1,517 @@ +#!/usr/bin/env -S deno run --allow-read +/** + * Ratchet on low-evidence type patterns — the assertions and broad types + * that make code look typed while the compiler has been told to stop + * checking. `deno lint` has no baseline mechanism, so these checks live here + * instead of as lint rules, enforced through the per-file ratchet this + * directory already uses for cwd-relative test reads. + * + * Candidate patterns that were measured against `src/` and deliberately NOT + * enforced: + * + * - ad hoc `typeof` narrowing, `unknown` parameters, and mandatory safety + * comments on assertions: thousands of hits each. Cross-runtime code + * (`typeof Deno`, `typeof process`) and untyped I/O boundaries are how + * this codebase works, not slop. + * - `Reflect.apply` / `Reflect.get`: `Reflect.*` here is deliberate typed + * dispatch in sandbox/proxy code (e.g. `src/proxy/cache/validation.ts`). + * - `*Shape` symbol names: an established naming convention in this repo + * (`SleepToolInputShape`, `ContractSchemaShape`). + * + * ## The enforced rules + * + * - `no-chained-type-assertions` — `x as unknown as Y` (and any nested + * assertion chain that is not all-`const`) fabricates type evidence: the + * compiler is told to forget what it knew and then told something new + * with no proof. Parse or narrow at the boundary instead. + * - `no-unknown-type-aliases` — `type Foo = unknown` (directly or through + * alias chains) hides the fact that a value is unparsed. `unknown` must + * stay visible where it exists. + * - `no-object-parameters` — a parameter typed `object` (including union + * members) accepts nearly anything while promising nothing; take a named + * domain type instead. Local aliases to `object` are not resolved — + * direct annotations only, which keeps the check free of scope analysis. + * + * Test files (`*.test.ts(x)`) are not scanned: `as unknown as` is the + * idiomatic way to build partial doubles in tests, and banning it there + * fights ~1300 existing sites for little signal. + * + * Counts are frozen per rule per file in `anti-slop-baseline.json` and may + * only shrink. Regenerate after paying debt down with: + * + * deno task lint:anti-slop -- --print-baseline + */ + +import { parse } from "npm:@babel/parser@7.29.2"; +import { fromFileUrl } from "#std/path"; + +export type AntiSlopRule = + | "no-chained-type-assertions" + | "no-unknown-type-aliases" + | "no-object-parameters"; + +export interface AntiSlopFinding { + rule: AntiSlopRule; + file: string; + line: number; + /** What was flagged: the alias name, the parameter name, or a chain label. */ + detail: string; +} + +interface Node { + type: string; + loc?: { start: { line: number } }; + [key: string]: unknown; +} + +function isNode(value: unknown): value is Node { + return typeof value === "object" && value !== null && + typeof (value as { type?: unknown }).type === "string"; +} + +/** + * Attached comments carry a `type` too, so the walk would descend into them. + * Nothing in a comment can violate these rules, and skipping them makes that + * structural rather than incidental. + */ +const COMMENT_KEYS = new Set([ + "leadingComments", + "trailingComments", + "innerComments", + "comments", +]); + +const ASSERTION_TYPES = new Set(["TSAsExpression", "TSTypeAssertion"]); + +/** `x as const` — the one assertion form that adds evidence instead of discarding it. */ +function isConstAssertion(node: Node): boolean { + const annotation = node.typeAnnotation; + if (!isNode(annotation) || annotation.type !== "TSTypeReference") { + return false; + } + const name = annotation.typeName; + return isNode(name) && name.type === "Identifier" && name.name === "const"; +} + +/** + * Walk `.expression` through a nested assertion chain. Babel keeps + * parentheses transparent, so `(x as A) as B` nests directly. + */ +function assertionChain(node: Node): { length: number; hasNonConst: boolean } { + let length = 0; + let hasNonConst = false; + let current: unknown = node; + while (isNode(current) && ASSERTION_TYPES.has(current.type)) { + length += 1; + if (!isConstAssertion(current)) hasNonConst = true; + current = current.expression; + } + return { length, hasNonConst }; +} + +/** Node types that own a function parameter list, under either babel key. */ +const PARAMETER_OWNERS = new Set([ + "FunctionDeclaration", + "FunctionExpression", + "ArrowFunctionExpression", + "ObjectMethod", + "ClassMethod", + "ClassPrivateMethod", + "TSDeclareFunction", + "TSDeclareMethod", + "TSFunctionType", + "TSConstructorType", + "TSMethodSignature", + "TSCallSignatureDeclaration", + "TSConstructSignatureDeclaration", +]); + +/** Plain functions store `params`; TS signature nodes store `parameters`. */ +function parametersOf(node: Node): Node[] { + const raw = node.params ?? node.parameters; + return Array.isArray(raw) ? raw.filter(isNode) : []; +} + +function parameterAnnotation(parameter: Node): Node | undefined { + if (parameter.type === "TSParameterProperty") { + return isNode(parameter.parameter) + ? parameterAnnotation(parameter.parameter) + : undefined; + } + if (parameter.type === "AssignmentPattern") { + const own = parameter.typeAnnotation; + if (isNode(own)) return own; + return isNode(parameter.left) + ? parameterAnnotation(parameter.left) + : undefined; + } + const annotation = parameter.typeAnnotation; + if (isNode(annotation)) return annotation; + if (parameter.type === "RestElement" && isNode(parameter.argument)) { + return parameterAnnotation(parameter.argument); + } + return undefined; +} + +function parameterName(parameter: Node): string { + if (parameter.type === "Identifier") return parameter.name as string; + if (parameter.type === "RestElement" && isNode(parameter.argument)) { + return `...${parameterName(parameter.argument)}`; + } + if (parameter.type === "AssignmentPattern" && isNode(parameter.left)) { + return parameterName(parameter.left); + } + if (parameter.type === "TSParameterProperty" && isNode(parameter.parameter)) { + return parameterName(parameter.parameter); + } + return `<${parameter.type}>`; +} + +/** The broad `object` keyword, directly or as a union member. */ +function isBroadObjectType(type: Node): boolean { + if (type.type === "TSObjectKeyword") return true; + if (type.type === "TSUnionType" && Array.isArray(type.types)) { + return type.types.some((member) => isNode(member) && isBroadObjectType(member)); + } + return false; +} + +/** Top-level type aliases by name, including `export type` forms. */ +function collectTopLevelAliases(program: Node): Map { + const aliases = new Map(); + const body = Array.isArray(program.body) ? program.body : []; + for (const statement of body) { + if (!isNode(statement)) continue; + const declaration = statement.type === "ExportNamedDeclaration" + ? statement.declaration + : statement; + if (isNode(declaration) && declaration.type === "TSTypeAliasDeclaration") { + const id = declaration.id; + if (isNode(id) && typeof id.name === "string") { + aliases.set(id.name, declaration); + } + } + } + return aliases; +} + +function resolvesToUnknown( + type: Node, + aliases: Map, + visited: Set, +): boolean { + if (type.type === "TSUnknownKeyword") return true; + if (type.type !== "TSTypeReference") return false; + const name = type.typeName; + if (!isNode(name) || name.type !== "Identifier") return false; + // A reference with type arguments is not a bare alias to `unknown`. + const args = type.typeParameters ?? type.typeArguments; + if (isNode(args) && Array.isArray(args.params) && args.params.length > 0) { + return false; + } + const aliasName = name.name as string; + if (visited.has(aliasName)) return false; + const alias = aliases.get(aliasName); + // Generic aliases would need instantiation to resolve; leave them alone. + if (alias === undefined || isNode(alias.typeParameters)) return false; + const annotation = alias.typeAnnotation; + if (!isNode(annotation)) return false; + return resolvesToUnknown( + annotation, + aliases, + new Set(visited).add(aliasName), + ); +} + +/** Raised when a scanned file cannot be parsed, so the audit fails closed. */ +export class ParseFailure extends Error {} + +/** + * Report every anti-slop violation in `source`. + * + * `.ts` sources are parsed without the JSX plugin so angle-bracket type + * assertions (`value`) parse as assertions rather than as JSX. + */ +export function findAntiSlop(source: string, file: string): AntiSlopFinding[] { + let ast; + try { + ast = parse(source, { + sourceType: "module", + allowAwaitOutsideFunction: true, + allowReturnOutsideFunction: true, + errorRecovery: false, + plugins: file.endsWith(".tsx") + ? ["typescript", "jsx", "decorators-legacy", "importAttributes"] + : ["typescript", "decorators-legacy", "importAttributes"], + }); + } catch (error) { + throw new ParseFailure( + `${file}: ${error instanceof Error ? error.message : String(error)}`, + ); + } + + const program = ast.program as unknown as Node; + const findings: AntiSlopFinding[] = []; + + const aliases = collectTopLevelAliases(program); + for (const [name, alias] of aliases) { + const annotation = alias.typeAnnotation; + if (!isNode(annotation)) continue; + if (!resolvesToUnknown(annotation, aliases, new Set([name]))) continue; + findings.push({ + rule: "no-unknown-type-aliases", + file, + line: alias.loc?.start.line ?? 0, + detail: name, + }); + } + + /** + * `underAssertion` marks a node reached through an assertion's + * `.expression`, so only the outermost link of a chain reports. + */ + const visit = (node: Node, underAssertion: boolean): void => { + if (ASSERTION_TYPES.has(node.type) && !underAssertion) { + const { length, hasNonConst } = assertionChain(node); + if (length > 1 && hasNonConst) { + findings.push({ + rule: "no-chained-type-assertions", + file, + line: node.loc?.start.line ?? 0, + detail: `${length} chained assertions`, + }); + } + } + + if (PARAMETER_OWNERS.has(node.type)) { + for (const parameter of parametersOf(node)) { + const annotation = parameterAnnotation(parameter); + const type = annotation === undefined + ? undefined + : annotation.typeAnnotation; + if (!isNode(type) || !isBroadObjectType(type)) continue; + findings.push({ + rule: "no-object-parameters", + file, + line: (isNode(annotation) ? annotation.loc?.start.line : undefined) ?? + 0, + detail: parameterName(parameter), + }); + } + } + + for (const key of Object.keys(node)) { + if (key === "loc" || COMMENT_KEYS.has(key)) continue; + const value = node[key]; + const intoExpression = ASSERTION_TYPES.has(node.type) && + key === "expression"; + if (Array.isArray(value)) { + for (const item of value) if (isNode(item)) visit(item, false); + } else if (isNode(value)) { + visit(value, intoExpression); + } + } + }; + + visit(program, false); + return findings.sort((a, b) => a.line - b.line); +} + +/** Per-rule, per-file counts — the shape stored in the baseline. */ +export type AntiSlopBaseline = Record>; + +export function countsByRuleAndFile( + findings: readonly AntiSlopFinding[], +): AntiSlopBaseline { + const counts: Record> = {}; + for (const finding of findings) { + const perFile = counts[finding.rule] ?? (counts[finding.rule] = {}); + perFile[finding.file] = (perFile[finding.file] ?? 0) + 1; + } + const sorted: AntiSlopBaseline = {}; + for (const rule of Object.keys(counts).sort()) { + sorted[rule] = Object.fromEntries( + Object.entries(counts[rule]).sort(([a], [b]) => a.localeCompare(b)), + ); + } + return sorted; +} + +export interface BaselineComparison { + /** `rule file: then -> now` where a count grew. The ratchet slipping. */ + regressions: string[]; + /** Where a count shrank. The ratchet earning a new floor. */ + improvements: string[]; +} + +/** + * Compare current counts with the frozen baseline, per rule per file: a file + * already carrying two chained assertions must not quietly grow a third. + */ +export function compareBaseline( + current: AntiSlopBaseline, + baseline: AntiSlopBaseline, +): BaselineComparison { + const regressions: string[] = []; + const improvements: string[] = []; + const rules = new Set([...Object.keys(current), ...Object.keys(baseline)]); + for (const rule of rules) { + const now = current[rule] ?? {}; + const then = baseline[rule] ?? {}; + for (const file of new Set([...Object.keys(now), ...Object.keys(then)])) { + const a = now[file] ?? 0; + const b = then[file] ?? 0; + if (a > b) regressions.push(`${rule} ${file}: ${b} -> ${a}`); + else if (a < b) improvements.push(`${rule} ${file}: ${b} -> ${a}`); + } + } + return { regressions: regressions.sort(), improvements: improvements.sort() }; +} + +export function parseBaseline(value: unknown, path: string): AntiSlopBaseline { + if (typeof value !== "object" || value === null || Array.isArray(value)) { + throw new Error(`Invalid anti-slop baseline: ${path}`); + } + for (const [rule, files] of Object.entries(value)) { + if (typeof files !== "object" || files === null || Array.isArray(files)) { + throw new Error(`Invalid anti-slop baseline entry for ${rule}: ${path}`); + } + for (const [file, count] of Object.entries(files)) { + if (typeof count !== "number" || !Number.isInteger(count) || count < 1) { + throw new Error( + `Invalid anti-slop baseline count for ${rule} ${file}: ${count}`, + ); + } + } + } + return value as AntiSlopBaseline; +} + +/** + * Baseline key for a scanned file: repo-relative, always posix separators, + * so a Windows checkout produces the same keys a Linux one does. + */ +export function toRepoRelative(file: string, repoRoot: string): string { + return file.slice(repoRoot.length).replaceAll("\\", "/"); +} + +const BASELINE_PATH = "scripts/lint/anti-slop-baseline.json"; +/** Mirrors the production surface of `lint.include` in deno.json. */ +const SCAN_ROOTS = ["src", "cli", "templates", "extensions", "react"] as const; +/** Mirrors `lint.exclude` in deno.json. */ +const EXCLUDED_PREFIXES = ["src/studio/bridge/"] as const; + +function isProdSource(name: string): boolean { + if (!name.endsWith(".ts") && !name.endsWith(".tsx")) return false; + if (name.endsWith(".test.ts") || name.endsWith(".test.tsx")) return false; + return !name.endsWith(".d.ts"); +} + +async function collectProdFiles(root: string): Promise { + const files: string[] = []; + let entries: AsyncIterable; + try { + entries = Deno.readDir(root); + } catch { + return files; // expected: a scan root may not exist in every checkout + } + for await (const entry of entries) { + const path = `${root}/${entry.name}`; + if (entry.isDirectory) { + if (entry.name === "node_modules" || entry.name.startsWith(".")) continue; + files.push(...await collectProdFiles(path)); + } else if (entry.isFile && isProdSource(entry.name)) { + files.push(path); + } + } + return files; +} + +function printFindings(title: string, findings: readonly string[]): void { + if (findings.length === 0) return; + console.error(`\n${title}`); + for (const finding of findings) console.error(` ${finding}`); +} + +async function main(): Promise { + // `fromFileUrl`, not `URL.pathname`: pathname keeps the URL's leading slash + // and percent encoding, so a Windows checkout would scan `/C:/...`. + const repoRoot = fromFileUrl(new URL("../../", import.meta.url)); + const findings: AntiSlopFinding[] = []; + const parseFailures: string[] = []; + + for (const root of SCAN_ROOTS) { + for (const file of await collectProdFiles(`${repoRoot}${root}`)) { + const relative = toRepoRelative(file, repoRoot); + if (EXCLUDED_PREFIXES.some((prefix) => relative.startsWith(prefix))) { + continue; + } + try { + findings.push(...findAntiSlop(await Deno.readTextFile(file), relative)); + } catch (error) { + parseFailures.push( + error instanceof Error ? error.message : String(error), + ); + } + } + } + + const current = countsByRuleAndFile(findings); + if (Deno.args.includes("--print-baseline")) { + console.log(JSON.stringify(current, null, 2)); + return; + } + + const baseline = parseBaseline( + JSON.parse(await Deno.readTextFile(`${repoRoot}${BASELINE_PATH}`)), + BASELINE_PATH, + ); + const { regressions, improvements } = compareBaseline(current, baseline); + + printFindings("Files that could not be parsed:", parseFailures); + if (regressions.length > 0) { + const regressedFiles = new Set( + regressions.map((entry) => entry.split(" ")[1]?.replace(/:$/, "")), + ); + printFindings( + "Anti-slop counts above the baseline (new low-evidence type patterns):", + regressions, + ); + printFindings( + "Current findings in the regressed files:", + findings + .filter((finding) => regressedFiles.has(finding.file)) + .map((finding) => + `${finding.file}:${finding.line} ${finding.rule} (${finding.detail})` + ), + ); + } + + if (parseFailures.length > 0 || regressions.length > 0) { + console.error( + `\nKeep the precise type or parse at the boundary instead of asserting ` + + `through it — see the header of scripts/lint/audit-anti-slop.ts. ` + + `Do not raise ${BASELINE_PATH} for new violations.`, + ); + Deno.exit(1); + } + + if (improvements.length > 0) { + printFindings("Anti-slop debt decreased:", improvements); + console.log( + `\nRegenerate ${BASELINE_PATH} with ` + + `\`deno task lint:anti-slop -- --print-baseline\` to lock in the improvement.`, + ); + return; + } + + const total = findings.length; + const fileCount = new Set(findings.map((finding) => finding.file)).size; + console.log( + `Anti-slop baseline ok: ${total} baselined finding(s) across ${fileCount} file(s).`, + ); +} + +if (import.meta.main) { + await main(); +} From b44cad5fa21118fc3bb5e82632dceba9b5b85d28 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 10:31:02 +0200 Subject: [PATCH 02/11] refactor(types): remove chained assertions and broad object parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace 'as unknown as' chains with honest single assertions (intersection types, precise origins, truthful signatures) and narrow 'object' parameters to what each function actually accepts — 209 fixes across 136 files, all type-level: every prebundle regenerates byte-identical. Sites where 'object' is the precise type (descriptor introspection, prototype walks, opaque handles) or where the double assertion is the only expressible form (CJS interop, cross-version duals) stay baselined. API-reference pins regenerated; the framework-candidates index picks up the new type tokens. --- cli/app/shell.ts | 2 +- cli/app/startup.ts | 2 +- cli/auth/callback-server.ts | 2 +- cli/commands/test/handler.ts | 4 +-- docs/api-reference/veryfront/agent.md | 2 +- docs/api-reference/veryfront/errors.md | 6 ++-- docs/api-reference/veryfront/skill.md | 6 ++-- extensions/ext-auth-jwt/src/index.ts | 6 ++-- extensions/ext-blob-s3/src/s3-storage.ts | 4 +-- .../src/compiler/mdx-compile.ts | 6 ++-- extensions/ext-css-lightning/src/index.ts | 4 +-- extensions/ext-db-sqlite/src/index.ts | 9 +++--- .../src/dashboard/components/MCPTab.tsx | 2 +- .../ext-document-kreuzberg/src/kreuzberg.ts | 4 +-- extensions/ext-parser-babel/src/index.ts | 2 +- .../ext-parser-babel/src/parser-only.ts | 3 +- extensions/ext-schema-zod/src/adapter.ts | 23 ++++++++------- react/react.ts | 9 +++--- .../lifecycle-run-event-adapter.ts | 10 ++----- src/agent/memory/memory.ts | 21 +++++++------- src/agent/react/use-voice-input.ts | 4 +-- .../chat-stream-handler.test-helpers.ts | 2 +- src/agent/runtime/error-utils.ts | 6 ++-- src/agent/runtime/project-skill-loader.ts | 2 +- src/agent/runtime/skill-metadata.ts | 2 +- src/agent/runtime/skill-prompt.ts | 2 +- src/agent/streaming/lifecycle/testing.ts | 2 +- src/channels/control-plane.ts | 6 ++-- src/client/spa/component-loader.ts | 6 +++- .../declarative-evaluator-worker-protocol.ts | 9 ++++-- src/config/loader.ts | 4 +-- src/errors/safe-diagnostics.ts | 2 +- src/errors/veryfront-error.ts | 5 +--- .../auth/rsc-action-authorization-provider.ts | 2 +- src/extensions/discovery.ts | 4 +-- .../distributed/redis-runtime-provider.ts | 2 +- src/extensions/entrypoint-identity.ts | 2 +- src/extensions/manifest-reader.ts | 8 +++--- .../parser/skill-document-parser.ts | 2 +- src/extensions/promise-intrinsics-internal.ts | 2 +- src/extensions/validation.ts | 6 ++-- .../hydration-script-builder/runtime/main.ts | 16 +++++------ .../runtime/navigation-store.ts | 4 ++- .../runtime/renderer.ts | 2 +- .../runtime/route-timing.ts | 2 +- .../runtime/router.ts | 11 ++++---- src/html/styles-builder/css-hash-cache.ts | 2 +- src/internal-agents/ag-ui-sse.ts | 9 ++---- src/internal-agents/run-stream.ts | 5 +--- .../loader-primordial-poisoning.worker.ts | 5 ++-- src/modules/import-map/loader.ts | 11 +++++--- src/modules/import-map/merger.ts | 4 +-- .../preloader-primordial-poisoning.worker.ts | 5 ++-- src/modules/import-map/preloader.ts | 4 +-- .../transformed-module-coordinator.ts | 3 +- src/oauth/providers/base.ts | 4 +-- src/observability/application-errors.ts | 2 +- .../auto-instrument.test-helpers.ts | 2 +- src/observability/telemetry-error.ts | 6 ++-- .../adapters/file-system-capabilities.ts | 2 +- src/platform/adapters/fs/integration.ts | 4 +-- .../adapters/fs/veryfront/request-context.ts | 2 +- src/platform/adapters/fs/wrapper.ts | 4 +-- .../runtime/deno/filesystem-adapter.ts | 2 +- .../adapters/runtime/deno/http-server.ts | 2 +- .../adapters/runtime/node/http-server.ts | 4 +-- .../runtime/shared/node-filesystem-adapter.ts | 2 +- src/platform/compat/error-introspection.ts | 2 +- src/platform/compat/fs.ts | 2 +- src/platform/compat/http/native-response.ts | 2 +- src/platform/compat/http/pinned-fetch.ts | 2 +- src/platform/compat/kv/factory.ts | 2 +- src/platform/compat/native-brand-checks.ts | 2 +- src/platform/compat/not-found-error.ts | 2 +- src/platform/compat/process/command.ts | 2 +- src/platform/compat/process/lifecycle.ts | 2 +- src/platform/compat/std/expect.ts | 4 +-- src/platform/compat/std/fs.ts | 2 +- src/platform/compat/std/testing/time.ts | 12 ++++++-- src/prompt/validation.ts | 2 +- src/provider/runtime-loader/json-snapshot.ts | 10 +++---- src/proxy/routing-invalidation-redis.ts | 12 ++++---- src/proxy/shutdown-hooks.ts | 8 +++--- src/proxy/shutdown-intrinsics.ts | 2 +- src/proxy/shutdown-lifecycle.ts | 2 +- .../chat/persistence/conversation-codec.ts | 28 +++++++++---------- src/react/components/ui/tooltip.tsx | 6 ++-- src/react/primitives/input-box.tsx | 2 +- src/react/server-render-context.ts | 2 +- .../project-scoped-registry-manager.ts | 2 +- .../dependency-artifact-builder.ts | 9 ++++-- src/release-assets/manifest-schema.ts | 2 +- src/rendering/client/router.ts | 2 +- src/rendering/client/state-bridge.ts | 2 +- src/rendering/orchestrator/html.ts | 3 +- .../rsc/server-renderer/tree-processor.ts | 2 +- src/rendering/utils/react-helpers.ts | 2 +- src/routing/client/dom-utils.test-helpers.ts | 16 +++++++---- src/runtime/model-call-context.ts | 4 +-- src/schemas/lazy.ts | 2 +- src/security/http/response/builder.ts | 2 +- src/security/sandbox/project-worker.ts | 2 +- src/security/sandbox/worker-egress-guard.ts | 2 +- src/security/secure-fs.ts | 2 +- src/server/handlers/dev/dashboard/api.ts | 2 +- .../dev/framework-candidates.generated.ts | 7 ++++- .../internal-agent-run.test-helpers.ts | 5 ++-- .../request/ssr/ssr.handler.test-helpers.ts | 5 ++-- src/server/index.ts | 6 ++-- .../action-authorization-snapshot.ts | 2 +- .../services/rsc/endpoints/action-parser.ts | 2 +- .../endpoints/endpoint-router.test-helpers.ts | 11 ++++---- src/server/unhandled-rejection-guard.ts | 2 +- src/skill/document-parser.ts | 2 +- src/skill/parser.ts | 2 +- src/skill/path-safety.ts | 2 +- src/skill/tools.ts | 5 ++-- src/skill/validation.ts | 4 +-- src/tool/data-properties.ts | 2 +- src/tool/factory.ts | 2 +- src/tool/sleep.ts | 2 +- src/transforms/esm/http-cache-types.ts | 2 +- src/transforms/mdx/compiler/mdx-compiler.ts | 2 +- .../esm-module-loader/jsx/runtime-loader.ts | 7 ++++- src/transforms/mdx/index.ts | 2 +- src/transforms/pipeline/cache-identity.ts | 2 +- .../stages/browser-server-exports-strip.ts | 4 +-- src/types/entities/getEntityInfo.ts | 8 ++++-- src/utils/import-lockfile.ts | 2 +- src/utils/response-body.ts | 2 +- src/webhook/validation.ts | 2 +- src/workflow/claude-code/tool.ts | 4 +-- src/workflow/claude-code/wire-protocol.ts | 9 ++++-- src/workflow/dsl/workflow.ts | 2 +- .../executor/workflow-definition-snapshot.ts | 8 +++--- src/workflow/react/use-workflow-list.ts | 2 +- src/workflow/registry.ts | 9 +++++- src/workflow/types.ts | 2 +- 138 files changed, 333 insertions(+), 289 deletions(-) diff --git a/cli/app/shell.ts b/cli/app/shell.ts index c3f80acdd2..1d2e06bd8d 100644 --- a/cli/app/shell.ts +++ b/cli/app/shell.ts @@ -178,7 +178,7 @@ export function createApp(config: AppConfig): App { if (spinnerInterval) return; spinnerInterval = setInterval(() => { render(); - }, 80) as unknown as number; + }, 80); } function stopSpinner(): void { diff --git a/cli/app/startup.ts b/cli/app/startup.ts index 660d4c3fb0..725b39dbd1 100644 --- a/cli/app/startup.ts +++ b/cli/app/startup.ts @@ -45,7 +45,7 @@ export interface StartupProgressDeps { function platformDeps(): StartupProgressDeps { return { write: (text) => writeStdout(text), - setInterval: (fn, ms) => setInterval(fn, ms) as unknown as number, + setInterval: (fn, ms) => setInterval(fn, ms), clearInterval: (handle) => clearInterval(handle), }; } diff --git a/cli/auth/callback-server.ts b/cli/auth/callback-server.ts index 2c0a81f3a8..e56d36abd8 100644 --- a/cli/auth/callback-server.ts +++ b/cli/auth/callback-server.ts @@ -231,7 +231,7 @@ function tryStartDenoServer(port: number, options: CallbackServerOptions = {}): }); // Access native Deno.serve via `self` to bypass dnt shim transform. - const nativeDeno = (self as unknown as Record)["Deno"]!; + const nativeDeno = (self as typeof self & { Deno?: typeof Deno })["Deno"]!; const server = nativeDeno.serve( { port, hostname: "127.0.0.1", onListen: () => {} }, (request: Request) => { diff --git a/cli/commands/test/handler.ts b/cli/commands/test/handler.ts index 4c9c64b8db..4a7c504054 100644 --- a/cli/commands/test/handler.ts +++ b/cli/commands/test/handler.ts @@ -9,7 +9,7 @@ import { isJsonMode, outputJson, } from "../../shared/json-output.ts"; -import { parseTestOutput } from "./command.ts"; +import { parseTestOutput, type TestResult } from "./command.ts"; const getTestArgsSchema = defineSchema((v) => v.object({ @@ -62,7 +62,7 @@ export async function handleTestCommand(args: ParsedArgs): Promise { code: "TEST_FAILURE", slug: "tests-failed", message: `${parsed.summary.failed} test(s) failed`, - context: parsed as unknown as Record, + context: parsed as TestResult & Record, })); } } else { diff --git a/docs/api-reference/veryfront/agent.md b/docs/api-reference/veryfront/agent.md index 33b080abd9..27b4f06e5e 100644 --- a/docs/api-reference/veryfront/agent.md +++ b/docs/api-reference/veryfront/agent.md @@ -714,7 +714,7 @@ Input delivered to a hosted agent-service detached execution callback. | `createInitialForkRuntimeMessages` | Create initial fork runtime messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/fork-runtime-stream.ts#L429) | | `createInputRequest` | Request payload for create input. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/input/request-protocol.ts#L210) | | `createLiveStudioMcpTools` | Create live studio MCP tools. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/project/live-studio-mcp-tools.ts#L115) | -| `createMemory` | Create memory. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/memory/memory.ts#L352) | +| `createMemory` | Create memory. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/memory/memory.ts#L353) | | `createMirroredToolChunkState` | State for create mirrored tool chunk. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/mirrored-tool-chunk-state.ts#L40) | | `createNodeAgentServiceRuntimeInfrastructure` | Create node agent service runtime infrastructure. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/node-runtime-infrastructure.ts#L52) | | `createNodeVeryfrontCloudAgentServiceRuntime` | Create node Veryfront Cloud agent service runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L204) | diff --git a/docs/api-reference/veryfront/errors.md b/docs/api-reference/veryfront/errors.md index 5072c2c656..758652f773 100644 --- a/docs/api-reference/veryfront/errors.md +++ b/docs/api-reference/veryfront/errors.md @@ -183,7 +183,7 @@ throw INVALID_WIDGET.create({ detail: "The widget id is malformed." }); | `createProblemResponse` | Create an RFC 9457 error Response from raw parameters | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L70) | | `createSimpleError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/factory.ts#L29) | | `defineError` | Define an error in the registry | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/types.ts#L98) | -| `ensureError` | Ensure a value is an Error while preserving the established identity contract for ordinary Error instances. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/veryfront-error.ts#L837) | +| `ensureError` | Ensure a value is an Error while preserving the established identity contract for ordinary Error instances. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/veryfront-error.ts#L834) | | `errorToResponse` | Convert any error to an RFC 9457 Response | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L106) | | `errorToRFC9457Response` | Convert any error to an RFC 9457 Response with environment-aware filtering | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/http-error-boundary.ts#L98) | | `formatCLIError` | Format any error for CLI output | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/cli-error-boundary.ts#L163) | @@ -192,7 +192,7 @@ throw INVALID_WIDGET.create({ detail: "The widget id is malformed." }); | `fromError` | Decode legacy Veryfront error data attached by `toError()`. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/legacy-error-codec.ts#L17) | | `getAllSlugs` | Get all registered slugs | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry.ts#L72) | | `getErrorBySlug` | Get an error definition by slug | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry.ts#L58) | -| `getErrorMessage` | Extract error message from any error type | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/veryfront-error.ts#L678) | +| `getErrorMessage` | Extract error message from any error type | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/veryfront-error.ts#L675) | | `getErrorsByCategory` | Get all errors in a category | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry.ts#L65) | | `getErrorSolution` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/index.ts#L44) | | `handleErrorWithFallback` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-handlers.ts#L41) | @@ -208,7 +208,7 @@ throw INVALID_WIDGET.create({ detail: "The widget id is malformed." }); | `safeReadDir` | Safe directory read with logging | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L163) | | `sanitizeTerminalDiagnosticText` | Prepare one untrusted diagnostic field for terminal or plain-text output. Apply framework-owned ANSI styling only after this sanitizer returns. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/safe-diagnostics.ts#L97) | | `searchErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/index.ts#L48) | -| `toError` | Convert a VeryfrontErrorData (plain object) to a throwable Error instance. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/veryfront-error.ts#L622) | +| `toError` | Convert a VeryfrontErrorData (plain object) to a throwable Error instance. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/veryfront-error.ts#L619) | | `withErrorContext` | Execute async operation with error logging and fallback | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L109) | | `withErrorContextSync` | Execute sync operation with error logging and fallback | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L123) | | `wrapErrorHandler` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/user-friendly/error-wrapper.ts#L26) | diff --git a/docs/api-reference/veryfront/skill.md b/docs/api-reference/veryfront/skill.md index 8a651573cd..7162588a6d 100644 --- a/docs/api-reference/veryfront/skill.md +++ b/docs/api-reference/veryfront/skill.md @@ -50,9 +50,9 @@ validateSkillMetadata(parsed.frontmatter, "review"); | Name | Description | Source | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -| `createExecuteSkillScriptTool` | Create the execute_skill_script tool. Executes a script from a skill's scripts/ directory. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/skill/tools.ts#L597) | -| `createLoadSkillReferenceTool` | Create the load_skill_reference tool. Reads a reference file from a skill's references/, resources/, or assets/ directory. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/skill/tools.ts#L553) | -| `createLoadSkillTool` | Create the load_skill tool. Loads a skill's full instructions, available references, and scripts. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/skill/tools.ts#L483) | +| `createExecuteSkillScriptTool` | Create the execute_skill_script tool. Executes a script from a skill's scripts/ directory. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/skill/tools.ts#L598) | +| `createLoadSkillReferenceTool` | Create the load_skill_reference tool. Reads a reference file from a skill's references/, resources/, or assets/ directory. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/skill/tools.ts#L554) | +| `createLoadSkillTool` | Create the load_skill tool. Loads a skill's full instructions, available references, and scripts. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/skill/tools.ts#L484) | | `filterToolNamesForSkill` | Filter provider-native or other name-only tool inventories through the same boundary. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/skill/allowed-tools.ts#L86) | | `filterToolsForSkill` | Filter tool definitions before sending them to the model. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/skill/allowed-tools.ts#L64) | | `getAllSkills` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/skill/registry.ts#L277) | diff --git a/extensions/ext-auth-jwt/src/index.ts b/extensions/ext-auth-jwt/src/index.ts index a32e72e7c9..1fd0c2e5a7 100644 --- a/extensions/ext-auth-jwt/src/index.ts +++ b/extensions/ext-auth-jwt/src/index.ts @@ -55,7 +55,7 @@ export interface ExtJwtConfig { } function defaultJwksResolverFactory(jwksUrl: string): JwksResolver { - return createRemoteJWKSet(new URL(jwksUrl)) as unknown as JwksResolver; + return createRemoteJWKSet(new URL(jwksUrl)); } function toUint8Array(secret: string | Uint8Array): Uint8Array { @@ -140,8 +140,8 @@ function createAuthProvider(config: ExtJwtConfig): AuthProvider { if (algorithms) verifyOpts.algorithms = algorithms; const { payload } = await jwtVerify( token, - resolver as unknown as KeyLike, - verifyOpts, + resolver, + verifyOpts as Parameters[2], ); return payload as TokenPayload; }, diff --git a/extensions/ext-blob-s3/src/s3-storage.ts b/extensions/ext-blob-s3/src/s3-storage.ts index a10da40a31..1ed420ccd9 100644 --- a/extensions/ext-blob-s3/src/s3-storage.ts +++ b/extensions/ext-blob-s3/src/s3-storage.ts @@ -405,7 +405,7 @@ function createDefaultResources(config: NormalizedS3Config): DefaultS3Resources useFipsEndpoint: config.useFipsEndpoint, userAgentAppId: "veryfront-ext-blob-s3", }); - const send = client.send.bind(client) as unknown as S3BlobStorageClient["send"]; + const send = client.send.bind(client) as S3BlobStorageClient["send"]; return { client: { send, @@ -585,7 +585,7 @@ function toNodeReadable(stream: ReadableStream): Readable { // The npm AWS SDK selects its Node transport in Deno, Bun, and Node. Convert // the framework's web-stream contract at this extension boundary. return Readable.fromWeb( - stream as unknown as Parameters[0], + stream as Parameters[0], { objectMode: false }, ); } diff --git a/extensions/ext-content-mdx/src/compiler/mdx-compile.ts b/extensions/ext-content-mdx/src/compiler/mdx-compile.ts index 0938cd4ab5..7d6cb36b3e 100644 --- a/extensions/ext-content-mdx/src/compiler/mdx-compile.ts +++ b/extensions/ext-content-mdx/src/compiler/mdx-compile.ts @@ -25,14 +25,14 @@ export async function compileMdx(options: ContentCompileOptions): Promise, + ); } catch (cause) { throw new TypeError( "ext-css-lightning browserQueries could not be inspected", diff --git a/extensions/ext-db-sqlite/src/index.ts b/extensions/ext-db-sqlite/src/index.ts index a5b8e1b878..040177b9b2 100644 --- a/extensions/ext-db-sqlite/src/index.ts +++ b/extensions/ext-db-sqlite/src/index.ts @@ -9,12 +9,13 @@ import type { ExtensionFactory } from "veryfront/extensions"; import type { SqliteDatabase, SqliteStore } from "veryfront/extensions/compat"; +type SqliteDatabaseCtor = new (path: string) => SqliteDatabase; + async function loadSqliteDatabase(path?: string): Promise { const mod = await import("better-sqlite3"); - // deno-lint-ignore no-explicit-any - const DatabaseCtor = (mod as any).default ?? mod; - // deno-lint-ignore no-explicit-any - return new DatabaseCtor(path ?? ":memory:") as any as SqliteDatabase; + const DatabaseCtor = ((mod as { default?: SqliteDatabaseCtor }).default ?? + mod) as SqliteDatabaseCtor; + return new DatabaseCtor(path ?? ":memory:"); } export class BetterSqliteStore implements SqliteStore { diff --git a/extensions/ext-dev-ui-react/src/dashboard/components/MCPTab.tsx b/extensions/ext-dev-ui-react/src/dashboard/components/MCPTab.tsx index 6216bf0001..ebb32826e3 100644 --- a/extensions/ext-dev-ui-react/src/dashboard/components/MCPTab.tsx +++ b/extensions/ext-dev-ui-react/src/dashboard/components/MCPTab.tsx @@ -49,7 +49,7 @@ export function MCPTab({ tools, resources, prompts }: MCPTabProps): React.JSX.El setSelectedId(e.detail.itemId); } - const listener = handleNavigate as unknown as EventListener; + const listener = handleNavigate as EventListener; globalThis.addEventListener("mcp-navigate", listener); return () => globalThis.removeEventListener("mcp-navigate", listener); }, []); diff --git a/extensions/ext-document-kreuzberg/src/kreuzberg.ts b/extensions/ext-document-kreuzberg/src/kreuzberg.ts index e447899c7c..e048567f10 100644 --- a/extensions/ext-document-kreuzberg/src/kreuzberg.ts +++ b/extensions/ext-document-kreuzberg/src/kreuzberg.ts @@ -18,7 +18,7 @@ type KreuzbergModule = KreuzbergExtractor & { export async function loadKreuzbergNative(): Promise { try { - return await import("@kreuzberg/node") as unknown as KreuzbergExtractor; + return await import("@kreuzberg/node") as KreuzbergExtractor; } catch (error) { if (!isMissingPackageError(error)) throw error; throw new Error( @@ -60,7 +60,7 @@ export async function loadKreuzberg(): Promise { async function importKreuzbergWasm(): Promise { try { - return await import("@kreuzberg/wasm") as unknown as KreuzbergModule; + return await import("@kreuzberg/wasm") as KreuzbergModule; } catch (error) { if (!isMissingPackageError(error)) throw error; throw new Error( diff --git a/extensions/ext-parser-babel/src/index.ts b/extensions/ext-parser-babel/src/index.ts index f03669658b..2092f3163c 100644 --- a/extensions/ext-parser-babel/src/index.ts +++ b/extensions/ext-parser-babel/src/index.ts @@ -85,7 +85,7 @@ function functionHasDirective(node: ASTNode, directive: string): boolean { class BabelCodeParser extends BabelParseOnlyParser implements CodeParser { traverse(ast: ASTNode, visitor: TraverseVisitor): void { - traverse(ast, visitor as unknown as Record); + traverse(ast, visitor); } generate(ast: ASTNode, options?: GenerateOptions): Promise { diff --git a/extensions/ext-parser-babel/src/parser-only.ts b/extensions/ext-parser-babel/src/parser-only.ts index 77f580c6c2..3e429e2ddb 100644 --- a/extensions/ext-parser-babel/src/parser-only.ts +++ b/extensions/ext-parser-babel/src/parser-only.ts @@ -53,6 +53,7 @@ export class BabelParseOnlyParser implements BabelParseOnlyParserContract { allowReturnOutsideFunction: options.filePath?.toLowerCase().endsWith(".cjs") === true, plugins: pickPlugins(options.filePath), }); - return Promise.resolve(ast as unknown as ASTNode); + const node: { type: string } = ast; + return Promise.resolve(node as ASTNode); } } diff --git a/extensions/ext-schema-zod/src/adapter.ts b/extensions/ext-schema-zod/src/adapter.ts index 2b045f6c62..0dd669e228 100644 --- a/extensions/ext-schema-zod/src/adapter.ts +++ b/extensions/ext-schema-zod/src/adapter.ts @@ -40,9 +40,12 @@ const addFormats = addFormatsModule as unknown as ( // deno-lint-ignore no-explicit-any -- zod's chainable APIs return parametric types type AnyZodSchema = z.ZodType; +/** A wrapped Schema carrying its backing zod schema for adapter round-trips. */ +type ZodBackedSchema = Schema & { __zod: AnyZodSchema }; + /** Unwrap our opaque Schema back to the underlying zod schema. */ function toZod(schema: Schema): AnyZodSchema { - return (schema as unknown as { __zod: AnyZodSchema }).__zod; + return (schema as ZodBackedSchema).__zod; } /** Wrap a zod schema as an opaque Schema with chainables routed through zod. */ @@ -53,7 +56,7 @@ function wrap(zs: AnyZodSchema): Schema { // deno-lint-ignore no-explicit-any -- safe within this adapter const anyZs = zs as any; const s: Schema = { - _output: undefined as unknown as T, + _output: undefined as T, optional: () => wrap(zs.optional()), nullable: () => wrap(zs.nullable()), nullish: () => wrap(zs.nullish()), @@ -131,7 +134,7 @@ function wrap(zs: AnyZodSchema): Schema { }; // Attach the underlying zod schema for adapter round-trips without // widening the public Schema surface. - (s as unknown as { __zod: AnyZodSchema }).__zod = zs; + (s as ZodBackedSchema).__zod = zs; return s; } @@ -817,7 +820,7 @@ export function createZodAdapter(): SchemaValidator { function: (): Schema<(...args: unknown[]) => unknown> => // zod 4's z.function() is callable without args/returns and produces a // schema accepting any function. We wrap it as Schema. - wrap(z.function() as unknown as AnyZodSchema), + wrap(z.function() as AnyZodSchema), object: >>(shape: S): Schema> => wrap(z.object(toZodShape(shape))), @@ -827,7 +830,7 @@ export function createZodAdapter(): SchemaValidator { tuple: []>( items: T, ): Schema<{ [K in keyof T]: T[K] extends Schema ? U : never }> => { - const zodItems = items.map((s) => toZod(s)) as unknown as [ + const zodItems = items.map((s) => toZod(s)) as [ AnyZodSchema, ...AnyZodSchema[], ]; @@ -837,12 +840,12 @@ export function createZodAdapter(): SchemaValidator { record: ( keys: Schema, values: Schema, - ): Schema> => wrap(z.record(toZod(keys) as unknown as z.ZodString, toZod(values))), + ): Schema> => wrap(z.record(toZod(keys) as z.ZodString, toZod(values))), union: , ...Schema[]]>( schemas: T, ): Schema ? U : never> => { - const zodSchemas = schemas.map((s: Schema) => toZod(s)) as unknown as [ + const zodSchemas = schemas.map((s: Schema) => toZod(s)) as [ AnyZodSchema, AnyZodSchema, ...AnyZodSchema[], @@ -857,7 +860,7 @@ export function createZodAdapter(): SchemaValidator { discriminator: K, schemas: T, ): Schema ? U : never> => { - const zodSchemas = schemas.map((s: Schema) => toZod(s)) as unknown as [ + const zodSchemas = schemas.map((s: Schema) => toZod(s)) as [ // deno-lint-ignore no-explicit-any -- discriminated-union variants widen here z.ZodObject, // deno-lint-ignore no-explicit-any -- discriminated-union variants widen here @@ -873,7 +876,7 @@ export function createZodAdapter(): SchemaValidator { enum: (values: T): Schema => wrap( - (z.enum as unknown as (v: readonly [string, ...string[]]) => AnyZodSchema)(values), + (z.enum as (v: readonly [string, ...string[]]) => AnyZodSchema)(values), ), lazy: (factory: () => Schema): Schema => wrap(z.lazy(() => toZod(factory()))), @@ -883,7 +886,7 @@ export function createZodAdapter(): SchemaValidator { // contract uses a slimmer `new`-able shape; bridge with a single cast // through unknown. wrap( - (z.instanceof as unknown as (c: unknown) => AnyZodSchema)(ctor), + (z.instanceof as (c: unknown) => AnyZodSchema)(ctor), ), custom: (check?: (value: unknown) => boolean, message?: string): Schema => diff --git a/react/react.ts b/react/react.ts index 7bc45c19fb..bff5dfddf7 100644 --- a/react/react.ts +++ b/react/react.ts @@ -44,14 +44,15 @@ export { } from "@veryfront/react-upstream"; export const __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = ( - ReactUpstream as unknown as { + ReactUpstream as typeof ReactUpstream & { __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE: unknown; } ).__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE; -export const __COMPILER_RUNTIME = (ReactUpstream as unknown as { __COMPILER_RUNTIME: unknown }) - .__COMPILER_RUNTIME; +export const __COMPILER_RUNTIME = + (ReactUpstream as typeof ReactUpstream & { __COMPILER_RUNTIME: unknown }) + .__COMPILER_RUNTIME; export const unstable_useCacheRefresh = - (ReactUpstream as unknown as { unstable_useCacheRefresh: unknown }) + (ReactUpstream as typeof ReactUpstream & { unstable_useCacheRefresh: unknown }) .unstable_useCacheRefresh; export type { diff --git a/src/agent/conversation/lifecycle-run-event-adapter.ts b/src/agent/conversation/lifecycle-run-event-adapter.ts index b65b643bd0..53bada7601 100644 --- a/src/agent/conversation/lifecycle-run-event-adapter.ts +++ b/src/agent/conversation/lifecycle-run-event-adapter.ts @@ -48,15 +48,9 @@ export function createLifecycleRunEventAdapter(input: { DEFAULT_MAX_BUFFERED_CONTENT_BYTES; const flushDelayMs = input.flushDelayMs ?? DEFAULT_FLUSH_DELAY_MS; const setTimer = input.setTimer ?? - ((callback: () => void, delayMs: number) => - globalThis.setTimeout(callback, delayMs) as unknown as number); + ((callback: () => void, delayMs: number) => globalThis.setTimeout(callback, delayMs)); const clearTimer = input.clearTimer ?? - ((timerId: number) => - globalThis.clearTimeout( - timerId as unknown as ReturnType< - typeof globalThis.setTimeout - >, - )); + ((timerId: number) => globalThis.clearTimeout(timerId)); let logicalSequence = 0; let pending: PendingDurableContent | null = null; diff --git a/src/agent/memory/memory.ts b/src/agent/memory/memory.ts index 7da037dadc..3bd9312935 100644 --- a/src/agent/memory/memory.ts +++ b/src/agent/memory/memory.ts @@ -196,19 +196,20 @@ export class SummaryMemory implements () => { if (!this.summary) return [...this.messages]; - const summaryMessage = { + const summaryParts = [ + { + type: "text", + text: `${SUMMARY_MESSAGE_PREFIX}${this.summary}`, + }, + ]; + const summaryMessage: MinimalMessage = { id: "summary", - role: "system" as const, - parts: [ - { - type: "text" as const, - text: `${SUMMARY_MESSAGE_PREFIX}${this.summary}`, - }, - ], + role: "system", + parts: summaryParts, timestamp: Date.now(), - } as unknown as M; + }; - return [summaryMessage, ...this.messages]; + return [summaryMessage as M, ...this.messages]; }, { "memory.type": "summary", "memory.has_summary": !!this.summary }, ), diff --git a/src/agent/react/use-voice-input.ts b/src/agent/react/use-voice-input.ts index 4a884a03a3..7845b37dc0 100644 --- a/src/agent/react/use-voice-input.ts +++ b/src/agent/react/use-voice-input.ts @@ -120,14 +120,14 @@ export function useVoiceInput( const isSupported = React.useMemo((): boolean => { if (typeof globalThis === "undefined") return false; - const g = globalThis as unknown as GlobalWithSpeechRecognition; + const g = globalThis as GlobalWithSpeechRecognition; return Boolean(g.SpeechRecognition ?? g.webkitSpeechRecognition); }, []); React.useEffect(() => { if (!isSupported) return; - const g = globalThis as unknown as GlobalWithSpeechRecognition; + const g = globalThis as GlobalWithSpeechRecognition; const SpeechRecognitionAPI = g.SpeechRecognition ?? g.webkitSpeechRecognition; if (!SpeechRecognitionAPI) return; diff --git a/src/agent/runtime/chat-stream-handler.test-helpers.ts b/src/agent/runtime/chat-stream-handler.test-helpers.ts index 14f50cba5f..e34bab5f05 100644 --- a/src/agent/runtime/chat-stream-handler.test-helpers.ts +++ b/src/agent/runtime/chat-stream-handler.test-helpers.ts @@ -14,7 +14,7 @@ export function createSSECollector() { events.push(JSON.parse(line.slice(6))); } }, - } as unknown as ReadableStreamDefaultController; + } as ReadableStreamDefaultController; return { events, controller, encoder }; } diff --git a/src/agent/runtime/error-utils.ts b/src/agent/runtime/error-utils.ts index 979c5ddf7c..4fccdafd8d 100644 --- a/src/agent/runtime/error-utils.ts +++ b/src/agent/runtime/error-utils.ts @@ -40,7 +40,7 @@ const MAX_BEST_EFFORT_DEPTH = 8; const MAX_BEST_EFFORT_NODES = 256; const OMIT_DIAGNOSTIC_VALUE = Symbol("omit-diagnostic-value"); -function hasOwn(object: object, key: PropertyKey): boolean { +function hasOwn(object: PropertyDescriptor, key: PropertyKey): boolean { return apply(objectHasOwnProperty, object, [key]) as boolean; } @@ -169,7 +169,7 @@ function inspectOwnDescriptor( } function defineDiagnosticProperty( - target: object, + target: BestEffortDiagnosticValue[] | Record, key: PropertyKey, value: BestEffortDiagnosticValue, ): void { @@ -181,7 +181,7 @@ function defineDiagnosticProperty( }); } -function defineDiagnosticSerializationGuard(target: object): void { +function defineDiagnosticSerializationGuard(target: BestEffortDiagnosticValue[]): void { objectDefineProperty(target, "toJSON", { configurable: false, enumerable: false, diff --git a/src/agent/runtime/project-skill-loader.ts b/src/agent/runtime/project-skill-loader.ts index a1a1ac3033..612f6d0daa 100644 --- a/src/agent/runtime/project-skill-loader.ts +++ b/src/agent/runtime/project-skill-loader.ts @@ -42,7 +42,7 @@ const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; const ReflectApply = Reflect.apply; -function hasOwnProperty(value: object, key: PropertyKey): boolean { +function hasOwnProperty(value: Readonly>, key: PropertyKey): boolean { return ReflectApply(ObjectPrototypeHasOwnProperty, value, [key]) as boolean; } diff --git a/src/agent/runtime/skill-metadata.ts b/src/agent/runtime/skill-metadata.ts index d0be65bba1..54f03291d5 100644 --- a/src/agent/runtime/skill-metadata.ts +++ b/src/agent/runtime/skill-metadata.ts @@ -48,7 +48,7 @@ const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; const ReflectApply = Reflect.apply; const ReflectOwnKeys = Reflect.ownKeys; -function hasOwnProperty(value: object, key: PropertyKey): boolean { +function hasOwnProperty(value: Record, key: PropertyKey): boolean { return ReflectApply(ObjectPrototypeHasOwnProperty, value, [key]) as boolean; } diff --git a/src/agent/runtime/skill-prompt.ts b/src/agent/runtime/skill-prompt.ts index 3500789d7a..590e5cde5f 100644 --- a/src/agent/runtime/skill-prompt.ts +++ b/src/agent/runtime/skill-prompt.ts @@ -52,7 +52,7 @@ if (typeof mapIteratorNext !== "function") { throw new NativeError("Map iterator next intrinsic is unavailable"); } -function hasOwn(value: object, key: PropertyKey): boolean { +function hasOwn(value: PropertyDescriptor | Record, key: PropertyKey): boolean { return apply(hasOwnProperty, value, [key]) as boolean; } diff --git a/src/agent/streaming/lifecycle/testing.ts b/src/agent/streaming/lifecycle/testing.ts index 84abd5bfae..e101ab7e83 100644 --- a/src/agent/streaming/lifecycle/testing.ts +++ b/src/agent/streaming/lifecycle/testing.ts @@ -186,7 +186,7 @@ export function createScriptedStreamProvider( }; }, decode(part: T, _snapshot: Readonly): readonly StreamSignal[] { - return [part as unknown as StreamSignal]; + return [part as StreamSignal]; }, classifyError(): StreamProviderError { return { diff --git a/src/channels/control-plane.ts b/src/channels/control-plane.ts index 4baa829a18..90670c82ae 100644 --- a/src/channels/control-plane.ts +++ b/src/channels/control-plane.ts @@ -492,7 +492,7 @@ function requireSignedRequestBinding( } } -function readExpectedRequestBinding(options: object): { +function readExpectedRequestBinding(options: { requestMethod: string; requestPath: string }): { method: string; path: string; } { @@ -784,7 +784,7 @@ export function getRuntimeAgentPublicMetadata( id: string, agent: Agent, ): RuntimeAgentPublicMetadata { - const rawConfig = agent.config as unknown as Record; + const rawConfig = agent.config; const suggestionsParseResult = rawConfig.suggestions === undefined ? null : RuntimeSuggestionsSchema.safeParse( @@ -809,7 +809,7 @@ export function getRuntimeAgentPublicMetadata( } function getRuntimeAgentMetadata(id: string, agent: Agent): RuntimeAgent { - const rawConfig = agent.config as unknown as Record; + const rawConfig = agent.config as { version?: unknown }; const publicMetadata = getRuntimeAgentPublicMetadata(id, agent); return RuntimeAgentSchema.parse({ diff --git a/src/client/spa/component-loader.ts b/src/client/spa/component-loader.ts index ea2892c1ec..0d19ca7510 100644 --- a/src/client/spa/component-loader.ts +++ b/src/client/spa/component-loader.ts @@ -198,7 +198,11 @@ export function clearComponentCache(): void { // Expose component loader globally for hydration scripts if (typeof window !== "undefined") { - const global = window as unknown as Record; + const global = window as { + __VERYFRONT_LOAD_COMPONENT__?: typeof loadComponent; + __VERYFRONT_PRELOAD_COMPONENT__?: typeof preloadComponent; + __VERYFRONT_GET_CACHED_COMPONENT__?: typeof getCachedComponent; + }; global.__VERYFRONT_LOAD_COMPONENT__ = loadComponent; global.__VERYFRONT_PRELOAD_COMPONENT__ = preloadComponent; global.__VERYFRONT_GET_CACHED_COMPONENT__ = getCachedComponent; diff --git a/src/config/declarative-evaluator-worker-protocol.ts b/src/config/declarative-evaluator-worker-protocol.ts index 276caddfd1..f49281150b 100644 --- a/src/config/declarative-evaluator-worker-protocol.ts +++ b/src/config/declarative-evaluator-worker-protocol.ts @@ -182,12 +182,12 @@ const ERROR_REASON_TABLE = ObjectFreeze( } as const satisfies Readonly>, ); -function hasOwn(value: object, key: PropertyKey): boolean { +function hasOwn(value: PropertyDescriptor, key: PropertyKey): boolean { return ReflectApply(ObjectPrototypeHasOwnProperty, value, [key]) as boolean; } function defineDataProperty( - target: object, + target: Record, key: PropertyKey, value: unknown, ): void { @@ -318,7 +318,10 @@ function captureStringMap(value: unknown): Readonly> { return ObjectFreeze(captured); } -function isKnownEnumValue(value: unknown, table: object): value is string { +function isKnownEnumValue( + value: unknown, + table: Readonly>, +): value is string { if (typeof value !== "string") return false; let descriptor: PropertyDescriptor | undefined; try { diff --git a/src/config/loader.ts b/src/config/loader.ts index 3f68218958..0b68f17565 100644 --- a/src/config/loader.ts +++ b/src/config/loader.ts @@ -902,9 +902,7 @@ function createHostedConfigSourceReadFlight( // Register the deferred operation in the caller's async context now. A // queued multi-project read must not inherit the request context of whichever // earlier flight later releases capacity. - const promise = thenPromise(start.promise, operation) as unknown as Promise< - HostedConfigSourceSelection | null - >; + const promise = thenPromise(start.promise, operation); const flight: HostedConfigSourceReadFlight = { key, start, diff --git a/src/errors/safe-diagnostics.ts b/src/errors/safe-diagnostics.ts index 40546ce2e2..1bc72877c8 100644 --- a/src/errors/safe-diagnostics.ts +++ b/src/errors/safe-diagnostics.ts @@ -108,7 +108,7 @@ export function sanitizeOptionalDiagnosticText(value: unknown): string | undefin } function ownDataField( - value: object, + value: Error, key: PropertyKey, ): unknown | typeof MISSING_DATA_FIELD { const descriptor = getOwnPropertyDescriptor(value, key); diff --git a/src/errors/veryfront-error.ts b/src/errors/veryfront-error.ts index bf77fea00c..5531b7176a 100644 --- a/src/errors/veryfront-error.ts +++ b/src/errors/veryfront-error.ts @@ -177,10 +177,7 @@ function snapshotPlainValue( state.seen.add(value); try { if (arrayIsArray(value)) { - const descriptors = getOwnPropertyDescriptors(value) as unknown as Record< - string, - PropertyDescriptor - >; + const descriptors: Record = getOwnPropertyDescriptors(value); const lengthDescriptor = descriptors["length"]; if ( !lengthDescriptor || diff --git a/src/extensions/auth/rsc-action-authorization-provider.ts b/src/extensions/auth/rsc-action-authorization-provider.ts index 2530001e00..845489a623 100644 --- a/src/extensions/auth/rsc-action-authorization-provider.ts +++ b/src/extensions/auth/rsc-action-authorization-provider.ts @@ -21,7 +21,7 @@ const objectPrototype = Object.prototype; const ownKeys = Reflect.ownKeys; const NativeTypeError = TypeError; -function hasOwn(value: object, key: PropertyKey): boolean { +function hasOwn(value: PropertyDescriptor, key: PropertyKey): boolean { return apply(hasOwnProperty, value, [key]) as boolean; } diff --git a/src/extensions/discovery.ts b/src/extensions/discovery.ts index 2c6f8487d5..afd23f31cc 100644 --- a/src/extensions/discovery.ts +++ b/src/extensions/discovery.ts @@ -145,7 +145,7 @@ function parseContractMetadata(value: unknown): PackageContractMetadata | undefi } function readActivationMode( - metadata: Record, + metadata: unknown, ): | ExtensionActivationMode | typeof MISSING_METADATA_PROPERTY @@ -171,7 +171,7 @@ function quotedPath(path: string): string { export function resolvePackageActivation( metadata: PackageMetadata, ): ExtensionActivationMode { - const activation = readActivationMode(metadata as unknown as Record); + const activation = readActivationMode(metadata); return activation === MISSING_METADATA_PROPERTY || activation === "auto" ? "auto" : "explicit"; } diff --git a/src/extensions/distributed/redis-runtime-provider.ts b/src/extensions/distributed/redis-runtime-provider.ts index ae5876c7e7..3ae1baf92c 100644 --- a/src/extensions/distributed/redis-runtime-provider.ts +++ b/src/extensions/distributed/redis-runtime-provider.ts @@ -342,7 +342,7 @@ function captureNodeRedisClient(value: unknown): NodeRedisClient { on(event: "error", listener: (error: unknown) => void): unknown { return Reflect.apply(on, value, [event, listener]); }, - }) as unknown as NodeRedisClient; + }) as NodeRedisClient; } function captureRedisModule(value: unknown): NodeRedisModule { diff --git a/src/extensions/entrypoint-identity.ts b/src/extensions/entrypoint-identity.ts index 2690f09884..726cc6a179 100644 --- a/src/extensions/entrypoint-identity.ts +++ b/src/extensions/entrypoint-identity.ts @@ -64,7 +64,7 @@ async function statWithIdentity(path: string): Promise { if (isNode || isBun) { const fs = await import("node:fs/promises"); - const info = await fs.stat(path, { bigint: true }) as unknown as NodeBigIntFileInfo; + const info: NodeBigIntFileInfo = await fs.stat(path, { bigint: true }); return { isFile: info.isFile(), isDirectory: info.isDirectory(), diff --git a/src/extensions/manifest-reader.ts b/src/extensions/manifest-reader.ts index ce042cf0d5..bbf50d4f53 100644 --- a/src/extensions/manifest-reader.ts +++ b/src/extensions/manifest-reader.ts @@ -183,8 +183,8 @@ async function defaultLstat(path: string): Promise { if (isNode || isBun) { const fs = await import("node:fs/promises"); - const info = await fs.lstat(path, { bigint: true }); - return fromNodeFileInfo(info as unknown as NodeBigIntFileInfo); + const info: NodeBigIntFileInfo = await fs.lstat(path, { bigint: true }); + return fromNodeFileInfo(info); } throw new Error("The current runtime does not provide filesystem access"); @@ -203,7 +203,7 @@ async function defaultOpen(path: string): Promise { if (isNode || isBun) { const fs = await import("node:fs/promises"); - const handle = await fs.open(path, "r") as unknown as NodeFileHandle; + const handle: NodeFileHandle = await fs.open(path, "r"); return { async read(buffer): Promise { const bufferLength = reflectApply(typedArrayByteLength, buffer, []) as number; @@ -228,7 +228,7 @@ function errorContext(path: string, operation: string): Record return { path, operation }; } -function ownStringProperty(value: object, property: string): string | undefined { +function ownStringProperty(value: Error, property: string): string | undefined { const descriptor = reflectApply(objectGetOwnPropertyDescriptor, undefined, [value, property]) as | PropertyDescriptor | undefined; diff --git a/src/extensions/parser/skill-document-parser.ts b/src/extensions/parser/skill-document-parser.ts index 5755805daf..965e722482 100644 --- a/src/extensions/parser/skill-document-parser.ts +++ b/src/extensions/parser/skill-document-parser.ts @@ -36,7 +36,7 @@ function call(fn: (...args: never[]) => T, receiver: unknown, args: unknown[] return apply(fn, receiver, args) as T; } -function hasOwn(value: object, key: PropertyKey): boolean { +function hasOwn(value: PropertyDescriptor, key: PropertyKey): boolean { return call(objectHasOwnProperty, value, [key]); } diff --git a/src/extensions/promise-intrinsics-internal.ts b/src/extensions/promise-intrinsics-internal.ts index 752294a6ba..bc5ede4e42 100644 --- a/src/extensions/promise-intrinsics-internal.ts +++ b/src/extensions/promise-intrinsics-internal.ts @@ -39,7 +39,7 @@ defineProperty( ); freeze(safePromiseSpeciesHolder); -function hasOwn(object: object, key: PropertyKey): boolean { +function hasOwn(object: PropertyDescriptor, key: PropertyKey): boolean { return apply(hasOwnProperty, object, [key]) as boolean; } diff --git a/src/extensions/validation.ts b/src/extensions/validation.ts index 5b7b19a262..5fc99ab0ad 100644 --- a/src/extensions/validation.ts +++ b/src/extensions/validation.ts @@ -226,7 +226,7 @@ function snapshotLegacyProvides( } function readOwnMetadataField( - extension: Extension, + extension: Extension | Record, field: "contracts" | "provides", ): unknown { let descriptor: PropertyDescriptor | undefined; @@ -450,12 +450,12 @@ export function validateExtension(ext: unknown): string[] { let contractsValue: unknown; let legacyProvidesValue: unknown; try { - contractsValue = readOwnMetadataField(candidate as unknown as Extension, "contracts"); + contractsValue = readOwnMetadataField(candidate, "contracts"); } catch (error) { issues.push(describeThrownValue(error)); } try { - legacyProvidesValue = readOwnMetadataField(candidate as unknown as Extension, "provides"); + legacyProvidesValue = readOwnMetadataField(candidate, "provides"); } catch (error) { issues.push(describeThrownValue(error)); } diff --git a/src/html/hydration-script-builder/runtime/main.ts b/src/html/hydration-script-builder/runtime/main.ts index 8c1273565f..e1c41b4744 100644 --- a/src/html/hydration-script-builder/runtime/main.ts +++ b/src/html/hydration-script-builder/runtime/main.ts @@ -21,10 +21,8 @@ import type { HydrationRuntimeEnv, ModuleNamespace, ReactLike, - ReactRoot, RuntimeDocument, RuntimeFetchInit, - RuntimeResponse, RuntimeWindow, } from "./env.ts"; import { createLogging, moduleServerUrl } from "./shared.ts"; @@ -39,18 +37,18 @@ import { createRouterRuntime } from "./router.ts"; import { createHydrationRenderer } from "./renderer.ts"; import { resolveNavigationStore } from "./navigation-store.ts"; -const runtimeWindow = globalThis as unknown as RuntimeWindow; -const runtimeDocument = (globalThis as unknown as { document: RuntimeDocument }).document; +const runtimeWindow: RuntimeWindow = globalThis as typeof globalThis & RuntimeWindow; +const runtimeDocument: RuntimeDocument = + (globalThis as typeof globalThis & { document: RuntimeDocument }).document; const env: HydrationRuntimeEnv = { window: runtimeWindow, document: runtimeDocument, - fetch: (url: string, init?: RuntimeFetchInit) => - fetch(url, init as RequestInit) as unknown as Promise, - React: React as unknown as ReactLike, + fetch: (url: string, init?: RuntimeFetchInit) => fetch(url, init as RequestInit), + React: React as typeof React & ReactLike, RouterProvider, PageContextProvider, - createRoot: (container: unknown) => createRoot(container as HTMLElement) as unknown as ReactRoot, + createRoot: (container: unknown) => createRoot(container as HTMLElement), importModule: (moduleUrl: string) => import(moduleUrl) as Promise, useRouterFromModule, setTimeout: (handler: () => void, timeout?: number) => setTimeout(handler, timeout), @@ -68,7 +66,7 @@ const snapshotModules = createSnapshotModuleImporter({ importModule: env.importModule, fetchModule: env.fetch, reloadDocument: () => runtimeWindow.location.reload(), - recoveryState: runtimeWindow as unknown as Record, + recoveryState: runtimeWindow as RuntimeWindow & Record, }); const componentLoader = createComponentLoader({ diff --git a/src/html/hydration-script-builder/runtime/navigation-store.ts b/src/html/hydration-script-builder/runtime/navigation-store.ts index 85dfcd16bf..69530a005d 100644 --- a/src/html/hydration-script-builder/runtime/navigation-store.ts +++ b/src/html/hydration-script-builder/runtime/navigation-store.ts @@ -48,7 +48,9 @@ export function resolveNavigationStore( usesRegistryFallback, getNavigationStore: () => { const storeKey = Symbol.for(NAVIGATION_STORE_REGISTRY_KEY); - const registry = globalThis as unknown as Record; + const registry = globalThis as + & typeof globalThis + & Record; const existing = registry[storeKey]; if (existing) return existing; diff --git a/src/html/hydration-script-builder/runtime/renderer.ts b/src/html/hydration-script-builder/runtime/renderer.ts index 17b5622e25..903410d7af 100644 --- a/src/html/hydration-script-builder/runtime/renderer.ts +++ b/src/html/hydration-script-builder/runtime/renderer.ts @@ -394,7 +394,7 @@ export function createHydrationRenderer(deps: HydrationRendererDeps): HydrationR container.__reactRoot.render(tree); log("Client-side React app rendered successfully"); } else { - const { hydrateRoot } = await import("react-dom/client") as unknown as { + const { hydrateRoot } = await import("react-dom/client") as { hydrateRoot: (container: unknown, tree: unknown, options?: unknown) => ReactRoot; }; const options = { diff --git a/src/html/hydration-script-builder/runtime/route-timing.ts b/src/html/hydration-script-builder/runtime/route-timing.ts index afc52c6da4..47415da839 100644 --- a/src/html/hydration-script-builder/runtime/route-timing.ts +++ b/src/html/hydration-script-builder/runtime/route-timing.ts @@ -189,7 +189,7 @@ export function createRouteTimingRecorder( if (!entries.length) return null; for (let index = entries.length - 1; index >= 0; index--) { - const entry = entries[index] as unknown as Record; + const entry = entries[index] as PerformanceEntry & Record; const responseEnd = entry?.responseEnd; if ( typeof responseEnd === "number" && diff --git a/src/html/hydration-script-builder/runtime/router.ts b/src/html/hydration-script-builder/runtime/router.ts index bf2a2149fd..595e4b6c49 100644 --- a/src/html/hydration-script-builder/runtime/router.ts +++ b/src/html/hydration-script-builder/runtime/router.ts @@ -7,6 +7,7 @@ import type { ClientRouter, HydrationRuntimeEnv, PageDataPayload, + RuntimeDocument, RuntimeElement, RuntimeEvent, RuntimeFetchInit, @@ -415,11 +416,11 @@ export function createRouterRuntime(deps: RouterRuntimeDeps): RouterRuntime { function handlePageDataVersionMismatch( path: string, data: PageDataPayload, - ): PageDataPayload { + ): PageDataPayload | Promise { if (data.buildVersion && checkVersionMismatch(data.buildVersion)) { log("Version mismatch detected, performing full page reload to:", path); navigateDocument(path); - return new Promise(() => {}) as unknown as PageDataPayload; + return new Promise(() => {}); } return data; @@ -705,7 +706,7 @@ export function createRouterRuntime(deps: RouterRuntimeDeps): RouterRuntime { handoffClientRouteMetadata( pageData.frontmatter ?? {}, - document as unknown as Document, + document as RuntimeDocument & Document, ); if (pageData.css) { @@ -1035,7 +1036,7 @@ export function createRouterRuntime(deps: RouterRuntimeDeps): RouterRuntime { viewportPrefetchObserver?.unobserve(entry.target); const href = getInternalRouteHrefFromLink( - entry.target as unknown as RuntimeElement, + entry.target as Element & RuntimeElement, ); if (href) prefetchPage(href); } @@ -1052,7 +1053,7 @@ export function createRouterRuntime(deps: RouterRuntimeDeps): RouterRuntime { if (observedPrefetchLinks.has(link)) continue; observedPrefetchLinks.add(link); - observer.observe(link as unknown as Element); + observer.observe(link as RuntimeElement & Element); } } diff --git a/src/html/styles-builder/css-hash-cache.ts b/src/html/styles-builder/css-hash-cache.ts index ae7a20a13b..7762750778 100644 --- a/src/html/styles-builder/css-hash-cache.ts +++ b/src/html/styles-builder/css-hash-cache.ts @@ -134,7 +134,7 @@ function createCSSCacheEntry( ); return Object.freeze({ css: detachRetainedString(css), - candidates: Object.freeze(candidates) as unknown as string[], + candidates: Object.freeze(candidates) as string[], stylesheet, pipelineIdentity, }); diff --git a/src/internal-agents/ag-ui-sse.ts b/src/internal-agents/ag-ui-sse.ts index 8d09193ee9..2fddf648df 100644 --- a/src/internal-agents/ag-ui-sse.ts +++ b/src/internal-agents/ag-ui-sse.ts @@ -39,16 +39,13 @@ function buildAgUiEventPayloadSchemas(): Record, + shape: Record>, ): Schema> => - // deno-lint-ignore no-explicit-any - (v.object({ + v.object({ ...shape, elapsedMs: v.number().optional(), emittedAt: v.number().optional(), - } as any) as unknown) as Schema< - Record - >; + }); const schemas: Record>> = { RunStarted: withTiming({ runId: v.string().min(1), diff --git a/src/internal-agents/run-stream.ts b/src/internal-agents/run-stream.ts index 3542fe51bc..92eddb2155 100644 --- a/src/internal-agents/run-stream.ts +++ b/src/internal-agents/run-stream.ts @@ -1032,10 +1032,7 @@ export async function createRuntimeAgentStreamResponse( // Replays whatever the first model call already produced, then // forwards later steps as they happen. RunStarted stays first. modelCallContextRelay.attach((event) => - enqueueIfAttached( - MODEL_CALL_CONTEXT_SSE_EVENT_NAME, - event as unknown as Record, - ) + enqueueIfAttached(MODEL_CALL_CONTEXT_SSE_EVENT_NAME, event) ); heartbeatTimer = setInterval( enqueueHeartbeatIfAttached, diff --git a/src/modules/import-map/loader-primordial-poisoning.worker.ts b/src/modules/import-map/loader-primordial-poisoning.worker.ts index dce259ad38..ed91c85195 100644 --- a/src/modules/import-map/loader-primordial-poisoning.worker.ts +++ b/src/modules/import-map/loader-primordial-poisoning.worker.ts @@ -8,7 +8,7 @@ const denoJson = JSON.stringify({ package: "https://example.com/deno-package.ts", }, }); -const adapter = { +const adapterMock: unknown = { fs: { getAdapterType: () => "VeryfrontFSAdapter", getUnderlyingAdapter: () => ({}), @@ -17,7 +17,8 @@ const adapter = { readFile: () => denoJson, }, env: { get: () => undefined }, -} as unknown as RuntimeAdapter; +}; +const adapter = adapterMock as RuntimeAdapter; const config = { resolve: { importMap: { diff --git a/src/modules/import-map/loader.ts b/src/modules/import-map/loader.ts index ae950f77f0..51665a0842 100644 --- a/src/modules/import-map/loader.ts +++ b/src/modules/import-map/loader.ts @@ -42,8 +42,8 @@ function stringSlice(value: string, start: number, end?: number): string { ) as string; } -function hasOwn(object: object, key: PropertyKey): boolean { - return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; +function hasOwn(descriptor: PropertyDescriptor, key: PropertyKey): boolean { + return ReflectApply(ObjectPrototypeHasOwnProperty, descriptor, [key]) as boolean; } function isFrameworkOwnedSpecifier(specifier: string): boolean { @@ -64,7 +64,7 @@ function removeFrameworkOwnedMappings(record: Record): void { } function readOwnDataProperty( - value: object, + value: Record, key: PropertyKey, label: string, ): unknown { @@ -78,7 +78,10 @@ function readOwnDataProperty( return descriptor.value; } -function assertPlainObject(value: unknown, label: string): asserts value is object { +function assertPlainObject( + value: unknown, + label: string, +): asserts value is Record { if (value === null || typeof value !== "object" || ArrayIsArray(value)) { throw IMPORT_MAP_INVALID.create({ detail: `${label} must be a plain object` }); } diff --git a/src/modules/import-map/merger.ts b/src/modules/import-map/merger.ts index 0969af7ef1..e8df2d8635 100644 --- a/src/modules/import-map/merger.ts +++ b/src/modules/import-map/merger.ts @@ -11,8 +11,8 @@ const ReflectApply = Reflect.apply; const ReflectOwnKeys = Reflect.ownKeys; const IntrinsicTypeError = TypeError; -function hasOwn(object: object, key: PropertyKey): boolean { - return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; +function hasOwn(descriptor: PropertyDescriptor, key: PropertyKey): boolean { + return ReflectApply(ObjectPrototypeHasOwnProperty, descriptor, [key]) as boolean; } function copySnapshotField( diff --git a/src/modules/import-map/preloader-primordial-poisoning.worker.ts b/src/modules/import-map/preloader-primordial-poisoning.worker.ts index 66894d8ba1..46594238bf 100644 --- a/src/modules/import-map/preloader-primordial-poisoning.worker.ts +++ b/src/modules/import-map/preloader-primordial-poisoning.worker.ts @@ -2,10 +2,11 @@ import type { VeryfrontConfig } from "#veryfront/config"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import { ImportMapPreloader } from "./preloader.ts"; -const adapter = { +const adapterMock: unknown = { fs: {}, env: {}, -} as unknown as RuntimeAdapter; +}; +const adapter = adapterMock as RuntimeAdapter; const configA = { resolve: { importMap: { diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index 23f4ad4632..972e50334d 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -68,8 +68,8 @@ function monotonicNow(): number { return ReflectApply(PerformanceNow, IntrinsicPerformance, []) as number; } -function hasOwn(object: object, key: PropertyKey): boolean { - return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; +function hasOwn(descriptor: PropertyDescriptor, key: PropertyKey): boolean { + return ReflectApply(ObjectPrototypeHasOwnProperty, descriptor, [key]) as boolean; } function snapshotEmbeddedImportMap(value: unknown): ImportMapConfig { diff --git a/src/modules/react-loader/transformed-module-coordinator.ts b/src/modules/react-loader/transformed-module-coordinator.ts index e55db91e19..164fadb714 100644 --- a/src/modules/react-loader/transformed-module-coordinator.ts +++ b/src/modules/react-loader/transformed-module-coordinator.ts @@ -837,7 +837,8 @@ export class TransformedModuleCoordinator { }); }, this.#heartbeatIntervalMs); - const timer = this.#heartbeatTimer as unknown as { unref?: () => void }; + const rawTimer: unknown = this.#heartbeatTimer; + const timer = rawTimer as { unref?: () => void }; if (typeof timer.unref === "function") timer.unref(); const deno = (globalThis as { Deno?: { unrefTimer?: (id: number) => void } }).Deno; if (typeof this.#heartbeatTimer === "number" && typeof deno?.unrefTimer === "function") { diff --git a/src/oauth/providers/base.ts b/src/oauth/providers/base.ts index 3dba952a71..beda6501e1 100644 --- a/src/oauth/providers/base.ts +++ b/src/oauth/providers/base.ts @@ -286,7 +286,7 @@ function cloneProviderConfig(config: OAuthProviderConfig): OAuthProviderConfig { raw.defaultScopes = defaultScopes; } - const snapshot: Record = Object.create(null); + const snapshot: OAuthProviderConfig = Object.create(null); for (const [key, value] of Object.entries(raw)) { Object.defineProperty(snapshot, key, { configurable: true, @@ -302,7 +302,7 @@ function cloneProviderConfig(config: OAuthProviderConfig): OAuthProviderConfig { if (tokenMapping.snapshot !== undefined) { snapshot.tokenResponseMapping = tokenMapping.snapshot; } - return snapshot as unknown as OAuthProviderConfig; + return snapshot; } function encodeBasicCredentials(clientId: string, clientSecret: string): string { diff --git a/src/observability/application-errors.ts b/src/observability/application-errors.ts index f08bc9d907..06ec6fd1a2 100644 --- a/src/observability/application-errors.ts +++ b/src/observability/application-errors.ts @@ -236,7 +236,7 @@ const TENANT_BUILD_ERROR_CLASS = "tenant-build"; */ const TENANT_BUILD_FAILURE_TAG = Symbol.for("veryfront.module-loader.tenant-build-failure"); -function hasOwnTrueSymbol(value: object, key: symbol): boolean { +function hasOwnTrueSymbol(value: Error, key: symbol): boolean { const descriptor = ReflectGetOwnPropertyDescriptor(value, key); return descriptor !== undefined && ReflectApply(ObjectPrototypeHasOwnProperty, descriptor, ["value"]) === true && diff --git a/src/observability/auto-instrument.test-helpers.ts b/src/observability/auto-instrument.test-helpers.ts index 89c84298d6..c8c0280db5 100644 --- a/src/observability/auto-instrument.test-helpers.ts +++ b/src/observability/auto-instrument.test-helpers.ts @@ -17,5 +17,5 @@ export function createResolvedFetch(response: Response): typeof fetch { export function createThrowingFetch(error: Error): typeof fetch { return (() => { throw error; - }) as unknown as typeof fetch; + }) as typeof fetch; } diff --git a/src/observability/telemetry-error.ts b/src/observability/telemetry-error.ts index 89241bf2e1..374e25eb04 100644 --- a/src/observability/telemetry-error.ts +++ b/src/observability/telemetry-error.ts @@ -43,12 +43,12 @@ const URL_HREF_GETTER = readOwnDescriptorGetter(NativeURL.prototype, "href"); const INVALID_ERROR_FIELD = Symbol("invalid-error-field"); -function hasOwn(object: object, key: PropertyKey): boolean { - return apply(objectHasOwnProperty, object, [key]) as boolean; +function hasOwn(descriptor: PropertyDescriptor, key: PropertyKey): boolean { + return apply(objectHasOwnProperty, descriptor, [key]) as boolean; } function readOwnDescriptorGetter( - object: object, + object: URL, key: PropertyKey, ): ((this: unknown) => unknown) | undefined { try { diff --git a/src/platform/adapters/file-system-capabilities.ts b/src/platform/adapters/file-system-capabilities.ts index 0e475f4ac0..e5bb02c34d 100644 --- a/src/platform/adapters/file-system-capabilities.ts +++ b/src/platform/adapters/file-system-capabilities.ts @@ -128,7 +128,7 @@ export interface CapturedStaticReaders { }; } -function hasOwn(value: object, key: PropertyKey): boolean { +function hasOwn(value: PropertyDescriptor, key: PropertyKey): boolean { return apply(objectHasOwnProperty, value, [key]) as boolean; } diff --git a/src/platform/adapters/fs/integration.ts b/src/platform/adapters/fs/integration.ts index 63bab312b3..e89f0beb0d 100644 --- a/src/platform/adapters/fs/integration.ts +++ b/src/platform/adapters/fs/integration.ts @@ -43,7 +43,7 @@ function materializeAdapterWithFS( adapter: RuntimeAdapter, wrappedFS: RuntimeAdapter["fs"], ): RuntimeAdapter { - const enhanced: Record = {}; + const enhanced = {} as RuntimeAdapter & Record; const seen = new Set(); let current: object | null = adapter; @@ -58,7 +58,7 @@ function materializeAdapterWithFS( } enhanced.fs = wrappedFS; - return enhanced as unknown as RuntimeAdapter; + return enhanced; } export function enhanceAdapterWithFS( diff --git a/src/platform/adapters/fs/veryfront/request-context.ts b/src/platform/adapters/fs/veryfront/request-context.ts index a1da4ac074..e1c58faaa1 100644 --- a/src/platform/adapters/fs/veryfront/request-context.ts +++ b/src/platform/adapters/fs/veryfront/request-context.ts @@ -40,7 +40,7 @@ export function wrapWithCurrentContext unknown>( return ((...args: Parameters) => { return asyncLocalStorage.run(store, () => fn(...args)); - }) as unknown as T; + }) as T; } export function getRequestScopedFile(cacheKey: string): string | undefined { diff --git a/src/platform/adapters/fs/wrapper.ts b/src/platform/adapters/fs/wrapper.ts index c1e3a5d4f1..8e9ca116a0 100644 --- a/src/platform/adapters/fs/wrapper.ts +++ b/src/platform/adapters/fs/wrapper.ts @@ -18,7 +18,7 @@ import { type CapturedMethod = (...args: never[]) => unknown; -function captureOptionalMethod(value: object, key: string): CapturedMethod | undefined { +function captureOptionalMethod(value: FSAdapter, key: string): CapturedMethod | undefined { const seen = new Set(); let owner: object | null = value; for (let depth = 0; owner !== null && depth < 64; depth++) { @@ -42,7 +42,7 @@ function captureOptionalMethod(value: object, key: string): CapturedMethod | und return undefined; } -function publishFrozen(target: object, key: PropertyKey, value: unknown): void { +function publishFrozen(target: FSAdapterWrapper, key: PropertyKey, value: unknown): void { Object.defineProperty(target, key, { configurable: false, enumerable: true, diff --git a/src/platform/adapters/runtime/deno/filesystem-adapter.ts b/src/platform/adapters/runtime/deno/filesystem-adapter.ts index dd8b2829cd..51a90d7844 100644 --- a/src/platform/adapters/runtime/deno/filesystem-adapter.ts +++ b/src/platform/adapters/runtime/deno/filesystem-adapter.ts @@ -42,7 +42,7 @@ interface DenoFileSystemCapabilityOptions extends NodeFileSystemCapabilityOption readonly denoCreateRuntime?: DenoCreateRuntime | null; } -function hasOwn(value: object, property: PropertyKey): boolean { +function hasOwn(value: DenoFileSystemCapabilityOptions, property: PropertyKey): boolean { return Object.prototype.hasOwnProperty.call(value, property); } diff --git a/src/platform/adapters/runtime/deno/http-server.ts b/src/platform/adapters/runtime/deno/http-server.ts index f01b1d6233..08fa7a4152 100644 --- a/src/platform/adapters/runtime/deno/http-server.ts +++ b/src/platform/adapters/runtime/deno/http-server.ts @@ -254,7 +254,7 @@ export function createDenoServer( }); } return createDenoServerWithRuntime( - runtime as unknown as DenoServeRuntime, + runtime as DenoServeRuntime, handler, options, ); diff --git a/src/platform/adapters/runtime/node/http-server.ts b/src/platform/adapters/runtime/node/http-server.ts index 9857d6de15..2644fe19ea 100644 --- a/src/platform/adapters/runtime/node/http-server.ts +++ b/src/platform/adapters/runtime/node/http-server.ts @@ -160,7 +160,7 @@ export class NodeServer implements Server { /** @internal Native transport for compatibility facades that expose Node's server. */ get nativeHttpServer(): import("node:http").Server { - return this.server as unknown as import("node:http").Server; + return this.server as import("node:http").Server; } /** @internal Update an ephemeral (`port: 0`) listener with its bound port. */ @@ -853,7 +853,7 @@ async function createNodeServerInternal( }; const nodeServer = new NodeServer( - server as unknown as NodeHttpServer, + server as NodeHttpServer, hostname, port, disposeUpgrades, diff --git a/src/platform/adapters/runtime/shared/node-filesystem-adapter.ts b/src/platform/adapters/runtime/shared/node-filesystem-adapter.ts index cb57e2d6e6..30ae0eff89 100644 --- a/src/platform/adapters/runtime/shared/node-filesystem-adapter.ts +++ b/src/platform/adapters/runtime/shared/node-filesystem-adapter.ts @@ -125,7 +125,7 @@ const nodeFileSystemOperations: NodeFileSystemOperations = { }, }; -function hasOwn(value: object, property: PropertyKey): boolean { +function hasOwn(value: NodeFileSystemCapabilityOptions, property: PropertyKey): boolean { return Object.prototype.hasOwnProperty.call(value, property); } diff --git a/src/platform/compat/error-introspection.ts b/src/platform/compat/error-introspection.ts index 7b8fbbee46..30e0338b61 100644 --- a/src/platform/compat/error-introspection.ts +++ b/src/platform/compat/error-introspection.ts @@ -18,7 +18,7 @@ const NativeError = Error; const NativeAsyncFunctionPrototype = getPrototypeOf(async function () {}); const toStringTagSymbol = Symbol.toStringTag; -function hasOwn(object: object, key: PropertyKey): boolean { +function hasOwn(object: PropertyDescriptor, key: PropertyKey): boolean { return apply(objectHasOwnProperty, object, [key]) as boolean; } diff --git a/src/platform/compat/fs.ts b/src/platform/compat/fs.ts index 7f236cd92d..33f1e37a0c 100644 --- a/src/platform/compat/fs.ts +++ b/src/platform/compat/fs.ts @@ -180,7 +180,7 @@ class NodeFileSystem implements FileSystem { import("node:path"), ]); - this.fs = fsModule as unknown as NodeFsPromises; + this.fs = fsModule as NodeFsPromises; this.os = osModule; this.path = pathModule; this.initialized = true; diff --git a/src/platform/compat/http/native-response.ts b/src/platform/compat/http/native-response.ts index c3220918b3..265fc4b22c 100644 --- a/src/platform/compat/http/native-response.ts +++ b/src/platform/compat/http/native-response.ts @@ -89,7 +89,7 @@ export function toNativeResponse( // Re-wrap polyfilled Response as native Response. // At runtime, `response` may be an undici Response (from the dnt shim) that // fails Deno's native instanceof check. Cast to access its properties. - const r = response as unknown as Response; + const r = response as Response; return new NativeResponse(r.body, { status: r.status, statusText: r.statusText, diff --git a/src/platform/compat/http/pinned-fetch.ts b/src/platform/compat/http/pinned-fetch.ts index 3e9b06dd5f..fd323a012c 100644 --- a/src/platform/compat/http/pinned-fetch.ts +++ b/src/platform/compat/http/pinned-fetch.ts @@ -206,7 +206,7 @@ async function writeRequestBody(request: ClientRequest, body: BodyInit | null): const { Readable } = await import("node:stream"); const webStream = body instanceof Blob ? body.stream() : body; const source = Readable.fromWeb( - webStream as unknown as import("node:stream/web").ReadableStream, + webStream as import("node:stream/web").ReadableStream, ); await new Promise((resolve, reject) => { source.once("error", reject); diff --git a/src/platform/compat/kv/factory.ts b/src/platform/compat/kv/factory.ts index d9dea43f4c..33eee959f2 100644 --- a/src/platform/compat/kv/factory.ts +++ b/src/platform/compat/kv/factory.ts @@ -42,7 +42,7 @@ export async function openKv(path?: string): Promise { const db = await sqliteStore.openSqliteDatabase(path); // Extension SqliteDatabase is structurally identical to SqliteDatabase; // cast to satisfy the SqliteKv constructor's nominal type check. - return new SqliteKv(db as unknown as SqliteDatabase); + return new SqliteKv(db as SqliteDatabase); } catch (error) { backendFailures.push(error); serverLogger.warn( diff --git a/src/platform/compat/native-brand-checks.ts b/src/platform/compat/native-brand-checks.ts index bcea357b2c..c372208b69 100644 --- a/src/platform/compat/native-brand-checks.ts +++ b/src/platform/compat/native-brand-checks.ts @@ -24,7 +24,7 @@ const freeze = Object.freeze; const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const objectHasOwnProperty = Object.prototype.hasOwnProperty; -function hasOwn(object: object, key: PropertyKey): boolean { +function hasOwn(object: PropertyDescriptor, key: PropertyKey): boolean { return apply(objectHasOwnProperty, object, [key]) as boolean; } diff --git a/src/platform/compat/not-found-error.ts b/src/platform/compat/not-found-error.ts index 8bcc2a2255..6870632f5b 100644 --- a/src/platform/compat/not-found-error.ts +++ b/src/platform/compat/not-found-error.ts @@ -16,7 +16,7 @@ const reflectApply = Reflect.apply; const setAdd = Set.prototype.add; const setHas = Set.prototype.has; -function hasOwn(value: object, key: PropertyKey): boolean { +function hasOwn(value: PropertyDescriptor, key: PropertyKey): boolean { return reflectApply(hasOwnProperty, value, [key]) as boolean; } diff --git a/src/platform/compat/process/command.ts b/src/platform/compat/process/command.ts index 5262a616da..df595a4aac 100644 --- a/src/platform/compat/process/command.ts +++ b/src/platform/compat/process/command.ts @@ -573,7 +573,7 @@ export async function runCommand( } if (IS_BUN) { - const bunGlobal = globalThis as unknown as { + const bunGlobal = globalThis as typeof globalThis & { Bun: BunCommandRuntime; }; diff --git a/src/platform/compat/process/lifecycle.ts b/src/platform/compat/process/lifecycle.ts index 3c7d7453e8..c33cdf3b07 100644 --- a/src/platform/compat/process/lifecycle.ts +++ b/src/platform/compat/process/lifecycle.ts @@ -124,7 +124,7 @@ export function getRuntimeVersion(): string { const deno = IS_DENO ? getDenoRuntime() : undefined; if (deno) return `Deno ${deno.version.deno}`; if ("Bun" in globalThis) { - return `Bun ${(globalThis as unknown as { Bun: { version: string } }).Bun.version}`; + return `Bun ${(globalThis as typeof globalThis & { Bun: { version: string } }).Bun.version}`; } if (runtimeProcess) return `Node.js ${runtimeProcess.version}`; return "unknown"; diff --git a/src/platform/compat/std/expect.ts b/src/platform/compat/std/expect.ts index e0d503bd6e..404b9addfe 100644 --- a/src/platform/compat/std/expect.ts +++ b/src/platform/compat/std/expect.ts @@ -695,14 +695,14 @@ let expect: ExpectFn; if (isDeno) { const stdExpect = await import("#std/expect.ts"); - expect = stdExpect.expect as unknown as ExpectFn; + expect = stdExpect.expect as ExpectFn; } else if (isBun) { const importBunTest = new Function("return import('bun:test')") as () => Promise<{ expect?: ExternalExpectFn; default?: { expect?: ExternalExpectFn }; }>; const bunTestModule = await importBunTest(); - expect = (bunTestModule.expect ?? bunTestModule.default?.expect) as unknown as ExpectFn; + expect = (bunTestModule.expect ?? bunTestModule.default?.expect) as ExpectFn; } else { expect = createNodeExpect(); } diff --git a/src/platform/compat/std/fs.ts b/src/platform/compat/std/fs.ts index d12aff48cd..cd0cbde2b3 100644 --- a/src/platform/compat/std/fs.ts +++ b/src/platform/compat/std/fs.ts @@ -69,7 +69,7 @@ interface NodeFileSystemModule { // API cannot lazily await its implementation at call time, so load it once only // in runtimes that actually provide Node-compatible filesystem APIs. const nodeFileSystem = isNode || isBun - ? await import("node:fs") as unknown as NodeFileSystemModule + ? await import("node:fs") as NodeFileSystemModule : undefined; const denoRuntime = getDenoRuntime(); diff --git a/src/platform/compat/std/testing/time.ts b/src/platform/compat/std/testing/time.ts index be5d1ce492..4d97e6bd05 100644 --- a/src/platform/compat/std/testing/time.ts +++ b/src/platform/compat/std/testing/time.ts @@ -28,7 +28,13 @@ type TimerGlobals = { Date: DateConstructor; }; -type MutableGlobals = Record; +type MutableGlobals = { + setTimeout: unknown; + clearTimeout: unknown; + setInterval: unknown; + clearInterval: unknown; + Date: unknown; +}; // A callback that reschedules itself with no delay would otherwise spin until // the process is killed, which reads as a hung suite rather than a bad test. @@ -69,7 +75,7 @@ export class FakeTime { throw new Error("FakeTime is already installed; restore the previous instance first"); } - const globals = globalThis as unknown as MutableGlobals; + const globals = globalThis as MutableGlobals; this.#originals = { setTimeout: globalThis.setTimeout, clearTimeout: globalThis.clearTimeout, @@ -145,7 +151,7 @@ export class FakeTime { if (this.#restored) return; this.#restored = true; - const globals = globalThis as unknown as MutableGlobals; + const globals = globalThis as MutableGlobals; globals.setTimeout = this.#originals.setTimeout; globals.clearTimeout = this.#originals.clearTimeout; globals.setInterval = this.#originals.setInterval; diff --git a/src/prompt/validation.ts b/src/prompt/validation.ts index dea5cb010f..6ae1c00e0a 100644 --- a/src/prompt/validation.ts +++ b/src/prompt/validation.ts @@ -19,7 +19,7 @@ function isObjectRecord(value: unknown): value is Record { } function readOwnDataProperty( - object: object, + object: Record | readonly unknown[], property: PropertyKey, field: string, ): OwnDataProperty { diff --git a/src/provider/runtime-loader/json-snapshot.ts b/src/provider/runtime-loader/json-snapshot.ts index b293b1eb72..5ca557a404 100644 --- a/src/provider/runtime-loader/json-snapshot.ts +++ b/src/provider/runtime-loader/json-snapshot.ts @@ -38,7 +38,7 @@ const weakSetHas = WeakSet.prototype.has; const NativeArrayPrototype = Array.prototype; const NativeObjectPrototype = Object.prototype; -function hasOwn(object: object, key: PropertyKey): boolean { +function hasOwn(object: PropertyDescriptor, key: PropertyKey): boolean { return apply(objectHasOwnProperty, object, [key]) as boolean; } @@ -319,7 +319,7 @@ function assertRawJsonTextWithinByteLimit(value: string, maxBytes: number): void } } -function inspectPrototype(value: object): object | null { +function inspectPrototype(value: Record | unknown[]): object | null { try { return objectGetPrototypeOf(value); } catch { @@ -327,7 +327,7 @@ function inspectPrototype(value: object): object | null { } } -function inspectOwnKeys(value: object): (string | symbol)[] { +function inspectOwnKeys(value: Record | unknown[]): (string | symbol)[] { try { return ownKeys(value); } catch { @@ -336,7 +336,7 @@ function inspectOwnKeys(value: object): (string | symbol)[] { } function inspectOwnDescriptor( - value: object, + value: Record | unknown[], key: string | symbol, ): PropertyDescriptor { try { @@ -351,7 +351,7 @@ function inspectOwnDescriptor( } function readDataProperty( - value: object, + value: Record | unknown[], key: string, requireEnumerable: boolean, ): unknown { diff --git a/src/proxy/routing-invalidation-redis.ts b/src/proxy/routing-invalidation-redis.ts index b37585ba40..7c5f011b49 100644 --- a/src/proxy/routing-invalidation-redis.ts +++ b/src/proxy/routing-invalidation-redis.ts @@ -88,7 +88,7 @@ function parseSignedEnvelope(message: string): SignedRoutingInvalidationEnvelope return null; } if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null; - const envelope = parsed as Record; + const envelope = parsed as Partial; if ( envelope.version !== 1 || typeof envelope.issuedAtMs !== "number" || @@ -102,7 +102,7 @@ function parseSignedEnvelope(message: string): SignedRoutingInvalidationEnvelope ) { return null; } - return envelope as unknown as SignedRoutingInvalidationEnvelope; + return envelope as SignedRoutingInvalidationEnvelope; } function signatureDomainPrefix(domain: SignatureDomain): string { @@ -223,7 +223,7 @@ function parseEvent(message: string): ProxyRoutingInvalidationEvent | null { return null; } if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null; - const event = parsed as Record; + const event = parsed as Partial; if ( event.version !== 1 || typeof event.eventId !== "string" || !event.eventId || @@ -236,7 +236,7 @@ function parseEvent(message: string): ProxyRoutingInvalidationEvent | null { ) { return null; } - return event as unknown as ProxyRoutingInvalidationEvent; + return event as ProxyRoutingInvalidationEvent; } function parseAcknowledgement(message: string): RoutingInvalidationAcknowledgement | null { @@ -248,14 +248,14 @@ function parseAcknowledgement(message: string): RoutingInvalidationAcknowledgeme return null; } if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null; - const acknowledgement = parsed as Record; + const acknowledgement = parsed as Partial; if ( typeof acknowledgement.eventId !== "string" || !acknowledgement.eventId || typeof acknowledgement.replicaId !== "string" || !acknowledgement.replicaId ) { return null; } - return acknowledgement as unknown as RoutingInvalidationAcknowledgement; + return acknowledgement as RoutingInvalidationAcknowledgement; } async function createDefaultClient(redisUrl: string): Promise { diff --git a/src/proxy/shutdown-hooks.ts b/src/proxy/shutdown-hooks.ts index e1e500e377..fa094f43ad 100644 --- a/src/proxy/shutdown-hooks.ts +++ b/src/proxy/shutdown-hooks.ts @@ -43,7 +43,7 @@ function appendArrayValue(array: T[], value: T): void { } function defineOwnDataProperty( - object: object, + object: Record, key: PropertyKey, value: unknown, ): void { @@ -60,7 +60,7 @@ export function createProxyShutdownAggregateError( failures: readonly unknown[], message: string, ): AggregateError { - const iterable = createObject(null) as Record; + const iterable = createObject(null) as Record & Iterable; defineOwnDataProperty(iterable, arrayIteratorSymbol, () => { let index = 0; const iterator = createObject(null) as Record; @@ -77,7 +77,7 @@ export function createProxyShutdownAggregateError( return iterator; }); return new NativeAggregateError( - iterable as unknown as Iterable, + iterable, message, ); } @@ -98,7 +98,7 @@ function defineArrayValue(array: T[], index: number, value: T): void { defineProperty(array, index, descriptor); } -function hasOwn(object: object, key: PropertyKey): boolean { +function hasOwn(object: readonly unknown[], key: PropertyKey): boolean { return apply(hasOwnProperty, object, [key]) as boolean; } diff --git a/src/proxy/shutdown-intrinsics.ts b/src/proxy/shutdown-intrinsics.ts index 77b703a2a0..a4ab592a8b 100644 --- a/src/proxy/shutdown-intrinsics.ts +++ b/src/proxy/shutdown-intrinsics.ts @@ -18,7 +18,7 @@ const hasOwnProperty = Object.prototype.hasOwnProperty; const nativePromiseThen = Promise.prototype.then; const promiseSpecies = Symbol.species; -function hasOwn(object: object, key: PropertyKey): boolean { +function hasOwn(object: PropertyDescriptor, key: PropertyKey): boolean { return apply(hasOwnProperty, object, [key]) as boolean; } diff --git a/src/proxy/shutdown-lifecycle.ts b/src/proxy/shutdown-lifecycle.ts index 5004f450f9..d895235eba 100644 --- a/src/proxy/shutdown-lifecycle.ts +++ b/src/proxy/shutdown-lifecycle.ts @@ -70,7 +70,7 @@ function defineRecordValue( defineProperty(record, key, descriptor); } -function hasOwn(object: object, key: PropertyKey): boolean { +function hasOwn(object: Record, key: PropertyKey): boolean { return apply(hasOwnProperty, object, [key]) as boolean; } diff --git a/src/react/components/chat/chat/persistence/conversation-codec.ts b/src/react/components/chat/chat/persistence/conversation-codec.ts index 6f45dc51df..54a712e0ab 100644 --- a/src/react/components/chat/chat/persistence/conversation-codec.ts +++ b/src/react/components/chat/chat/persistence/conversation-codec.ts @@ -228,7 +228,11 @@ function descriptorValue(descriptor: PropertyDescriptor, path: string): unknown return descriptor.value; } -function defineDataProperty(target: object, key: PropertyKey, value: JsonValue): void { +function defineDataProperty( + target: JsonValue[] | JsonRecord, + key: PropertyKey, + value: JsonValue, +): void { const descriptor = ObjectCreate(null) as PropertyDescriptor; descriptor.configurable = true; descriptor.enumerable = true; @@ -813,10 +817,10 @@ function validateConversationValue(value: JsonValue): Conversation { } ids.add(id); }); - return value as unknown as Conversation; + return value as JsonRecord & Conversation; } -function validateSummaryValue(value: JsonValue, path: string): ConversationSummary { +function validateSummaryValue(value: JsonValue, path: string): JsonRecord & ConversationSummary { if (!isRecord(value)) { return fail(path, "conversation summary must be a record"); } @@ -841,14 +845,14 @@ function validateSummaryValue(value: JsonValue, path: string): ConversationSumma `exceeds ${CONVERSATION_STORAGE_LIMITS.maxMessagesPerConversation}`, ); } - return value as unknown as ConversationSummary; + return value as JsonRecord & ConversationSummary; } function byNewest(left: ConversationSummary, right: ConversationSummary): number { return right.updatedAt - left.updatedAt; } -function validateIndexValue(value: JsonValue): ConversationSummary[] { +function validateIndexValue(value: JsonValue): (JsonRecord & ConversationSummary)[] { if (!ArrayIsArray(value)) { return fail("$", "conversation index must be an array"); } @@ -1067,7 +1071,7 @@ export function encodeConversationRecord(conversation: Conversation): EncodedCon snapshot, CONVERSATION_STORAGE_LIMITS.maxConversationBytes, ); - const value = rehydrateJsonValue(snapshot) as unknown as Conversation; + const value = rehydrateJsonValue(snapshot) as JsonRecord & Conversation; return { serialized, value, @@ -1089,7 +1093,7 @@ export function decodeConversationRecord(raw: string): DecodedConversationRecord ); validateConversationValue(snapshot); return { - value: rehydrateJsonValue(snapshot) as unknown as Conversation, + value: rehydrateJsonValue(snapshot) as JsonRecord & Conversation, legacy: unwrapped.legacy, }; } @@ -1105,12 +1109,10 @@ export function encodeConversationIndex( const canonical = validateIndexValue(snapshot); const serialized = serializeEnvelope( "index", - canonical as unknown as JsonValue, + canonical, CONVERSATION_STORAGE_LIMITS.maxIndexBytes, ); - const value = rehydrateJsonValue( - canonical as unknown as JsonValue, - ) as unknown as ConversationSummary[]; + const value = rehydrateJsonValue(canonical) as (JsonRecord & ConversationSummary)[]; return { serialized, value, @@ -1132,9 +1134,7 @@ export function decodeConversationIndex(raw: string): DecodedConversationIndex { ); const canonical = validateIndexValue(snapshot); return { - value: rehydrateJsonValue( - canonical as unknown as JsonValue, - ) as unknown as ConversationSummary[], + value: rehydrateJsonValue(canonical) as (JsonRecord & ConversationSummary)[], legacy: unwrapped.legacy, }; } diff --git a/src/react/components/ui/tooltip.tsx b/src/react/components/ui/tooltip.tsx index cbc52785bb..11b26f359a 100644 --- a/src/react/components/ui/tooltip.tsx +++ b/src/react/components/ui/tooltip.tsx @@ -597,7 +597,7 @@ export function TooltipTrigger( if (event.key === "Escape") context?.dismiss(); }; - const triggerProps: React.HTMLAttributes & { + const triggerProps: AnyProps & React.HTMLAttributes & { ref: React.RefCallback; } = { ...props, @@ -614,8 +614,8 @@ export function TooltipTrigger( if (!child) return {children}; const mergedProps = mergeAsChildProps( - triggerProps as unknown as AnyProps, - childProps as unknown as AnyProps, + triggerProps, + childProps as AnyProps, ); mergedProps["aria-describedby"] = resolvedDescribedBy; mergedProps.id = resolvedId; diff --git a/src/react/primitives/input-box.tsx b/src/react/primitives/input-box.tsx index dcd00f27fc..3478f261d6 100644 --- a/src/react/primitives/input-box.tsx +++ b/src/react/primitives/input-box.tsx @@ -44,7 +44,7 @@ export function handleInputBoxKeyDown( const nativeEvent = e.nativeEvent as KeyboardEvent | undefined; const isComposing = nativeEvent?.isComposing === true || - (e as unknown as { isComposing?: boolean }).isComposing === true || + (e as { isComposing?: boolean }).isComposing === true || e.keyCode === 229; if (e.key !== "Enter" || e.shiftKey || isComposing || !onSubmit) return; diff --git a/src/react/server-render-context.ts b/src/react/server-render-context.ts index 1553d42a7d..0d6148966a 100644 --- a/src/react/server-render-context.ts +++ b/src/react/server-render-context.ts @@ -76,7 +76,7 @@ if (!installedRegistry) { * is stored globally; request data is carried only by the provider stack. */ export function getServerRenderContext( - react: ReactContextRuntime = React as unknown as ReactContextRuntime, + react: ReactContextRuntime = React as ReactContextRuntime, ): unknown { return contextOwner[SERVER_RENDER_CONTEXT_REGISTRY_SYMBOL]!.get(react); } diff --git a/src/registry/project-scoped-registry-manager.ts b/src/registry/project-scoped-registry-manager.ts index 62e31c86e3..c667cbd59c 100644 --- a/src/registry/project-scoped-registry-manager.ts +++ b/src/registry/project-scoped-registry-manager.ts @@ -147,7 +147,7 @@ interface RequestScopeBinding { function trackRegistryManager(manager: ProjectScopedRegistryManager): void { const reference = new WeakRef( - manager as unknown as ProjectScopedRegistryManager, + manager as ProjectScopedRegistryManager, ); registryManagerReferences.add(reference); registryManagerFinalizer.register(manager, reference, reference); diff --git a/src/release-assets/dependency-artifact-builder.ts b/src/release-assets/dependency-artifact-builder.ts index 2f70cbcbc3..95cdfde130 100644 --- a/src/release-assets/dependency-artifact-builder.ts +++ b/src/release-assets/dependency-artifact-builder.ts @@ -134,8 +134,11 @@ function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } -function hasOnlyKeys(value: Record, allowed: readonly string[]): boolean { - const allowedKeys = new Set(allowed); +function hasOnlyKeys( + value: Record, + allowed: readonly K[], +): value is { [P in K]?: unknown } { + const allowedKeys = new Set(allowed); return Object.keys(value).every((key) => allowedKeys.has(key)); } @@ -183,7 +186,7 @@ function parseIdentity(value: unknown): DependencyArtifactIdentity { : value.profile === "standard-v1"; if (!profileMatches) return invalidTaskInput(); - return value as unknown as DependencyArtifactIdentity; + return value as DependencyArtifactIdentity; } function parsePolicy(value: unknown): DependencyArtifactPolicyDecision { diff --git a/src/release-assets/manifest-schema.ts b/src/release-assets/manifest-schema.ts index 2317ee2d4c..2923d46295 100644 --- a/src/release-assets/manifest-schema.ts +++ b/src/release-assets/manifest-schema.ts @@ -173,7 +173,7 @@ function hasUniqueStrings(values: readonly string[]): boolean { return new Set(values).size === values.length; } -function recordEntryCountWithin(value: object, limit: number): boolean { +function recordEntryCountWithin(value: Record, limit: number): boolean { return Object.keys(value).length <= limit; } diff --git a/src/rendering/client/router.ts b/src/rendering/client/router.ts index 5530da9e5b..9f29b743f7 100644 --- a/src/rendering/client/router.ts +++ b/src/rendering/client/router.ts @@ -194,7 +194,7 @@ export class VeryfrontRouter { return; } - const ReactDOMToUse = (globalThis as unknown as GlobalWithReactDOM).ReactDOM ?? ReactDOM; + const ReactDOMToUse = (globalThis as GlobalWithReactDOM).ReactDOM ?? ReactDOM; this.root = ReactDOMToUse.createRoot(rootElement); document.addEventListener("click", this.handleClick); diff --git a/src/rendering/client/state-bridge.ts b/src/rendering/client/state-bridge.ts index 81a4474a8b..5bb6b9fe2f 100644 --- a/src/rendering/client/state-bridge.ts +++ b/src/rendering/client/state-bridge.ts @@ -66,7 +66,7 @@ class StateBridge implements StateStore { this.listeners.set(key, callbacks); } - const typedCallback = callback as unknown as (value: unknown) => void; + const typedCallback = callback as (value: unknown) => void; callbacks.add(typedCallback); return () => { diff --git a/src/rendering/orchestrator/html.ts b/src/rendering/orchestrator/html.ts index a1b19dbc25..4795f44d0f 100644 --- a/src/rendering/orchestrator/html.ts +++ b/src/rendering/orchestrator/html.ts @@ -70,7 +70,8 @@ function toShellFrontmatter( // frontmatter index, while the HTML pipeline supports structured meta/link/ // script/style fields. This boundary narrows only the type view; the shell // immediately validates and snapshots every structured value before use. - return frontmatter as unknown as NonNullable; + const record: Record = frontmatter; + return record as NonNullable; } function injectHeadScriptsAfterImportMap(html: string, scripts: string): string { diff --git a/src/rendering/rsc/server-renderer/tree-processor.ts b/src/rendering/rsc/server-renderer/tree-processor.ts index 67fc1ca1ee..3893ceb6ca 100644 --- a/src/rendering/rsc/server-renderer/tree-processor.ts +++ b/src/rendering/rsc/server-renderer/tree-processor.ts @@ -34,7 +34,7 @@ export async function renderTree { const keys = Object.keys(child); const result = keys.length === 1 && keys[0] === "children" - ? (child as unknown as { children: React.ReactNode }).children + ? (child as { children?: React.ReactNode }).children : child; cache.set(child, result); diff --git a/src/routing/client/dom-utils.test-helpers.ts b/src/routing/client/dom-utils.test-helpers.ts index 3add7f59dc..d12ba55b30 100644 --- a/src/routing/client/dom-utils.test-helpers.ts +++ b/src/routing/client/dom-utils.test-helpers.ts @@ -101,7 +101,7 @@ export function createMockAnchor( href: string, attributes: Record = {}, ): HTMLAnchorElement { - return new MockHTMLAnchorElement(href, attributes) as unknown as HTMLAnchorElement; + return new MockHTMLAnchorElement(href, attributes) as MockHTMLAnchorElement & HTMLAnchorElement; } export function createMockElement( @@ -112,8 +112,8 @@ export function createMockElement( return new MockHTMLElement( tagName, attributes, - parent as unknown as MockHTMLElement | MockHTMLAnchorElement | null, - ) as unknown as HTMLElement; + parent as (HTMLElement & MockHTMLElement) | (HTMLAnchorElement & MockHTMLAnchorElement) | null, + ) as MockHTMLElement & HTMLElement; } function setupGlobalMock( @@ -132,7 +132,7 @@ function setupGlobalMock( export function setupHTMLAnchorElementMock(): { cleanup: () => void } { return setupGlobalMock( "HTMLAnchorElement", - MockHTMLAnchorElement as unknown as typeof HTMLAnchorElement, + MockHTMLAnchorElement as typeof MockHTMLAnchorElement & typeof HTMLAnchorElement, originalHTMLAnchorElement, ); } @@ -140,13 +140,17 @@ export function setupHTMLAnchorElementMock(): { cleanup: () => void } { export function setupHTMLElementMock(): { cleanup: () => void } { return setupGlobalMock( "HTMLElement", - MockHTMLElement as unknown as typeof HTMLElement, + MockHTMLElement as typeof MockHTMLElement & typeof HTMLElement, originalHTMLElement, ); } export function setupElementMock(): { cleanup: () => void } { - return setupGlobalMock("Element", MockElement as unknown as typeof Element, originalElement); + return setupGlobalMock( + "Element", + MockElement as typeof MockElement & typeof Element, + originalElement, + ); } export function setupDOMMocks(): { cleanup: () => void } { diff --git a/src/runtime/model-call-context.ts b/src/runtime/model-call-context.ts index dafe9964eb..c0a7473261 100644 --- a/src/runtime/model-call-context.ts +++ b/src/runtime/model-call-context.ts @@ -51,11 +51,11 @@ export type ModelCallTool = * provider options contain only validated prompt-cache metadata. Other * provider-specific values are excluded because run events are durable. */ -export interface AgentRunModelCallContextEvent { +export type AgentRunModelCallContextEvent = { type: "AGENT_RUN_MODEL_CALL_CONTEXT"; messages: ModelCallMessage[]; tools?: ModelCallTool[]; -} +}; /** Event produced by an agent run runtime boundary. */ export type AgentRunEvent = AgentRunModelCallContextEvent; diff --git a/src/schemas/lazy.ts b/src/schemas/lazy.ts index 63c679a8c9..401ff5f1f3 100644 --- a/src/schemas/lazy.ts +++ b/src/schemas/lazy.ts @@ -59,7 +59,7 @@ export function lazySchema(getSchema: () => Schema): Schema { } }; const facade: Schema = { - _output: undefined as unknown as T, + _output: undefined as never, optional: () => schema().optional(), nullable: () => schema().nullable(), nullish: () => schema().nullish(), diff --git a/src/security/http/response/builder.ts b/src/security/http/response/builder.ts index c992a4112f..f2fe3908d6 100644 --- a/src/security/http/response/builder.ts +++ b/src/security/http/response/builder.ts @@ -52,7 +52,7 @@ export class ResponseBuilder implements FluentMethodsContext, ResponseMethodsCon // but TS can't verify this because property-assigned methods with generic // `this` parameters resolve to the constraint type, not the class type. staticHelpers.setResponseBuilderClass( - ResponseBuilder as unknown as Parameters[0], + ResponseBuilder as Parameters[0], ); export function createResponseBuilder(config?: ResponseBuilderConfig): ResponseBuilder { diff --git a/src/security/sandbox/project-worker.ts b/src/security/sandbox/project-worker.ts index 1600f14217..082d3bb547 100644 --- a/src/security/sandbox/project-worker.ts +++ b/src/security/sandbox/project-worker.ts @@ -1135,7 +1135,7 @@ export class ProjectWorker { return; } clearTimeout(pending.timer); - pending.resolve(data as unknown as WorkerResponse); + pending.resolve(data as WorkerResponse); this.pending.delete(id); } return; diff --git a/src/security/sandbox/worker-egress-guard.ts b/src/security/sandbox/worker-egress-guard.ts index 0bb72593e2..d219751f01 100644 --- a/src/security/sandbox/worker-egress-guard.ts +++ b/src/security/sandbox/worker-egress-guard.ts @@ -1775,7 +1775,7 @@ export function installWorkerEgressGuard( ); } return await guardedWorkerConnect( - options as unknown as Deno.ConnectOptions, + options as Deno.ConnectOptions & Record, baseOptions, runtime, ); diff --git a/src/security/secure-fs.ts b/src/security/secure-fs.ts index b80f0e8da1..5bbb719085 100644 --- a/src/security/secure-fs.ts +++ b/src/security/secure-fs.ts @@ -120,7 +120,7 @@ const SECURE_FS_IMMUTABLE_AUTHORITY_KEYS = [ "maxWholeFileReadBytes", ] as const; -function hardenSecureFsAuthority(target: object): void { +function hardenSecureFsAuthority(target: SecureFs): void { for (const key of SECURE_FS_IMMUTABLE_AUTHORITY_KEYS) { const descriptor = objectGetOwnPropertyDescriptor(target, key); if (descriptor === undefined || !("value" in descriptor)) { diff --git a/src/server/handlers/dev/dashboard/api.ts b/src/server/handlers/dev/dashboard/api.ts index a7cb7a5022..c45b7c93da 100644 --- a/src/server/handlers/dev/dashboard/api.ts +++ b/src/server/handlers/dev/dashboard/api.ts @@ -191,7 +191,7 @@ function handleListAgents(): Response { const allTools = Array.from(toolRegistry.getAll().entries()); const list = Array.from(agentRegistry.getAll().entries()).map(([id, agent]) => { - const cfg = agent.config as unknown as Record; + const cfg = agent.config; let system: string | null = null; if (typeof cfg.system === "string") system = cfg.system; diff --git a/src/server/handlers/dev/framework-candidates.generated.ts b/src/server/handlers/dev/framework-candidates.generated.ts index fac7aa2f01..64c046da29 100644 --- a/src/server/handlers/dev/framework-candidates.generated.ts +++ b/src/server/handlers/dev/framework-candidates.generated.ts @@ -3080,6 +3080,9 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "ConversationStore}.", "ConversationSummary", "ConversationSummary):", + "ConversationSummary)[]", + "ConversationSummary)[],", + "ConversationSummary)[];", "ConversationSummary,", "ConversationSummary;", "ConversationSummary[]", @@ -9318,6 +9321,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "cannot", "cannot.", "canonical", + "canonical,", "canonical;", "canvas", "capabilities", @@ -11268,9 +11272,9 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "deferredRefreshScopesRef.current.delete(candidate)", "deferredRefreshScopesRef.current.delete(previous);", "deferredRefreshScopesRef.current.delete(scope)", + "defineDataProperty(", "defineDataProperty(output,", "defineDataProperty(target,", - "defineDataProperty(target:", "defineJsonProperty(", "defineJsonProperty(envelope,", "defineJsonProperty(output,", @@ -18326,6 +18330,7 @@ export const FRAMEWORK_CANDIDATES: readonly string[] = [ "registryReady:", "regular", "rehydrateJsonValue(", + "rehydrateJsonValue(canonical)", "rehydrateJsonValue(snapshot)", "rehydrateJsonValue(value:", "reject", diff --git a/src/server/handlers/request/internal-agent-run.test-helpers.ts b/src/server/handlers/request/internal-agent-run.test-helpers.ts index ceccee24b6..ef23f5b435 100644 --- a/src/server/handlers/request/internal-agent-run.test-helpers.ts +++ b/src/server/handlers/request/internal-agent-run.test-helpers.ts @@ -86,7 +86,7 @@ export async function createControlPlaneSignature( } export function createCtx(publicKeyPem?: string): HandlerContext { - return { + const ctx = { projectDir: "/project", adapter: { env: { @@ -99,7 +99,8 @@ export function createCtx(publicKeyPem?: string): HandlerContext { projectSlug: "demo-project", projectId: "proj-1", isLocalProject: false, - } as unknown as HandlerContext; + }; + return ctx as HandlerContext & typeof ctx; } export function createAgent(id = "agent-1"): Agent { diff --git a/src/server/handlers/request/ssr/ssr.handler.test-helpers.ts b/src/server/handlers/request/ssr/ssr.handler.test-helpers.ts index 5fe83ebe8f..a4b68c3a7b 100644 --- a/src/server/handlers/request/ssr/ssr.handler.test-helpers.ts +++ b/src/server/handlers/request/ssr/ssr.handler.test-helpers.ts @@ -3,7 +3,7 @@ import type { HandlerContext } from "../../types.ts"; import type { SSRRenderOptions, SSRServiceLike } from "../../../services/rendering/ssr.service.ts"; export function createMockAdapter(): RuntimeAdapter { - return { + const adapter = { id: "memory", name: "mock", capabilities: { @@ -31,7 +31,8 @@ export function createMockAdapter(): RuntimeAdapter { }, server: { createHandler: () => () => new Response() }, serve: () => Promise.resolve({ close: () => Promise.resolve() } as any), - } as unknown as RuntimeAdapter; + }; + return adapter as RuntimeAdapter & typeof adapter; } export function makeCtx(overrides: Partial = {}): HandlerContext { diff --git a/src/server/index.ts b/src/server/index.ts index 55eba8e64c..b9185e6338 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -204,7 +204,7 @@ function toNativeResponse(res: Response): Response { if (res instanceof _NativeResponse) return res; // TS narrows to `never` after the instanceof check because it can't see the // runtime class divergence between DNT's polyfill Response and native Response. - const src = res as unknown as Response; + const src = res as Response; return new _NativeResponse(src.body, { status: src.status, statusText: src.statusText, @@ -444,8 +444,8 @@ export async function createHandler( }; nodeUpgradeLifecycle.attach( - httpServer as unknown as NodeUpgradeEventSource, - upgradeListener as unknown as (...args: unknown[]) => void, + httpServer as NodeUpgradeEventSource, + upgradeListener as (...args: unknown[]) => void, ); }; diff --git a/src/server/services/rsc/endpoints/action-authorization-snapshot.ts b/src/server/services/rsc/endpoints/action-authorization-snapshot.ts index 198a875285..35ea7eeec3 100644 --- a/src/server/services/rsc/endpoints/action-authorization-snapshot.ts +++ b/src/server/services/rsc/endpoints/action-authorization-snapshot.ts @@ -243,7 +243,7 @@ function snapshotArray( iteratorSymbol, freeze(() => createStableArrayIterator(output)), ); - return freeze(output) as unknown as Readonly; + return freeze(output) as Readonly; } finally { leaveContainer(state, value); } diff --git a/src/server/services/rsc/endpoints/action-parser.ts b/src/server/services/rsc/endpoints/action-parser.ts index 4b73cc97e2..7cf48dd18b 100644 --- a/src/server/services/rsc/endpoints/action-parser.ts +++ b/src/server/services/rsc/endpoints/action-parser.ts @@ -51,7 +51,7 @@ function snapshotArgs(value: unknown): unknown[] | null { ) return null; try { - const descriptors = getOwnPropertyDescriptors(value) as unknown as Record< + const descriptors = getOwnPropertyDescriptors(value) as Record< string, PropertyDescriptor >; diff --git a/src/server/services/rsc/endpoints/endpoint-router.test-helpers.ts b/src/server/services/rsc/endpoints/endpoint-router.test-helpers.ts index 5933118746..06a780bc4a 100644 --- a/src/server/services/rsc/endpoints/endpoint-router.test-helpers.ts +++ b/src/server/services/rsc/endpoints/endpoint-router.test-helpers.ts @@ -28,7 +28,7 @@ export function createMockAdapter( if (await exists(path)) return ""; throw new Deno.errors.NotFound("not found"); }); - return { + const adapter = { id: "memory", name: "mock", capabilities: { @@ -69,7 +69,8 @@ export function createMockAdapter( createHandler: () => () => new Response(), }, serve: () => Promise.resolve({ close: () => Promise.resolve() } as any), - } as unknown as RuntimeAdapter; + }; + return adapter as RuntimeAdapter & typeof adapter; } function createKnownFilesReader( @@ -106,15 +107,15 @@ function createKnownFilesReader( /** Config with RSC enabled */ export const rscEnabledConfig: VeryfrontConfig = { experimental: { rsc: true }, -} as unknown as VeryfrontConfig; +} as VeryfrontConfig & { experimental: { rsc: boolean } }; /** Config with RSC disabled */ export const rscDisabledConfig: VeryfrontConfig = { experimental: { rsc: false }, -} as unknown as VeryfrontConfig; +} as VeryfrontConfig & { experimental: { rsc: boolean } }; /** Config with no experimental section */ -export const noExperimentalConfig: VeryfrontConfig = {} as unknown as VeryfrontConfig; +export const noExperimentalConfig: VeryfrontConfig = {} as VeryfrontConfig; export function makeParams( overrides: Partial & { pathname: string }, diff --git a/src/server/unhandled-rejection-guard.ts b/src/server/unhandled-rejection-guard.ts index 0438b0c6bd..0b2019e2fd 100644 --- a/src/server/unhandled-rejection-guard.ts +++ b/src/server/unhandled-rejection-guard.ts @@ -96,7 +96,7 @@ function describeReason(reason: unknown): { error: string; stack?: string } { } function resolveDefaultTarget(): GuardEventTarget | undefined { - const candidate = globalThis as unknown as Partial; + const candidate = globalThis as Partial; return typeof candidate.addEventListener === "function" && typeof candidate.removeEventListener === "function" ? candidate as GuardEventTarget diff --git a/src/skill/document-parser.ts b/src/skill/document-parser.ts index 59aa92ca0d..f94e12f916 100644 --- a/src/skill/document-parser.ts +++ b/src/skill/document-parser.ts @@ -70,7 +70,7 @@ function invalidFrontmatter(): never { ); } -function hasOwn(value: object, key: PropertyKey): boolean { +function hasOwn(value: PropertyDescriptor, key: PropertyKey): boolean { return call(objectHasOwnProperty, value, [key]); } diff --git a/src/skill/parser.ts b/src/skill/parser.ts index 0af719b0c8..a52806d892 100644 --- a/src/skill/parser.ts +++ b/src/skill/parser.ts @@ -58,7 +58,7 @@ function trim(value: string): string { return apply(stringTrim, value, []) as string; } -function hasOwn(value: object, key: PropertyKey): boolean { +function hasOwn(value: PropertyDescriptor, key: PropertyKey): boolean { return apply(objectHasOwnProperty, value, [key]) as boolean; } diff --git a/src/skill/path-safety.ts b/src/skill/path-safety.ts index 37c6effee2..b65975c38e 100644 --- a/src/skill/path-safety.ts +++ b/src/skill/path-safety.ts @@ -46,7 +46,7 @@ const stringReplaceAll = String.prototype.replaceAll; const stringSplit = String.prototype.split; const stringStartsWith = String.prototype.startsWith; -function hasOwn(value: object, key: PropertyKey): boolean { +function hasOwn(value: PropertyDescriptor, key: PropertyKey): boolean { return apply(objectHasOwnProperty, value, [key]) as boolean; } diff --git a/src/skill/tools.ts b/src/skill/tools.ts index 6d833e5f3b..7a1a5450d1 100644 --- a/src/skill/tools.ts +++ b/src/skill/tools.ts @@ -13,7 +13,8 @@ import { defineSchema } from "#veryfront/schemas/index.ts"; import { LOAD_SKILL_POLICY_CLAUSES } from "./load-skill-policy.ts"; import { tool } from "#veryfront/tool/factory.ts"; import type { Tool, ToolExecutionContext } from "#veryfront/tool"; -import { createFileSystem } from "#veryfront/platform/compat/fs.ts"; +import { createFileSystem, type FileSystem } from "#veryfront/platform/compat/fs.ts"; +import type { FileSystemAdapter } from "#veryfront/platform/adapters/base.ts"; import { isProxyWithoutHooks } from "#veryfront/platform/compat/error-introspection.ts"; import { captureByteReadCapabilities, @@ -153,7 +154,7 @@ async function readSkillFile( } function requireExactSkillReader( - fileSystem: object, + fileSystem: FileSystemAdapter | FileSystem, ): (path: string, byteLimit: number) => Promise { const reader = captureByteReadCapabilities(fileSystem, "Skill filesystem").exact; if (!reader) { diff --git a/src/skill/validation.ts b/src/skill/validation.ts index 2399e4eb96..70a36ff22a 100644 --- a/src/skill/validation.ts +++ b/src/skill/validation.ts @@ -33,7 +33,7 @@ const NativeRangeError = RangeError; const NativeTypeError = TypeError; const ownKeys = Reflect.ownKeys; -function hasOwn(object: object, key: PropertyKey): boolean { +function hasOwn(object: PropertyDescriptor, key: PropertyKey): boolean { return apply(hasOwnProperty, object, [key]) as boolean; } @@ -322,7 +322,7 @@ export function normalizeSkillDefinition(id: string, value: Skill): Skill { id: registryId, metadata, rootPath, - ...(fsAdapter === undefined ? {} : { fsAdapter: fsAdapter as unknown as FileSystemAdapter }), + ...(fsAdapter === undefined ? {} : { fsAdapter: fsAdapter as FileSystemAdapter }), ...(ownerAgentId === undefined ? {} : { ownerAgentId }), ...(shortName === undefined ? {} : { shortName }), }); diff --git a/src/tool/data-properties.ts b/src/tool/data-properties.ts index b5d79996af..0289fd4c19 100644 --- a/src/tool/data-properties.ts +++ b/src/tool/data-properties.ts @@ -19,7 +19,7 @@ function rejectProxy(value: object, label: string): void { if (isProxyWithoutHooks(value)) throw invalidDataProperties(label); } -function hasOwn(value: object, key: PropertyKey): boolean { +function hasOwn(value: PropertyDescriptor, key: PropertyKey): boolean { return ReflectApply(ObjectPrototypeHasOwnProperty, value, [key]) as boolean; } diff --git a/src/tool/factory.ts b/src/tool/factory.ts index 30d5adf418..e09acb454a 100644 --- a/src/tool/factory.ts +++ b/src/tool/factory.ts @@ -17,7 +17,7 @@ const objectHasOwnProperty = Object.prototype.hasOwnProperty; const ownKeys = Reflect.ownKeys; const structuredCloneValue = globalThis.structuredClone; -function hasOwn(object: object, key: PropertyKey): boolean { +function hasOwn(object: PropertyDescriptor, key: PropertyKey): boolean { return apply(objectHasOwnProperty, object, [key]) as boolean; } diff --git a/src/tool/sleep.ts b/src/tool/sleep.ts index 66f3c0a9e2..851ea1d7f0 100644 --- a/src/tool/sleep.ts +++ b/src/tool/sleep.ts @@ -38,7 +38,7 @@ function createSleepToolInputSchema(maxSeconds: number): Schema; + }) as Schema; } /** Input payload for sleep tool. */ diff --git a/src/transforms/esm/http-cache-types.ts b/src/transforms/esm/http-cache-types.ts index b7398f6a6a..d63867b444 100644 --- a/src/transforms/esm/http-cache-types.ts +++ b/src/transforms/esm/http-cache-types.ts @@ -61,7 +61,7 @@ export function brand>( ): TBranded { // Single, centralized unsound widening: attaching a phantom brand to a // runtime value. This is the only place this cast should occur. - return value as unknown as TBranded; + return value as TBranded; } /** diff --git a/src/transforms/mdx/compiler/mdx-compiler.ts b/src/transforms/mdx/compiler/mdx-compiler.ts index 11e6ee64af..bd2348ace9 100644 --- a/src/transforms/mdx/compiler/mdx-compiler.ts +++ b/src/transforms/mdx/compiler/mdx-compiler.ts @@ -15,7 +15,7 @@ const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; const ReflectApply = Reflect.apply; const ReflectGetOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor; -function readOwnDataProperty(value: object, key: PropertyKey): unknown { +function readOwnDataProperty(value: Error, key: PropertyKey): unknown { try { const descriptor = ReflectGetOwnPropertyDescriptor(value, key); if ( diff --git a/src/transforms/mdx/esm-module-loader/jsx/runtime-loader.ts b/src/transforms/mdx/esm-module-loader/jsx/runtime-loader.ts index 712a869586..9a038c3e9d 100644 --- a/src/transforms/mdx/esm-module-loader/jsx/runtime-loader.ts +++ b/src/transforms/mdx/esm-module-loader/jsx/runtime-loader.ts @@ -6,7 +6,12 @@ export interface JSXRuntime { } export async function loadJSXRuntime(): Promise { - const runtime = (await import("react/jsx-dev-runtime")) as unknown as Record; + const runtime: { + Fragment?: unknown; + jsx?: unknown; + jsxs?: unknown; + jsxDEV?: unknown; + } = await import("react/jsx-dev-runtime"); return { Fragment: runtime.Fragment, diff --git a/src/transforms/mdx/index.ts b/src/transforms/mdx/index.ts index 3d30502f04..288510e6ce 100644 --- a/src/transforms/mdx/index.ts +++ b/src/transforms/mdx/index.ts @@ -180,7 +180,7 @@ export const mdxRenderer = new Proxy({} as MDXRenderer, { }, set(_target, prop, value) { const instance = getMDXRendererInstance(); - (instance as unknown as Record)[prop] = value; + (instance as MDXRenderer & Record)[prop] = value; return true; }, has(_target, prop) { diff --git a/src/transforms/pipeline/cache-identity.ts b/src/transforms/pipeline/cache-identity.ts index 6fe24e4f10..d1772e03a8 100644 --- a/src/transforms/pipeline/cache-identity.ts +++ b/src/transforms/pipeline/cache-identity.ts @@ -52,7 +52,7 @@ function encodedByteLength(value: string): number { return ReflectApply(TypedArrayByteLengthGetter, bytes, []) as number; } -function hasOwn(object: object, key: PropertyKey): boolean { +function hasOwn(object: PropertyDescriptor, key: PropertyKey): boolean { return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; } diff --git a/src/transforms/pipeline/stages/browser-server-exports-strip.ts b/src/transforms/pipeline/stages/browser-server-exports-strip.ts index de4b076def..761d0fbf0a 100644 --- a/src/transforms/pipeline/stages/browser-server-exports-strip.ts +++ b/src/transforms/pipeline/stages/browser-server-exports-strip.ts @@ -107,7 +107,7 @@ function nodeName(value: unknown): string | null { function bodyOf(ast: ASTNode): Node[] { const program = (ast as { program?: unknown }).program; - const source = isNode(program) ? program : (ast as unknown as Node); + const source: Node = isNode(program) ? program : ast; const body = source.body; return Array.isArray(body) ? body.filter(isNode) : []; } @@ -1009,7 +1009,7 @@ function dropUnusedImportBindings(body: Node[], hookClosure: Set): Node[ function setBody(ast: ASTNode, body: Node[]): void { const program = (ast as { program?: unknown }).program; - const target = isNode(program) ? program : (ast as unknown as Node); + const target: Node = isNode(program) ? program : ast; target.body = body; } diff --git a/src/types/entities/getEntityInfo.ts b/src/types/entities/getEntityInfo.ts index 4d2aa43630..b9748f3684 100644 --- a/src/types/entities/getEntityInfo.ts +++ b/src/types/entities/getEntityInfo.ts @@ -1,12 +1,12 @@ /** Bounded page and layout entity discovery. @module types/entities/getEntityInfo */ import { extract } from "#std/front-matter/yaml.ts"; -import { createFileSystem } from "#veryfront/platform/compat/fs.ts"; +import { createFileSystem, type FileSystem } from "#veryfront/platform/compat/fs.ts"; import { isCanonicalNotFoundError } from "#veryfront/platform/compat/not-found-error.ts"; import * as pathHelper from "#veryfront/compat/path"; import { detectEntityType, normalizeFrontmatter } from "../entities.ts"; import type { Entity, EntityInfo, Frontmatter } from "../entities.ts"; -import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; +import type { FileSystemAdapter, RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import { withSpan } from "#veryfront/observability/tracing/otlp-setup.ts"; import { logger as baseLogger } from "#veryfront/utils/logger/index.ts"; import { DEFAULT_MAX_FILE_SIZE_BYTES } from "#veryfront/utils/constants/buffers.ts"; @@ -1639,7 +1639,9 @@ function isLexicallyWithinRoot( : hasPathPrefix(filePath, virtualRoot); } -function hasNoSymlinkSemantics(fileSystem: object): boolean { +function hasNoSymlinkSemantics( + fileSystem: FileSystemAdapter | FileSystem, +): boolean { try { const descriptor = Reflect.getOwnPropertyDescriptor(fileSystem, "symlinkSemantics"); return descriptor !== undefined && diff --git a/src/utils/import-lockfile.ts b/src/utils/import-lockfile.ts index 0ba764da1c..c06b8ab554 100644 --- a/src/utils/import-lockfile.ts +++ b/src/utils/import-lockfile.ts @@ -291,7 +291,7 @@ function isRecord(value: unknown): value is Record { } function getOwnDataProperty( - value: object, + value: Record | readonly unknown[], key: PropertyKey, ): { readonly value: unknown } | undefined { const descriptor = objectGetOwnPropertyDescriptor(value, key); diff --git a/src/utils/response-body.ts b/src/utils/response-body.ts index 887f4d9f90..3cda7adaa8 100644 --- a/src/utils/response-body.ts +++ b/src/utils/response-body.ts @@ -31,7 +31,7 @@ const arrayBufferByteLengthGetter = arrayBufferByteLengthGetterCandidate; function requireTypedArrayGetter( property: "buffer" | "byteLength" | "byteOffset", -): (this: object) => unknown { +): (this: Uint8Array) => unknown { const getter = Object.getOwnPropertyDescriptor(typedArrayPrototype, property)?.get; if (typeof getter !== "function") { throw new TypeError(`Required Uint8Array ${property} intrinsic is unavailable`); diff --git a/src/webhook/validation.ts b/src/webhook/validation.ts index 604f53bd55..e4fd779a27 100644 --- a/src/webhook/validation.ts +++ b/src/webhook/validation.ts @@ -338,7 +338,7 @@ function normalizeFilter(value: unknown): WebhookEventFilter | undefined { return snapshotFilterValue( normalized, "Webhook eventFilter", - ) as unknown as WebhookEventFilter; + ) as WebhookEventFilter; } function normalizeAgentMessage( diff --git a/src/workflow/claude-code/tool.ts b/src/workflow/claude-code/tool.ts index 808c670234..0ad554b4e5 100644 --- a/src/workflow/claude-code/tool.ts +++ b/src/workflow/claude-code/tool.ts @@ -5,7 +5,7 @@ */ import { defineSchema } from "#veryfront/schemas/index.ts"; -import type { InferSchema, Schema } from "#veryfront/extensions/schema/index.ts"; +import type { InferSchema } from "#veryfront/extensions/schema/index.ts"; import type { Tool } from "#veryfront/tool"; import { executeAgent } from "./agent.ts"; import type { ClaudeCodeMode, ClaudeCodeResult } from "./types.ts"; @@ -81,7 +81,7 @@ export const claudeCodeTool: Tool = { type: "function", description: "Run a Claude Code agent for complex coding tasks. " + "Supports file editing, bash commands, and iterative problem-solving.", - inputSchema: getClaudeCodeInputSchema() as unknown as Schema, + inputSchema: getClaudeCodeInputSchema(), inputSchemaJson: { type: "object", properties: { diff --git a/src/workflow/claude-code/wire-protocol.ts b/src/workflow/claude-code/wire-protocol.ts index 90ed4faa6c..7bcdb6f55d 100644 --- a/src/workflow/claude-code/wire-protocol.ts +++ b/src/workflow/claude-code/wire-protocol.ts @@ -52,13 +52,13 @@ function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } -function readDataProperty(record: object, key: string): unknown { +function readDataProperty(record: Record, key: string): unknown { const descriptor = Object.getOwnPropertyDescriptor(record, key); return descriptor?.enumerable === true && "value" in descriptor ? descriptor.value : undefined; } function hasExactDataProperties( - record: object, + record: Record, allowedKeys: readonly string[], ): boolean { const allowed = new Set(allowedKeys); @@ -69,7 +69,10 @@ function hasExactDataProperties( }); } -function hasExactEventProperties(record: object, eventKeys: readonly string[]): boolean { +function hasExactEventProperties( + record: Record, + eventKeys: readonly string[], +): boolean { return hasExactDataProperties(record, [...BASE_EVENT_KEYS, ...eventKeys]); } diff --git a/src/workflow/dsl/workflow.ts b/src/workflow/dsl/workflow.ts index 3d5ff8124d..4b48c8c509 100644 --- a/src/workflow/dsl/workflow.ts +++ b/src/workflow/dsl/workflow.ts @@ -69,7 +69,7 @@ export function workflow( // Auto-register for discovery in dev tools // Use type assertion since registry only stores metadata, not the full generic type - workflowRegistry.register(wf as unknown as Workflow); + workflowRegistry.register(wf as Workflow); return wf; } diff --git a/src/workflow/executor/workflow-definition-snapshot.ts b/src/workflow/executor/workflow-definition-snapshot.ts index fc7d4f37f0..68ed8b8c59 100644 --- a/src/workflow/executor/workflow-definition-snapshot.ts +++ b/src/workflow/executor/workflow-definition-snapshot.ts @@ -650,7 +650,7 @@ export function captureWorkflowStringList( seen.add(entry); captured.push(entry); } - return Object.freeze(captured) as unknown as string[]; + return Object.freeze(captured) as string[]; } function captureRetryConfig(value: unknown, label: string): RetryConfig | undefined { @@ -1040,7 +1040,7 @@ function captureNodeList( captured.push(captureNode(values[index], `${label} node at index ${index}`, state, depth)); } validateDependencyGraph(captured, label); - return Object.freeze(captured) as unknown as WorkflowNode[]; + return Object.freeze(captured) as WorkflowNode[]; } function captureDefinition( @@ -1153,7 +1153,7 @@ export function captureWorkflowDefinitions( seenIds.add(workflow.id); captured.push(workflow); } - return Object.freeze(captured) as unknown as WorkflowDefinition[]; + return Object.freeze(captured) as WorkflowDefinition[]; } /** Capture nodes returned by a workflow or composite builder. */ @@ -1179,5 +1179,5 @@ export function captureWorkflowNodes( export function captureWorkflowMapItems(value: unknown, label: string): unknown[] { const entries = inspectDenseArrayValues(value, label); const captured = captureWorkflowStaticValue(entries, label); - return Object.freeze(captured) as unknown as unknown[]; + return Object.freeze(captured) as unknown[]; } diff --git a/src/workflow/react/use-workflow-list.ts b/src/workflow/react/use-workflow-list.ts index 0d2321daa6..f0f38321a3 100644 --- a/src/workflow/react/use-workflow-list.ts +++ b/src/workflow/react/use-workflow-list.ts @@ -99,7 +99,7 @@ export function useWorkflowList(options: UseWorkflowListOptions = {}): UseWorkfl const data: { runs?: WorkflowRun[]; cursor?: string; totalCount?: number } = await response .json(); - const fetchedRuns: WorkflowRun[] = data.runs ?? (data as unknown as WorkflowRun[]); + const fetchedRuns: WorkflowRun[] = data.runs ?? (data as WorkflowRun[]); const nextCursor: string | undefined = data.cursor; const total: number | undefined = data.totalCount; diff --git a/src/workflow/registry.ts b/src/workflow/registry.ts index 17606b417e..1b427b3919 100644 --- a/src/workflow/registry.ts +++ b/src/workflow/registry.ts @@ -156,7 +156,14 @@ function extractMetadata(definition: WorkflowDefinition): WorkflowMetadata { dependsOn: node.dependsOn === undefined ? undefined : Object.freeze([...node.dependsOn]), }; - const config = node.config as unknown as Record; + const config = node.config as { + agent?: unknown; + tool?: unknown; + message?: unknown; + nodes?: unknown; + then?: unknown; + else?: unknown; + }; if (type === "step") { const agentValue = config.agent; diff --git a/src/workflow/types.ts b/src/workflow/types.ts index ce4f7f650e..c161246e97 100644 --- a/src/workflow/types.ts +++ b/src/workflow/types.ts @@ -432,7 +432,7 @@ export function captureApprovalApprovers( seen.add(approver); captured.push(approver); } - return Object.freeze(captured) as unknown as string[]; + return Object.freeze(captured) as string[]; } /** From a86f80befde5482dc302b74058f7a479c9af0b83 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 10:31:02 +0200 Subject: [PATCH 03/11] perf(test): scope test discovery and skip unchanged generators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'deno task test' spent its whole budget before running a test: discovery walked the repo root — including stale in-repo worktree trees holding millions of stray TypeScript files — and the six generate steps ran serially on every invocation. Add a test.include config so discovery only visits real source roots, and route the generate task through an orchestrator that fingerprints each generator's inputs (path/mtime/size, salted with the Deno version), skips units whose inputs are unchanged, and runs the rest concurrently. generate:force bypasses the stamps; CI checkouts are cold, so CI behavior is unchanged. Full suite wall time: 24+ min (never completing) to 3m31s. --- deno.json | 23 ++- scripts/build/run-generate.test.ts | 89 ++++++++++ scripts/build/run-generate.ts | 250 +++++++++++++++++++++++++++++ 3 files changed, 357 insertions(+), 5 deletions(-) create mode 100644 scripts/build/run-generate.test.ts create mode 100644 scripts/build/run-generate.ts diff --git a/deno.json b/deno.json index 072b6fee1a..60b9326899 100644 --- a/deno.json +++ b/deno.json @@ -457,7 +457,8 @@ }, "tasks": { "setup": "deno run --allow-all scripts/setup.ts", - "generate": "deno run -A scripts/build/generate-templates-manifest.ts && deno task generate:dev-ui && deno run -A scripts/build/prebundle-client-scripts.ts && deno run -A scripts/build/prebundle-bridge.ts && deno run -A scripts/build/prebundle-rsc-scripts.ts && deno run -A scripts/build/prebundle-hydration-runtime.ts", + "generate": "deno run -A scripts/build/run-generate.ts", + "generate:force": "deno run -A scripts/build/run-generate.ts --force", "generate:dev-ui": "deno run -A extensions/ext-dev-ui-react/scripts/generate-styles.ts && deno run -A extensions/ext-dev-ui-react/scripts/prebundle.ts", "generate:dev-ui:check": "deno run -A extensions/ext-dev-ui-react/scripts/generate-styles.ts --check && deno run -A extensions/ext-dev-ui-react/scripts/prebundle.ts --check", "generate:manifests:check": "deno run -A scripts/build/generate-templates-manifest.ts --check && deno task generate:dev-ui:check && deno run -A scripts/build/prebundle-hydration-runtime.ts --check && deno run -A scripts/build/prebundle-client-scripts.ts --check && deno run -A scripts/build/prebundle-bridge.ts --check && deno run -A scripts/build/prebundle-rsc-scripts.ts --check", @@ -496,12 +497,12 @@ "build:storybook": "npm --prefix storybook run build-storybook", "storybook:check": "deno test --no-lock --config=scripts/test.deno.json --no-check --allow-read scripts/storybook/storybook-workbench.test.ts", "lint": "DENO_NO_PACKAGE_JSON=1 deno lint && deno lint --config=scripts/test.deno.json scripts/test/ scripts/build/dnt-meta-property-safety.ts scripts/build/dnt-meta-property-safety.test.ts scripts/build/dnt-polyfill.ts scripts/build/dnt-polyfill.test.ts scripts/build/npm-package-metadata.test.ts scripts/build/prepare-framework-sources.test.ts && deno lint --config=scripts/codemods/deno.json scripts/codemods/", - "lint:ci": "deno task lint && deno task lint:core-deps && deno task lint:cross-runtime-jsr && deno task lint:dependency-boundaries && deno task lint:module-boundaries && deno task lint:client-bundle && deno task lint:extension-contracts && deno task lint:extension-capabilities && deno task lint:ban-test-only && deno task lint:sanitizer-baseline && deno task lint:skipped-tests && deno task lint:chat-ratchets && deno task lint:chat-composability && deno task lint:rfc-status && deno task lint:esm-sh-codemod && deno task lint:test-typecheck && deno task lint:cwd-relative-test-reads && deno task lint:dnt-meta-properties && deno task storybook:check && deno task docs:api-reference:check && deno task docs:errors:check && deno task docs:public:check && deno test --frozen --config=scripts/test.deno.json --no-check --allow-read --allow-write --allow-run=bash scripts/ci/setup-deno-workflow.test.ts scripts/ci/prepare-rc-build.test.ts scripts/build/generated-artifact-checks.test.ts scripts/release.test.ts", + "lint:ci": "deno task lint && deno task lint:core-deps && deno task lint:cross-runtime-jsr && deno task lint:dependency-boundaries && deno task lint:module-boundaries && deno task lint:client-bundle && deno task lint:extension-contracts && deno task lint:extension-capabilities && deno task lint:ban-test-only && deno task lint:sanitizer-baseline && deno task lint:skipped-tests && deno task lint:chat-ratchets && deno task lint:chat-composability && deno task lint:rfc-status && deno task lint:esm-sh-codemod && deno task lint:test-typecheck && deno task lint:cwd-relative-test-reads && deno task lint:anti-slop && deno task lint:dnt-meta-properties && deno task storybook:check && deno task docs:api-reference:check && deno task docs:errors:check && deno task docs:public:check && deno test --frozen --config=scripts/test.deno.json --no-check --allow-read --allow-write --allow-run=bash scripts/ci/setup-deno-workflow.test.ts scripts/ci/prepare-rc-build.test.ts scripts/build/generated-artifact-checks.test.ts scripts/release.test.ts", "fmt": "deno fmt src/ cli/ react/ templates/ && deno fmt --config=scripts/test.deno.json scripts/test/ scripts/build/dnt-meta-property-safety.ts scripts/build/dnt-meta-property-safety.test.ts scripts/build/dnt-polyfill.ts scripts/build/dnt-polyfill.test.ts scripts/build/prepare-framework-sources.test.ts && deno fmt --config=scripts/codemods/deno.json scripts/codemods/", "fmt:check": "deno fmt --check src/ cli/ react/ templates/ && deno fmt --check --config=scripts/test.deno.json scripts/test/ scripts/build/dnt-meta-property-safety.ts scripts/build/dnt-meta-property-safety.test.ts scripts/build/dnt-polyfill.ts scripts/build/dnt-polyfill.test.ts scripts/build/prepare-framework-sources.test.ts && deno fmt --check --config=scripts/codemods/deno.json scripts/codemods/", "typecheck": "deno task generate:manifests:check && deno check src/index.ts cli/main.ts src/server/index.ts src/routing/api/index.ts src/rendering/index.ts src/platform/index.ts src/platform/adapters/index.ts src/build/index.ts src/build/production-build/index.ts src/transforms/index.ts src/config/index.ts src/utils/index.ts src/data/index.ts src/security/index.ts src/middleware/index.ts src/server/handlers/dev/index.ts src/server/handlers/request/api/index.ts src/rendering/cache/index.ts src/rendering/cache/stores/index.ts src/rendering/rsc/actions/index.ts src/html/index.ts src/html/hydration-script-builder/runtime/main.ts src/modules/index.ts src/proxy/main.ts src/react/components/ui/index.ts src/chat/index.ts src/markdown/index.ts src/mdx/index.ts src/fs/index.ts src/oauth/index.ts src/agent/index.ts src/agent/service/route-export.check.ts src/eval/index.ts src/tool/index.ts src/workflow/index.ts src/prompt/index.ts src/resource/index.ts src/runs/index.ts src/mcp/index.ts src/provider/index.ts", - "verify": "deno task generate:manifests:check && deno task fmt:check && deno task lint && deno task lint:style && deno task lint:chat-composability && deno task lint:rfc-status && deno task lint:chat-ratchets && deno task lint:esm-sh-codemod && deno task lint:cli-boundary && deno task lint:wildcard-exports && deno task lint:barrel-jsdoc && deno task lint:ban-test-only && deno task lint:sanitizer-baseline && deno task lint:skipped-tests && deno task lint:ban-zod && deno task lint:cwd-relative-test-reads && deno task lint:core-deps && deno task lint:cross-runtime-jsr && deno task lint:dependency-boundaries && deno task lint:module-boundaries && deno task lint:extension-contracts && deno task lint:extension-capabilities && deno task docs:api-reference:check && deno task docs:errors:check && deno task docs:validate && deno task typecheck && deno task typecheck:consumer && deno task test && deno task test:scripts && deno task test:e2e:binary", - "verify:quick": "deno task generate:manifests:check && deno task fmt:check && deno task lint && deno task lint:style && deno task lint:chat-composability && deno task lint:rfc-status && deno task lint:chat-ratchets && deno task lint:esm-sh-codemod && deno task lint:cli-boundary && deno task lint:wildcard-exports && deno task lint:barrel-jsdoc && deno task lint:ban-test-only && deno task lint:sanitizer-baseline && deno task lint:skipped-tests && deno task lint:ban-zod && deno task lint:cwd-relative-test-reads && deno task lint:core-deps && deno task lint:cross-runtime-jsr && deno task lint:dependency-boundaries && deno task lint:module-boundaries && deno task lint:extension-contracts && deno task lint:extension-capabilities && deno task docs:api-reference:check && deno task docs:errors:check && deno task docs:validate && deno task typecheck", + "verify": "deno task generate:manifests:check && deno task fmt:check && deno task lint && deno task lint:style && deno task lint:chat-composability && deno task lint:rfc-status && deno task lint:chat-ratchets && deno task lint:esm-sh-codemod && deno task lint:cli-boundary && deno task lint:wildcard-exports && deno task lint:barrel-jsdoc && deno task lint:ban-test-only && deno task lint:sanitizer-baseline && deno task lint:skipped-tests && deno task lint:ban-zod && deno task lint:cwd-relative-test-reads && deno task lint:anti-slop && deno task lint:core-deps && deno task lint:cross-runtime-jsr && deno task lint:dependency-boundaries && deno task lint:module-boundaries && deno task lint:extension-contracts && deno task lint:extension-capabilities && deno task docs:api-reference:check && deno task docs:errors:check && deno task docs:validate && deno task typecheck && deno task typecheck:consumer && deno task test && deno task test:scripts && deno task test:e2e:binary", + "verify:quick": "deno task generate:manifests:check && deno task fmt:check && deno task lint && deno task lint:style && deno task lint:chat-composability && deno task lint:rfc-status && deno task lint:chat-ratchets && deno task lint:esm-sh-codemod && deno task lint:cli-boundary && deno task lint:wildcard-exports && deno task lint:barrel-jsdoc && deno task lint:ban-test-only && deno task lint:sanitizer-baseline && deno task lint:skipped-tests && deno task lint:ban-zod && deno task lint:cwd-relative-test-reads && deno task lint:anti-slop && deno task lint:core-deps && deno task lint:cross-runtime-jsr && deno task lint:dependency-boundaries && deno task lint:module-boundaries && deno task lint:extension-contracts && deno task lint:extension-capabilities && deno task docs:api-reference:check && deno task docs:errors:check && deno task docs:validate && deno task typecheck", "typecheck:consumer": "deno run --allow-read --allow-run --allow-env --allow-write scripts/typecheck/run-consumer-typecheck.ts", "codemod:chat": "deno run --frozen --config=scripts/codemods/deno.json --allow-read --allow-write --allow-env=BABEL_TYPES_8_BREAKING scripts/codemods/migrate-chat-composition.ts", "codemod:esm-sh": "deno run --frozen --config=scripts/codemods/deno.json --allow-read --allow-write --allow-env=BABEL_TYPES_8_BREAKING scripts/codemods/migrate-esm-sh-imports.ts", @@ -547,8 +548,9 @@ "lint:sanitizer-baseline": "deno run --allow-read scripts/lint/check-sanitizer-baseline.ts", "lint:skipped-tests": "deno run --allow-read scripts/lint/check-skipped-tests-baseline.ts", "lint:cwd-relative-test-reads": "deno run --allow-read scripts/lint/audit-cwd-relative-test-reads.ts", + "lint:anti-slop": "deno run --allow-read scripts/lint/audit-anti-slop.ts", "lint:dnt-meta-properties": "deno run --config=scripts/test.deno.json --frozen --allow-read scripts/build/dnt-meta-property-safety.ts", - "test:scripts": "deno test --config=scripts/test.deno.json --no-check --allow-read --allow-write --allow-run scripts/ci/prepare-rc-build.test.ts scripts/ci/publish-npm-packages.test.ts scripts/ci/setup-deno-workflow.test.ts scripts/build/compile-binary.test.ts scripts/build/dnt-meta-property-safety.test.ts scripts/build/dnt-polyfill.test.ts scripts/build/generate-sbom.test.ts scripts/build/generated-artifact-checks.test.ts scripts/build/npm-dependency-sources.test.ts scripts/build/npm-extension-package-metadata.test.ts scripts/build/npm-package-metadata.test.ts scripts/build/npm-react-shims.test.ts scripts/build/npm-runtime-helper-contract.test.ts scripts/build/prepare-framework-sources.test.ts scripts/build/report-artifact-sizes.test.ts scripts/docs/docs-coverage.test.ts scripts/docs/generate-api-reference.test.ts scripts/docs/guide-validation.test.ts scripts/lint/audit-chat-composability.test.ts scripts/lint/audit-rfc-status.test.ts scripts/lint/audit-core-deps.test.ts scripts/lint/audit-cwd-relative-test-reads.test.ts scripts/lint/audit-cross-runtime-jsr.test.ts scripts/lint/audit-dependency-boundaries.test.ts scripts/lint/audit-extension-capabilities.test.ts scripts/lint/audit-extension-contracts.test.ts scripts/lint/audit-deps.test.ts scripts/lint/check-module-boundaries.test.ts scripts/lint/lint-config.test.ts scripts/lint/ban-test-only.test.ts scripts/lint/check-sanitizer-baseline.test.ts scripts/lint/check-skipped-tests-baseline.test.ts scripts/lint/check-test-typecheck-baseline.test.ts scripts/lint/check-coverage.test.ts scripts/security/audit-npm.test.ts scripts/security/submit-dependency-snapshot.test.ts scripts/test/template-runtime-e2e.test.ts && deno task test:tool-search-live", + "test:scripts": "deno test --config=scripts/test.deno.json --no-check --allow-read --allow-write --allow-run scripts/ci/prepare-rc-build.test.ts scripts/ci/publish-npm-packages.test.ts scripts/ci/setup-deno-workflow.test.ts scripts/build/compile-binary.test.ts scripts/build/dnt-meta-property-safety.test.ts scripts/build/dnt-polyfill.test.ts scripts/build/generate-sbom.test.ts scripts/build/generated-artifact-checks.test.ts scripts/build/npm-dependency-sources.test.ts scripts/build/npm-extension-package-metadata.test.ts scripts/build/npm-package-metadata.test.ts scripts/build/npm-react-shims.test.ts scripts/build/npm-runtime-helper-contract.test.ts scripts/build/prepare-framework-sources.test.ts scripts/build/report-artifact-sizes.test.ts scripts/build/run-generate.test.ts scripts/docs/docs-coverage.test.ts scripts/docs/generate-api-reference.test.ts scripts/docs/guide-validation.test.ts scripts/lint/audit-chat-composability.test.ts scripts/lint/audit-rfc-status.test.ts scripts/lint/audit-core-deps.test.ts scripts/lint/audit-cwd-relative-test-reads.test.ts scripts/lint/audit-cross-runtime-jsr.test.ts scripts/lint/audit-dependency-boundaries.test.ts scripts/lint/audit-extension-capabilities.test.ts scripts/lint/audit-extension-contracts.test.ts scripts/lint/audit-deps.test.ts scripts/lint/check-module-boundaries.test.ts scripts/lint/lint-config.test.ts scripts/lint/ban-test-only.test.ts scripts/lint/check-sanitizer-baseline.test.ts scripts/lint/check-skipped-tests-baseline.test.ts scripts/lint/audit-anti-slop.test.ts scripts/lint/check-test-typecheck-baseline.test.ts scripts/lint/check-coverage.test.ts scripts/security/audit-npm.test.ts scripts/security/submit-dependency-snapshot.test.ts scripts/test/template-runtime-e2e.test.ts && deno task test:tool-search-live", "test:sentry-runtime-packages": "deno test --config=scripts/test.deno.json --no-check --no-lock --allow-read --allow-write --allow-run --allow-env=DENO_DIR,HOME,XDG_CACHE_HOME,LOCALAPPDATA,USERPROFILE scripts/build/sentry-runtime-packages.test.ts", "test:tool-search-live": "VF_DISABLE_LRU_INTERVAL=1 deno test --no-check -A tests/agent/verify-tool-search-live.test.ts", "test:cross-runtime": "deno run --allow-all src/platform/compat/cross-runtime.test.ts", @@ -577,6 +579,17 @@ "submit-deps": "deno run --allow-read --allow-env --allow-net=api.github.com scripts/security/submit-dependency-snapshot.ts", "audit": "deno run --allow-read --allow-run --allow-write scripts/security/audit-npm.ts && npm --prefix storybook audit --package-lock-only --audit-level=high" }, + "test": { + "include": [ + "src/", + "cli/", + "templates/", + "tests/", + "react/", + "extensions/", + "scripts/" + ] + }, "lint": { "include": [ "src/**/*.ts", diff --git a/scripts/build/run-generate.test.ts b/scripts/build/run-generate.test.ts new file mode 100644 index 0000000000..3159bdfa35 --- /dev/null +++ b/scripts/build/run-generate.test.ts @@ -0,0 +1,89 @@ +import { assertEquals, assertNotEquals } from "#veryfront/testing/assert.ts"; +import { describe, it } from "#veryfront/testing/bdd.ts"; +import { + digestEntries, + isFingerprintExcluded, + selectUnitsToRun, + UNITS, +} from "./run-generate.ts"; + +describe("isFingerprintExcluded", () => { + it("excludes generated outputs wherever they live", () => { + assertEquals(isFingerprintExcluded("templates/manifest.generated.ts"), true); + assertEquals( + isFingerprintExcluded("src/html/hydration-script-builder/hydration-runtime.generated.ts"), + true, + ); + }); + + it("excludes the outputs that do not follow the generated naming", () => { + assertEquals(isFingerprintExcluded("templates/manifest.json"), true); + assertEquals(isFingerprintExcluded("src/build/production-build/templates.ts"), true); + }); + + it("excludes test files and keeps ordinary sources", () => { + assertEquals(isFingerprintExcluded("src/utils/version.test.ts"), true); + assertEquals(isFingerprintExcluded("src/utils/version.ts"), false); + assertEquals(isFingerprintExcluded("templates/basic/deno.json"), false); + }); +}); + +describe("digestEntries", () => { + const a = { path: "a.ts", mtime: 1, size: 10 }; + const b = { path: "b.ts", mtime: 2, size: 20 }; + + it("is order-independent over entries", async () => { + assertEquals( + await digestEntries([a, b], "s"), + await digestEntries([b, a], "s"), + ); + }); + + it("changes when an mtime, a size, or the salt changes", async () => { + const base = await digestEntries([a, b], "s"); + assertNotEquals(base, await digestEntries([{ ...a, mtime: 9 }, b], "s")); + assertNotEquals(base, await digestEntries([a, { ...b, size: 9 }], "s")); + assertNotEquals(base, await digestEntries([a, b], "other-deno")); + }); +}); + +describe("selectUnitsToRun", () => { + it("selects stale and unstamped units, skips matching ones", () => { + const selected = selectUnitsToRun( + { fresh: "h1", stale: "h2", unstamped: "h3" }, + { fresh: "h1", stale: "old" }, + false, + ); + + assertEquals(selected.sort(), ["stale", "unstamped"]); + }); + + it("selects everything under force", () => { + assertEquals( + selectUnitsToRun({ a: "h" }, { a: "h" }, true), + ["a"], + ); + }); +}); + +describe("UNITS", () => { + it("covers the six generator steps of the stock chain", () => { + assertEquals(UNITS.map((u) => u.name).sort(), [ + "bridge", + "client-scripts", + "dev-ui", + "hydration-runtime", + "rsc-scripts", + "templates-manifest", + ]); + }); + + it("declares its own generator script as an input for every scripts/build unit", () => { + for (const unit of UNITS) { + const script = unit.commands[0][unit.commands[0].length - 1]; + if (script.startsWith("scripts/build/")) { + assertEquals(unit.inputFiles.includes(script), true, unit.name); + } + } + }); +}); diff --git a/scripts/build/run-generate.ts b/scripts/build/run-generate.ts new file mode 100644 index 0000000000..755a17b705 --- /dev/null +++ b/scripts/build/run-generate.ts @@ -0,0 +1,250 @@ +#!/usr/bin/env -S deno run -A +/** + * Orchestrates the `generate` task: skip-when-unchanged, run-when-needed, + * and run independent generators concurrently. + * + * The stock chain ran six generator processes serially on every invocation, + * which put a multi-minute prefix in front of `deno task test` even when no + * input had changed. Each generator is deterministic over its inputs, so a + * generator whose inputs are byte-for-byte where they were after its last + * successful run cannot produce different output and is safe to skip. + * + * Inputs are fingerprinted as (path, mtime, size) over coarse input roots — + * a superset of what each generator actually reads. That direction of error + * is deliberate: an input-set superset can only over-trigger, never skip a + * needed run. The fingerprint also folds in the Deno version (bundler and + * gzip output change across versions) and deno.json (import map changes + * reach every bundle). + * + * Generator OUTPUTS under the input roots are excluded from fingerprints — + * `*.generated.*` plus the two outputs that don't follow that naming + * (`templates/manifest.json`, `src/build/production-build/templates.ts`). + * Without this, a unit would invalidate itself by running. Test files are + * excluded too: no bundle imports them. + * + * Stamps live in `.cache/generate-stamps.json` (gitignored). CI checkouts + * are cold, so CI always runs everything, exactly as before. `--force` + * bypasses the stamps locally. + */ + +import { fromFileUrl } from "#std/path"; + +export interface GeneratorUnit { + name: string; + /** argv lists run sequentially within the unit. */ + commands: string[][]; + /** Directories whose files form the input fingerprint (coarse superset). */ + inputRoots: string[]; + /** Individual files folded into the fingerprint (the generator itself, config). */ + inputFiles: string[]; +} + +export const UNITS: GeneratorUnit[] = [ + { + name: "templates-manifest", + commands: [["deno", "run", "-A", "scripts/build/generate-templates-manifest.ts"]], + inputRoots: ["templates"], + inputFiles: ["scripts/build/generate-templates-manifest.ts", "deno.json"], + }, + { + name: "dev-ui", + commands: [ + ["deno", "run", "-A", "extensions/ext-dev-ui-react/scripts/generate-styles.ts"], + ["deno", "run", "-A", "extensions/ext-dev-ui-react/scripts/prebundle.ts"], + ], + inputRoots: ["extensions/ext-dev-ui-react"], + inputFiles: ["deno.json"], + }, + { + name: "client-scripts", + commands: [["deno", "run", "-A", "scripts/build/prebundle-client-scripts.ts"]], + inputRoots: ["src"], + inputFiles: ["scripts/build/prebundle-client-scripts.ts", "deno.json"], + }, + { + name: "bridge", + commands: [["deno", "run", "-A", "scripts/build/prebundle-bridge.ts"]], + inputRoots: ["src"], + inputFiles: ["scripts/build/prebundle-bridge.ts", "deno.json"], + }, + { + name: "rsc-scripts", + commands: [["deno", "run", "-A", "scripts/build/prebundle-rsc-scripts.ts"]], + inputRoots: ["src"], + inputFiles: ["scripts/build/prebundle-rsc-scripts.ts", "deno.json"], + }, + { + name: "hydration-runtime", + commands: [["deno", "run", "-A", "scripts/build/prebundle-hydration-runtime.ts"]], + inputRoots: ["src"], + inputFiles: ["scripts/build/prebundle-hydration-runtime.ts", "deno.json"], + }, +]; + +/** Generator outputs that do not follow the `*.generated.*` naming. */ +const OUTPUT_FILES = new Set([ + "templates/manifest.json", + "src/build/production-build/templates.ts", +]); + +/** True for files that must not participate in an input fingerprint. */ +export function isFingerprintExcluded(relPath: string): boolean { + const base = relPath.slice(relPath.lastIndexOf("/") + 1); + if (base.includes(".generated.")) return true; + if (OUTPUT_FILES.has(relPath)) return true; + return base.endsWith(".test.ts") || base.endsWith(".test.tsx"); +} + +export interface FileEntry { + path: string; + mtime: number; + size: number; +} + +/** + * A stable digest over file entries. Order-independent: entries are sorted + * by path before hashing, so directory-walk order cannot flip the hash. + */ +export async function digestEntries( + entries: readonly FileEntry[], + salt: string, +): Promise { + const sorted = [...entries].sort((a, b) => a.path.localeCompare(b.path)); + const text = salt + "\n" + + sorted.map((e) => `${e.path}|${e.mtime}|${e.size}`).join("\n"); + const bytes = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(text), + ); + return Array.from(new Uint8Array(bytes)) + .map((b) => b.toString(16).padStart(2, "0")).join(""); +} + +/** Units whose stamp is missing or stale (or all of them under `force`). */ +export function selectUnitsToRun( + hashes: Record, + stamps: Record, + force: boolean, +): string[] { + return Object.keys(hashes).filter((name) => + force || stamps[name] !== hashes[name] + ); +} + +async function walkEntries( + repoRoot: string, + root: string, + out: FileEntry[], +): Promise { + let entries: AsyncIterable; + try { + entries = Deno.readDir(`${repoRoot}${root}`); + } catch { + return; // an input root may not exist in every checkout + } + for await (const entry of entries) { + const rel = `${root}/${entry.name}`; + if (entry.isDirectory) { + if (entry.name === "node_modules" || entry.name.startsWith(".")) continue; + await walkEntries(repoRoot, rel, out); + } else if (entry.isFile && !isFingerprintExcluded(rel)) { + const stat = await Deno.stat(`${repoRoot}${rel}`); + out.push({ path: rel, mtime: stat.mtime?.getTime() ?? 0, size: stat.size }); + } + } +} + +const STAMP_PATH = ".cache/generate-stamps.json"; + +async function main(): Promise { + const repoRoot = fromFileUrl(new URL("../../", import.meta.url)); + const force = Deno.args.includes("--force"); + + const rootCache = new Map>(); + const entriesFor = (root: string): Promise => { + let cached = rootCache.get(root); + if (cached === undefined) { + cached = (async () => { + const out: FileEntry[] = []; + await walkEntries(repoRoot, root, out); + return out; + })(); + rootCache.set(root, cached); + } + return cached; + }; + + const salt = `deno=${Deno.version.deno}`; + const hashes: Record = {}; + for (const unit of UNITS) { + const entries: FileEntry[] = []; + for (const root of unit.inputRoots) entries.push(...await entriesFor(root)); + for (const file of unit.inputFiles) { + try { + const stat = await Deno.stat(`${repoRoot}${file}`); + entries.push({ + path: file, + mtime: stat.mtime?.getTime() ?? 0, + size: stat.size, + }); + } catch { + // a missing declared input keeps the unit running every time + entries.push({ path: file, mtime: -1, size: -1 }); + } + } + hashes[unit.name] = await digestEntries(entries, salt); + } + + let stamps: Record = {}; + try { + stamps = JSON.parse(await Deno.readTextFile(`${repoRoot}${STAMP_PATH}`)); + } catch { + // no stamps yet: every unit runs + } + + const toRun = new Set(selectUnitsToRun(hashes, stamps, force)); + const skipped = UNITS.filter((u) => !toRun.has(u.name)).map((u) => u.name); + if (skipped.length > 0) { + console.log(`[generate] up to date, skipping: ${skipped.join(", ")}`); + } + if (toRun.size === 0) return; + + const results = await Promise.all( + UNITS.filter((u) => toRun.has(u.name)).map(async (unit) => { + for (const argv of unit.commands) { + const output = await new Deno.Command(argv[0], { + args: argv.slice(1), + cwd: repoRoot, + stdout: "piped", + stderr: "piped", + }).output(); + const text = new TextDecoder().decode(output.stdout) + + new TextDecoder().decode(output.stderr); + if (text.trim().length > 0) { + console.log(text.trimEnd().split("\n").map((l) => `[${unit.name}] ${l}`).join("\n")); + } + if (!output.success) return { name: unit.name, ok: false }; + } + return { name: unit.name, ok: true }; + }), + ); + + for (const result of results) { + if (result.ok) stamps[result.name] = hashes[result.name]; + } + await Deno.mkdir(`${repoRoot}.cache`, { recursive: true }); + await Deno.writeTextFile( + `${repoRoot}${STAMP_PATH}`, + JSON.stringify(stamps, null, 2) + "\n", + ); + + const failed = results.filter((r) => !r.ok).map((r) => r.name); + if (failed.length > 0) { + console.error(`[generate] FAILED: ${failed.join(", ")}`); + Deno.exit(1); + } +} + +if (import.meta.main) { + await main(); +} From aaeeea04233e2c93b19ea0643c11617959b43f24 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 10:35:54 +0200 Subject: [PATCH 04/11] fix(build): content-hash generator inputs and rebuild missing outputs Review follow-ups on the generate orchestrator: fingerprint files by SHA-256 of their bytes instead of (mtime, size), so a same-length in-place edit cannot be mistaken for unchanged input; and declare each unit's outputs, refusing to honor a stamp while any declared output is missing from disk. Content hashing costs ~60ms on a warm skip. --- scripts/build/run-generate.test.ts | 29 +++++-- scripts/build/run-generate.ts | 126 ++++++++++++++++++++--------- 2 files changed, 114 insertions(+), 41 deletions(-) diff --git a/scripts/build/run-generate.test.ts b/scripts/build/run-generate.test.ts index 3159bdfa35..b0f9bd6710 100644 --- a/scripts/build/run-generate.test.ts +++ b/scripts/build/run-generate.test.ts @@ -29,8 +29,8 @@ describe("isFingerprintExcluded", () => { }); describe("digestEntries", () => { - const a = { path: "a.ts", mtime: 1, size: 10 }; - const b = { path: "b.ts", mtime: 2, size: 20 }; + const a = { path: "a.ts", digest: "aaa" }; + const b = { path: "b.ts", digest: "bbb" }; it("is order-independent over entries", async () => { assertEquals( @@ -39,10 +39,9 @@ describe("digestEntries", () => { ); }); - it("changes when an mtime, a size, or the salt changes", async () => { + it("changes when a content digest or the salt changes", async () => { const base = await digestEntries([a, b], "s"); - assertNotEquals(base, await digestEntries([{ ...a, mtime: 9 }, b], "s")); - assertNotEquals(base, await digestEntries([a, { ...b, size: 9 }], "s")); + assertNotEquals(base, await digestEntries([{ ...a, digest: "changed" }, b], "s")); assertNotEquals(base, await digestEntries([a, b], "other-deno")); }); }); @@ -58,6 +57,17 @@ describe("selectUnitsToRun", () => { assertEquals(selected.sort(), ["stale", "unstamped"]); }); + it("selects a stamped unit whose declared output is missing", () => { + assertEquals( + selectUnitsToRun({ a: "h" }, { a: "h" }, false, (name) => name === "a"), + ["a"], + ); + assertEquals( + selectUnitsToRun({ a: "h" }, { a: "h" }, false, () => false), + [], + ); + }); + it("selects everything under force", () => { assertEquals( selectUnitsToRun({ a: "h" }, { a: "h" }, true), @@ -86,4 +96,13 @@ describe("UNITS", () => { } } }); + + it("declares at least one output per unit, all excluded from fingerprints", () => { + for (const unit of UNITS) { + assertEquals(unit.outputs.length > 0, true, unit.name); + for (const output of unit.outputs) { + assertEquals(isFingerprintExcluded(output), true, output); + } + } + }); }); diff --git a/scripts/build/run-generate.ts b/scripts/build/run-generate.ts index 755a17b705..e36834c014 100644 --- a/scripts/build/run-generate.ts +++ b/scripts/build/run-generate.ts @@ -9,12 +9,14 @@ * generator whose inputs are byte-for-byte where they were after its last * successful run cannot produce different output and is safe to skip. * - * Inputs are fingerprinted as (path, mtime, size) over coarse input roots — - * a superset of what each generator actually reads. That direction of error - * is deliberate: an input-set superset can only over-trigger, never skip a - * needed run. The fingerprint also folds in the Deno version (bundler and - * gzip output change across versions) and deno.json (import map changes - * reach every bundle). + * Inputs are fingerprinted by content digest (path + SHA-256 of bytes) over + * coarse input roots — a superset of what each generator actually reads. + * That direction of error is deliberate: an input-set superset can only + * over-trigger, never skip a needed run. The fingerprint also folds in the + * Deno version (bundler and gzip output change across versions) and + * deno.json (import map changes reach every bundle). A stamp is honored + * only while every declared output exists on disk — deleting a generated + * artifact forces its unit to rebuild. * * Generator OUTPUTS under the input roots are excluded from fingerprints — * `*.generated.*` plus the two outputs that don't follow that naming @@ -37,6 +39,8 @@ export interface GeneratorUnit { inputRoots: string[]; /** Individual files folded into the fingerprint (the generator itself, config). */ inputFiles: string[]; + /** Files the unit writes. A missing output forces a run, whatever the stamp says. */ + outputs: string[]; } export const UNITS: GeneratorUnit[] = [ @@ -45,6 +49,7 @@ export const UNITS: GeneratorUnit[] = [ commands: [["deno", "run", "-A", "scripts/build/generate-templates-manifest.ts"]], inputRoots: ["templates"], inputFiles: ["scripts/build/generate-templates-manifest.ts", "deno.json"], + outputs: ["templates/manifest.json", "templates/manifest.generated.ts"], }, { name: "dev-ui", @@ -54,30 +59,41 @@ export const UNITS: GeneratorUnit[] = [ ], inputRoots: ["extensions/ext-dev-ui-react"], inputFiles: ["deno.json"], + outputs: [ + "extensions/ext-dev-ui-react/src/dev-ui-styles.generated.ts", + "extensions/ext-dev-ui-react/src/dev-ui-bundle.generated.ts", + ], }, { name: "client-scripts", commands: [["deno", "run", "-A", "scripts/build/prebundle-client-scripts.ts"]], inputRoots: ["src"], inputFiles: ["scripts/build/prebundle-client-scripts.ts", "deno.json"], + outputs: [ + "src/build/production-build/templates.ts", + "src/server/handlers/dev/framework-candidates.generated.ts", + ], }, { name: "bridge", commands: [["deno", "run", "-A", "scripts/build/prebundle-bridge.ts"]], inputRoots: ["src"], inputFiles: ["scripts/build/prebundle-bridge.ts", "deno.json"], + outputs: ["src/studio/bridge/bridge-bundle.generated.ts"], }, { name: "rsc-scripts", commands: [["deno", "run", "-A", "scripts/build/prebundle-rsc-scripts.ts"]], inputRoots: ["src"], inputFiles: ["scripts/build/prebundle-rsc-scripts.ts", "deno.json"], + outputs: ["src/server/services/rsc/endpoints/rsc-bundles.generated.ts"], }, { name: "hydration-runtime", commands: [["deno", "run", "-A", "scripts/build/prebundle-hydration-runtime.ts"]], inputRoots: ["src"], inputFiles: ["scripts/build/prebundle-hydration-runtime.ts", "deno.json"], + outputs: ["src/html/hydration-script-builder/hydration-runtime.generated.ts"], }, ]; @@ -97,13 +113,15 @@ export function isFingerprintExcluded(relPath: string): boolean { export interface FileEntry { path: string; - mtime: number; - size: number; + /** Hex SHA-256 of the file's bytes — content, not metadata. */ + digest: string; } /** * A stable digest over file entries. Order-independent: entries are sorted * by path before hashing, so directory-walk order cannot flip the hash. + * Entries carry content digests, so a same-length in-place edit — invisible + * to mtime/size fingerprints — still changes the unit hash. */ export async function digestEntries( entries: readonly FileEntry[], @@ -111,30 +129,39 @@ export async function digestEntries( ): Promise { const sorted = [...entries].sort((a, b) => a.path.localeCompare(b.path)); const text = salt + "\n" + - sorted.map((e) => `${e.path}|${e.mtime}|${e.size}`).join("\n"); - const bytes = await crypto.subtle.digest( + sorted.map((e) => `${e.path}|${e.digest}`).join("\n"); + return await sha256Hex(new TextEncoder().encode(text)); +} + +async function sha256Hex(bytes: Uint8Array): Promise { + const digest = await crypto.subtle.digest( "SHA-256", - new TextEncoder().encode(text), + bytes as unknown as BufferSource, ); - return Array.from(new Uint8Array(bytes)) + return Array.from(new Uint8Array(digest)) .map((b) => b.toString(16).padStart(2, "0")).join(""); } -/** Units whose stamp is missing or stale (or all of them under `force`). */ +/** + * Units whose stamp is missing or stale, whose declared outputs are not all + * present on disk, or all of them under `force`. The output check keeps a + * stamp from vouching for artifacts that were deleted after it was written. + */ export function selectUnitsToRun( hashes: Record, stamps: Record, force: boolean, + missingOutputs: (name: string) => boolean = () => false, ): string[] { return Object.keys(hashes).filter((name) => - force || stamps[name] !== hashes[name] + force || stamps[name] !== hashes[name] || missingOutputs(name) ); } -async function walkEntries( +async function walkPaths( repoRoot: string, root: string, - out: FileEntry[], + out: string[], ): Promise { let entries: AsyncIterable; try { @@ -146,14 +173,35 @@ async function walkEntries( const rel = `${root}/${entry.name}`; if (entry.isDirectory) { if (entry.name === "node_modules" || entry.name.startsWith(".")) continue; - await walkEntries(repoRoot, rel, out); + await walkPaths(repoRoot, rel, out); } else if (entry.isFile && !isFingerprintExcluded(rel)) { - const stat = await Deno.stat(`${repoRoot}${rel}`); - out.push({ path: rel, mtime: stat.mtime?.getTime() ?? 0, size: stat.size }); + out.push(rel); } } } +const HASH_CONCURRENCY = 64; + +/** Content-digest a set of files with bounded concurrency. */ +async function hashFiles( + repoRoot: string, + paths: readonly string[], +): Promise { + const out: FileEntry[] = []; + for (let i = 0; i < paths.length; i += HASH_CONCURRENCY) { + const batch = paths.slice(i, i + HASH_CONCURRENCY); + out.push(...await Promise.all(batch.map(async (path) => { + try { + return { path, digest: await sha256Hex(await Deno.readFile(`${repoRoot}${path}`)) }; + } catch { + // a missing declared input keeps the unit running every time + return { path, digest: "missing" }; + } + }))); + } + return out; +} + const STAMP_PATH = ".cache/generate-stamps.json"; async function main(): Promise { @@ -165,9 +213,9 @@ async function main(): Promise { let cached = rootCache.get(root); if (cached === undefined) { cached = (async () => { - const out: FileEntry[] = []; - await walkEntries(repoRoot, root, out); - return out; + const paths: string[] = []; + await walkPaths(repoRoot, root, paths); + return await hashFiles(repoRoot, paths); })(); rootCache.set(root, cached); } @@ -179,19 +227,7 @@ async function main(): Promise { for (const unit of UNITS) { const entries: FileEntry[] = []; for (const root of unit.inputRoots) entries.push(...await entriesFor(root)); - for (const file of unit.inputFiles) { - try { - const stat = await Deno.stat(`${repoRoot}${file}`); - entries.push({ - path: file, - mtime: stat.mtime?.getTime() ?? 0, - size: stat.size, - }); - } catch { - // a missing declared input keeps the unit running every time - entries.push({ path: file, mtime: -1, size: -1 }); - } - } + entries.push(...await hashFiles(repoRoot, unit.inputFiles)); hashes[unit.name] = await digestEntries(entries, salt); } @@ -202,7 +238,25 @@ async function main(): Promise { // no stamps yet: every unit runs } - const toRun = new Set(selectUnitsToRun(hashes, stamps, force)); + const outputExists = (path: string): boolean => { + try { + Deno.statSync(`${repoRoot}${path}`); + return true; + } catch { + return false; + } + }; + const unitsByName = new Map(UNITS.map((u) => [u.name, u])); + const toRun = new Set(selectUnitsToRun( + hashes, + stamps, + force, + (name) => { + const missing = unitsByName.get(name)?.outputs.some((o) => !outputExists(o)) ?? false; + if (missing) console.log(`[generate] output missing, rebuilding: ${name}`); + return missing; + }, + )); const skipped = UNITS.filter((u) => !toRun.has(u.name)).map((u) => u.name); if (skipped.length > 0) { console.log(`[generate] up to date, skipping: ${skipped.join(", ")}`); From 3a5ae6cf1522e515be5c6404b9686930acb45544 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 10:39:39 +0200 Subject: [PATCH 05/11] fix(test): await async unmount in attachment CSRF teardown unmountReactRoot returns a promise whose macrotask yield keeps a timer alive past the step when the caller does not await it; whether the leak sanitizer catches the stray timer is Deno-version timing luck (green on 2.7.7, deterministic suite failure on 2.7.12). Type the unmount helpers as returning Promise and await them in every teardown. --- .../chat/chat/hooks/attachment-csrf.test.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/react/components/chat/chat/hooks/attachment-csrf.test.tsx b/src/react/components/chat/chat/hooks/attachment-csrf.test.tsx index 59f15fad33..40bec3eeec 100644 --- a/src/react/components/chat/chat/hooks/attachment-csrf.test.tsx +++ b/src/react/components/chat/chat/hooks/attachment-csrf.test.tsx @@ -192,7 +192,7 @@ function installXhrCsrfEdge(): { function renderUpload( options: Parameters[0], -): { upload: () => UseUploadResult; unmount: () => void } { +): { upload: () => UseUploadResult; unmount: () => Promise } { let latest: UseUploadResult | null = null; function Capture(): null { latest = useUpload(options); @@ -205,7 +205,7 @@ function renderUpload( function renderAttachments( url: string, -): { attachments: () => UseAttachmentsResult; unmount: () => void } { +): { attachments: () => UseAttachmentsResult; unmount: () => Promise } { let latest: UseAttachmentsResult | null = null; function Capture(): null { latest = useAttachments({ url }); @@ -233,7 +233,7 @@ describe("chat attachment CSRF", () => { assertEquals(edge.statuses.get("POST /api/uploads"), 200); } finally { - view.unmount(); + await view.unmount(); edge.restore(); restoreDom(); } @@ -250,7 +250,7 @@ describe("chat attachment CSRF", () => { assertEquals(edge.tokens.get("POST /api/uploads"), null); } finally { - view.unmount(); + await view.unmount(); edge.restore(); restoreDom(); } @@ -271,7 +271,7 @@ describe("chat attachment CSRF", () => { assertEquals(edge.statuses.get("POST /api/uploads"), 200); } finally { - view.unmount(); + await view.unmount(); edge.restore(); restoreDom(); } @@ -294,7 +294,7 @@ describe("chat attachment CSRF", () => { assertEquals(edge.statuses.get("DELETE /api/uploads"), 204); } finally { - view.unmount(); + await view.unmount(); edge.restore(); restoreDom(); } @@ -322,7 +322,7 @@ describe("chat attachment CSRF", () => { assertEquals(sentToken, null); } finally { - view.unmount(); + await view.unmount(); globalThis.fetch = originalFetch; restoreDom(); } From ab5ec5fdc1abef4729be64add75720f1f22364d0 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 10:58:23 +0200 Subject: [PATCH 06/11] fix(test): pin fetch stub signatures for program-independent typecheck The test-typecheck gate checks entry points in one combined program; fetch stubs with inferred (input, init) parameters pick up whichever merged fetch declaration that program resolves, and property access on the node/Deno union fails. Annotate the stubs' parameters explicitly and cast to typeof fetch, and give the router's globalThis assertion an intersection so it converts under every lib set. --- .../components/chat/chat/hooks/attachment-csrf.test.tsx | 8 ++++---- src/rendering/client/router.ts | 3 ++- src/server/project-env/hosted-authorization.test.ts | 2 +- src/transforms/esm/http-cache.test.ts | 6 +++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/react/components/chat/chat/hooks/attachment-csrf.test.tsx b/src/react/components/chat/chat/hooks/attachment-csrf.test.tsx index 40bec3eeec..6c580e0b33 100644 --- a/src/react/components/chat/chat/hooks/attachment-csrf.test.tsx +++ b/src/react/components/chat/chat/hooks/attachment-csrf.test.tsx @@ -111,7 +111,7 @@ function installCsrfEdge( const statuses = new Map(); const handler = new CsrfHandler(); - globalThis.fetch = async (input, init) => { + globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { const url = new URL(String(input), document.baseURI); const headers = new Headers(init?.headers); if (document.cookie) headers.set("cookie", document.cookie); @@ -125,7 +125,7 @@ function installCsrfEdge( const response = result.response ?? endpoint(req); statuses.set(`${req.method} ${url.pathname}`, response.status); return response; - }; + }) as typeof fetch; return { restore: () => (globalThis.fetch = originalFetch), statuses }; } @@ -306,7 +306,7 @@ describe("chat attachment CSRF", () => { const originalFetch = globalThis.fetch; let sentToken: string | null | undefined; - globalThis.fetch = (input, init) => { + globalThis.fetch = ((input: RequestInfo | URL, init?: RequestInit) => { const method = init?.method ?? "GET"; if (method === "POST") sentToken = new Headers(init?.headers).get("x-csrf-token"); return Promise.resolve( @@ -314,7 +314,7 @@ describe("chat attachment CSRF", () => { ? Response.json({ items: [] }) : Response.json({ id: "up-1", name: "a.txt", url: `${String(input)}/a.txt`, size: 1 }), ); - }; + }) as typeof fetch; const view = renderAttachments("https://uploads.example.net/api/uploads"); try { view.attachments().upload([new File(["a"], "a.txt", { type: "text/plain" })]); diff --git a/src/rendering/client/router.ts b/src/rendering/client/router.ts index 9f29b743f7..f1df0224b4 100644 --- a/src/rendering/client/router.ts +++ b/src/rendering/client/router.ts @@ -194,7 +194,8 @@ export class VeryfrontRouter { return; } - const ReactDOMToUse = (globalThis as GlobalWithReactDOM).ReactDOM ?? ReactDOM; + const ReactDOMToUse = (globalThis as typeof globalThis & GlobalWithReactDOM).ReactDOM ?? + ReactDOM; this.root = ReactDOMToUse.createRoot(rootElement); document.addEventListener("click", this.handleClick); diff --git a/src/server/project-env/hosted-authorization.test.ts b/src/server/project-env/hosted-authorization.test.ts index b52d050716..bde3673f57 100644 --- a/src/server/project-env/hosted-authorization.test.ts +++ b/src/server/project-env/hosted-authorization.test.ts @@ -64,7 +64,7 @@ describe("hosted project environment authorization", () => { setEnv("VERYFRONT_API_INTERNAL_USER", "test-internal-user"); setEnv("VERYFRONT_API_INTERNAL_PASS", "test-internal-pass"); const requests: Array<{ url: string; authorization: string | null }> = []; - globalThis.fetch = ((input, init) => { + globalThis.fetch = ((input: RequestInfo | URL, init?: RequestInit) => { const url = input instanceof Request ? input.url : String(input); requests.push({ url, diff --git a/src/transforms/esm/http-cache.test.ts b/src/transforms/esm/http-cache.test.ts index 1ed5ab43b0..351329602e 100644 --- a/src/transforms/esm/http-cache.test.ts +++ b/src/transforms/esm/http-cache.test.ts @@ -283,7 +283,7 @@ describe("HTTP Bundle Cache", { sanitizeResources: false, sanitizeOps: false }, it("allows a cold HTTP module response to exceed five seconds", async () => { let fetchCount = 0; - const mockFetch = ((_input, init) => { + const mockFetch = ((_input: RequestInfo | URL, init?: RequestInit) => { fetchCount += 1; return new Promise((resolve, reject) => { const signal = init?.signal; @@ -326,7 +326,7 @@ describe("HTTP Bundle Cache", { sanitizeResources: false, sanitizeOps: false }, releaseFetch = resolve; }); - const mockFetch = ((_input, init) => { + const mockFetch = ((_input: RequestInfo | URL, init?: RequestInit) => { fetchCount += 1; markFetchStarted(); return new Promise((resolve, reject) => { @@ -759,7 +759,7 @@ describe("HTTP Bundle Cache", { sanitizeResources: false, sanitizeOps: false }, let releaseFetch!: () => void; const fetchStarted = Promise.withResolvers(); - const mockFetch = ((_input, init) => { + const mockFetch = ((_input: RequestInfo | URL, init?: RequestInit) => { fetchCount += 1; fetchStarted.resolve(); return new Promise((resolve, reject) => { From 2619e6d5e889832e0e254234825c39a05a6945ff Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 11:05:36 +0200 Subject: [PATCH 07/11] fix: apply review feedback on orchestrator inputs and type boundaries - dev-ui also reads the css extensions and scans src, and client-scripts imports ext-bundler-esbuild: add those roots to their fingerprints. - Validate the parsed stamp file shape before trusting it; a corrupted .cache/generate-stamps.json now means a full run, not a TypeError. - Skip dist/ and coverage/ inside anti-slop scan roots and document the full baseline-regeneration command including the redirect. - Constrain createScriptedStreamProvider's generic to StreamSignal and type CSSCacheEntry.candidates as readonly, dropping both assertions. - Rename the rest-parameter lint test to match what it asserts. --- scripts/build/run-generate.ts | 21 +++++++++++++++++---- scripts/lint/audit-anti-slop.test.ts | 3 ++- scripts/lint/audit-anti-slop.ts | 6 ++++-- src/agent/streaming/lifecycle/testing.ts | 4 ++-- src/html/styles-builder/css-hash-cache.ts | 6 +++--- 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/scripts/build/run-generate.ts b/scripts/build/run-generate.ts index e36834c014..b9c654f3a3 100644 --- a/scripts/build/run-generate.ts +++ b/scripts/build/run-generate.ts @@ -57,7 +57,12 @@ export const UNITS: GeneratorUnit[] = [ ["deno", "run", "-A", "extensions/ext-dev-ui-react/scripts/generate-styles.ts"], ["deno", "run", "-A", "extensions/ext-dev-ui-react/scripts/prebundle.ts"], ], - inputRoots: ["extensions/ext-dev-ui-react"], + inputRoots: [ + "extensions/ext-dev-ui-react", + "extensions/ext-css-lightning", + "extensions/ext-css-tailwind", + "src", + ], inputFiles: ["deno.json"], outputs: [ "extensions/ext-dev-ui-react/src/dev-ui-styles.generated.ts", @@ -67,7 +72,7 @@ export const UNITS: GeneratorUnit[] = [ { name: "client-scripts", commands: [["deno", "run", "-A", "scripts/build/prebundle-client-scripts.ts"]], - inputRoots: ["src"], + inputRoots: ["src", "extensions/ext-bundler-esbuild"], inputFiles: ["scripts/build/prebundle-client-scripts.ts", "deno.json"], outputs: [ "src/build/production-build/templates.ts", @@ -233,9 +238,17 @@ async function main(): Promise { let stamps: Record = {}; try { - stamps = JSON.parse(await Deno.readTextFile(`${repoRoot}${STAMP_PATH}`)); + const parsed: unknown = JSON.parse( + await Deno.readTextFile(`${repoRoot}${STAMP_PATH}`), + ); + if ( + typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && + Object.values(parsed).every((v) => typeof v === "string") + ) { + stamps = parsed as Record; + } } catch { - // no stamps yet: every unit runs + // no stamps yet (or an unreadable file): every unit runs } const outputExists = (path: string): boolean => { diff --git a/scripts/lint/audit-anti-slop.test.ts b/scripts/lint/audit-anti-slop.test.ts index 308926fe00..d152bc48fa 100644 --- a/scripts/lint/audit-anti-slop.test.ts +++ b/scripts/lint/audit-anti-slop.test.ts @@ -109,7 +109,8 @@ describe("no-object-parameters", () => { ]); }); - it("reports rest and defaulted parameters", () => { + it("flags defaulted parameters but not object-array rest parameters", () => { + // `object[]` is an array type, not the broad `object` keyword. assertEquals(rulesOf(`function f(...values: object[]): void {}`), []); assertEquals(rulesOf(`function f(value: object = {}): void {}`), [ "no-object-parameters", diff --git a/scripts/lint/audit-anti-slop.ts b/scripts/lint/audit-anti-slop.ts index 1fbcd3cc1c..0928816945 100644 --- a/scripts/lint/audit-anti-slop.ts +++ b/scripts/lint/audit-anti-slop.ts @@ -39,7 +39,7 @@ * Counts are frozen per rule per file in `anti-slop-baseline.json` and may * only shrink. Regenerate after paying debt down with: * - * deno task lint:anti-slop -- --print-baseline + * deno task lint:anti-slop -- --print-baseline > scripts/lint/anti-slop-baseline.json */ import { parse } from "npm:@babel/parser@7.29.2"; @@ -419,6 +419,8 @@ async function collectProdFiles(root: string): Promise { const path = `${root}/${entry.name}`; if (entry.isDirectory) { if (entry.name === "node_modules" || entry.name.startsWith(".")) continue; + // Mirrors lint.exclude: emitted output inside a scan root is not source. + if (entry.name === "dist" || entry.name === "coverage") continue; files.push(...await collectProdFiles(path)); } else if (entry.isFile && isProdSource(entry.name)) { files.push(path); @@ -500,7 +502,7 @@ async function main(): Promise { printFindings("Anti-slop debt decreased:", improvements); console.log( `\nRegenerate ${BASELINE_PATH} with ` + - `\`deno task lint:anti-slop -- --print-baseline\` to lock in the improvement.`, + `\`deno task lint:anti-slop -- --print-baseline > ${BASELINE_PATH}\` to lock in the improvement.`, ); return; } diff --git a/src/agent/streaming/lifecycle/testing.ts b/src/agent/streaming/lifecycle/testing.ts index e101ab7e83..5d3c231f68 100644 --- a/src/agent/streaming/lifecycle/testing.ts +++ b/src/agent/streaming/lifecycle/testing.ts @@ -95,7 +95,7 @@ export interface ScriptedStreamProvider extends StreamProviderAdapter { rejectNext(error: unknown): void; } -export function createScriptedStreamProvider( +export function createScriptedStreamProvider( values: readonly T[], options: { autoComplete?: boolean; returnError?: unknown } = {}, ): ScriptedStreamProvider { @@ -186,7 +186,7 @@ export function createScriptedStreamProvider( }; }, decode(part: T, _snapshot: Readonly): readonly StreamSignal[] { - return [part as StreamSignal]; + return [part]; }, classifyError(): StreamProviderError { return { diff --git a/src/html/styles-builder/css-hash-cache.ts b/src/html/styles-builder/css-hash-cache.ts index 7762750778..8a8290c176 100644 --- a/src/html/styles-builder/css-hash-cache.ts +++ b/src/html/styles-builder/css-hash-cache.ts @@ -34,7 +34,7 @@ const MAX_SERIALIZED_CSS_CACHE_BYTES = 128 * 1024 * 1024; export interface CSSCacheEntry { readonly css: string; - readonly candidates: string[]; + readonly candidates: readonly string[]; readonly stylesheet: string; readonly pipelineIdentity?: string; } @@ -134,7 +134,7 @@ function createCSSCacheEntry( ); return Object.freeze({ css: detachRetainedString(css), - candidates: Object.freeze(candidates) as string[], + candidates: Object.freeze(candidates), stylesheet, pipelineIdentity, }); @@ -309,7 +309,7 @@ export async function persistRegeneratedCSSEntry( throw new TypeError("Regenerated CSS entry requires a pipeline identity"); } await cacheCSSAsync(entry.css, hash, { - candidates: entry.candidates, + candidates: [...entry.candidates], stylesheet: entry.stylesheet, pipelineIdentity: entry.pipelineIdentity, }); From 6dc929d0ac9ad5ca9c5c681e22c591cb9643128c Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 11:08:22 +0200 Subject: [PATCH 08/11] fix(tool): drop unneeded unknown hop in JSON-RPC error result guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isRecord has already narrowed value, and Record is directly comparable to Partial — the double assertion (which landed on main after the anti-slop baseline froze) tripped the ratchet on the merge ref. --- src/tool/remote-mcp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/remote-mcp.ts b/src/tool/remote-mcp.ts index 433314f4a7..4763bfb3dc 100644 --- a/src/tool/remote-mcp.ts +++ b/src/tool/remote-mcp.ts @@ -95,7 +95,7 @@ function isJsonRpcErrorObject( function isJsonRpcToolErrorResult(value: unknown): value is JsonRpcToolErrorResult { if (!isRecord(value)) return false; - const candidate = value as unknown as Partial; + const candidate = value as Partial; return candidate[JSON_RPC_TOOL_ERROR_RESULT] === true && isRecord(candidate.result); } From 89de1b4c219349768d34037d5bbb873f5fbd4fa3 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 11:19:09 +0200 Subject: [PATCH 09/11] fix(test): drain jsdom's focus timer in tabs and context-menu teardown element.focus() under jsdom schedules a selectionchange 0ms timer; both suites tore down synchronously, so the timer leaked past the step and tripped the sanitizer on most local runs. Await one macrotask between unmount and DOM restore, same drain the rest of the react suite uses. --- .../ui/adapter/tabs.conformance.test.tsx | 15 ++++--- .../ui/context-menu.behaviour.test.tsx | 43 ++++++++++--------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/react/components/ui/adapter/tabs.conformance.test.tsx b/src/react/components/ui/adapter/tabs.conformance.test.tsx index bd644f6a7a..980bf80f96 100644 --- a/src/react/components/ui/adapter/tabs.conformance.test.tsx +++ b/src/react/components/ui/adapter/tabs.conformance.test.tsx @@ -41,7 +41,7 @@ function installDom(dom: JSDOM): () => void { }; } -function render(el: React.ReactElement): { host: HTMLElement; unmount: () => void } { +function render(el: React.ReactElement): { host: HTMLElement; unmount: () => Promise } { const dom = new JSDOM(`
`); const restore = installDom(dom); const host = dom.window.document.getElementById("root")!; @@ -49,9 +49,12 @@ function render(el: React.ReactElement): { host: HTMLElement; unmount: () => voi flushSync(() => root.render(el)); return { host: host as unknown as HTMLElement, - unmount: () => { + unmount: async () => { try { root.unmount(); + // Drain one macrotask so jsdom's selectionchange 0ms timer (started + // by element.focus()) completes inside the test's sanitizer window. + await new Promise((resolve) => setTimeout(resolve, 0)); } finally { restore(); } @@ -88,7 +91,7 @@ function Harness(): React.ReactElement { function runTabsConformance(label: string, Wrap: React.FC<{ children: React.ReactNode }>): void { describe(`Tabs adapter conformance - ${label}`, () => { - it("role=tablist/tab; clicking a tab selects it (aria-selected + data-state)", () => { + it("role=tablist/tab; clicking a tab selects it (aria-selected + data-state)", async () => { const { host, unmount } = render( @@ -107,7 +110,7 @@ function runTabsConformance(label: string, Wrap: React.FC<{ children: React.Reac assert(a!.getAttribute("aria-selected") === "false", "a deselected"); assert(b!.getAttribute("data-state") === "active", "b data-state active"); } finally { - unmount(); + await unmount(); } }); }); @@ -118,7 +121,7 @@ const Identity: React.FC<{ children: React.ReactNode }> = ({ children }) => <>{c runTabsConformance("builtin (default)", Identity); describe("Tabs adapter conformance - builtin keyboard navigation", () => { - it("uses one tab stop and selects tabs with roving keyboard commands", () => { + it("uses one tab stop and selects tabs with roving keyboard commands", async () => { const { host, unmount } = render( @@ -141,7 +144,7 @@ describe("Tabs adapter conformance - builtin keyboard navigation", () => { key(a!, "End"); assert(b!.getAttribute("aria-selected") === "true", "End selects the last tab"); } finally { - unmount(); + await unmount(); } }); }); diff --git a/src/react/components/ui/context-menu.behaviour.test.tsx b/src/react/components/ui/context-menu.behaviour.test.tsx index a1d1980065..4a8b54f718 100644 --- a/src/react/components/ui/context-menu.behaviour.test.tsx +++ b/src/react/components/ui/context-menu.behaviour.test.tsx @@ -102,8 +102,11 @@ function mountInScope(element: React.ReactElement): { root, win: dom.window as unknown as Window & typeof globalThis, rightClickTrigger, - cleanup: () => { + cleanup: async () => { root.unmount(); + // Drain one macrotask so jsdom's selectionchange 0ms timer (started by + // element.focus()) completes inside the test's sanitizer window. + await new Promise((resolve) => setTimeout(resolve, 0)); restore(); }, }; @@ -129,7 +132,7 @@ function Menu( } describe("ContextMenu behaviour (builtin)", () => { - it("is closed until a right-click; contextmenu opens it, portalled into the token scope", () => { + it("is closed until a right-click; contextmenu opens it, portalled into the token scope", async () => { const { scope, rightClickTrigger, cleanup } = mountInScope(); try { assertEquals(scope.querySelector('[role="menu"]'), null, "closed initially"); @@ -142,11 +145,11 @@ describe("ContextMenu behaviour (builtin)", () => { "portalled surface stays within the token scope, not document.body", ); } finally { - cleanup(); + await cleanup(); } }); - it("makes the default trigger focusable and opens from Shift+F10", () => { + it("makes the default trigger focusable and opens from Shift+F10", async () => { const { scope, cleanup } = mountInScope(); try { const trigger = scope.querySelector('[data-testid="trigger"]')!; @@ -166,11 +169,11 @@ describe("ContextMenu behaviour (builtin)", () => { }); assert(scope.querySelector('[role="menu"]'), "Shift+F10 opens the menu"); } finally { - cleanup(); + await cleanup(); } }); - it("renders items as role=menuitem with their labels", () => { + it("renders items as role=menuitem with their labels", async () => { const { scope, rightClickTrigger, cleanup } = mountInScope(); try { rightClickTrigger(); @@ -188,11 +191,11 @@ describe("ContextMenu behaviour (builtin)", () => { "disabled native activation keeps the menu open", ); } finally { - cleanup(); + await cleanup(); } }); - it("suppresses disabled asChild activation at the composed-control boundary", () => { + it("suppresses disabled asChild activation at the composed-control boundary", async () => { let childClicks = 0; let selections = 0; const { scope, rightClickTrigger, cleanup } = mountInScope( @@ -218,11 +221,11 @@ describe("ContextMenu behaviour (builtin)", () => { assertEquals(selections, 0, "the item selection handler does not run"); assert(scope.querySelector('[role="menu"]'), "the menu stays open"); } finally { - cleanup(); + await cleanup(); } }); - it("suppresses the native menu (preventDefault) on the contextmenu event", () => { + it("suppresses the native menu (preventDefault) on the contextmenu event", async () => { const { scope, cleanup } = mountInScope(); try { const trigger = scope.querySelector('[data-testid="trigger"]')!; @@ -235,11 +238,11 @@ describe("ContextMenu behaviour (builtin)", () => { flushSync(() => trigger.dispatchEvent(evt)); assert(evt.defaultPrevented, "native context menu is prevented"); } finally { - cleanup(); + await cleanup(); } }); - it("selecting an item fires onSelect and closes the menu", () => { + it("selecting an item fires onSelect and closes the menu", async () => { let selected = 0; const { scope, rightClickTrigger, cleanup } = mountInScope( selected++} />, @@ -253,7 +256,7 @@ describe("ContextMenu behaviour (builtin)", () => { assertEquals(selected, 1, "onSelect fired once"); assertEquals(scope.querySelector('[role="menu"]'), null, "menu closed on select"); } finally { - cleanup(); + await cleanup(); } }); @@ -284,11 +287,11 @@ describe("ContextMenu behaviour (builtin)", () => { assertEquals(scope.querySelector('[role="menu"]'), null, "menu closed on keyboard select"); assertEquals(document.activeElement, trigger, "focus returns to the context menu trigger"); } finally { - cleanup(); + await cleanup(); } }); - it("honors a consumer-cancelled click before selecting or closing", () => { + it("honors a consumer-cancelled click before selecting or closing", async () => { let clicks = 0; let selections = 0; const { scope, rightClickTrigger, cleanup } = mountInScope( @@ -318,11 +321,11 @@ describe("ContextMenu behaviour (builtin)", () => { assertEquals(selections, 0, "onSelect does not run after cancellation"); assert(scope.querySelector('[role="menu"]'), "cancelled click keeps the menu open"); } finally { - cleanup(); + await cleanup(); } }); - it("closes on Escape (native document keydown listener)", () => { + it("closes on Escape (native document keydown listener)", async () => { const { scope, rightClickTrigger, cleanup } = mountInScope(); try { rightClickTrigger(); @@ -334,11 +337,11 @@ describe("ContextMenu behaviour (builtin)", () => { }); assertEquals(scope.querySelector('[role="menu"]'), null, "Escape dismissed the menu"); } finally { - cleanup(); + await cleanup(); } }); - it("closes on outside mousedown (native document pointer listener)", () => { + it("closes on outside mousedown (native document pointer listener)", async () => { const { scope, rightClickTrigger, cleanup } = mountInScope(); try { rightClickTrigger(); @@ -350,7 +353,7 @@ describe("ContextMenu behaviour (builtin)", () => { }); assertEquals(scope.querySelector('[role="menu"]'), null, "outside click dismissed the menu"); } finally { - cleanup(); + await cleanup(); } }); }); From 9bdb3084dc3a790f2c8b9a31f0fb1eda926e80c3 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 11:27:47 +0200 Subject: [PATCH 10/11] fix(test): keep the scripted provider generic unconstrained MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit createScriptedStreamProvider's T is the raw provider frame type, not StreamSignal — testing.test.ts scripts {type: "text-delta"} frames and the double's decode deliberately bridges them. The T-extends-StreamSignal constraint from review feedback broke that consumer; restore the unconstrained generic with the single bridging assertion. --- src/agent/streaming/lifecycle/testing.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/agent/streaming/lifecycle/testing.ts b/src/agent/streaming/lifecycle/testing.ts index 5d3c231f68..e101ab7e83 100644 --- a/src/agent/streaming/lifecycle/testing.ts +++ b/src/agent/streaming/lifecycle/testing.ts @@ -95,7 +95,7 @@ export interface ScriptedStreamProvider extends StreamProviderAdapter { rejectNext(error: unknown): void; } -export function createScriptedStreamProvider( +export function createScriptedStreamProvider( values: readonly T[], options: { autoComplete?: boolean; returnError?: unknown } = {}, ): ScriptedStreamProvider { @@ -186,7 +186,7 @@ export function createScriptedStreamProvider( }; }, decode(part: T, _snapshot: Readonly): readonly StreamSignal[] { - return [part]; + return [part as StreamSignal]; }, classifyError(): StreamProviderError { return { From 242014e91dff8f147ed95953ba3da0574be30e4a Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 11:36:07 +0200 Subject: [PATCH 11/11] test(build): derive generator list from the orchestrator's units MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generated-artifact contract test parsed generator scripts out of the generate task string; with generate routed through run-generate.ts the task names one script and the assertions went vacuous-then-red. Source the generate-side list from UNITS so the invariant — every generator has a --check counterpart, no orphaned checks — keeps holding. --- scripts/build/generated-artifact-checks.test.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/scripts/build/generated-artifact-checks.test.ts b/scripts/build/generated-artifact-checks.test.ts index a2f45aee72..a1aeeb843e 100644 --- a/scripts/build/generated-artifact-checks.test.ts +++ b/scripts/build/generated-artifact-checks.test.ts @@ -11,10 +11,16 @@ * This checks the wiring rather than the bundling. Running the generators for * real costs an esbuild pass each, and `generate:manifests:check` already * proves they work on every PR. + * + * `generate` runs through the run-generate.ts orchestrator, so the + * generate-side script list comes from its UNITS table rather than from + * parsing the task string; the check side is still parsed out of the task + * chain, so a unit added without a `--check` counterpart still fails here. */ import { assertEquals } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; +import { UNITS } from "./run-generate.ts"; type Tasks = Record; @@ -53,7 +59,13 @@ function scriptInvocations(command: string): Map { return found; } -const generators = scriptInvocations(expandTask("generate")); +const generators = new Map(); +for (const unit of UNITS) { + for (const argv of unit.commands) { + const script = argv.find((arg) => arg.endsWith(".ts")); + if (script !== undefined) generators.set(script, argv.includes("--check")); + } +} const checks = scriptInvocations(expandTask("generate:manifests:check")); describe("generated artifact checks", () => {