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
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
"ui:version-audit": "node scripts/check-ui-mcp-version-copy.mjs",
"ui:version-audit:sync": "node scripts/check-ui-mcp-version-copy.mjs --write",
"docs:drift-check": "node scripts/check-docs-drift.mjs",
"branding-drift:check": "node scripts/check-branding-drift.mjs",
"branding-drift:update": "node scripts/check-branding-drift.mjs --update",
"manifest:drift-check": "tsx scripts/check-manifest-drift.mjs",
"engine-parity:drift-check": "tsx scripts/check-engine-parity.ts",
"ui:deploy": "npm run ui:build && npm run ui:deploy:built",
Expand Down Expand Up @@ -94,7 +96,7 @@
"test:smoke:observability:metrics": "node scripts/smoke-observability-metrics.mjs",
"test:smoke:browser:install": "playwright install chromium",
"test:smoke:browser": "node scripts/smoke-ui-browser.mjs",
"test:ci": "git diff --check && npm run actionlint && npm run db:migrations:check && npm run db:schema-drift:check && npm run selfhost:env-reference:check && npm run miner:env-reference:check && npm run selfhost:validate-observability && npm run cf-typegen:check && npm run build --workspace @loopover/engine && npm run typecheck && npm run test:coverage && npm run test:engine-parity && npm run test:live-gate-parity && npm run test:driver-parity && npm run test --workspace @loopover/engine && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run build:miner && npm run test:miner-pack && npm run test:miner-deployment-docs-audit && npm run rees:test && npm run ui:openapi:check && npm run ui:openapi:settings-parity && npm run ui:version-audit && npm run docs:drift-check && npm run manifest:drift-check && npm run engine-parity:drift-check && npm run command-reference:check && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build",
"test:ci": "git diff --check && npm run actionlint && npm run db:migrations:check && npm run db:schema-drift:check && npm run selfhost:env-reference:check && npm run miner:env-reference:check && npm run selfhost:validate-observability && npm run cf-typegen:check && npm run build --workspace @loopover/engine && npm run typecheck && npm run test:coverage && npm run test:engine-parity && npm run test:live-gate-parity && npm run test:driver-parity && npm run test --workspace @loopover/engine && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run build:miner && npm run test:miner-pack && npm run test:miner-deployment-docs-audit && npm run rees:test && npm run ui:openapi:check && npm run ui:openapi:settings-parity && npm run ui:version-audit && npm run docs:drift-check && npm run branding-drift:check && npm run manifest:drift-check && npm run engine-parity:drift-check && npm run command-reference:check && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build",
"test:release": "npm run test:ci && npm run changelog:check",
"test:release:mcp": "npm run test:ci",
"test:watch": "vitest",
Expand Down
38 changes: 38 additions & 0 deletions scripts/branding-drift-baseline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"packages/loopover-engine/src/signals/engine.ts": 2,
"packages/loopover-mcp/bin/loopover-mcp.js": 5,
"src/api/routes.ts": 3,
"src/db/repositories.ts": 1,
"src/github/app.ts": 11,
"src/github/backfill.ts": 7,
"src/github/commands.ts": 2,
"src/github/comments.ts": 3,
"src/integrations/project-tracker-adapter.ts": 1,
"src/orb/federated-bundle.ts": 1,
"src/queue/processors.ts": 6,
"src/review/check-names.ts": 8,
"src/review/contributor-calibration.ts": 2,
"src/review/enrichment-analyzers-taxonomy.ts": 1,
"src/review/enrichment-wire.ts": 1,
"src/review/ops-wire.ts": 3,
"src/review/outcomes-wire.ts": 2,
"src/review/parity-wire.ts": 9,
"src/review/planner.ts": 1,
"src/review/rag.ts": 3,
"src/review/repo-doc-render.ts": 2,
"src/review/repo-skill-render.ts": 2,
"src/review/selftune-wire.ts": 1,
"src/selfhost/ai.ts": 6,
"src/selfhost/health.ts": 3,
"src/selfhost/monitored-work.ts": 1,
"src/selfhost/orb-collector.ts": 1,
"src/selfhost/sentry.ts": 2,
"src/services/ai-review.ts": 4,
"src/services/ai-slop.ts": 1,
"src/services/ai-summaries.ts": 1,
"src/services/contributor-issue-draft.ts": 2,
"src/services/merge-failure.ts": 1,
"src/services/notify-pagerduty.ts": 2,
"src/upstream/ruleset.ts": 3,
"src/utils/crypto.ts": 1
}
9 changes: 9 additions & 0 deletions scripts/check-branding-drift.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const BASELINE_RELATIVE_PATH: string;
export const BRANDING_DRIFT_PATHSPECS: string[];

export function scanBrandingHits(options: {
root: string;
exec?: (root: string, args: string[]) => string;
}): Record<string, number>;

export function diffBrandingBaseline(baseline: Record<string, number>, current: Record<string, number>): string[];
123 changes: 123 additions & 0 deletions scripts/check-branding-drift.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env node
// Guards against the "gittensory" branding silently creeping back into runtime source after the LoopOver
// rebrand -- distinct from docs/prose residue (already owned by the ongoing rebrand-sweep PR series), this
// targets the class of bug that actually broke things: a hardcoded metric name, MCP resource URI, or Qdrant
// collection default left on the pre-rebrand string. That exact pattern caused live drift more than once
// (e.g. #6786 -- ENRICHMENT_ANALYZERS_URI silently left as "gittensory://enrichment-analyzers" while its
// sibling FINDING_TAXONOMY_URI was correctly renamed in the same PR). Scoped to executable code in `src/**`
// and each workspace package's `bin/`, `lib/`, `src/`, `scripts/` dirs -- NOT `test/**` or `*.md`, where a
// literal "gittensory" is usually an intentional, permanent historical reference (a Sentry ticket ID like
// GITTENSORY-K/8, a stable comment-marker the bot must keep matching in already-posted PR bodies, a DB
// `source` column value joined against historical rows) rather than drift; those files churn constantly and
// would make this check pure noise if included.
//
// Baseline-diff, not a hard "zero gittensory" ban: scripts/branding-drift-baseline.json snapshots today's
// known-legitimate per-file hit count (grandfathered, same shape as KNOWN_MIGRATION_DUPLICATES in
// src/db/migration-collisions.ts). A file's count rising means new drift; falling means a cleanup landed
// without updating the baseline. Either way the fix is the same: run `npm run branding-drift:update` and
// commit the regenerated baseline -- mirrors this repo's existing generated-artifact convention (openapi.json,
// cf-typegen, migrations) rather than inventing a new one.
import { execFileSync } from "node:child_process";
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url";

export const BASELINE_RELATIVE_PATH = "scripts/branding-drift-baseline.json";

// git pathspecs: executable code only. Each workspace package's bin/lib/src/scripts dirs mirror the
// top-level src/** scope; docs/README/CHANGELOG/schema/terraform/css and every test dir are deliberately
// excluded (see header comment).
export const BRANDING_DRIFT_PATHSPECS = [
"src/**/*.ts",
"src/**/*.tsx",
"packages/*/bin/**",
"packages/*/lib/**/*.js",
"packages/*/lib/**/*.ts",
"packages/*/src/**/*.ts",
"packages/*/scripts/**/*.mjs",
":(exclude)**/*.test.ts",
":(exclude)**/*.test.tsx",
":(exclude)packages/*/test/**",
];

function defaultExec(root, args) {
try {
return execFileSync("git", args, { cwd: root, encoding: "utf8" });
} catch (error) {
// git grep exits 1 for "zero matches" -- not a real failure, just an empty result.
if (error.status === 1) return "";
throw error;
}
}

/** Every tracked, non-excluded file with >=1 case-insensitive "gittensory" MATCHING LINE, and that line
* count (line-granularity, not raw occurrence count -- sufficient to detect "something new appeared" without
* the fragility of an exact substring-occurrence count). Uses `git grep`, so it only ever sees tracked files
* exactly as CI would check them out -- no node_modules/dist/build noise to exclude by hand. */
export function scanBrandingHits({ root, exec = defaultExec }) {
const output = exec(root, ["grep", "-ciI", "gittensory", "--", ...BRANDING_DRIFT_PATHSPECS]);
const counts = {};
for (const line of output.split("\n")) {
if (!line) continue;
const separatorIndex = line.lastIndexOf(":");
const file = line.slice(0, separatorIndex);
counts[file] = Number(line.slice(separatorIndex + 1));
}
return counts;
}

/** Pure comparison: every failure is one of "increased" (new drift -- or a file that didn't exist in the
* baseline at all, same failure shape) or "decreased" (a cleanup landed; still a failure so the baseline
* never silently drifts stale, but a one-line fix -- regenerate). Sorted for stable, reviewable CI output. */
export function diffBrandingBaseline(baseline, current) {
const failures = [];
const files = new Set([...Object.keys(baseline), ...Object.keys(current)]);
for (const file of [...files].sort()) {
const before = baseline[file] ?? 0;
const after = current[file] ?? 0;
if (after > before) {
failures.push(
`${file}: "gittensory" mentions increased from ${before} to ${after} -- looks like new branding drift, not an intentional historical reference. If it genuinely belongs (e.g. a permanent Sentry ticket ID or a stable comment-marker already posted to live PRs), run \`npm run branding-drift:update\` and commit the regenerated baseline.`,
);
} else if (after < before) {
failures.push(
`${file}: "gittensory" mentions decreased from ${before} to ${after} -- looks like a cleanup landed without regenerating the baseline. Run \`npm run branding-drift:update\` and commit the result.`,
);
}
}
return failures;
}

function readBaseline(root) {
return JSON.parse(readFileSync(join(root, BASELINE_RELATIVE_PATH), "utf8"));
}

function writeBaseline(root, counts) {
const sorted = Object.fromEntries(Object.entries(counts).sort(([a], [b]) => a.localeCompare(b)));
writeFileSync(join(root, BASELINE_RELATIVE_PATH), `${JSON.stringify(sorted, null, 2)}\n`);
}

function main() {
const root = process.cwd();
const update = process.argv.includes("--update");
const current = scanBrandingHits({ root });

if (update) {
writeBaseline(root, current);
console.log(`Branding-drift baseline regenerated: ${Object.keys(current).length} file(s) with a "gittensory" reference.`);
return;
}

const baseline = readBaseline(root);
const failures = diffBrandingBaseline(baseline, current);

if (failures.length > 0) {
console.error(`Branding-drift check found ${failures.length} issue(s):`);
for (const failure of failures) console.error(failure);
process.exit(1);
}

console.log(`Branding-drift check ok: ${Object.keys(current).length} file(s) match the recorded baseline.`);
}

if (process.argv[1] === fileURLToPath(import.meta.url)) main();
108 changes: 108 additions & 0 deletions test/unit/check-branding-drift-script.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { execFileSync } from "node:child_process";
import { describe, expect, it } from "vitest";
import { diffBrandingBaseline, scanBrandingHits } from "../../scripts/check-branding-drift.mjs";

describe("scanBrandingHits", () => {
it("parses git grep -c output into a { file: count } map", () => {
const exec = () => "src/a.ts:2\nsrc/b.ts:1\n";
const result = scanBrandingHits({ root: "/fake", exec });

expect(result).toEqual({ "src/a.ts": 2, "src/b.ts": 1 });
});

it("returns an empty map when there is no output (git grep found nothing)", () => {
const exec = () => "";
const result = scanBrandingHits({ root: "/fake", exec });

expect(result).toEqual({});
});

it("uses the LAST colon as the file/count separator, so a path containing a colon still parses", () => {
const exec = () => "src/weird:name.ts:3\n";
const result = scanBrandingHits({ root: "/fake", exec });

expect(result).toEqual({ "src/weird:name.ts": 3 });
});

it("passes the branding-drift pathspecs through to the injected exec", () => {
let capturedArgs: string[] = [];
const exec = (_root: string, args: string[]) => {
capturedArgs = args;
return "";
};
scanBrandingHits({ root: "/fake", exec });

expect(capturedArgs[0]).toBe("grep");
expect(capturedArgs).toContain("src/**/*.ts");
expect(capturedArgs).toContain(":(exclude)**/*.test.ts");
});

// Real regression guard, mirroring check-manifest-drift-script.test.ts's own real-repo-state test: proves
// the actual defaultExec (real `git grep` subprocess, real exit-1-means-empty handling) works against this
// repo's real tracked files, not just the injected fake above.
it("runs the real git grep against this repo without throwing", () => {
const result = scanBrandingHits({ root: process.cwd() });

expect(typeof result).toBe("object");
for (const count of Object.values(result)) {
expect(count).toBeGreaterThan(0);
}
});
});

describe("diffBrandingBaseline", () => {
it("reports no failures when baseline and current match exactly", () => {
const failures = diffBrandingBaseline({ "src/a.ts": 2 }, { "src/a.ts": 2 });

expect(failures).toEqual([]);
});

it("flags a file whose count increased (new drift)", () => {
const failures = diffBrandingBaseline({ "src/a.ts": 1 }, { "src/a.ts": 2 });

expect(failures).toHaveLength(1);
expect(failures[0]).toContain("src/a.ts");
expect(failures[0]).toContain("increased from 1 to 2");
expect(failures[0]).toContain("branding-drift:update");
});

it("flags a brand-new file not present in the baseline at all (increased from 0)", () => {
const failures = diffBrandingBaseline({}, { "src/new.ts": 1 });

expect(failures).toHaveLength(1);
expect(failures[0]).toContain("increased from 0 to 1");
});

it("flags a file whose count decreased (stale baseline after a cleanup)", () => {
const failures = diffBrandingBaseline({ "src/a.ts": 3 }, { "src/a.ts": 1 });

expect(failures).toHaveLength(1);
expect(failures[0]).toContain("decreased from 3 to 1");
});

it("flags a file removed entirely from current (decreased to 0)", () => {
const failures = diffBrandingBaseline({ "src/gone.ts": 2 }, {});

expect(failures).toHaveLength(1);
expect(failures[0]).toContain("decreased from 2 to 0");
});

it("reports one failure per affected file, sorted, when several files differ", () => {
const failures = diffBrandingBaseline({ "src/b.ts": 1, "src/a.ts": 1 }, { "src/b.ts": 2, "src/a.ts": 2 });

expect(failures).toHaveLength(2);
expect(failures[0]).toContain("src/a.ts");
expect(failures[1]).toContain("src/b.ts");
});
});

describe("check-branding-drift script (real repo state)", () => {
// Most important test in this file: proves the checked-in baseline actually matches the real repo right
// now. If this fails, real drift landed (or a cleanup did) without regenerating the baseline -- either way,
// fix it with `npm run branding-drift:update`, don't weaken this test.
it("the committed baseline matches the real current repo state (regression guard)", () => {
const output = execFileSync("node", ["scripts/check-branding-drift.mjs"], { encoding: "utf8" });

expect(output).toMatch(/Branding-drift check ok: \d+ file\(s\) match the recorded baseline\./);
});
});
Loading