
Manifest Auditor
- 1 installs
- 1 repo stars
- Updated August 3, 2026
- ar9av/game-exa
manifest-auditor is a static-analysis skill that cross-references animation and texture keys in a generated Phaser Game.js against manifest.json to catch key-mismatch bugs before the browser opens.
About
This skill runs static analysis on codesmith-generated Game.js, extracting every animation key and texture key and checking each against manifest.json. It catches the most common class of silent Phaser failures, mismatched key names, before the dev server starts. A developer runs it after code generation and after refiner patches to surface key mismatches to the refiner for correction.
- Cross-references every animation and texture key in Game.js against manifest.json
- Catches 'Animation not found' and 'Texture not found' bugs in milliseconds without a browser
- Emits a JSON issue report with exit codes and optional --fix corrections
Manifest Auditor by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,750 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
manifest-auditor capabilities & compatibility
Free; local static analysis with no API calls.
- Capabilities
- palette enforcer · static analysis · code review
- Use cases
- testing · code review · debugging
- Pricing
- Free
What manifest-auditor says it does
Static analysis of Game.js that cross-references every animation key and texture key against manifest.json.
Catches the most common class of silent Phaser failures — mismatched key names — in milliseconds without a browser.
npx skills add https://github.com/ar9av/game-exa --skill manifest-auditorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 1 |
| Last updated | August 3, 2026 |
| Repository | ar9av/game-exa ↗ |
What it does
Statically validate a generated Phaser Game.js against its manifest to catch key-mismatch bugs before playtesting.
Who is it for?
Validating a generated Phaser game's key consistency before running it.
Skip if: Non-Phaser projects or runtime/gameplay testing.
When should I use this skill?
After codesmith writes Game.js and before starting the dev server, and after refiner patches.
What you get
A JSON report of key-mismatch errors and warnings with suggested corrections, catchable before playtesting.
- JSON issue report
- manifest-key-mismatch failures for the refiner
By the numbers
- 3 exit codes (0 ok, 5 errors, 3 missing files)
Files
Manifest Auditor — Key Consistency Check
Scans the codesmith-generated Game.js for every string literal used as an animation key or texture key, then checks each against the manifest. Catches the most common class of silent Phaser failures — mismatched key names — in milliseconds without a browser.
When to use
After codesmith writes src/scenes/Game.js, before starting the dev server. Also run after refiner patches to catch regressions.
What it checks
Animation keys
Patterns: .play('KEY'), .play("KEY"), .chain('KEY'), .chain("KEY")
Valid animation keys are derived from manifest.sprites:
for each sheet s:
for each row r in s.rows:
for each col c in s.cols:
valid key = `${r}-${c.toLowerCase()}`Example: KNIGHT-walk, SLIME-idle, ORB-idle.
Texture keys
Patterns: this.add.sprite(x, y, 'KEY'), this.add.image(x, y, 'KEY'), this.physics.add.sprite(x, y, 'KEY'), this.textures.get('KEY'), this.load.image('KEY', ...), this.load.spritesheet('KEY', ...)
Valid texture keys:
- Each
s.textureKeyfrommanifest.sprites(e.g.entities-1) 'tiles'— always present'bg'— present whenmanifest.bgexists
Frame indices
Pattern: setFrame(N) or .frame = N — warns if N is out of range for the sprite sheet cell count (rows.length * cols.length).
Output contract
{
"ok": true,
"errors": 0,
"warnings": 2,
"total": 2,
"issues": [
{
"kind": "unknown-anim-key",
"key": "KNIGHT-run",
"line": 87,
"suggestion": "Did you mean KNIGHT-walk?",
"severity": "error"
},
{
"kind": "unknown-texture-key",
"key": "Player",
"line": 34,
"suggestion": "Texture keys are case-sensitive. Manifest has: entities-1",
"severity": "error"
}
]
}Exit code 0 = all OK, 5 = errors found, 3 = missing files.
Process
1. Run scripts/audit_manifest.mjs <project-dir> after codesmith writes Game.js. 2. If errors: surface to refiner as manifest-key-mismatch failures. Refiner fixes key strings. 3. Re-run. If clean, proceed to playtester.
Feed errors into refiner as:
{ "kind": "manifest-key-mismatch", "message": "KNIGHT-run not in manifest (line 87). Use KNIGHT-walk." }Scripts
scripts/audit_manifest.mjs <project-dir> [--fix]— static analysis.--fixapplies best-guess corrections in-place.
#!/usr/bin/env node
// Static cross-reference of Game.js animation/texture keys against manifest.json.
// Usage: node audit_manifest.mjs <project-dir> [--fix]
import { readFile, writeFile } from 'node:fs/promises';
import { resolve, join } from 'node:path';
const args = process.argv.slice(2);
const projectDir = resolve(args.find((a) => !a.startsWith('--')) ?? '.');
const autoFix = args.includes('--fix');
const manifestPath = join(projectDir, 'public', 'assets', 'manifest.json');
const gamePath = join(projectDir, 'src', 'scenes', 'Game.js');
let manifest, gameSource;
try {
manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
} catch {
console.error('audit_manifest: manifest.json not found');
process.exit(3);
}
try {
gameSource = await readFile(gamePath, 'utf8');
} catch {
console.error('audit_manifest: src/scenes/Game.js not found');
process.exit(3);
}
// Build valid animation key set from manifest
const validAnimKeys = new Set();
for (const sheet of manifest.sprites ?? []) {
for (const row of sheet.rows ?? []) {
for (const col of sheet.cols ?? []) {
validAnimKeys.add(`${row}-${col.toLowerCase()}`);
}
}
}
// Build valid texture key set
const validTexKeys = new Set(['tiles']);
for (const sheet of manifest.sprites ?? []) {
if (sheet.textureKey) validTexKeys.add(sheet.textureKey);
}
if (manifest.bg) validTexKeys.add('bg');
// Extract all string literals with line numbers
function extractStrings(src) {
const results = [];
const lines = src.split('\n');
lines.forEach((line, lineIdx) => {
const re = /(['"])([^'"\\]*(?:\\.[^'"\\]*)*)\1/g;
let m;
while ((m = re.exec(line)) !== null) {
results.push({ value: m[2], line: lineIdx + 1 });
}
});
return results;
}
// Patterns that indicate animation key usage
const animPatterns = [
/\.play\(\s*['"]([^'"]+)['"]/g,
/\.chain\(\s*['"]([^'"]+)['"]/g,
];
// Patterns that indicate texture key usage
const texPatterns = [
/\.add\.sprite\([^)]*,\s*['"]([^'"]+)['"]/g,
/\.add\.image\([^)]*,\s*['"]([^'"]+)['"]/g,
/\.physics\.add\.sprite\([^)]*,\s*['"]([^'"]+)['"]/g,
/\.textures\.get\(\s*['"]([^'"]+)['"]/g,
/createMultiple\([^)]*key:\s*['"]([^'"]+)['"]/g,
];
const issues = [];
function extractKeysWithLines(src, patterns) {
const results = [];
const lines = src.split('\n');
lines.forEach((line, lineIdx) => {
for (const pat of patterns) {
const re = new RegExp(pat.source, pat.flags);
let m;
while ((m = re.exec(line)) !== null) {
results.push({ key: m[1], line: lineIdx + 1 });
}
}
});
return results;
}
const animRefs = extractKeysWithLines(gameSource, animPatterns);
const texRefs = extractKeysWithLines(gameSource, texPatterns);
// Check animation keys
for (const { key, line } of animRefs) {
if (!validAnimKeys.has(key)) {
// Find closest match (edit distance heuristic: same entity prefix)
const entity = key.split('-')[0];
const suggestions = [...validAnimKeys].filter((k) => k.startsWith(entity + '-'));
issues.push({
kind: 'unknown-anim-key',
key,
line,
suggestion: suggestions.length ? `Did you mean: ${suggestions.join(', ')}?` : `Valid keys: ${[...validAnimKeys].join(', ')}`,
severity: 'error',
});
}
}
// Check texture keys — only flag if the key looks like it's supposed to be a texture
// (skip short common words that are args to other APIs)
const skipTexKeys = new Set(['top', 'left', 'right', 'bottom', 'center', 'Game', 'Boot', 'Preload', 'Menu', 'GameOver']);
for (const { key, line } of texRefs) {
if (skipTexKeys.has(key)) continue;
if (!validTexKeys.has(key)) {
const suggestions = [...validTexKeys].filter((k) => k.toLowerCase().includes(key.toLowerCase().split('-')[0]));
issues.push({
kind: 'unknown-texture-key',
key,
line,
suggestion: suggestions.length ? `Manifest has: ${suggestions.join(', ')}` : `Valid texture keys: ${[...validTexKeys].join(', ')}`,
severity: 'error',
});
}
}
// Auto-fix: apply best-guess substitutions in Game.js
if (autoFix && issues.length > 0) {
let fixed = gameSource;
let fixCount = 0;
for (const issue of issues) {
if (issue.kind === 'unknown-anim-key') {
const entity = issue.key.split('-')[0];
const candidates = [...validAnimKeys].filter((k) => k.startsWith(entity + '-'));
if (candidates.length === 1) {
fixed = fixed.replaceAll(`'${issue.key}'`, `'${candidates[0]}'`).replaceAll(`"${issue.key}"`, `"${candidates[0]}"`);
issue.fixed = candidates[0];
fixCount++;
}
}
}
if (fixCount > 0) {
await writeFile(gamePath, fixed);
console.error(`[manifest-auditor] auto-fixed ${fixCount} animation key(s) in Game.js`);
}
}
const errors = issues.filter((i) => i.severity === 'error').length;
const warnings = issues.filter((i) => i.severity === 'warning').length;
console.log(JSON.stringify({ ok: errors === 0, errors, warnings, total: issues.length, issues }, null, 2));
process.exit(errors > 0 ? 5 : 0);
Related skills
FAQ
Does it need a browser?
No, it catches key mismatches in milliseconds without opening a browser.
Can it fix issues automatically?
Yes, running the audit script with --fix applies best-guess corrections in-place.