From 701d6262cced44a47f9772281db3282b39141b7c Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Wed, 8 Jul 2026 01:28:58 -0700 Subject: [PATCH] refactor(mcp): depend on the published gittensory-engine, drop the hand-ports Now that @jsonbored/gittensory-engine is published (0.1.0), gittensory-mcp adds it as a real dependency and both packages/gittensory-mcp/lib/local-branch.js and scripts/gittensor-score-preview.mjs import isTestFile/isCodeFile from @jsonbored/gittensory-engine/signals/test-evidence instead of hand-porting the regex logic -- the two remaining copies from the three-way drift that motivated the gittensory-engine consolidation (#4144). package-lock.json: hand-edited a single line rather than running `npm install --package-lock-only`, which was proven (twice, with and without --workspace scoping) to destructively prune ~150 unrelated packages from this monorepo's lockfile. Workspace-to-workspace dependencies only need the manifest-mirror entry updated; the shared node_modules symlink already resolves it. ci.yml: gittensory-mcp's "Build MCP"/"MCP package check" steps and the mcpCliHarness test-skip filter only checked the mcp path filter, not engine -- a PR touching only packages/gittensory-engine/** would have silently skipped rebuilding, pack-checking, and testing its now-real dependent. Verified end-to-end outside the monorepo: packed the mcp tarball, installed it into an isolated scratch directory, confirmed gittensory-engine resolved as a real registry copy (not a workspace symlink) and the classifier functions work through the full published dependency chain. --- .github/workflows/ci.yml | 13 ++++++-- package-lock.json | 1 + packages/gittensory-mcp/lib/local-branch.js | 33 ++----------------- packages/gittensory-mcp/package.json | 1 + .../scripts/gittensor-score-preview.mjs | 25 +------------- 5 files changed, 16 insertions(+), 57 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0840a00bc4..7b8559883b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,7 +101,10 @@ jobs: # their shared test/unit/support/mcp-cli-harness.ts import nothing but node:* builtins + vitest, # and mcp-discovery.test.ts spawns packages/gittensory-mcp/bin/gittensory-mcp.js as a real # subprocess via StdioClientTransport -- none of the 6 ever load root src/ in-process. This mirrors - # the `mcp` filter's own established trust boundary above (a self-contained package build). + # the `mcp` filter's own established trust boundary above (a self-contained package build). They are + # NOT self-contained w.r.t. packages/gittensory-engine/**, though: gittensory-mcp now has a real + # dependency on @jsonbored/gittensory-engine (isTestFile/isCodeFile), which the bin subprocess and + # local-branch.js load at runtime -- hence that path below, alongside the mcp package's own. # test/unit/mcp-output-schemas.test.ts is DELIBERATELY NOT in this filter: unlike the other 6, it # imports src/mcp/server.ts in-process, and server.ts alone directly imports ~40 other src/ modules # (src/github/app.ts, src/signals/slop.ts, src/settings/autonomy.ts, src/orb/analytics.ts, and @@ -121,6 +124,7 @@ jobs: - 'migrations/**' - '.github/workflows/**' - 'packages/gittensory-mcp/**' + - 'packages/gittensory-engine/**' - 'test/helpers/**' - 'test/unit/mcp-cli-*.test.ts' - 'test/unit/mcp-discovery.test.ts' @@ -396,11 +400,14 @@ jobs: - name: Worker runtime tests if: ${{ github.event_name == 'push' || needs.changes.outputs.backend == 'true' }} run: npm run test:workers + # gittensory-mcp now depends on @jsonbored/gittensory-engine for real (isTestFile/isCodeFile), so a + # PR that only touches packages/gittensory-engine/** must also rebuild + pack-check the mcp package, + # not just the mcp filter's own (deliberately narrower) path list. - name: Build MCP - if: ${{ github.event_name == 'push' || needs.changes.outputs.mcp == 'true' }} + if: ${{ github.event_name == 'push' || needs.changes.outputs.mcp == 'true' || needs.changes.outputs.engine == 'true' }} run: npm run build:mcp - name: MCP package check - if: ${{ github.event_name == 'push' || needs.changes.outputs.mcp == 'true' }} + if: ${{ github.event_name == 'push' || needs.changes.outputs.mcp == 'true' || needs.changes.outputs.engine == 'true' }} run: npm run test:mcp-pack - name: Build miner CLI if: ${{ github.event_name == 'push' || needs.changes.outputs.miner == 'true' }} diff --git a/package-lock.json b/package-lock.json index 5c30fe29dd..98c7ab69dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15899,6 +15899,7 @@ "version": "0.7.0", "license": "AGPL-3.0-only", "dependencies": { + "@jsonbored/gittensory-engine": "^0.1.0", "@modelcontextprotocol/sdk": "1.29.0", "zod": "^4.4.3" }, diff --git a/packages/gittensory-mcp/lib/local-branch.js b/packages/gittensory-mcp/lib/local-branch.js index 492e8b4dcf..edf3a91225 100644 --- a/packages/gittensory-mcp/lib/local-branch.js +++ b/packages/gittensory-mcp/lib/local-branch.js @@ -2,6 +2,9 @@ import { execFileSync } from "node:child_process"; import { realpathSync } from "node:fs"; import { dirname, isAbsolute, join, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { isCodeFile, isTestPath as isTestFile } from "@jsonbored/gittensory-engine/signals/test-evidence"; + +export { isCodeFile, isTestFile }; const packageRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); @@ -587,36 +590,6 @@ function firstCommitTitle(messages) { return messages.find((message) => message.trim().length > 0)?.split("\n")[0]?.trim(); } -// Must mirror the canonical server-side matcher in src/signals/test-evidence.ts (isTestPath); the -// server local-branch analysis delegates to it, so this client copy classifies the same diff and must -// agree. The last two branches (Cypress/e2e `*.cy.*`/`*.e2e.*` and `__snapshots__/`) were missing here, -// so a changed `Button.cy.ts` / snapshot file was misclassified as source (isCodeFile) and dropped from -// testFiles, inflating source-line/token counts and under-reporting test evidence in the local packet. -export function isTestFile(file) { - return ( - /(^|\/)(test|tests|spec|__tests__)\//i.test(file) || - /(^|\/)src\/test\//i.test(file) || - /(^|\/)[^/]+_test\.(go|py|rb|dart)$/i.test(file) || // Dart/Flutter `foo_test.dart` co-located with source - /(^|\/)test_[^/]*\.py$/i.test(file) || // pytest's default `test_*.py` prefix convention (the suffix rule above only catches `*_test.py`) - /(^|\/)[^/]+_spec\.rb$/i.test(file) || - /\.(test|spec)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs)$/i.test(file) || - /(^|\/)[^/]+\.(cy|e2e)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/i.test(file) || - // JVM / C# / Swift / PHP `SomethingTest(s)`/`SomethingSpec` class-suffix convention (JUnit, Kotlin/ScalaTest, - // Spock, xUnit/NUnit, XCTest, PHPUnit/PHPSpec). Case-sensitive on the PascalCase suffix so it can't false-positive on words - // that merely end in "test"/"spec" (Latest.java, Contest.cs, manifest.scala, Latest.php). - /(^|\/)\w*(Tests?|Spec)\.(java|kt|kts|scala|cs|swift|groovy|php)$/.test(file) || - /(^|\/)__snapshots__\//i.test(file) - ); -} - -function isGeneratedCodeFile(file) { - return /\.(g|freezed|gr)\.dart$/i.test(file); -} - -export function isCodeFile(file) { - return /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql|cs|swift|groovy|php|cpp|cc|c|h|hpp|m|vue|svelte|astro|dart)$/i.test(file) && !isTestFile(file) && !isGeneratedCodeFile(file); -} - function numberValue(value) { const number = Number(value); return Number.isFinite(number) ? number : undefined; diff --git a/packages/gittensory-mcp/package.json b/packages/gittensory-mcp/package.json index 69d6ffc25a..20ffe8a77f 100644 --- a/packages/gittensory-mcp/package.json +++ b/packages/gittensory-mcp/package.json @@ -38,6 +38,7 @@ "build": "node --check bin/gittensory-mcp.js && node --check lib/local-branch.js && node --check scripts/gittensor-score-preview.mjs" }, "dependencies": { + "@jsonbored/gittensory-engine": "^0.1.0", "@modelcontextprotocol/sdk": "1.29.0", "zod": "^4.4.3" }, diff --git a/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs b/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs index ed2c55a7bd..7e936b51a6 100644 --- a/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs +++ b/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs @@ -1,30 +1,7 @@ #!/usr/bin/env node import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; - -function isTestFile(file) { - return ( - /(^|\/)(test|tests|spec|__tests__)\//i.test(file) || - /(^|\/)src\/test\//i.test(file) || - /(^|\/)[^/]+_test\.(go|py|rb|dart)$/i.test(file) || // Dart/Flutter `foo_test.dart` co-located with source - /(^|\/)test_[^/]*\.py$/i.test(file) || // pytest's default `test_*.py` prefix (the suffix rule above only catches `*_test.py`) - /(^|\/)[^/]+_spec\.rb$/i.test(file) || - /\.(test|spec)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs)$/i.test(file) || - /(^|\/)[^/]+\.(cy|e2e)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/i.test(file) || - // JVM/.NET/Swift/PHP PascalCase test-class suffix (case-sensitive, matching the - // signal classifiers) so C#/Swift/Groovy/PHP tests aren't counted as source. - /(^|\/)\w*(Tests?|Spec)\.(java|kt|kts|scala|cs|swift|groovy|php)$/.test(file) || - /(^|\/)__snapshots__\//i.test(file) - ); -} - -function isGeneratedCodeFile(file) { - return /\.(g|freezed|gr)\.dart$/i.test(file); -} - -function isCodeFile(file) { - return /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql|cs|swift|groovy|php|cpp|cc|c|h|hpp|m|vue|svelte|astro|dart)$/i.test(file) && !isTestFile(file) && !isGeneratedCodeFile(file); -} +import { isCodeFile, isTestPath as isTestFile } from "@jsonbored/gittensory-engine/signals/test-evidence"; function lineCount(file) { const additions = Number(file.additions ?? 0);