ktworkflow-mcp/scripts/audit-live2d-variable-names.ts

170 lines
5.6 KiB
JavaScript

#!/usr/bin/env node
import { readFileSync } from "node:fs";
import path from "node:path";
import { resolveInsideRoot } from "../src/core/workspace.js";
import { analyzeLive2DMinJsSource } from "../src/tools/deobfuscation.js";
import {
auditLive2DModuleMigrations,
type Live2DModuleMigrationDecisionCatalog,
type Live2DModuleMigrationDraftManifest,
} from "../src/tools/live2dModuleMigrations.js";
import {
auditLive2DIdentifierNameDecisionCatalog,
assertLive2DVariableDecisionCatalogIsOffline,
auditLive2DVariableNameDecisionCatalog,
type Live2DIdentifierNameDecisionCatalog,
type Live2DVariableNameDecisionCatalog,
type Live2DVariableNameSummary,
} from "../src/tools/live2dVariableNames.js";
interface CliOptions {
identifierDecisionPath: string;
implementationRoot: string;
manifestPath: string;
moduleDecisionPath: string;
productionRoot: string;
sourcePath: string;
variableDecisionPath: string;
}
const EXPECTED_SUMMARY: Live2DVariableNameSummary = {
directImplementationBindings: {
classifiedSynthetic: 67,
coveredFromSource: 1325,
locals: 810,
parameters: 582,
total: 1392,
},
functions: 567,
helperSyntheticBindings: 5,
ignoredLeadingImplementationThis: 116,
nonDirectSourceMappings: 413,
reviewedBindingSets: 76,
sourceBindings: { locals: 1169, parameters: 578, total: 1747 },
sourceDispositions: {
inlined: 233,
merged: 110,
"omitted-unreachable": 1,
"removed-source-defect": 5,
renamed: 1398,
},
syntheticDispositions: {
"defensive-extension": 3,
"semantic-split": 53,
"synthetic-helper": 16,
},
};
/** Parses only the workspace-bounded paths consumed by the current-state audit. */
function parseOptions(argv: string[]): CliOptions {
const args = new Map<string, string>();
for (let index = 0; index < argv.length; index += 2) {
const key = argv[index];
const value = argv[index + 1];
if (!key?.startsWith("--") || value === undefined) {
throw new Error(`Invalid Live2D variable audit argument near ${key ?? "<end>"}`);
}
args.set(key.slice(2), value);
}
return {
identifierDecisionPath: resolveInsideRoot(
args.get("identifier-decisions") ??
"docs/live2d-deobfuscation/identifier-name-decisions.json",
),
implementationRoot: resolveInsideRoot(
args.get("implementation-root") ??
"Vue/kt-blog-web/src/components/blog/live2d/vendor/cubism2Core",
),
manifestPath: resolveInsideRoot(
args.get("manifest") ?? "docs/live2d-deobfuscation/module-splits.json",
),
moduleDecisionPath: resolveInsideRoot(
args.get("module-decisions") ??
"docs/live2d-deobfuscation/module-migration-decisions.json",
),
productionRoot: resolveInsideRoot(
args.get("production-root") ?? "Vue/kt-blog-web/src/components/blog/live2d",
),
sourcePath: resolveInsideRoot(
args.get("source") ?? "Vue/kt-blog-web/public/live2d/wordpress-moc/live2d.min.js",
),
variableDecisionPath: resolveInsideRoot(
args.get("variable-decisions") ??
"docs/live2d-deobfuscation/variable-name-decisions.json",
),
};
}
/** Reads one typed JSON catalog and leaves semantic validation to its audit function. */
function readJson<T>(filePath: string): T {
return JSON.parse(readFileSync(filePath, "utf8")) as T;
}
/** Runs function-level migration proof before variable-level coverage and offline-only gates. */
function main(): void {
const options = parseOptions(process.argv.slice(2));
const source = readFileSync(options.sourcePath, "utf8");
const sourceAudit = analyzeLive2DMinJsSource(source, {
sourcePath: path
.relative(resolveInsideRoot("."), options.sourcePath)
.replaceAll("\\", "/"),
});
const moduleCatalog = readJson<Live2DModuleMigrationDecisionCatalog>(
options.moduleDecisionPath,
);
const migration = auditLive2DModuleMigrations({
decisions: moduleCatalog,
implementationRoot: options.implementationRoot,
moduleManifest: readJson<Live2DModuleMigrationDraftManifest>(options.manifestPath),
source,
sourceFunctions: sourceAudit.functions,
workspaceRoot: resolveInsideRoot("."),
});
if (
migration.summary.migrated !== 567 ||
migration.summary.omittedUnreachable !== 60 ||
migration.summary.pending !== 0 ||
migration.summary.total !== 627
) {
throw new Error(`Live2D module migration prerequisite is incomplete: ${JSON.stringify(migration.summary)}`);
}
const variable = auditLive2DVariableNameDecisionCatalog({
catalog: readJson<Live2DVariableNameDecisionCatalog>(options.variableDecisionPath),
implementationRoot: options.implementationRoot,
moduleCatalog,
});
if (JSON.stringify(variable.summary) !== JSON.stringify(EXPECTED_SUMMARY)) {
throw new Error(`Live2D variable census drifted: ${JSON.stringify(variable.summary)}`);
}
const identifiers = auditLive2DIdentifierNameDecisionCatalog(
readJson<Live2DIdentifierNameDecisionCatalog>(options.identifierDecisionPath),
options.productionRoot,
);
const expectedIdentifierSummary = {
"keep-semantic": 1,
remove: 10,
rename: 42,
total: 53,
unresolved: 0,
};
if (JSON.stringify(identifiers.summary) !== JSON.stringify(expectedIdentifierSummary)) {
throw new Error(
`Live2D identifier census drifted: ${JSON.stringify(identifiers.summary)}`,
);
}
assertLive2DVariableDecisionCatalogIsOffline(options.productionRoot);
process.stdout.write(
`${JSON.stringify({
identifiers,
migration: migration.summary,
offlineOnly: true,
ownerHelperTargets: variable.ownerHelperTargets,
sourceSha256: variable.sourceSha256,
variable: variable.summary,
})}\n`,
);
}
main();