diff --git a/scripts/typecheck-test-baseline.json b/scripts/typecheck-test-baseline.json index 2054663b8..bb83f5d76 100644 --- a/scripts/typecheck-test-baseline.json +++ b/scripts/typecheck-test-baseline.json @@ -1,7 +1,7 @@ { "version": 1, "diagnostics": { - "browse/test/batch.test.ts\tTS2339\tProperty 'startServer' does not exist on type 'typeof import(\"/workspace/gstack/browse/src/server\")'.": 1, + "browse/test/batch.test.ts\tTS2339\tProperty 'startServer' does not exist on type 'typeof import(\"browse/src/server\")'.": 1, "browse/test/batch.test.ts\tTS2554\tExpected 4 arguments, but got 3.": 5, "browse/test/bridge-chromium-e2e.test.ts\tTS2339\tProperty 'readUInt16BE' does not exist on type 'string | NonSharedBuffer'. Property 'readUInt16BE' does not exist on type 'string'.": 2, "browse/test/bridge-chromium-e2e.test.ts\tTS2339\tProperty 'subarray' does not exist on type 'string | NonSharedBuffer'. Property 'subarray' does not exist on type 'string'.": 4, @@ -35,7 +35,7 @@ "browse/test/cookie-import-transport.test.ts\tTS2741\tProperty 'preconnect' is missing in type '() => Promise' but required in type 'typeof fetch'.": 2, "browse/test/cookie-import-transport.test.ts\tTS2741\tProperty 'preconnect' is missing in type '() => Promise' but required in type 'typeof fetch'.": 1, "browse/test/dia-gui-readiness.test.ts\tTS2352\tConversion of type '() => { status: number; stdout: string; stderr: string; }' to type '{ (command: string): SpawnSyncReturns; (command: string, options: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns<...>; (command: string, options: SpawnSyncOptionsWithBufferEncoding): SpawnSyncReturns<...>; (command: string, options?: SpawnSyncOptions | undefined): SpawnSyncReturns<...>; (comm...' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type '{ status: number; stdout: string; stderr: string; }' is missing the following properties from type 'SpawnSyncReturns': pid, output, signal": 1, - "browse/test/dia-launch-comparison.test.ts\tTS7016\tCould not find a declaration file for module '../../.github/scripts/dia-launch-driver.mjs'. '/workspace/gstack/.github/scripts/dia-launch-driver.mjs' implicitly has an 'any' type.": 1, + "browse/test/dia-launch-comparison.test.ts\tTS7016\tCould not find a declaration file for module '../../.github/scripts/dia-launch-driver.mjs'. '.github/scripts/dia-launch-driver.mjs' implicitly has an 'any' type.": 1, "browse/test/dia-macos-qualification.test.ts\tTS2352\tConversion of type '() => { error?: undefined; status: number; stdout: string; stderr: string; } | { status: null; stdout: null; stderr: null; error: Error; }' to type '{ (command: string): SpawnSyncReturns; (command: string, options: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns<...>; (command: string, options: SpawnSyncOptionsWithBufferEncoding): SpawnSyncReturns<...>; (command: string, options?: SpawnSyncOptions | undefined): SpawnSyncReturns<...>; (comm...' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type '{ error?: undefined; status: number; stdout: string; stderr: string; } | { status: null; stdout: null; stderr: null; error: Error; }' is not comparable to type 'SpawnSyncReturns'. Type '{ status: null; stdout: null; stderr: null; error: Error; }' is missing the following properties from type 'SpawnSyncReturns': pid, output, signal": 2, "browse/test/dia-macos-qualification.test.ts\tTS2352\tConversion of type '() => { status: number; stdout: string; stderr: string; }' to type '{ (command: string): SpawnSyncReturns; (command: string, options: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns<...>; (command: string, options: SpawnSyncOptionsWithBufferEncoding): SpawnSyncReturns<...>; (command: string, options?: SpawnSyncOptions | undefined): SpawnSyncReturns<...>; (comm...' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type '{ status: number; stdout: string; stderr: string; }' is missing the following properties from type 'SpawnSyncReturns': pid, output, signal": 2, "browse/test/dia-macos-qualification.test.ts\tTS2769\tNo overload matches this call. The last overload gave the following error. Argument of type 'string' is not assignable to parameter of type '\"browser_profile_unavailable\" | \"code_signing_error\" | \"debugging_pipe_unavailable\" | \"default_profile_policy\" | \"dynamic_library_error\" | \"graphics_or_bootstrap_error\" | \"keychain_access_failed\" | \"keychain_interaction_disallowed\" | \"keychain_interaction_required\"'.": 1, @@ -185,7 +185,7 @@ "test/eng-published-navigation.test.ts\tTS2352\tConversion of type '{ sessionId: string; toolUseId: string; questions: { question: string; header: string; multiSelect: boolean; options: { label: string; description: string; }[]; }[]; answered: boolean; failed: boolean; answers: { ...; }; unansweredQuestionIndices: never[]; answeredAt: string; } | ... 8 more ... | { ...; }' to type 'NativePlanQuestionCall' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type '{ sessionId: string; toolUseId: string; questions: { question: string; header: string; multiSelect: boolean; options: { label: string; description: string; }[]; }[]; answered: boolean; failed: boolean; answers: { \"D1 \\u2014 Defer the Promise.all IDP parallelization out of this refactor?\\nProject/branch/task: main \\u...' is not comparable to type 'NativePlanQuestionCall'. Types of property 'answers' are incompatible. Type '{ \"D1 \\u2014 Defer the Promise.all IDP parallelization out of this refactor?\\nProject/branch/task: main \\u2014 reviewing PLAN.md \\\"Multi-tenant Auth Refactor\\\", a stated no-behavior-change reorg of tenant auth.\\nELI10: The plan promises to move code around without changing what users experience, but it also bundles ...' is not comparable to type 'Record'. Property '\"D1 — Defer the Promise.all IDP parallelization out of this refactor?\\nProject/branch/task: main — reviewing PLAN.md \\\"Multi-tenant Auth Refactor\\\", a stated no-behavior-change reorg of tenant auth.\\nELI10: The plan promises to move code around without changing what users experience, but it also bundles in making 5 identity-provider calls run at once instead of one after another. That is a real behavior change: timing changes, and if one call fails the others are abandoned mid-flight, which changes which error the user sees. Mixing a rewrite with a speed-up means if something breaks after deploy, you cannot tell which change did it.\\nStakes if we pick wrong: a login regression after ship that nobody can bisect, because the structural move and the timing change landed in the same diff.\\nRecommendation: A because Beck's rule (separate structural and behavioral changes) makes the rollback and the bisect trivial, and the perf PR is a 10-line follow-up once the refactor is green.\\nNote: options differ in kind, not coverage — no completeness score.\\nPros / cons:\\nA) Defer Promise.all to a follow-up PR (recommended)\\n ✅ Refactor stays provably behavior-preserving; the legacy characterization test passes unchanged\\n ✅ Perf change gets its own review of error semantics (first-rejection, partial failure, IDP rate limits)\\n ❌ Users wait one more release for the ~5x faster token validation (human: ~1h / CC: ~5 min follow-up)\\nB) Keep Promise.all in this PR\\n ✅ One PR, one deploy, faster validation lands immediately\\n ✅ Avoids touching the validation path twice in two weeks\\n ❌ Rewrite and timing change share a blast radius; a 3am incident has two suspects\\n ❌ Error-path behavior changes silently unless the plan also specifies allSettled vs all semantics\\nNet: trading one release of latency for a clean bisect on the highest-blast-radius path in the product.\"' is incompatible with index signature. Type 'undefined' is not comparable to type 'string'.": 1, "test/eng-published-navigation.test.ts\tTS2352\tConversion of type '{ status: string; calls: ({ sessionId: string; toolUseId: string; questions: { question: string; header: string; multiSelect: boolean; options: { label: string; description: string; }[]; }[]; answered: boolean; failed: boolean; answers: { ...; }; unansweredQuestionIndices: never[]; answeredAt: string; } | ... 11 mor...' to type 'PlanCountTranscript' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type '{ status: string; calls: ({ sessionId: string; toolUseId: string; questions: { header: string; question: string; options: { label: string; description: string; }[]; }[]; answered: boolean; failed: boolean; answers: { \"D1 \\u2014 Add gstack skill routing rules to this project's CLAUDE.md?\\nProject/branch/task: main br...' is not comparable to type 'PlanCountTranscript'. Types of property 'calls' are incompatible. Type '({ sessionId: string; toolUseId: string; questions: { header: string; question: string; options: { label: string; description: string; }[]; }[]; answered: boolean; failed: boolean; answers: { \"D1 \\u2014 Add gstack skill routing rules to this project's CLAUDE.md?\\nProject/branch/task: main branch of the plan-review f...' is not comparable to type 'NativePlanQuestionCall[]'. Type '{ sessionId: string; toolUseId: string; questions: { header: string; question: string; options: { label: string; description: string; }[]; }[]; answered: boolean; failed: boolean; answers: { \"D1 \\u2014 Add gstack skill routing rules to this project's CLAUDE.md?\\nProject/branch/task: main branch of the plan-review fi...' is not comparable to type 'NativePlanQuestionCall'. Type '{ sessionId: string; toolUseId: string; questions: { header: string; question: string; options: { label: string; description: string; }[]; }[]; answered: boolean; failed: boolean; answers: { \"D1 \\u2014 Add gstack skill routing rules to this project's CLAUDE.md?\\nProject/branch/task: main branch of the plan-review fi...' is not comparable to type 'NativePlanQuestionCall'. Types of property 'answers' are incompatible. Type '{ \"D1 \\u2014 Add gstack skill routing rules to this project's CLAUDE.md?\\nProject/branch/task: main branch of the plan-review fixture repo; one-time gstack setup before the review starts.\\nELI10: gstack has a bunch of skills (/investigate, /ship, /plan-eng-review...). A short routing section in CLAUDE.md tells Claud...' is not comparable to type 'Record'. Property '\"D1 — Add gstack skill routing rules to this project's CLAUDE.md?\\nProject/branch/task: main branch of the plan-review fixture repo; one-time gstack setup before the review starts.\\nELI10: gstack has a bunch of skills (/investigate, /ship, /plan-eng-review...). A short routing section in CLAUDE.md tells Claude which one to reach for when you say things like \\\"this is broken\\\" or \\\"ship it\\\", so you don't have to remember the names. Without it, you invoke skills by hand.\\nStakes if we pick wrong: none of this is irreversible; skipping just means more manual skill invocation, adding means a ~15-line append to CLAUDE.md (deferred until we leave plan mode, since plan mode forbids edits and commits).\\nRecommendation: A because auto-routing is the whole point of installing gstack and the cost is one small committed section.\\nNote: options differ in kind, not coverage — no completeness score.\\nNet: convenience of automatic skill routing vs keeping CLAUDE.md untouched.\"' is incompatible with index signature. Type 'undefined' is not comparable to type 'string'.": 1, "test/eng-resolution-block-position.test.ts\tTS2345\tArgument of type '{ signature: string; promptSnippet: string; options: { index: number; label: string; }[]; observedAtMs: number; preReview: boolean; nativeCall: { sessionId: string; toolUseId: string; questions: { ...; }[]; ... 4 more ...; answeredAt: string; }; } | ... 9 more ... | { ...; }' is not assignable to parameter of type 'AskUserQuestionFingerprint'. Type '{ signature: string; promptSnippet: string; options: { index: number; label: string; }[]; observedAtMs: number; preReview: boolean; nativeCall: { sessionId: string; toolUseId: string; questions: { question: string; header: string; multiSelect: boolean; options: { label: string; description: string; }[]; }[]; ... 4 m...' is not assignable to type 'AskUserQuestionFingerprint'. The types of 'nativeCall.answers' are incompatible between these types. Type '{ \"D1 \\u2014 Add gstack skill routing rules to CLAUDE.md?\\nProject/branch/task: main branch of the plan-review fixture repo; one-time gstack onboarding step before the plan review.\\nELI10: gstack works best when your project's CLAUDE.md includes skill routing rules. These tell Claude which /skill to reach for when y...' is not assignable to type 'Record'. Property '\"D2 \\u2014 Run /office-hours first, or go straight to the engineering review?\\nProject/branch/task: main branch; reviewing PLAN.md \\\"Add background job retry framework\\\".\\nELI10: No design doc exists for this change. /office-hours is a ~10 minute structured session that produces a problem statement, challenges the premise, and lists alternatives considered. It gives this review sharper input, because right now the plan says what it will build but not why retries are needed, what the failure modes are, or what \\\"at-most-once\\\" currently protects.\\nStakes if we pick wrong: skipping means I review the plan's mechanics without a stated problem; running it costs ~10 minutes before any review output.\\nRecommendation: B because the plan is short and its four sections already expose the key architecture and test risks; I can flag the missing problem statement inside the review instead.\\nNote: options differ in kind, not coverage \\u2014 no completeness score.\\nNet: sharper problem framing now vs faster feedback on a plan whose issues are already visible.\"' is incompatible with index signature. Type 'undefined' is not assignable to type 'string'.": 1, - "test/eng-seeded-completion-ai.test.ts\tTS2339\tProperty 'isUnknownSlashCommandVisible' does not exist on type 'typeof import(\"/workspace/gstack/test/helpers/claude-pty-runner\")'.": 1, + "test/eng-seeded-completion-ai.test.ts\tTS2339\tProperty 'isUnknownSlashCommandVisible' does not exist on type 'typeof import(\"test/helpers/claude-pty-runner\")'.": 1, "test/eng-seeded-completion-ai.test.ts\tTS2769\tNo overload matches this call. The last overload gave the following error. Argument of type '0' is not assignable to parameter of type 'null'.": 1, "test/eng-semantic-terminal.test.ts\tTS2322\tType '({ sessionId: string; toolUseId: string; questions: { question: string; header: string; multiSelect: boolean; options: { label: string; description: string; }[]; }[]; answered: boolean; failed: boolean; answers: { ...; }; unansweredQuestionIndices: never[]; answeredAt: string; } | ... 10 more ... | { ...; })[]' is not assignable to type 'NativePlanQuestionCall[]'. Type '{ sessionId: string; toolUseId: string; questions: { question: string; header: string; multiSelect: boolean; options: { label: string; description: string; }[]; }[]; answered: boolean; failed: boolean; answers: { ...; }; unansweredQuestionIndices: never[]; answeredAt: string; } | ... 10 more ... | { ...; }' is not assignable to type 'NativePlanQuestionCall'. Type '{ sessionId: string; toolUseId: string; questions: { question: string; header: string; multiSelect: boolean; options: { label: string; description: string; }[]; }[]; answered: boolean; failed: boolean; answers: { \"D1 \\u2014 Should the Promise.all IDP parallelization ship in this refactor PR, or as its own follow-up?...' is not assignable to type 'NativePlanQuestionCall'. Types of property 'answers' are incompatible. Type '{ \"D1 \\u2014 Should the Promise.all IDP parallelization ship in this refactor PR, or as its own follow-up?\\nProject/branch/task: main \\u2014 Multi-tenant Auth Refactor (PLAN.md), Scope Challenge complexity gate.\\nELI10: The plan promises \\\"no product behavior change\\\" (PLAN.md:8-9), then also proposes turning 5 sequ...' is not assignable to type 'Record'. Property '\"D2 — Should legacyAuthFlow() be deleted in this PR, or kept alive behind a flag until the new path proves parity?\\nProject/branch/task: main — Multi-tenant Auth Refactor (PLAN.md), Scope Challenge complexity gate (D1 answered: parallelization deferred).\\nELI10: The plan rewrites legacyAuthFlow() and removes the old code in the same change (PLAN.md:36-37). If the new AuthBroker path gets one tenant edge case wrong, the only way back is a revert of a 12-file PR. A strangler approach lands AuthBroker next to the old flow, routes traffic with a flag (per tenant or percentage), and deletes legacyAuthFlow() in a small follow-up once nobody has been paged. This question is about sequencing only. Whether and how the old behavior gets regression tests is a separate mandatory question in the Tests section; it stays pending here regardless of your answer.\\nStakes if we pick wrong: big-bang and a bad tenant edge case means a full revert under incident pressure; strangler and you carry two auth paths for a short window and must remember to delete the old one.\\nRecommendation: A because auth is the wrong place to make a wrong choice expensive to undo, and the flag costs minutes.\\nNote: options differ in kind, not coverage — no completeness score.\\nPros / cons:\\nA) Strangler: flag-routed, legacy deleted in follow-up (recommended)\\n ✅ One-line rollback (flip the flag) instead of a 12-file revert during an incident\\n ✅ Can canary one internal tenant first and compare allow/deny decisions side by side\\n ❌ Two live auth paths for a sprint or so; someone must own the deletion follow-up (human: ~2h / CC: ~10 min)\\nB) Rewrite and delete legacyAuthFlow() in this PR as planned\\n ✅ No dual-path window, no flag to clean up, smaller total diff\\n ✅ Forces the team to fully understand the legacy behavior now rather than later\\n ❌ Rollback is a full revert; any missed tenant-specific quirk hits production with no soft landing\\nNet: a flag and a follow-up deletion buy you a cheap undo on the one code path where undo matters most.\"' is incompatible with index signature. Type 'undefined' is not assignable to type 'string'.": 1, "test/eng-test-plan-edit-approval.test.ts\tTS2339\tProperty 'isError' does not exist on type '{ kind: string; sessionId: any; toolUseId: any; name: any; input: any; timestamp: string; messageId: string; requestId: string; } | { kind: string; sessionId: any; toolUseId: any; timestamp: string; isError: any; content: any; }'. Property 'isError' does not exist on type '{ kind: string; sessionId: any; toolUseId: any; name: any; input: any; timestamp: string; messageId: string; requestId: string; }'.": 1, diff --git a/scripts/typecheck-test.ts b/scripts/typecheck-test.ts index f25ed3722..1e065e582 100644 --- a/scripts/typecheck-test.ts +++ b/scripts/typecheck-test.ts @@ -21,8 +21,12 @@ const ROOT = path.resolve(import.meta.dir, '..'); export type DiagnosticCounts = Record; /** Parse `tsc --pretty false` output into identity → count. Continuation lines belong to the preceding diagnostic. */ -export function parseDiagnostics(output: string): DiagnosticCounts { +export function parseDiagnostics(output: string, root = ROOT): DiagnosticCounts { const counts: DiagnosticCounts = {}; + // Messages can embed absolute import paths; strip the checkout root so the + // identity is the same in every clone and CI workspace. + const roots = [root, root.replaceAll('\\', '/')].filter(Boolean); + const portable = (text: string) => roots.reduce((value, prefix) => value.split(prefix + '/').join('').split(prefix).join('.'), text); let current: string | null = null; const flush = () => { if (current !== null) counts[current] = (counts[current] ?? 0) + 1; @@ -32,9 +36,9 @@ export function parseDiagnostics(output: string): DiagnosticCounts { const match = /^(.+?)\(\d+,\d+\): error (TS\d+): (.*)$/.exec(line); if (match) { flush(); - current = `${match[1]!.replaceAll('\\', '/')}\t${match[2]}\t${match[3]!.trim()}`; + current = `${portable(match[1]!).replaceAll('\\', '/')}\t${match[2]}\t${portable(match[3]!.trim())}`; } else if (current !== null && /^\s+\S/.test(line)) { - current += ` ${line.trim()}`; + current += ` ${portable(line.trim())}`; } else { flush(); } diff --git a/test/typecheck-test-ratchet.test.ts b/test/typecheck-test-ratchet.test.ts index 8fdf36281..fb6d86c86 100644 --- a/test/typecheck-test-ratchet.test.ts +++ b/test/typecheck-test-ratchet.test.ts @@ -20,6 +20,14 @@ describe('test typecheck ratchet', () => { }); }); + test('strips the checkout root from messages so every clone shares one identity', () => { + const line = "test/d.test.ts(1,1): error TS2322: Type 'import(\"/tmp/clone-a/lib/x\").A' is not assignable to type 'B'."; + const a = parseDiagnostics(line, '/tmp/clone-a'); + const b = parseDiagnostics(line.replace('/tmp/clone-a', '/home/ci/work/gstack'), '/home/ci/work/gstack'); + expect(Object.keys(a)).toEqual(Object.keys(b)); + expect(Object.keys(a)[0]).toContain('import(\"lib/x\")'); + }); + test('ignores line and column so moving code does not churn the baseline', () => { const moved = output.replace('(3,5)', '(30,7)').replace('(9,5)', '(90,1)'); expect(parseDiagnostics(moved)).toEqual(parseDiagnostics(output));