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();