fix(skills/understand): bundle build-fingerprints.mjs and reorder Phase 7

The Phase 7 step 2.5 code example in SKILL.md called
buildFingerprintStore() with 2 arguments, but the real signature
requires 4 (projectDir, filePaths, registry: PluginRegistry,
gitCommitHash: string). It also omitted the required
`await TreeSitterPlugin.init()`. Any LLM following the example
threw TypeError on `registry.analyzeFile()` and never produced a
baseline — which is why fingerprints.json never existed in a usable
form after a fresh /understand, and is the root cause behind
issue #152's "every auto-update escalates to FULL_UPDATE" cascade.

Replace the LLM-written script with a bundled `build-fingerprints.mjs`
that mirrors `extract-structure.mjs`: resolves @understand-anything/core
via createRequire, initializes TreeSitterPlugin + PluginRegistry
correctly, calls buildFingerprintStore with all four arguments, and
persists via saveFingerprints. Smoke-tested on this repo (3 files,
correct functions/classes/imports extracted).

Reorder Phase 7 so fingerprints are written BEFORE meta.json. If
fingerprint generation fails, the new step explicitly says to abort
Phase 7 — meta.json must not advance without a valid baseline, or
the next auto-update sees a fresh commit hash with no fingerprints
and classifies every file as STRUCTURAL.

Affects every install since 2.7.0 (when the broken example was
introduced). Users running /understand --full on 2.7.3+ will get
a usable fingerprints.json on the first try.
This commit is contained in:
Lum1104
2026-05-18 10:13:02 +08:00
Unverified
parent 97fa2f3cab
commit e7af9ae35e
2 changed files with 117 additions and 16 deletions
@@ -682,7 +682,30 @@ Pass these parameters in the dispatch prompt:
1. Write the final knowledge graph to `$PROJECT_ROOT/.understand-anything/knowledge-graph.json`. 1. Write the final knowledge graph to `$PROJECT_ROOT/.understand-anything/knowledge-graph.json`.
2. Write metadata to `$PROJECT_ROOT/.understand-anything/meta.json`: 2. **Generate structural fingerprints baseline.** This creates the basis for future automatic incremental updates and **must succeed before `meta.json` is written** — otherwise auto-update sees a fresh commit hash with no fingerprints to compare against, classifies every file as STRUCTURAL, and escalates to `FULL_UPDATE` on every subsequent commit (issue #152).
Write the input file:
```bash
cat > $PROJECT_ROOT/.understand-anything/intermediate/fingerprint-input.json <<EOF
{
"projectRoot": "$PROJECT_ROOT",
"sourceFilePaths": [<all source file paths from Phase 1, as JSON array>],
"gitCommitHash": "<current commit hash>"
}
EOF
```
Then invoke the bundled script (located next to this SKILL.md):
```bash
node <SKILL_DIR>/build-fingerprints.mjs \
$PROJECT_ROOT/.understand-anything/intermediate/fingerprint-input.json
```
The script uses `TreeSitterPlugin + PluginRegistry` exactly like `extract-structure.mjs`, so the baseline matches the comparison logic used during auto-updates.
**If the script exits non-zero or stdout does not include `Fingerprints baseline:`, abort Phase 7 and report the error. Do NOT proceed to step 3 (writing `meta.json`).**
3. Write metadata to `$PROJECT_ROOT/.understand-anything/meta.json` (only after step 2 succeeded):
```json ```json
{ {
"lastAnalyzedAt": "<ISO 8601 timestamp>", "lastAnalyzedAt": "<ISO 8601 timestamp>",
@@ -692,25 +715,13 @@ Pass these parameters in the dispatch prompt:
} }
``` ```
2.5. **Generate structural fingerprints** for all analyzed files and save to `$PROJECT_ROOT/.understand-anything/fingerprints.json`. This creates the baseline for future automatic incremental updates. 4. Clean up intermediate files:
Write and execute a Node.js script that uses the core fingerprint module (tree-sitter-based, not regex):
```javascript
import { buildFingerprintStore } from '@understand-anything/core';
import { saveFingerprints } from '@understand-anything/core';
const store = await buildFingerprintStore('<PROJECT_ROOT>', sourceFilePaths);
saveFingerprints('<PROJECT_ROOT>', store);
```
Where `sourceFilePaths` is the list of all analyzed source file paths from Phase 1. This uses the same tree-sitter analysis pipeline as the main fingerprint engine, ensuring the baseline matches the comparison logic used during auto-updates.
3. Clean up intermediate files:
```bash ```bash
rm -rf $PROJECT_ROOT/.understand-anything/intermediate rm -rf $PROJECT_ROOT/.understand-anything/intermediate
rm -rf $PROJECT_ROOT/.understand-anything/tmp rm -rf $PROJECT_ROOT/.understand-anything/tmp
``` ```
4. Report a summary to the user containing: 5. Report a summary to the user containing:
- Project name and description - Project name and description
- Files analyzed / total files (with breakdown by fileCategory: code, config, docs, infra, data, script, markup) - Files analyzed / total files (with breakdown by fileCategory: code, config, docs, infra, data, script, markup)
- Nodes created (broken down by type: file, function, class, config, document, service, table, endpoint, pipeline, schema, resource) - Nodes created (broken down by type: file, function, class, config, document, service, table, endpoint, pipeline, schema, resource)
@@ -720,7 +731,7 @@ Pass these parameters in the dispatch prompt:
- Any warnings from the reviewer - Any warnings from the reviewer
- Path to the output file: `$PROJECT_ROOT/.understand-anything/knowledge-graph.json` - Path to the output file: `$PROJECT_ROOT/.understand-anything/knowledge-graph.json`
5. Only automatically launch the dashboard by invoking the `/understand-dashboard` skill if final graph validation passed after normalization/review fixes. 6. Only automatically launch the dashboard by invoking the `/understand-dashboard` skill if final graph validation passed after normalization/review fixes.
If final validation did not pass, report that the graph was saved with warnings and dashboard launch was skipped. If final validation did not pass, report that the graph was saved with warnings and dashboard launch was skipped.
--- ---
@@ -0,0 +1,90 @@
#!/usr/bin/env node
/**
* build-fingerprints.mjs
*
* Builds the structural-fingerprint baseline used by auto-update's
* incremental change detection. Runs once per /understand full rebuild
* (Phase 7 step 2.5), generating .understand-anything/fingerprints.json.
*
* Replaces the LLM-written fingerprint script that previously sat in
* SKILL.md as a code example — that example had the wrong signature
* for buildFingerprintStore() and never successfully produced a baseline,
* which silently broke auto-update for every install (see issue #152).
*
* Usage:
* node build-fingerprints.mjs <input.json>
*
* Input JSON:
* { projectRoot: string, sourceFilePaths: string[], gitCommitHash: string }
*
* Writes: <projectRoot>/.understand-anything/fingerprints.json
* Exit code: 0 on success (including 0 files analyzed); non-zero on error.
*/
import { createRequire } from 'node:module';
import { dirname, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { readFileSync } from 'node:fs';
const __dirname = dirname(fileURLToPath(import.meta.url));
// skills/understand/ -> plugin root is two dirs up
const pluginRoot = resolve(__dirname, '../..');
const require = createRequire(resolve(pluginRoot, 'package.json'));
// ---------------------------------------------------------------------------
// Resolve @understand-anything/core (matches extract-structure.mjs).
// pathToFileURL() is required for Windows: dynamic import() of a raw
// "C:\..." path throws ERR_UNSUPPORTED_ESM_URL_SCHEME.
// ---------------------------------------------------------------------------
let core;
try {
core = await import(pathToFileURL(require.resolve('@understand-anything/core')).href);
} catch {
core = await import(pathToFileURL(resolve(pluginRoot, 'packages/core/dist/index.js')).href);
}
const {
TreeSitterPlugin,
PluginRegistry,
builtinLanguageConfigs,
registerAllParsers,
buildFingerprintStore,
saveFingerprints,
} = core;
async function main() {
const [, , inputPath] = process.argv;
if (!inputPath) {
process.stderr.write('Usage: node build-fingerprints.mjs <input.json>\n');
process.exit(1);
}
const { projectRoot, sourceFilePaths, gitCommitHash } = JSON.parse(
readFileSync(inputPath, 'utf-8'),
);
if (!projectRoot || !Array.isArray(sourceFilePaths) || typeof gitCommitHash !== 'string') {
throw new Error(
'Invalid input: requires { projectRoot: string, sourceFilePaths: string[], gitCommitHash: string }',
);
}
// Create tree-sitter plugin with all configs that have WASM grammars,
// mirroring extract-structure.mjs so the baseline matches the comparison
// logic used during auto-updates.
const tsConfigs = builtinLanguageConfigs.filter((c) => c.treeSitter);
const tsPlugin = new TreeSitterPlugin(tsConfigs);
await tsPlugin.init();
const registry = new PluginRegistry();
registry.register(tsPlugin);
registerAllParsers(registry);
const store = buildFingerprintStore(projectRoot, sourceFilePaths, registry, gitCommitHash);
saveFingerprints(projectRoot, store);
const fileCount = Object.keys(store.files).length;
process.stdout.write(`Fingerprints baseline: ${fileCount} files\n`);
}
await main();