import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; /** * Growth caps for the three rectification files split by * TASK-rectification-code-split-20260926, same shape as * home-shell-growth-contract.test.ts: a coarse line guardrail (measured * baseline + 150) plus hook-count caps that stop state from drifting back * into the container. Line counts are `\n` counts, same as `wc -l`. * Hook counts use `\buseX[<(]` so `useState(` is not missed. */ const read = (relativePath: string) => readFileSync(new URL(relativePath, import.meta.url), "utf8"); function lineCount(source: string): number { return (source.match(/\n/g) ?? []).length; } function countMatches(source: string, pattern: RegExp): number { return source.match(new RegExp(pattern.source, "g"))?.length ?? 0; } function sourceFrom(source: string, marker: string): string { const start = source.indexOf(marker); assert.notEqual(start, -1, `missing marker: ${marker}`); return source.slice(start); } const REACT_HOOK_CALL = /\buse(?:State|Ref|Effect|LayoutEffect|InsertionEffect|Callback|Memo|Reducer|Context|Id|Transition|DeferredValue|SyncExternalStore|Optimistic|ActionState)\s*[<(]/; // --------------------------------------------------------------------------- // T1 ยท rectification-agentic-chat.tsx // Measured 2026-09-26 on codex/rectification-code-split-20260926 after the // component split. Before (origin/staging 12cbe6f8): file 2043 lines, component // body 1625 lines, useState 35, useRef 16, useEffect 7, useLayoutEffect 2, // useCallback 12. After: file 737, body 630, useState 12, useRef 9, // useEffect 5, useLayoutEffect 0, useCallback 6. const CHAT_PATH = "../src/components/rectification-agentic-chat.tsx"; const CHAT_LINE_BASELINE = 737; const CHAT_LINE_CAP = CHAT_LINE_BASELINE + 150; /** D2 of the task: the component body stays at or under 900 lines. */ const CHAT_BODY_LINE_CAP = 900; const CHAT_USE_STATE_CAP = 12; const CHAT_USE_REF_CAP = 9; const CHAT_EFFECT_CAP = 5; const CHAT_USE_CALLBACK_CAP = 6; const CHAT_COMPONENT_MARKER = "export function RectificationAgenticChat("; /** Parameter functions split out of the chat; they take state and setters, never call hooks. */ const CHAT_PARAMETER_MODULES = [ "../src/lib/rectification-chat-messages.ts", "../src/lib/rectification-chat-snapshot.ts", "../src/lib/rectification-chat-turn-run.ts", "../src/lib/rectification-chat-choice-run.ts", "../src/lib/rectification-chat-accept-run.ts", "../src/lib/rectification-chat-message-actions.ts", "../src/lib/rectification-chat-question-repair.ts", "../src/lib/rectification-chat-view.ts", ] as const; test("rectification-agentic-chat.tsx line count stays within the coarse guardrail", () => { const n = lineCount(read(CHAT_PATH)); assert.ok(n <= CHAT_LINE_CAP, `rectification-agentic-chat.tsx has ${n} lines; cap is ${CHAT_LINE_CAP} (${CHAT_LINE_BASELINE} baseline + 150).`); }); test("RectificationAgenticChat body stays at or under 900 lines", () => { const n = lineCount(sourceFrom(read(CHAT_PATH), CHAT_COMPONENT_MARKER)); assert.ok(n <= CHAT_BODY_LINE_CAP, `RectificationAgenticChat body has ${n} lines; cap is ${CHAT_BODY_LINE_CAP}.`); }); test("RectificationAgenticChat hook counts must not grow", () => { const body = sourceFrom(read(CHAT_PATH), CHAT_COMPONENT_MARKER); const useState = countMatches(body, /\buseState[<(]/); const useRef = countMatches(body, /\buseRef[<(]/); const effects = countMatches(body, /\buse(?:Layout)?Effect[<(]/); const useCallback = countMatches(body, /\buseCallback[<(]/); assert.ok(useState <= CHAT_USE_STATE_CAP, `useState ${useState}; cap ${CHAT_USE_STATE_CAP}. New state belongs in a hook such as use-rectification-case-snapshot.ts.`); assert.ok(useRef <= CHAT_USE_REF_CAP, `useRef ${useRef}; cap ${CHAT_USE_REF_CAP}. Do not rewrite state as refs to dodge the useState cap.`); assert.ok(effects <= CHAT_EFFECT_CAP, `useEffect + useLayoutEffect ${effects}; cap ${CHAT_EFFECT_CAP}.`); assert.ok(useCallback <= CHAT_USE_CALLBACK_CAP, `useCallback ${useCallback}; cap ${CHAT_USE_CALLBACK_CAP}. Turn bodies belong in lib/rectification-chat-*-run.ts.`); }); test("rectification chat parameter functions call no React hooks", () => { for (const path of CHAT_PARAMETER_MODULES) { assert.doesNotMatch(read(path), REACT_HOOK_CALL, `${path} must stay a parameter function module (no React hooks).`); } });