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