Skip to content

Commit d6c4334

Browse files
authored
chore(mcp): delete orphaned changelog config and dead schema literals, lock the package against unused locals (#9528)
* chore(mcp): delete orphaned changelog config and dead schema literals, lock the package against unused locals cliff.mcp.toml was referenced by nothing -- the MCP changelog is generated by scripts/generate-mcp-changelog.ts via cliff.toml, and check-changelog.ts --mcp still passes without it. It carried a stale commit_preprocessors rewrite rule that read as live config. Five hand-written output-schema literals (repoContext, preflight, decisionPack, localStatus, agentPlan) sat unreferenced since #291. Enabling noUnusedLocals / noUnusedParameters is what keeps the next one from accumulating; it immediately surfaced two more dead items, both removed: isValidationStatus, superseded by isValidationStatusLike with callers reaching normalizeValidationStatus directly, and an unused node:path join import in local-branch.ts. Comment fixes for counts that had already rotted: the registration wrapper said 37 call sites, and the tools-search example cited get_subnet_stake_quote, a tool that does not exist in this repo. Both now describe the surface without pinning a number that goes stale. Refs #9516 * ci: build the engine for mcp/miner-only PRs, which already run typecheck The "Build engine package" step gated on backend/engine/ui, while "Typecheck" gated on backend/engine/mcp/miner. An mcp-only or miner-only PR therefore ran typecheck against an unbuilt engine: test/** imports @loopover/engine directly, and its "types" resolve to packages/loopover-engine/dist/index.d.ts, which that step is what produces. The result was a wall of phantom "Cannot find module '@loopover/engine'" errors plus every implicit-any that cascades from them, none of which related to the PR's actual diff. Widens the build gate to match typecheck's, the same fix typecheck itself already received for the mirror-image gap. Any trigger that typechecks must also have built what typechecking reads. Refs #9516
1 parent 6bb8588 commit d6c4334

5 files changed

Lines changed: 25 additions & 123 deletions

File tree

.github/workflows/ci.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,8 +467,16 @@ jobs:
467467
key: turbo-code-${{ hashFiles('package-lock.json') }}-${{ github.run_id }}
468468
restore-keys: |
469469
turbo-code-${{ hashFiles('package-lock.json') }}-
470+
# mcp/miner are in this gate because "Typecheck" below already runs for them, and typecheck's real
471+
# surface reaches @loopover/engine -- test/** imports it directly, and its "types" resolve to
472+
# packages/loopover-engine/dist/index.d.ts, which only exists once this step has run. An mcp-only or
473+
# miner-only PR therefore used to run Typecheck against an unbuilt engine and fail with a wall of
474+
# phantom "Cannot find module '@loopover/engine'" errors (plus every TS7006 implicit-any that
475+
# cascades from them), with no relation to the actual diff. Deliberately the same widening the
476+
# Typecheck step itself already received for the mirror-image gap; the two conditions must stay in
477+
# sync, since any trigger that typechecks must also have built what typechecking reads.
470478
- name: Build engine package
471-
if: ${{ github.event_name == 'push' || needs.changes.outputs.backend == 'true' || needs.changes.outputs.engine == 'true' || needs.changes.outputs.ui == 'true' }}
479+
if: ${{ github.event_name == 'push' || needs.changes.outputs.backend == 'true' || needs.changes.outputs.engine == 'true' || needs.changes.outputs.ui == 'true' || needs.changes.outputs.mcp == 'true' || needs.changes.outputs.miner == 'true' }}
472480
run: npx turbo run build --filter=@loopover/engine
473481
# Mirrors "MCP package check"/"Miner package check" below: the published npm tarball is a
474482
# different surface than the workspace build above (files field, forbidden paths/content,

cliff.mcp.toml

Lines changed: 0 additions & 48 deletions
This file was deleted.

packages/loopover-mcp/bin/loopover-mcp.ts

Lines changed: 10 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1732,8 +1732,8 @@ export const server = new McpServer({
17321732
version: packageVersion,
17331733
});
17341734

1735-
// #4777: register a stdio tool under its loopover_ name. Thin wrapper kept so all 37 call sites
1736-
// stay uniform with the rest of this file's registration style.
1735+
// #4777: register a stdio tool under its loopover_ name. Every tool registers through this wrapper,
1736+
// so registration style stays uniform across the file.
17371737
// Telemetry await/flush lives in wrapStdioToolHandler (lib/telemetry.ts, unit-tested) — #6238 / #8690.
17381738
// Reads telemetryState() HERE on purpose: registerStdioTool's second parameter is the TOOL's config and
17391739
// shadows the module-level `config`, so a read inside a nested function would silently see the wrong object.
@@ -2954,68 +2954,9 @@ registerStdioTool(
29542954
async (input: any) => toolResult("LoopOver base-agent public-safe PR packet.", await agentPreparePrPacket(await withClientWorkspaceRoots(input))),
29552955
);
29562956

2957-
// ── Output schemas for structured tool responses (#291) ──────────────────────
2958-
2959-
const repoContextOutputSchema = {
2960-
type: "object",
2961-
properties: {
2962-
repoFullName: { type: "string" },
2963-
lane: { type: "string" },
2964-
primaryLanguage: { type: ["string", "null"] },
2965-
openIssueCount: { type: "number" },
2966-
openPrCount: { type: "number" },
2967-
},
2968-
additionalProperties: true,
2969-
};
2970-
2971-
const preflightOutputSchema = {
2972-
type: "object",
2973-
properties: {
2974-
status: { type: "string", enum: ["pass", "warn", "fail", "unknown"] },
2975-
signals: { type: "array", items: { type: "object" } },
2976-
summary: { type: "string" },
2977-
},
2978-
additionalProperties: true,
2979-
};
2980-
2981-
const decisionPackOutputSchema = {
2982-
type: "object",
2983-
properties: {
2984-
login: { type: "string" },
2985-
decisions: { type: "array", items: { type: "object" } },
2986-
cachedAt: { type: ["string", "null"] },
2987-
},
2988-
additionalProperties: true,
2989-
};
2990-
2991-
const localStatusOutputSchema = {
2992-
type: "object",
2993-
properties: {
2994-
apiUrl: { type: "string" },
2995-
package: { type: "object", properties: { name: { type: "string" }, version: { type: "string" } }, additionalProperties: true },
2996-
hasToken: { type: "boolean" },
2997-
profile: { type: "object", additionalProperties: true },
2998-
authLogin: { type: ["string", "null"] },
2999-
sessionExpiresAt: { type: ["string", "null"] },
3000-
sourceUploadDefault: { type: "boolean" },
3001-
sourceUploadSupported: { type: "boolean" },
3002-
git: { type: "object", additionalProperties: true },
3003-
},
3004-
additionalProperties: true,
3005-
};
3006-
3007-
const agentPlanOutputSchema = {
3008-
type: "object",
3009-
properties: {
3010-
login: { type: "string" },
3011-
actions: { type: "array", items: { type: "object" } },
3012-
topAction: { type: ["object", "null"] },
3013-
},
3014-
additionalProperties: true,
3015-
};
3016-
3017-
// Attach outputSchema to key tools via registerTool with zod output schemas.
3018-
// All other tools continue to return unschematized text+structured content.
2957+
// Only this tool declares an outputSchema today; every other tool returns text + unschematized
2958+
// structured content. #9518 finishes the job by registering all of them from @loopover/contract,
2959+
// where each tool's output schema lives beside its input schema and is enforced by validate:mcp.
30192960

30202961
registerStdioTool(
30212962
"loopover_local_status_structured",
@@ -5192,10 +5133,11 @@ function toolsCommand(args: any) {
51925133
});
51935134
}
51945135

5195-
// `tools search <query>` — fuzzy discovery across the ~150-tool combined surface (#6300). Matches the
5196-
// query against each registered tool's name AND description (not name-only), so "stake" surfaces
5197-
// get_subnet_stake_quote even though "stake" is only in its description. Reuses this CLI's existing
5198-
// levenshteinDistance for typo tolerance rather than pulling in a fuzzy-match dependency.
5136+
// `tools search <query>` — fuzzy discovery across the whole registered tool surface (#6300). Matches
5137+
// the query against each registered tool's name AND description (not name-only), so "duplicate"
5138+
// surfaces loopover_check_before_start even though "duplicate" is only in its description. Reuses
5139+
// this CLI's existing levenshteinDistance for typo tolerance rather than pulling in a fuzzy-match
5140+
// dependency.
51995141
function toolsSearchCommand(args: any) {
52005142
const options = parseOptions(args);
52015143
const query = args.find((arg: any) => !arg.startsWith("--"));
@@ -6427,10 +6369,6 @@ function optionalNumber(value: any) {
64276369
return Number.isFinite(parsed) ? parsed : undefined;
64286370
}
64296371

6430-
function isValidationStatus(value: any) {
6431-
return Boolean(normalizeValidationStatus(value));
6432-
}
6433-
64346372
function normalizeValidationStatus(value: any) {
64356373
const text = String(value ?? "").trim().toLowerCase().replace(/[-\s]+/g, "_");
64366374
if (["passed", "pass", "success", "ok", "exit_0", "0"].includes(text)) return "passed";

packages/loopover-mcp/lib/local-branch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { execFileSync } from "node:child_process";
22
import { realpathSync } from "node:fs";
3-
import { isAbsolute, join, relative, resolve } from "node:path";
3+
import { isAbsolute, relative, resolve } from "node:path";
44
import { fileURLToPath } from "node:url";
55
import { assertScenarioLocalBranchInputSafe } from "@loopover/engine";
66
import { isCodeFile, isTestPath as isTestFile } from "@loopover/engine/signals/test-evidence";

packages/loopover-mcp/tsconfig.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@
2525
// Without this, the inherited root value ("./.tsbuildinfo") resolves relative to the ROOT config's
2626
// location, not this one -- both packages would then read/write the exact same cache file at the
2727
// repo root and corrupt each other's incremental state.
28-
"tsBuildInfoFile": "./.tsbuildinfo"
28+
"tsBuildInfoFile": "./.tsbuildinfo",
29+
// #9516: five hand-written output-schema literals sat here unreferenced because nothing rejected
30+
// dead locals. tsc is the gate now, so the next one fails the build instead of accumulating.
31+
"noUnusedLocals": true,
32+
"noUnusedParameters": true
2933
},
3034
// Every bin/lib runtime module is real TypeScript (#7291, phased across #7328/#7329/#7330): tsc owns
3135
// the dist/ .js emit, which is gitignored -- contributors and tests never touch it. The glob stays

0 commit comments

Comments
 (0)