From e7af9ae35e9d94ecba5566b074215e1670930b3b Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Mon, 18 May 2026 10:13:02 +0800 Subject: [PATCH] fix(skills/understand): bundle build-fingerprints.mjs and reorder Phase 7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../skills/understand/SKILL.md | 43 +++++---- .../skills/understand/build-fingerprints.mjs | 90 +++++++++++++++++++ 2 files changed, 117 insertions(+), 16 deletions(-) create mode 100644 understand-anything-plugin/skills/understand/build-fingerprints.mjs diff --git a/understand-anything-plugin/skills/understand/SKILL.md b/understand-anything-plugin/skills/understand/SKILL.md index 5faf83e..9b600aa 100644 --- a/understand-anything-plugin/skills/understand/SKILL.md +++ b/understand-anything-plugin/skills/understand/SKILL.md @@ -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 <], + "gitCommitHash": "" + } + EOF + ``` + + Then invoke the bundled script (located next to this SKILL.md): + ```bash + node /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": "", @@ -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('', sourceFilePaths); - saveFingerprints('', 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. --- diff --git a/understand-anything-plugin/skills/understand/build-fingerprints.mjs b/understand-anything-plugin/skills/understand/build-fingerprints.mjs new file mode 100644 index 0000000..b477379 --- /dev/null +++ b/understand-anything-plugin/skills/understand/build-fingerprints.mjs @@ -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: + * { projectRoot: string, sourceFilePaths: string[], gitCommitHash: string } + * + * Writes: /.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 \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();