Skip to content
Closed
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,9 @@ jobs:
- name: Miner package check
if: ${{ github.event_name == 'push' || needs.changes.outputs.miner == 'true' }}
run: npm run test:miner-pack
- name: Miner deployment docs audit
if: ${{ github.event_name == 'push' || needs.changes.outputs.miner == 'true' }}
run: npm run test:miner-deployment-docs-audit
# review-enrichment is not an npm workspace member (its own package-lock.json), so it needs its own
# cache entry -- same restore/save-after-success pattern and fork/trusted key split as the root
# install above, for the same reasons.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"build:miner": "npm --workspace @loopover/engine run build && npm --workspace @loopover/miner run build",
"test:mcp-pack": "node scripts/check-mcp-package.mjs",
"test:miner-pack": "node scripts/check-miner-package.mjs",
"test:miner-deployment-docs-audit": "node packages/loopover-miner/scripts/check-deployment-docs-audit.mjs",
"rees:install": "npm ci --prefix review-enrichment --prefer-offline --no-audit --no-fund",
"rees:test": "npm run rees:install && npm --prefix review-enrichment test",
"rees:metadata": "npm --prefix review-enrichment run metadata",
Expand Down
72 changes: 72 additions & 0 deletions packages/loopover-miner/scripts/check-deployment-docs-audit.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env node
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { join, resolve } from "node:path";
import {
assertDeploymentDocsInSync,
extractEnvVarClaims,
extractFilePathClaims,
extractSubcommandClaims,
scanEnvVarTokens,
scanRegisteredCommands,
} from "../lib/deployment-docs-audit.js";

const REPO_ROOT = resolve(import.meta.dirname, "../../..");
const MINER_DIR = resolve(REPO_ROOT, "packages/loopover-miner");
const DEPLOYMENT_MD = resolve(MINER_DIR, "DEPLOYMENT.md");
const BIN_DIR = resolve(MINER_DIR, "bin");
const BIN_ENTRY = resolve(BIN_DIR, "loopover-miner.js");
const LIB_DIR = resolve(MINER_DIR, "lib");
const ENGINE_MINER_DIR = resolve(REPO_ROOT, "packages/loopover-engine/src/miner");

function readFilesWithExtension(dir, extension) {
return readdirSync(dir)
.filter((name) => name.endsWith(extension))
.map((name) => readFileSync(join(dir, name), "utf8"));
}

function buildLiveReality() {
const envReads = scanEnvVarTokens(
[
...readFilesWithExtension(LIB_DIR, ".js"),
...readFilesWithExtension(BIN_DIR, ".js"),
...readFilesWithExtension(ENGINE_MINER_DIR, ".ts"),
].join("\n"),
);
const registered = scanRegisteredCommands(readFileSync(BIN_ENTRY, "utf8"));
return {
hasEnvRead: (name) => envReads.has(name),
pathExists: (relativePath) => existsSync(resolve(MINER_DIR, relativePath)),
isRegisteredCommand: (name) => registered.has(name),
};
}

function applyTestMode(reality) {
const mode = process.env.CHECK_MINER_DEPLOYMENT_DOCS_AUDIT_TEST_MODE;
if (!mode) return reality;
if (mode === "missing-env") return { ...reality, hasEnvRead: () => false };
if (mode === "missing-path") return { ...reality, pathExists: () => false };
if (mode === "missing-command") return { ...reality, isRegisteredCommand: () => false };
return reality;
}

export function runDeploymentDocsAuditCheck() {
const markdown = readFileSync(DEPLOYMENT_MD, "utf8");
const claims = {
envVars: extractEnvVarClaims(markdown),
filePaths: extractFilePathClaims(markdown),
subcommands: extractSubcommandClaims(markdown),
};
const result = assertDeploymentDocsInSync(claims, applyTestMode(buildLiveReality()));
return `Miner deployment docs audit ok: ${claims.envVars.length} env vars, ${claims.filePaths.length} paths, ${claims.subcommands.length} subcommands checked.\n`;
}

function main() {
try {
process.stdout.write(runDeploymentDocsAuditCheck());
} catch (error) {
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
process.exit(1);
}
}

if (import.meta.url === `file://${process.argv[1]}`) main();
30 changes: 30 additions & 0 deletions test/unit/miner-deployment-docs-audit-script.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { execFileSync } from "node:child_process";
import { describe, expect, it } from "vitest";

function runAudit(env: Record<string, string | undefined> = {}): { status: number; out: string } {
try {
const stdout = execFileSync(process.execPath, ["packages/loopover-miner/scripts/check-deployment-docs-audit.mjs"], {
encoding: "utf8",
env: { ...process.env, ...env },
});
return { status: 0, out: stdout };
} catch (error) {
const failure = error as { status?: number; stdout?: string; stderr?: string };
return { status: failure.status ?? 1, out: `${failure.stdout ?? ""}${failure.stderr ?? ""}` };
}
}

describe("check-deployment-docs-audit script (#6158)", () => {
it("passes on the real miner deployment docs and live source tree", () => {
const result = runAudit();
expect(result.status).toBe(0);
expect(result.out).toMatch(/^Miner deployment docs audit ok:/);
});

it("fails with a drift-style message when env-var backing reads are forced missing", () => {
const result = runAudit({ CHECK_MINER_DEPLOYMENT_DOCS_AUDIT_TEST_MODE: "missing-env" });
expect(result.status).toBe(1);
expect(result.out).toContain("DEPLOYMENT.md is out of sync");
expect(result.out).toContain("is documented in DEPLOYMENT.md but no read of it exists");
});
});
Loading