build(typescript): eliminate hand-written JavaScript and lock it out with a CI gate - #9534
Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
loopover-ui | ce4aa1c | Commit Preview URL Branch Preview URL |
Jul 28 2026, 08:22 AM |
|
Warning ⏸️ LoopOver review result - manual review recommendedReview updated: 2026-07-28 08:41:47 UTC
Review summary Nits — 7 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Decision record
Visual preview
Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy. Scroll preview
A short scroll-through clip (desktop) — click either thumbnail to open the full animation. Evidence for scroll-linked behavior a single screenshot can't show. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9534 +/- ##
=======================================
Coverage 89.59% 89.59%
=======================================
Files 858 858
Lines 110456 110456
Branches 26300 26300
=======================================
Hits 98961 98961
Misses 10231 10231
Partials 1264 1264
Flags with carried forward coverage won't be shown. Click here to find out more.
|
Bundle ReportBundle size has no change ✅ |
ff35db1 to
d542579
Compare
…ree dev scripts for real 3 test files (iterate-loop-load-test-script, miner-benchmark-script, miner-cross-repo-evaluation) still imported the OLD .mjs paths for load-test-iterate-loop/benchmark/cross-repo-evaluation after their #9527 rename to .ts -- the package.json runners were updated but these direct test imports were missed. Broke CI on #9534 (Cannot find module ... .mjs). Fixed, plus the same stale path in two docs, a manifest description string, a script's own --help text, and a code comment, none of which the original port's grep sweep caught. Typing the three scripts for the first time (they were always .mjs, so tsc never saw them) surfaced real gaps, not just missing annotations: - cross-repo-evaluation.ts's main() called parseCrossRepoEvaluationArgs() with zero arguments against a REQUIRED parameter -- silently fine in untyped JS because the function internally falls back via , but the parameter needed to actually be optional to say what was always true. - benchmark.ts's buildSyntheticCandidates() was missing owner/repo/assignees entirely and mixed real booleans into a field opportunity-fanout.ts types as literal -only. Traced rankCandidateIssues' actual runtime check () before deciding how to type it: it genuinely branches on false, so narrowing the benchmark's synthetic mix to true-only would have silently dropped real exercised coverage. Added the missing fields for real, kept the deliberate true/false mix with a documented cast rather than narrowing it away. - load-test-iterate-loop.ts's fake driver was typed to a hand-narrowed { attemptId: string } task shape until the test file's own fuller task literal (workingDirectory, acceptanceCriteriaPath, ...) failed against it -- switched to the real CodingAgentDriverTask/Result types instead of the narrower hand-rolled ones. Refs #9527
d542579 to
90f580e
Compare
90f580e to
6bd2155
Compare
…ree dev scripts for real 3 test files (iterate-loop-load-test-script, miner-benchmark-script, miner-cross-repo-evaluation) still imported the OLD .mjs paths for load-test-iterate-loop/benchmark/cross-repo-evaluation after their #9527 rename to .ts -- the package.json runners were updated but these direct test imports were missed. Broke CI on #9534 (Cannot find module ... .mjs). Fixed, plus the same stale path in two docs, a manifest description string, a script's own --help text, and a code comment, none of which the original port's grep sweep caught. Typing the three scripts for the first time (they were always .mjs, so tsc never saw them) surfaced real gaps, not just missing annotations: - cross-repo-evaluation.ts's main() called parseCrossRepoEvaluationArgs() with zero arguments against a REQUIRED parameter -- silently fine in untyped JS because the function internally falls back via , but the parameter needed to actually be optional to say what was always true. - benchmark.ts's buildSyntheticCandidates() was missing owner/repo/assignees entirely and mixed real booleans into a field opportunity-fanout.ts types as literal -only. Traced rankCandidateIssues' actual runtime check () before deciding how to type it: it genuinely branches on false, so narrowing the benchmark's synthetic mix to true-only would have silently dropped real exercised coverage. Added the missing fields for real, kept the deliberate true/false mix with a documented cast rather than narrowing it away. - load-test-iterate-loop.ts's fake driver was typed to a hand-narrowed { attemptId: string } task shape until the test file's own fuller task literal (workingDirectory, acceptanceCriteriaPath, ...) failed against it -- switched to the real CodingAgentDriverTask/Result types instead of the narrower hand-rolled ones. Refs #9527
First batch of #9527: control-plane-coverage, actionlint-download-attempts, and both packages' check-syntax + strip-bin-sourcemap become real TypeScript, with their package.json runners, importers, and tests updated. check-syntax scanned scripts/*.mjs, so porting those files would have silently removed them from the only syntax check that covered them. node --check accepts a .ts file under --experimental-strip-types (verified it still rejects broken syntax), so both scripts now scan .ts too and the file counts are unchanged. Refs #9527
…v-reference generator Second batch of #9527. eslint.config.js -> .ts in both UI apps (eslint 9 resolves TS configs through the hoisted jiti; lint still reports 0 errors), the three benchmark/load-test scripts, and generate-env-reference, which runs inside test:ci via miner:env-reference:check. The generator embeds its own filename in the header of both artifacts it emits, so the rename changes generated output -- regenerated and re-checked, 48 env var references unchanged in each. Refs #9527
…ript out with a CI gate Completes #9527. control-plane, discovery-index and review-enrichment's remaining .mjs scripts become TypeScript, and validate-no-hand-written-js now fails CI on any tracked .mjs/.cjs/.js or hand-written .d.ts outside a small allowlist, each entry carrying its reason in the file. The allowlist has an anti-rot guard: an entry matching nothing tracked FAILS the check. A watched path that silently stops existing is exactly how metagraphed's MCP version-sync workflow died unnoticed for months. Typing the ported files surfaced real defects the untyped versions hid: validate-posthog-release's fetchSymbolSets was annotated by its own callers as returning {results} when it returns a bare array; its config accessor was typed against the app's full ProcessEnv, which no caller could satisfy; and generate-env-reference dereferenced regex groups and a nullable defaultValue without guarding either. The generator's output is byte-identical (48 references, --check passes), so the port is provably behavior-preserving. check-syntax gained scripts/*.ts coverage in both packages so porting a file can never remove it from the only syntax check watching it, and the two upload- sourcemaps spawn sites pass --experimental-strip-types explicitly rather than relying on Node 22.18+ defaulting it on, since engines.node still allows 22.0. Closes #9527
…ree dev scripts for real 3 test files (iterate-loop-load-test-script, miner-benchmark-script, miner-cross-repo-evaluation) still imported the OLD .mjs paths for load-test-iterate-loop/benchmark/cross-repo-evaluation after their #9527 rename to .ts -- the package.json runners were updated but these direct test imports were missed. Broke CI on #9534 (Cannot find module ... .mjs). Fixed, plus the same stale path in two docs, a manifest description string, a script's own --help text, and a code comment, none of which the original port's grep sweep caught. Typing the three scripts for the first time (they were always .mjs, so tsc never saw them) surfaced real gaps, not just missing annotations: - cross-repo-evaluation.ts's main() called parseCrossRepoEvaluationArgs() with zero arguments against a REQUIRED parameter -- silently fine in untyped JS because the function internally falls back via , but the parameter needed to actually be optional to say what was always true. - benchmark.ts's buildSyntheticCandidates() was missing owner/repo/assignees entirely and mixed real booleans into a field opportunity-fanout.ts types as literal -only. Traced rankCandidateIssues' actual runtime check () before deciding how to type it: it genuinely branches on false, so narrowing the benchmark's synthetic mix to true-only would have silently dropped real exercised coverage. Added the missing fields for real, kept the deliberate true/false mix with a documented cast rather than narrowing it away. - load-test-iterate-loop.ts's fake driver was typed to a hand-narrowed { attemptId: string } task shape until the test file's own fuller task literal (workingDirectory, acceptanceCriteriaPath, ...) failed against it -- switched to the real CodingAgentDriverTask/Result types instead of the narrower hand-rolled ones. Refs #9527
benchmark.ts and cross-repo-evaluation.ts (packages/loopover-miner/scripts/) import from this package's OWN ../dist/lib/*.js -- real .ts since this PR's port, so Typecheck now actually opens and type-checks them, unlike when they were .mjs and invisible to tsc. "Build miner CLI" ran well after Typecheck (down by "Miner package check"), so a clean CI checkout hit Cannot find module on both scripts' dist imports every run. Same class of gap the contract/engine build-order fixes already cover, just for the miner package's own dist rather than a workspace dependency's -- moved the step ahead of Typecheck and widened its trigger to match Typecheck's exactly, for the same reason those two already match: any trigger that typechecks must also have built what typechecking reads. Refs #9527
6bd2155 to
ce4aa1c
Compare


Summary
Eliminates every hand-written JavaScript file in the repo and makes the state permanent with a CI gate.
Census (committed in the PR, not the repo): 37 tracked
.mjs/.cjs+ 3.js+ 7.d.ts. Classified and resolved as:.tscheck-syntax+strip-bin-sourcemap,control-plane-coverage,actionlint-download-attempts,generate-env-reference, both eslint configs, three benchmark/load-test scripts, and control-plane / discovery-index / review-enrichment'sgen-cf-typegen,validate-sourcemaps,validate-posthog-release,generate-analyzer-metadatatest/fixtures/**(subprocess doubles spawned by plainnode, several deliberately malformed),gittensor-score-preview.mjs(ships in the npm tarball, runs under end users' own node),sw.js(served byte-for-byte to browsers),rees-coverage-chdir.cjs(node --requireonly loads CommonJS).d.ts, allowlistedenv.d.ts×3 and thegifencmodule stub — ambient declaration is what.d.tsis forworker-configuration.d.ts—cf-typegen:checkalready proves these match their generator, a stronger guarantee than a reason stringThe gate (
scripts/validate-no-hand-written-js.ts, wired intotest:ci) fails on any tracked JavaScript or hand-written.d.tsoutside the allowlist. Its anti-rot guard: an allowlist entry matching nothing tracked fails the check. A watched path that silently stops existing is exactly how metagraphed's MCP version-sync workflow died unnoticed for months — that lesson is encoded here rather than repeated.Defects the port surfaced
Typing previously-untyped files found real bugs, all fixed:
fetchSymbolSetsreturns a bare array, but its own call sites treated it as{results}— the untyped version silently worked only because both shapes were handled defensively at one call site.loadPostHogReleaseValidationConfigwas implicitly typed against the app's fullProcessEnvonce ported, which no caller could satisfy (~50 unrelated vars for a script that reads three). Now typed to exactly what it reads.generate-env-referencedereferenced a regex capture group and a nullabledefaultValuewithout guarding either — both realnoUncheckedIndexedAccessviolations that were invisible in.mjs.Regressions avoided
check-syntaxscannedscripts/*.mjs, so porting those files would have silently removed them from the only check watching them. Both packages' scripts now scan.tstoo (node --checkaccepts it under--experimental-strip-types; verified it still rejects broken syntax). File counts unchanged: 9 and 136.upload-sourcemapsspawn sites pass--experimental-strip-typesexplicitly rather than relying on Node 22.18+ enabling it by default, becauseengines.nodestill allows22.0.generate-env-referenceembeds its own filename in both artifacts it emits; regenerated, and--checkconfirms 48 references unchanged in each — the port is provably behavior-preserving.Closes #9527
Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Closes #123) — a linked open issue is required for every contributor PR.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally (scoped — see note)npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderateGreen: 232 tests across the 18 affected vitest suites, the full 1,379-test review-enrichment suite (
rees:test),build:mcp+build:miner(bothcheck-syntaxruns at full file counts),test:mcp-pack,test:miner-pack,cf-typegen:check,docs:drift-check,miner:env-reference:check, andui:lint(0 errors on the ported eslint config). The gate has 13 of its own tests covering both sides of every branch — allowlist prefix vs exact match, generated vs hand-written.d.ts, stale-entry detection — plus one that runs the real gate against the real tree.If any required check was skipped, explain why:
Safety
UI Evidencesection below. (n/a — no visible change)Notes
Part of #9515 — the language-layer half of "nothing hand-maintained". Independent of every other sub-issue.