From b7300a7a42024df66a2fb554d98f354cad87641a Mon Sep 17 00:00:00 2001 From: oktofeesh1 <287075021+oktofeesh1@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:59:33 +0200 Subject: [PATCH] chore(miner): add a real tsc build pipeline for loopover-miner (#7290) Phase 1 of the plain-.js-to-TypeScript migration proposed in #7290: wires a real tsc build step into packages/loopover-miner (mirroring how @loopover/engine already builds), proven end-to-end against one converted file (lib/pr-number-parse.js -> .ts) before any wider conversion. Each converted file compiles in place (lib/foo.ts -> lib/foo.js), so the package's published bin/lib layout and every consumer's import path stay identical regardless of a given file's migration status -- no file needs to move as later phases convert the remaining files. Coverage remaps through the compiled output's inline sourcemap back to the .ts source, so Codecov patch coverage attributes correctly. The published npm package excludes .ts source (only the compiled .js/.d.ts ship, matching today's distribution shape). --- package-lock.json | 21 +++++++++++++ .../loopover-miner/lib/pr-number-parse.d.ts | 13 +++++--- .../loopover-miner/lib/pr-number-parse.js | 23 ++++++-------- .../loopover-miner/lib/pr-number-parse.ts | 22 +++++++++++++ packages/loopover-miner/package.json | 12 +++++-- packages/loopover-miner/tsconfig.json | 31 +++++++++++++++++++ test/unit/miner-package-skeleton.test.ts | 2 +- vitest.config.ts | 10 +++++- 8 files changed, 113 insertions(+), 21 deletions(-) create mode 100644 packages/loopover-miner/lib/pr-number-parse.ts create mode 100644 packages/loopover-miner/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 1ec353dd77..fe54685e43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20827,10 +20827,31 @@ "loopover-miner": "bin/loopover-miner.js", "loopover-miner-mcp": "bin/loopover-miner-mcp.js" }, + "devDependencies": { + "@types/node": "^22.20.0", + "typescript": "^5.9.3" + }, "engines": { "node": ">=22.13.0" } }, + "packages/loopover-miner/node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "packages/loopover-miner/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "packages/loopover-ui-kit": { "name": "@loopover/ui-kit", "version": "1.1.0", diff --git a/packages/loopover-miner/lib/pr-number-parse.d.ts b/packages/loopover-miner/lib/pr-number-parse.d.ts index c8bada8f68..1b61d71231 100644 --- a/packages/loopover-miner/lib/pr-number-parse.d.ts +++ b/packages/loopover-miner/lib/pr-number-parse.d.ts @@ -1,4 +1,9 @@ -export function parsePrNumberFromExecResult( - execResult: { stdout?: string | undefined; code?: number | null | undefined; timedOut?: boolean | undefined } | null | undefined, - repoFullName: string, -): number | null; +/** `gh pr create` (local-write-tools.ts's `buildOpenPrSpec` -- no `--json` flag) prints the created PR's own + * URL to stdout on success; this is `gh`'s real, documented, stable CLI behavior, not an invented contract. + * Scoped to the exact target repo so an unrelated URL elsewhere in stdout/stderr noise can never match. + */ +export declare function parsePrNumberFromExecResult(execResult: { + stdout?: string | undefined; + code?: number | null | undefined; + timedOut?: boolean | undefined; +} | null | undefined, repoFullName: string): number | null; diff --git a/packages/loopover-miner/lib/pr-number-parse.js b/packages/loopover-miner/lib/pr-number-parse.js index bf6884b4c7..72ba9d760a 100644 --- a/packages/loopover-miner/lib/pr-number-parse.js +++ b/packages/loopover-miner/lib/pr-number-parse.js @@ -2,22 +2,19 @@ // prints the new PR's URL to stdout on success -- this is the one place that URL is authoritatively parsed, // so loop-cli.js's CI/gate-status polling and attempt-cli.js's post-submission claim-conflict check agree on // exactly how a PR number is recovered from a real command's raw output. - /** `gh pr create` (local-write-tools.ts's `buildOpenPrSpec` -- no `--json` flag) prints the created PR's own * URL to stdout on success; this is `gh`'s real, documented, stable CLI behavior, not an invented contract. * Scoped to the exact target repo so an unrelated URL elsewhere in stdout/stderr noise can never match. - * - * @param {{ stdout?: string, code?: number | null, timedOut?: boolean } | null | undefined} execResult - * @param {string} repoFullName - * @returns {number | null} */ export function parsePrNumberFromExecResult(execResult, repoFullName) { - if (!execResult || execResult.timedOut || execResult.code !== 0 || typeof execResult.stdout !== "string") { - return null; - } - const escapedRepo = repoFullName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const match = execResult.stdout.match(new RegExp(`github\\.com/${escapedRepo}/pull/(\\d+)`)); - if (!match) return null; - const prNumber = Number(match[1]); - return Number.isInteger(prNumber) && prNumber > 0 ? prNumber : null; + if (!execResult || execResult.timedOut || execResult.code !== 0 || typeof execResult.stdout !== "string") { + return null; + } + const escapedRepo = repoFullName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const match = execResult.stdout.match(new RegExp(`github\\.com/${escapedRepo}/pull/(\\d+)`)); + if (!match) + return null; + const prNumber = Number(match[1]); + return Number.isInteger(prNumber) && prNumber > 0 ? prNumber : null; } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHItbnVtYmVyLXBhcnNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsicHItbnVtYmVyLXBhcnNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLDBHQUEwRztBQUMxRyw0R0FBNEc7QUFDNUcsNkdBQTZHO0FBQzdHLHlFQUF5RTtBQUV6RTs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsMkJBQTJCLENBQ3pDLFVBQWdJLEVBQ2hJLFlBQW9CO0lBRXBCLElBQUksQ0FBQyxVQUFVLElBQUksVUFBVSxDQUFDLFFBQVEsSUFBSSxVQUFVLENBQUMsSUFBSSxLQUFLLENBQUMsSUFBSSxPQUFPLFVBQVUsQ0FBQyxNQUFNLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDekcsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBQ0QsTUFBTSxXQUFXLEdBQUcsWUFBWSxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxNQUFNLENBQUMsQ0FBQztJQUN4RSxNQUFNLEtBQUssR0FBRyxVQUFVLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxJQUFJLE1BQU0sQ0FBQyxnQkFBZ0IsV0FBVyxjQUFjLENBQUMsQ0FBQyxDQUFDO0lBQzdGLElBQUksQ0FBQyxLQUFLO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDeEIsTUFBTSxRQUFRLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2xDLE9BQU8sTUFBTSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsSUFBSSxRQUFRLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztBQUN0RSxDQUFDIn0= \ No newline at end of file diff --git a/packages/loopover-miner/lib/pr-number-parse.ts b/packages/loopover-miner/lib/pr-number-parse.ts new file mode 100644 index 0000000000..b1a9a27752 --- /dev/null +++ b/packages/loopover-miner/lib/pr-number-parse.ts @@ -0,0 +1,22 @@ +// Shared PR-number extraction from a real `gh pr create` executeLocalWrite result (#4848). `gh pr create` +// prints the new PR's URL to stdout on success -- this is the one place that URL is authoritatively parsed, +// so loop-cli.js's CI/gate-status polling and attempt-cli.js's post-submission claim-conflict check agree on +// exactly how a PR number is recovered from a real command's raw output. + +/** `gh pr create` (local-write-tools.ts's `buildOpenPrSpec` -- no `--json` flag) prints the created PR's own + * URL to stdout on success; this is `gh`'s real, documented, stable CLI behavior, not an invented contract. + * Scoped to the exact target repo so an unrelated URL elsewhere in stdout/stderr noise can never match. + */ +export function parsePrNumberFromExecResult( + execResult: { stdout?: string | undefined; code?: number | null | undefined; timedOut?: boolean | undefined } | null | undefined, + repoFullName: string, +): number | null { + if (!execResult || execResult.timedOut || execResult.code !== 0 || typeof execResult.stdout !== "string") { + return null; + } + const escapedRepo = repoFullName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const match = execResult.stdout.match(new RegExp(`github\\.com/${escapedRepo}/pull/(\\d+)`)); + if (!match) return null; + const prNumber = Number(match[1]); + return Number.isInteger(prNumber) && prNumber > 0 ? prNumber : null; +} diff --git a/packages/loopover-miner/package.json b/packages/loopover-miner/package.json index e1e4d8acda..2d1c2120dd 100644 --- a/packages/loopover-miner/package.json +++ b/packages/loopover-miner/package.json @@ -33,12 +33,16 @@ "schema", "DEPLOYMENT.md", "Dockerfile", - "expected-engine.version" + "expected-engine.version", + "!bin/**/*.ts", + "!lib/**/*.ts", + "bin/**/*.d.ts", + "lib/**/*.d.ts" ], "scripts": { "benchmark": "node scripts/benchmark.mjs", "cross-repo-eval": "node scripts/cross-repo-evaluation.mjs", - "build": "node --check bin/loopover-miner.js && node --check bin/loopover-miner-mcp.js && node --check lib/ams-policy.js && node --check lib/attempt-cli.js && node --check lib/attempt-input-builder.js && node --check lib/attempt-log.js && node --check lib/attempt-runner.js && node --check lib/attempt-worktree.js && node --check lib/calibration-run.js && node --check lib/calibration-types.js && node --check lib/calibration.js && node --check lib/chat-action-dispatch.js && node --check lib/chat-action-registry.js && node --check lib/chat-discover-attempt-actions.js && node --check lib/chat-governor-actions.js && node --check lib/chat-portfolio-actions.js && node --check lib/ci-poller.js && node --check lib/claim-adjudication.js && node --check lib/claim-conflict-resolver.js && node --check lib/claim-ledger-cli.js && node --check lib/claim-ledger-expiry.js && node --check lib/claim-ledger.js && node --check lib/cli.js && node --check lib/coding-agent-construction.js && node --check lib/coding-agent-house-rules.js && node --check lib/coding-task-spec.js && node --check lib/contribution-profile.js && node --check lib/contribution-profile-cache.js && node --check lib/contribution-profile-extract.js && node --check lib/contribution-profile-filter.js && node --check lib/cross-repo-evaluation.js && node --check lib/deny-check.js && node --check lib/deny-hook-synthesis.js && node --check lib/deny-hooks.js && node --check lib/deployment-docs-audit.js && node --check lib/discover-cli.js && node --check lib/discovery-index-client.js && node --check lib/event-ledger-cli.js && node --check lib/event-ledger.js && node --check lib/execute-local-write.js && node --check lib/feasibility-cli.js && node --check lib/governor-chokepoint-persisted.js && node --check lib/governor-chokepoint.js && node --check lib/governor-kill-switch.js && node --check lib/governor-ledger-cli.js && node --check lib/governor-ledger.js && node --check lib/governor-metrics-cli.js && node --check lib/governor-pause-cli.js && node --check lib/governor-run-halt.js && node --check lib/governor-state.js && node --check lib/harness-submission-trigger.js && node --check lib/init-wizard.js && node --check lib/laptop-init.js && node --check lib/live-issue-snapshot.js && node --check lib/local-store.js && node --check lib/logger.js && node --check lib/loop-cli.js && node --check lib/loop-closure.js && node --check lib/loop-reentry.js && node --check lib/manage-poll.js && node --check lib/manage-status.js && node --check lib/metrics-cli.js && node --check lib/miner-goal-spec.js && node --check lib/opportunity-fanout.js && node --check lib/opportunity-ranker.js && node --check lib/orb-export.js && node --check lib/plan-store-cli.js && node --check lib/plan-store.js && node --check lib/policy-doc-cache.js && node --check lib/policy-verdict-cache.js && node --check lib/portfolio-dashboard.js && node --check lib/portfolio-discovery.js && node --check lib/portfolio-queue-cli.js && node --check lib/portfolio-queue-manager.js && node --check lib/portfolio-queue.js && node --check lib/portfolio-queue-expiry.js && node --check lib/pr-disposition-poller.js && node --check lib/pr-number-parse.js && node --check lib/pr-outcome.js && node --check lib/prediction-ledger.js && node --check lib/pretooluse-hook.js && node --check lib/purge-cli.js && node --check lib/ranked-candidates.js && node --check lib/rejection-signal.js && node --check lib/rejection-state-machine.js && node --check lib/rejection-templates.js && node --check lib/replay-objective-anchor.js && node --check lib/replay-snapshot.js && node --check lib/replay-task-bridge.js && node --check lib/replay-task-generation.js && node --check lib/repo-clone.js && node --check lib/run-state-cli.js && node --check lib/run-state.js && node --check lib/self-review-context.js && node --check lib/sentry.js && node --check lib/slop-assessment.js && node --check lib/stack-detection.js && node --check lib/status.js && node --check lib/submission-freshness-check.js && node --check lib/update-check.js && node --check lib/version.js && node --check lib/worktree-allocator.js" + "build": "tsc -p tsconfig.json && node --check bin/loopover-miner.js && node --check bin/loopover-miner-mcp.js && node --check lib/ams-policy.js && node --check lib/attempt-cli.js && node --check lib/attempt-input-builder.js && node --check lib/attempt-log.js && node --check lib/attempt-runner.js && node --check lib/attempt-worktree.js && node --check lib/calibration-run.js && node --check lib/calibration-types.js && node --check lib/calibration.js && node --check lib/chat-action-dispatch.js && node --check lib/chat-action-registry.js && node --check lib/chat-discover-attempt-actions.js && node --check lib/chat-governor-actions.js && node --check lib/chat-portfolio-actions.js && node --check lib/ci-poller.js && node --check lib/claim-adjudication.js && node --check lib/claim-conflict-resolver.js && node --check lib/claim-ledger-cli.js && node --check lib/claim-ledger-expiry.js && node --check lib/claim-ledger.js && node --check lib/cli.js && node --check lib/coding-agent-construction.js && node --check lib/coding-agent-house-rules.js && node --check lib/coding-task-spec.js && node --check lib/contribution-profile.js && node --check lib/contribution-profile-cache.js && node --check lib/contribution-profile-extract.js && node --check lib/contribution-profile-filter.js && node --check lib/cross-repo-evaluation.js && node --check lib/deny-check.js && node --check lib/deny-hook-synthesis.js && node --check lib/deny-hooks.js && node --check lib/deployment-docs-audit.js && node --check lib/discover-cli.js && node --check lib/discovery-index-client.js && node --check lib/event-ledger-cli.js && node --check lib/event-ledger.js && node --check lib/execute-local-write.js && node --check lib/feasibility-cli.js && node --check lib/governor-chokepoint-persisted.js && node --check lib/governor-chokepoint.js && node --check lib/governor-kill-switch.js && node --check lib/governor-ledger-cli.js && node --check lib/governor-ledger.js && node --check lib/governor-metrics-cli.js && node --check lib/governor-pause-cli.js && node --check lib/governor-run-halt.js && node --check lib/governor-state.js && node --check lib/harness-submission-trigger.js && node --check lib/init-wizard.js && node --check lib/laptop-init.js && node --check lib/live-issue-snapshot.js && node --check lib/local-store.js && node --check lib/logger.js && node --check lib/loop-cli.js && node --check lib/loop-closure.js && node --check lib/loop-reentry.js && node --check lib/manage-poll.js && node --check lib/manage-status.js && node --check lib/metrics-cli.js && node --check lib/miner-goal-spec.js && node --check lib/opportunity-fanout.js && node --check lib/opportunity-ranker.js && node --check lib/orb-export.js && node --check lib/plan-store-cli.js && node --check lib/plan-store.js && node --check lib/policy-doc-cache.js && node --check lib/policy-verdict-cache.js && node --check lib/portfolio-dashboard.js && node --check lib/portfolio-discovery.js && node --check lib/portfolio-queue-cli.js && node --check lib/portfolio-queue-manager.js && node --check lib/portfolio-queue.js && node --check lib/portfolio-queue-expiry.js && node --check lib/pr-disposition-poller.js && node --check lib/pr-number-parse.js && node --check lib/pr-outcome.js && node --check lib/prediction-ledger.js && node --check lib/pretooluse-hook.js && node --check lib/purge-cli.js && node --check lib/ranked-candidates.js && node --check lib/rejection-signal.js && node --check lib/rejection-state-machine.js && node --check lib/rejection-templates.js && node --check lib/replay-objective-anchor.js && node --check lib/replay-snapshot.js && node --check lib/replay-task-bridge.js && node --check lib/replay-task-generation.js && node --check lib/repo-clone.js && node --check lib/run-state-cli.js && node --check lib/run-state.js && node --check lib/self-review-context.js && node --check lib/sentry.js && node --check lib/slop-assessment.js && node --check lib/stack-detection.js && node --check lib/status.js && node --check lib/submission-freshness-check.js && node --check lib/update-check.js && node --check lib/version.js && node --check lib/worktree-allocator.js" }, "dependencies": { "@loopover/engine": "^3.4.0", @@ -46,6 +50,10 @@ "@sentry/node": "^10.63.0", "zod": "^4.4.3" }, + "devDependencies": { + "@types/node": "^22.20.0", + "typescript": "^5.9.3" + }, "engines": { "node": ">=22.13.0" } diff --git a/packages/loopover-miner/tsconfig.json b/packages/loopover-miner/tsconfig.json new file mode 100644 index 0000000000..4e46de9078 --- /dev/null +++ b/packages/loopover-miner/tsconfig.json @@ -0,0 +1,31 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": ["node"], + "declaration": true, + // Inline (not a separate .js.map) so the compiled output stays exactly the same file set the + // package has always shipped -- no new file type to teach the npm-pack allowlist about. Vitest's + // v8 coverage provider still remaps through it fine, attributing coverage to the .ts source. + "inlineSourceMap": true, + "noEmit": false, + // Overrides the root config's "dist" so each converted file compiles in place next to its .ts + // source (e.g. lib/foo.ts -> lib/foo.js) -- the package's published bin/lib layout never changes + // as more files convert, so no consumer (in this repo or published) ever needs a different import + // path depending on a given file's migration status. + "rootDir": ".", + "outDir": ".", + // Without this, the inherited root value ("./.tsbuildinfo") resolves relative to the ROOT config's + // location, not this one -- both packages would then read/write the exact same cache file at the + // repo root and corrupt each other's incremental state. + "tsBuildInfoFile": "./.tsbuildinfo" + }, + // Only files already converted to real TypeScript are included -- everything else in bin/lib stays + // plain, hand-maintained .js + .d.ts until its own migration PR lands (#7290). No edits needed here as + // later phases convert more files: the glob picks them up automatically. + "include": ["bin/**/*.ts", "lib/**/*.ts"], + // Without this, tsc's default exclude list (which always adds outDir) resolves to "." -- the whole + // package root -- and silently excludes every include match, since outDir is "." for in-place emit. + "exclude": [] +} diff --git a/test/unit/miner-package-skeleton.test.ts b/test/unit/miner-package-skeleton.test.ts index aead9d9681..4a5b9078ba 100644 --- a/test/unit/miner-package-skeleton.test.ts +++ b/test/unit/miner-package-skeleton.test.ts @@ -41,7 +41,7 @@ describe("loopover-miner package skeleton (#2287)", () => { expect(miner.dependencies["@loopover/engine"]).toBeDefined(); expect(miner.engines.node).toMatch(/^>=22(?:\.\d+){0,2}$/); expect(miner.files).toEqual(expect.arrayContaining(["bin", "lib"])); - expect(miner.scripts.build.startsWith("node --check bin/loopover-miner.js")).toBe(true); + expect(miner.scripts.build.startsWith("tsc -p tsconfig.json && node --check bin/loopover-miner.js")).toBe(true); }); it("starts the CLI bin with a node shebang", () => { diff --git a/vitest.config.ts b/vitest.config.ts index d70354db8e..68275da0bc 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -30,6 +30,10 @@ export default defineConfig({ "src/**/*.ts", "packages/loopover-engine/src/**/*.ts", "packages/loopover-miner/lib/**/*.js", + // Files already converted to real TypeScript (#7290) execute as the compiled .js above, but + // v8's coverage provider remaps through the inline sourcemap tsc emits, attributing coverage to + // the .ts source instead -- this entry is what keeps that remapped file in the report. + "packages/loopover-miner/lib/**/*.ts", "packages/discovery-index/src/**/*.ts", // review-enrichment is a standalone (non-workspace) package with its own node:test suite; its // coverage is collected separately via `npm run rees:coverage` (c8 over the built dist, remapped @@ -40,7 +44,11 @@ export default defineConfig({ // as a side effect of import) -- like the main app's own src/server.ts, it's exercised by a Docker // build+boot path, not unit-coverable without actually binding a port. See codecov.yml's matching // ignore entry; app.ts (everything server.ts wires together) is what tests actually import. - exclude: ["src/env.d.ts", "apps/**", "packages/discovery-index/src/server.ts"], + // + // packages/loopover-miner/lib/**/*.ts (above) also glob-matches its own still-hand-maintained + // *.d.ts siblings (a ".d.ts" path ends in ".ts" too) -- those aren't real modules and can't be + // parsed as coverage source, so they're excluded the same way src/env.d.ts already is. + exclude: ["src/env.d.ts", "apps/**", "packages/discovery-index/src/server.ts", "packages/loopover-miner/lib/**/*.d.ts"], // Emit lcov for Codecov to compute patch (changed-lines) coverage. reporter: ["text", "lcov"], // The 99% requirement now lives in codecov.yml as a *patch* gate (changed