Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/workflows/graph-tools.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Graph tools

# `scripts/graph/` is a SCIP code-graph indexer built for the maintainer's own
# agent tooling. It is not part of @ipv9/tokentracker-cli and ships to nobody,
# but its 12 test files were a tenth of the suite and ran in every `ci:local`
# on every PR — distorting the coverage picture while four product parsers have
# no test at all (#104), and charging every contributor for it (#107).
#
# So the product gate no longer runs them. This workflow does, and ONLY when
# the graph code or its tests change.
#
# Deliberately a real, blocking job rather than a `continue-on-error` one: a
# gate that cannot fail is a gate that reports success it did not earn. Path
# filtering makes it absent when irrelevant, not green when broken.
#
# Note this does NOT make the tests run on unrelated PRs, so a change elsewhere
# that breaks the indexer would be caught only on the next graph-touching PR.
# That is the accepted trade for taking it off the product's critical path; the
# tool has no runtime dependency on the product.

on:
pull_request:
branches: [main]
paths:
- "scripts/graph/**"
- "test/graph/**"
- ".github/workflows/graph-tools.yml"

concurrency:
group: graph-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test-graph:
name: test:graph
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6

- uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json

- name: Install root dependencies
run: npm ci

- name: Run the graph-tool tests
run: npm run test:graph
10 changes: 9 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,19 @@ node bin/tracker.js doctor # Health check
## Tests

```bash
npm test # Full suite (node --test over test/*.test.js)
npm test # Product suite (node --test over test/*.test.js)
node --test test/rollout-parser.test.js # A single test file
npm run ci:local # Tests + validations + builds (everything CI runs)
npm run test:graph # Maintainer tooling only — see below
```

`test/graph/` covers `scripts/graph/`, a SCIP code-graph indexer built for the
maintainer's own agent tooling. It is not part of the published package and
ships to nobody, so it is **not** in `npm test` or `ci:local` — its 12 files
were a tenth of the suite, which made the coverage picture look better than it
is while four product parsers still have no test at all. CI runs it in its own
job, triggered only when `scripts/graph/**` or `test/graph/**` changes.

If you're touching the dashboard:

```bash
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"audit:tokens": "node scripts/audit-token-correctness.cjs",
"graph:auto-index": "node scripts/graph/auto-index.cjs",
"graph:scip": "node scripts/graph/generate-scip.cjs",
"test:graph": "node --test test/graph/*.test.js",
"pricing:build-seed": "node scripts/build-pricing-seed.cjs",
"prepublishOnly": "node scripts/build-pricing-seed.cjs",
"test": "node --test test/*.test.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const test = require("node:test");
const assert = require("node:assert");
const path = require("node:path");
const { loadGraphConfig } = require("../scripts/graph/lib/config.cjs");
const { loadGraphConfig } = require("../../scripts/graph/lib/config.cjs");

test("loadGraphConfig collects tsconfig paths and defaults", () => {
const fakeFs = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const test = require("node:test");
const assert = require("node:assert");
const path = require("node:path");
const { discoverDomains } = require("../scripts/graph/lib/domain-discovery.cjs");
const { discoverDomains } = require("../../scripts/graph/lib/domain-discovery.cjs");

test("discoverDomains picks known roots that exist", () => {
const fakeFs = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const path = require("node:path");
const os = require("node:os");
const fs = require("node:fs");
const { execFileSync } = require("node:child_process");
const { importScip } = require("../scripts/graph/lib/importer.cjs");
const { importScip } = require("../../scripts/graph/lib/importer.cjs");

test("importScip writes sqlite with documents, symbols, and occurrences", () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "graph-import-"));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const test = require("node:test");
const assert = require("node:assert");
const path = require("node:path");
const { scanDomainMetrics } = require("../scripts/graph/lib/metrics.cjs");
const { scanDomainMetrics } = require("../../scripts/graph/lib/metrics.cjs");

test("scanDomainMetrics counts files and noise", () => {
const fakeFs = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const test = require("node:test");
const assert = require("node:assert");
const path = require("node:path");
const { runScipForPlan } = require("../scripts/graph/lib/scip-runner.cjs");
const { runScipForPlan } = require("../../scripts/graph/lib/scip-runner.cjs");

test("runScipForPlan invokes scip-typescript per domain", () => {
const calls = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const test = require("node:test");
const assert = require("node:assert");
const path = require("node:path");
const { buildPlan } = require("../scripts/graph/auto-index.cjs");
const { buildPlan } = require("../../scripts/graph/auto-index.cjs");

test("buildPlan returns decision and domains", () => {
const plan = buildPlan({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ test("parseScipFile deserializes scip index", () => {
const index = new scip.Index({ documents: [] });
fs.writeFileSync(scipPath, index.serializeBinary());

const autoIndexPath = path.join(__dirname, "..", "scripts", "graph", "auto-index.cjs");
const autoIndexPath = path.join(__dirname, "..", "..", "scripts", "graph", "auto-index.cjs");
const code = `${fs.readFileSync(autoIndexPath, "utf8")}\nmodule.exports._test = { parseScipFile };`;
const localRequire = createRequire(autoIndexPath);
const sandbox = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const test = require("node:test");
const assert = require("node:assert");
const { writePlan } = require("../scripts/graph/lib/plan-writer.cjs");
const { writePlan } = require("../../scripts/graph/lib/plan-writer.cjs");

test("writePlan writes graph.plan.json", () => {
let written = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const test = require("node:test");
const assert = require("node:assert");
const path = require("node:path");
const { runScipForPlan } = require("../scripts/graph/lib/scip-runner.cjs");
const { runScipForPlan } = require("../../scripts/graph/lib/scip-runner.cjs");

test("runScipForPlan throws when no tsconfig is available", () => {
const fakeFs = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const test = require("node:test");
const assert = require("node:assert");
const { decideSplit } = require("../scripts/graph/lib/split-decision.cjs");
const { decideSplit } = require("../../scripts/graph/lib/split-decision.cjs");

test("decideSplit chooses split when size threshold reached", () => {
const decision = decideSplit({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const assert = require("node:assert/strict");
const test = require("node:test");

const { validateScipCoverage } = require("../scripts/graph/lib/validate.cjs");
const { validateScipCoverage } = require("../../scripts/graph/lib/validate.cjs");

test("validateScipCoverage accepts relative_path fields", () => {
const result = validateScipCoverage({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const test = require("node:test");
const assert = require("node:assert");
const { validateScipCoverage } = require("../scripts/graph/lib/validate.cjs");
const { validateScipCoverage } = require("../../scripts/graph/lib/validate.cjs");

test("validateScipCoverage fails when domain has zero docs", () => {
const deps = {
Expand Down