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`.
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
{
"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.
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:
4. Clean up intermediate files:
```bash
rm -rf $PROJECT_ROOT/.understand-anything/intermediate
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
- 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)
@@ -720,7 +731,7 @@ Pass these parameters in the dispatch prompt:
- Any warnings from the reviewer
- 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.
---
@@ -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();