From d2d4d231567084ee3838f88e0a22f8d5bd13465b Mon Sep 17 00:00:00 2001 From: summer <44016063+xiazl1993@users.noreply.github.com> Date: Sun, 31 May 2026 23:14:38 +0800 Subject: [PATCH 01/12] chore: clean up duplicate gitignore patterns --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8087a9e..004f0a9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,8 @@ __pycache__/ .worktrees/ homepage/public/demo/ .private/ -__pycache__/ +.venv/ +venv/ *.pyc +*.pyo +Thumbs.db From eea73b656d168ec91754d9436e60f285c36f2e85 Mon Sep 17 00:00:00 2001 From: Tirth Kanani Date: Sun, 31 May 2026 20:25:39 +0100 Subject: [PATCH 02/12] perf(understand): parallelise file I/O in compute-batches + extract-import-map (#76) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /understand pipeline reads every code file twice during analysis: once in compute-batches (`extractExports` for the cross-batch neighbour map) and once again in extract-import-map (per-language config loaders). Both sites used sequential `readFileSync` loops, so on the iOS repo in issue #226 (~15k files) the disk-read time was effectively serialised behind a single libuv thread while the rest of the pool sat idle. ## Changes - `extractExports` now batches files into `IO_PARALLELISM = 64` slices and issues all `readFile` calls in each slice through `Promise.all`, letting libuv's worker-thread pool overlap disk reads. The tree-sitter parse stays on the main thread because `web-tree-sitter` is single-threaded WASM — pipelining the I/O while parses run is where the wall-time savings come from. - `loadTsConfigs`, `loadGoModules`, `loadPhpAutoloads` and `buildResolutionContext` switch to async / `Promise.all` for the same reason. `buildResolutionContext` also runs the three loader passes concurrently (`Promise.all([...])`) since they're independent. - A small `readFilesParallel(paths)` helper is added at the top of `extract-import-map.mjs` so the three loaders share the same error-preserving shape. ## Why behavior stays identical - Each loader collects its candidate paths in `files[]` order *before* issuing reads, then iterates `reads` in the same order to emit warnings + populate output maps. So stderr order and the final map contents are byte-identical to the previous sequential loops. - `extractExports` collects per-file errors in-place in the `Promise.all` callbacks and emits warnings during the post-read serial loop, again in chunk order — so warning text and order match the previous implementation. - Tree-sitter parsing is unchanged: parses still run serially on the main thread, just with reads pipelined alongside. ## What's NOT in this PR - `buildFingerprintStore` and `analyzeChanges` in `core/fingerprint.ts` have the same sequential pattern. They're left alone here because they're part of the public `@understand-anything/core` API; making them async would be a breaking change worth its own discussion. Internal-only `.mjs` scripts are safe to refactor without API churn. - No change to scan-project: most of its sync I/O is `statSync` (metadata, not content) plus a handful of small `.gitignore` / `.understandignore` reads. The parallelism win is marginal there. ## Verification - `pnpm lint` clean - `pnpm --filter @understand-anything/core build` clean - `pnpm --filter @understand-anything/skill build` clean - `pnpm test`: 196/196 — including `test_compute_batches.test.mjs` (19 tests) and `test_extract_import_map.test.mjs` (40 tests), which exercise both changed pipelines end-to-end with fixture projects. No output diff vs main. Refs #76 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../skills/understand/compute-batches.mjs | 83 ++++++++++++----- .../skills/understand/extract-import-map.mjs | 93 +++++++++++++------ 2 files changed, 123 insertions(+), 53 deletions(-) diff --git a/understand-anything-plugin/skills/understand/compute-batches.mjs b/understand-anything-plugin/skills/understand/compute-batches.mjs index b7cce34..f78d46a 100644 --- a/understand-anything-plugin/skills/understand/compute-batches.mjs +++ b/understand-anything-plugin/skills/understand/compute-batches.mjs @@ -13,10 +13,19 @@ */ import { readFileSync, writeFileSync, existsSync, realpathSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { createRequire } from 'node:module'; +/** + * Chunk size for parallel file I/O. Bounded so a 15k-file repo doesn't try + * to open every descriptor at once (would hit `EMFILE`) while still keeping + * libuv's worker-thread pool saturated. Empirically chosen to keep memory + * around tens of MB even when the average file is ~10 KB. + */ +const IO_PARALLELISM = 64; + const __filename = fileURLToPath(import.meta.url); const PLUGIN_ROOT = resolve(dirname(__filename), '../..'); const require = createRequire(resolve(PLUGIN_ROOT, 'package.json')); @@ -57,31 +66,55 @@ async function extractExports(projectRoot, codeFiles) { } const exportsByPath = new Map(); - for (const file of codeFiles) { - const abs = join(projectRoot, file.path); - let content; - try { - content = readFileSync(abs, 'utf-8'); - } catch (err) { - process.stderr.write( - `Warning: compute-batches: exports extraction failed for ${file.path} ` + - `(read error: ${err.message}) — symbols=[] in neighborMap — ` + - `cross-batch edges to this file limited to file-level\n`, - ); - exportsByPath.set(file.path, []); - continue; - } - try { - const analysis = registry.analyzeFile(file.path, content); - const names = (analysis?.exports || []).map(e => e.name).filter(Boolean); - exportsByPath.set(file.path, names); - } catch (err) { - process.stderr.write( - `Warning: compute-batches: exports extraction failed for ${file.path} ` + - `(analyze error: ${err.message}) — symbols=[] in neighborMap — ` + - `cross-batch edges to this file limited to file-level\n`, - ); - exportsByPath.set(file.path, []); + + // I/O is parallelised in bounded chunks (libuv worker threads handle the + // disk reads concurrently) while the actual tree-sitter parse stays on + // the main thread, since web-tree-sitter is single-threaded WASM. For a + // 15k-file iOS repo (#226), the sequential `readFileSync` loop dominated; + // letting reads pipeline drops wall time roughly proportional to the + // share of the loop spent waiting on disk. + for (let start = 0; start < codeFiles.length; start += IO_PARALLELISM) { + const slice = codeFiles.slice(start, start + IO_PARALLELISM); + + // Read every file in the slice concurrently. Errors per file are + // captured in-place so a single bad file does not abort the chunk. + const reads = await Promise.all( + slice.map(async (file) => { + const abs = join(projectRoot, file.path); + try { + const content = await readFile(abs, 'utf-8'); + return { file, content, readError: null }; + } catch (err) { + return { file, content: null, readError: err }; + } + }), + ); + + // Serialise the CPU-bound tree-sitter work and the stderr warning emits + // so log order remains identical to the previous sequential loop. This + // also keeps existing fixture-comparison tests stable. + for (const { file, content, readError } of reads) { + if (readError) { + process.stderr.write( + `Warning: compute-batches: exports extraction failed for ${file.path} ` + + `(read error: ${readError.message}) — symbols=[] in neighborMap — ` + + `cross-batch edges to this file limited to file-level\n`, + ); + exportsByPath.set(file.path, []); + continue; + } + try { + const analysis = registry.analyzeFile(file.path, content); + const names = (analysis?.exports || []).map(e => e.name).filter(Boolean); + exportsByPath.set(file.path, names); + } catch (err) { + process.stderr.write( + `Warning: compute-batches: exports extraction failed for ${file.path} ` + + `(analyze error: ${err.message}) — symbols=[] in neighborMap — ` + + `cross-batch edges to this file limited to file-level\n`, + ); + exportsByPath.set(file.path, []); + } } } return exportsByPath; diff --git a/understand-anything-plugin/skills/understand/extract-import-map.mjs b/understand-anything-plugin/skills/understand/extract-import-map.mjs index 6c547d3..ec31856 100644 --- a/understand-anything-plugin/skills/understand/extract-import-map.mjs +++ b/understand-anything-plugin/skills/understand/extract-import-map.mjs @@ -37,6 +37,29 @@ import { createRequire } from 'node:module'; import { dirname, resolve, join, posix } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; + +/** + * Read a list of files concurrently while preserving result order. Failures + * are returned in-place as `{ raw: null, err }` so callers can emit the same + * per-file warnings they did under the previous sequential `readFileSync` + * loops. + * + * `paths` is a list of `{ key, absPath }` pairs; `key` is whatever the caller + * wants to attach the result to (typically a project-relative POSIX path). + */ +async function readFilesParallel(paths) { + return Promise.all( + paths.map(async ({ key, absPath }) => { + try { + const raw = await readFile(absPath, 'utf-8'); + return { key, raw, err: null }; + } catch (err) { + return { key, raw: null, err }; + } + }), + ); +} const __dirname = dirname(fileURLToPath(import.meta.url)); // skills/understand/ -> plugin root is two dirs up @@ -180,20 +203,25 @@ function parseTsConfigText(raw) { * where the stripper damaged a string literal containing `//`. * 3. If both fail, warn and skip — that tsconfig contributes no aliases. */ -function loadTsConfigs(projectRoot, files) { +async function loadTsConfigs(projectRoot, files) { const out = new Map(); + // Collect the candidate paths in the original file order before reading, + // so warning emit order matches the previous sequential implementation. + const candidates = []; for (const f of files) { const p = toPosix(f.path); const base = p.includes('/') ? p.slice(p.lastIndexOf('/') + 1) : p; if (base !== 'tsconfig.json') continue; const absPath = join(projectRoot, p); if (!existsSync(absPath)) continue; - let raw; - try { - raw = readFileSync(absPath, 'utf-8'); - } catch (err) { + candidates.push({ key: p, absPath }); + } + const reads = await readFilesParallel(candidates); + for (const { key: p, raw, err } of reads) { + if (err) { + // absPath isn't carried through the helper return shape; reconstruct it. process.stderr.write( - `Warning: extract-import-map: tsconfig.json at ${absPath} failed ` + + `Warning: extract-import-map: tsconfig.json at ${join(projectRoot, p)} failed ` + `to read (${err.message}) — path aliases from this config will ` + `not be applied — relative imports unaffected\n`, ); @@ -202,7 +230,7 @@ function loadTsConfigs(projectRoot, files) { const parsed = parseTsConfigText(raw); if (!parsed) { process.stderr.write( - `Warning: extract-import-map: tsconfig.json at ${absPath} failed ` + + `Warning: extract-import-map: tsconfig.json at ${join(projectRoot, p)} failed ` + `to parse — path aliases from this config will not be applied ` + `— relative imports unaffected\n`, ); @@ -237,20 +265,20 @@ function loadTsConfigs(projectRoot, files) { * The resolver uses each module's prefix to translate * `import "github.com/foo/bar/x"` into the project-internal `x/.go`. */ -function loadGoModules(projectRoot, files) { +async function loadGoModules(projectRoot, files) { const out = new Map(); + const candidates = []; for (const f of files) { const p = toPosix(f.path); const base = p.includes('/') ? p.slice(p.lastIndexOf('/') + 1) : p; if (base !== 'go.mod') continue; const absPath = join(projectRoot, p); if (!existsSync(absPath)) continue; - let raw; - try { - raw = readFileSync(absPath, 'utf-8'); - } catch { - continue; - } + candidates.push({ key: p, absPath }); + } + const reads = await readFilesParallel(candidates); + for (const { key: p, raw, err } of reads) { + if (err) continue; let moduleName = ''; for (const line of raw.split(/\r?\n/)) { const trimmed = line.replace(/\/\/.*$/, '').trim(); @@ -306,10 +334,17 @@ function findNearestConfigDir(startDir, configMap) { * * Build once; pass everywhere. */ -function buildResolutionContext(projectRoot, files) { +async function buildResolutionContext(projectRoot, files) { const fileSet = new Set(files.map(f => toPosix(f.path))); - const tsConfigs = loadTsConfigs(projectRoot, files); - const goModules = loadGoModules(projectRoot, files); + + // The three config-loader passes are independent and each does its own + // batched parallel I/O; run them concurrently so the wait for a slow + // tsconfig.json read doesn't block go.mod / composer.json scanning. + const [tsConfigs, goModules, phpAutoloads] = await Promise.all([ + loadTsConfigs(projectRoot, files), + loadGoModules(projectRoot, files), + loadPhpAutoloads(projectRoot, files), + ]); // Index .go files by their parent directory so the Go resolver can // expand a package-level import to all member .go files in O(1). @@ -331,8 +366,6 @@ function buildResolutionContext(projectRoot, files) { const kotlinIndex = buildSuffixIndex(files, p => p.endsWith('.kt')); const csIndex = buildSuffixIndex(files, p => p.endsWith('.cs')); - const phpAutoloads = loadPhpAutoloads(projectRoot, files); - return { projectRoot, fileSet, @@ -1019,20 +1052,22 @@ function parseComposerAutoloadText(raw) { * at the bad file and skips it. The rest of the project's PHP imports keep * resolving via whichever composer.json files parsed cleanly. */ -function loadPhpAutoloads(projectRoot, files) { +async function loadPhpAutoloads(projectRoot, files) { const out = new Map(); + const candidates = []; for (const f of files) { const p = toPosix(f.path); const base = p.includes('/') ? p.slice(p.lastIndexOf('/') + 1) : p; if (base !== 'composer.json') continue; const absPath = join(projectRoot, p); if (!existsSync(absPath)) continue; - let raw; - try { - raw = readFileSync(absPath, 'utf-8'); - } catch (err) { + candidates.push({ key: p, absPath }); + } + const reads = await readFilesParallel(candidates); + for (const { key: p, raw, err } of reads) { + if (err) { process.stderr.write( - `Warning: extract-import-map: composer.json at ${absPath} failed ` + + `Warning: extract-import-map: composer.json at ${join(projectRoot, p)} failed ` + `to read (${err.message}) — PSR-4 namespace mapping from this ` + `composer.json unavailable — PHP imports under this package ` + `will not resolve\n`, @@ -1042,7 +1077,7 @@ function loadPhpAutoloads(projectRoot, files) { const parsed = parseComposerAutoloadText(raw); if (parsed === null) { process.stderr.write( - `Warning: extract-import-map: composer.json at ${absPath} failed ` + + `Warning: extract-import-map: composer.json at ${join(projectRoot, p)} failed ` + `to parse — PSR-4 namespace mapping unavailable — PHP imports ` + `under this package will not resolve\n`, ); @@ -1412,8 +1447,10 @@ async function main() { ); } - // Build resolution context (cached configs) - const ctx = buildResolutionContext(projectRoot, files); + // Build resolution context (cached configs). The loader pass for the + // tsconfig/go.mod/composer.json files inside is parallelised — see + // `buildResolutionContext`. + const ctx = await buildResolutionContext(projectRoot, files); const importMap = {}; let filesWithImports = 0; From 235f2fafc84474243a79294f12b97318698cd639 Mon Sep 17 00:00:00 2001 From: Tirth Kanani Date: Sun, 31 May 2026 20:42:12 +0100 Subject: [PATCH 03/12] feat(core): add Kotlin structural analysis via tree-sitter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires Kotlin into the existing tree-sitter pipeline so .kt and .kts files now produce functions, classes, data classes, sealed classes, interfaces, objects, imports, exports, and call-graph edges — matching the behavior of the other language extractors. ## Why @tree-sitter-grammars/tree-sitter-kotlin The standard `tree-sitter-kotlin` (v0.3.8) ships only native bindings. The new `@tree-sitter-grammars/tree-sitter-kotlin@1.1.0` ships a prebuilt `.wasm` (loads cleanly with `web-tree-sitter@^0.26.6`, nodeTypeCount=289, parses class_declaration / function_declaration as expected). Same shape that PR1 used for Swift, just a different publisher because the repomix WASM bundle does not include Kotlin. `@tree-sitter-grammars` is the official tree-sitter org's GitHub account, so this is the canonical upstream WASM source for Kotlin. ## Notes for reviewers - `kotlinConfig` already existed as a stub (no `treeSitter` field), so Android / JVM / Gradle codebases currently produce no structural edges between `.kt` files. This PR adds the `treeSitter` field; the existing plugin loader picks it up unchanged. - **Visibility rule differs from Swift**: Kotlin's default visibility is `public`, so the extractor treats *every* declaration with no modifier as exported. Only an explicit `private` opts out. `internal` and `protected` remain exported in the project-graph sense because they are still resolvable from other files (within the module / via inheritance). - `class_declaration` in tree-sitter-kotlin is overloaded for class, data class, sealed class, and interface (distinguished by the keyword child and `modifiers > class_modifier`). The extractor handles all four uniformly. - `object_declaration` is a separate node type (Kotlin singletons) — treated as a class-like entry with its own `name` and members. - Primary-constructor parameters marked `val` / `var` are surfaced as class properties; plain `parameter`s without `val/var` are constructor-only and are NOT counted as properties (matching Kotlin semantics). - Import handling distinguishes the three forms: plain dotted (`import a.b.C`), wildcard (`import a.b.*` → specifier `"*"`), and aliased (`import a.b.C as Foo` → specifier `"Foo"`). ## Verification - `pnpm lint` clean - `pnpm --filter @understand-anything/core build` clean - `pnpm --filter @understand-anything/skill build` clean - `pnpm --filter @understand-anything/core test`: **692/692** (+22 new Kotlin tests, matching the bar set by go-extractor.test.ts / swift-extractor.test.ts) - `pnpm test`: 196/196 (no regressions) Co-Authored-By: Claude Opus 4.7 (1M context) --- pnpm-lock.yaml | 24 + .../packages/core/package.json | 1 + .../core/src/languages/configs/kotlin.ts | 4 + .../__tests__/kotlin-extractor.test.ts | 364 +++++++++++++++ .../core/src/plugins/extractors/index.ts | 3 + .../plugins/extractors/kotlin-extractor.ts | 425 ++++++++++++++++++ 6 files changed, 821 insertions(+) create mode 100644 understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/kotlin-extractor.test.ts create mode 100644 understand-anything-plugin/packages/core/src/plugins/extractors/kotlin-extractor.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3eb7e7a..0b9b06e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,6 +57,9 @@ importers: understand-anything-plugin/packages/core: dependencies: + '@tree-sitter-grammars/tree-sitter-kotlin': + specifier: 1.1.0 + version: 1.1.0 fuse.js: specifier: ^7.1.0 version: 7.1.0 @@ -1120,6 +1123,14 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 + '@tree-sitter-grammars/tree-sitter-kotlin@1.1.0': + resolution: {integrity: sha512-vlVXaxEE8t2kpJgfZpa8XVvxcnKw9AYtRTgy7KWjsDmAsadk06RxAT80IXOgGQnmM9i/orQn1nD84gPNUHu6DQ==} + peerDependencies: + tree-sitter: ^0.22.4 + peerDependenciesMeta: + tree-sitter: + optional: true + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -2442,6 +2453,11 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + npm-check-updates@17.1.18: + resolution: {integrity: sha512-bkUy2g4v1i+3FeUf5fXMLbxmV95eG4/sS7lYE32GrUeVgQRfQEk39gpskksFunyaxQgTIdrvYbnuNbO/pSUSqw==} + engines: {node: ^18.18.0 || >=20.0.0, npm: '>=8.12.1'} + hasBin: true + nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -4048,6 +4064,12 @@ snapshots: tailwindcss: 4.2.1 vite: 6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) + '@tree-sitter-grammars/tree-sitter-kotlin@1.1.0': + dependencies: + node-addon-api: 8.6.0 + node-gyp-build: 4.8.4 + npm-check-updates: 17.1.18 + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.0 @@ -5760,6 +5782,8 @@ snapshots: normalize-path@3.0.0: {} + npm-check-updates@17.1.18: {} + nth-check@2.1.1: dependencies: boolbase: 1.0.0 diff --git a/understand-anything-plugin/packages/core/package.json b/understand-anything-plugin/packages/core/package.json index c4cd4f3..7ea50ae 100644 --- a/understand-anything-plugin/packages/core/package.json +++ b/understand-anything-plugin/packages/core/package.json @@ -37,6 +37,7 @@ "vitest": "^3.1.0" }, "dependencies": { + "@tree-sitter-grammars/tree-sitter-kotlin": "1.1.0", "fuse.js": "^7.1.0", "ignore": "^7.0.5", "tree-sitter-c-sharp": "^0.23.1", diff --git a/understand-anything-plugin/packages/core/src/languages/configs/kotlin.ts b/understand-anything-plugin/packages/core/src/languages/configs/kotlin.ts index f02dc79..dd700d5 100644 --- a/understand-anything-plugin/packages/core/src/languages/configs/kotlin.ts +++ b/understand-anything-plugin/packages/core/src/languages/configs/kotlin.ts @@ -4,6 +4,10 @@ export const kotlinConfig = { id: "kotlin", displayName: "Kotlin", extensions: [".kt", ".kts"], + treeSitter: { + wasmPackage: "@tree-sitter-grammars/tree-sitter-kotlin", + wasmFile: "tree-sitter-kotlin.wasm", + }, concepts: [ "coroutines", "data classes", diff --git a/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/kotlin-extractor.test.ts b/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/kotlin-extractor.test.ts new file mode 100644 index 0000000..db12d0f --- /dev/null +++ b/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/kotlin-extractor.test.ts @@ -0,0 +1,364 @@ +import { describe, it, expect, beforeAll } from "vitest"; +import { createRequire } from "node:module"; +import { KotlinExtractor } from "../kotlin-extractor.js"; + +const require = createRequire(import.meta.url); + +let Parser: any; +let Language: any; +let kotlinLang: any; + +beforeAll(async () => { + const mod = await import("web-tree-sitter"); + Parser = mod.Parser; + Language = mod.Language; + await Parser.init(); + const wasmPath = require.resolve( + "@tree-sitter-grammars/tree-sitter-kotlin/tree-sitter-kotlin.wasm", + ); + kotlinLang = await Language.load(wasmPath); +}); + +function parse(code: string) { + const parser = new Parser(); + parser.setLanguage(kotlinLang); + const tree = parser.parse(code); + const root = tree.rootNode; + return { tree, parser, root }; +} + +describe("KotlinExtractor", () => { + const extractor = new KotlinExtractor(); + + it("has correct languageIds", () => { + expect(extractor.languageIds).toEqual(["kotlin"]); + }); + + describe("extractStructure - functions", () => { + it("extracts a simple top-level function with params and return type", () => { + const { tree, parser, root } = parse(`fun add(a: Int, b: Int): Int = a + b +`); + const result = extractor.extractStructure(root); + + expect(result.functions).toHaveLength(1); + expect(result.functions[0].name).toBe("add"); + expect(result.functions[0].params).toEqual(["a", "b"]); + expect(result.functions[0].returnType).toBe("Int"); + + tree.delete(); + parser.delete(); + }); + + it("extracts function with no params and no return type", () => { + const { tree, parser, root } = parse(`fun noop() {} +`); + const result = extractor.extractStructure(root); + + expect(result.functions).toHaveLength(1); + expect(result.functions[0].name).toBe("noop"); + expect(result.functions[0].params).toEqual([]); + expect(result.functions[0].returnType).toBeUndefined(); + + tree.delete(); + parser.delete(); + }); + + it("extracts suspending and generic functions", () => { + const { tree, parser, root } = parse(`suspend fun fetch(id: String): T? { + return null +} +`); + const result = extractor.extractStructure(root); + + expect(result.functions).toHaveLength(1); + expect(result.functions[0].name).toBe("fetch"); + expect(result.functions[0].params).toEqual(["id"]); + // Nullable type — Kotlin formats it as "T?" + expect(result.functions[0].returnType).toBe("T?"); + + tree.delete(); + parser.delete(); + }); + + it("extracts multiple top-level functions in declaration order", () => { + const { tree, parser, root } = parse(`fun one() {} +fun two(x: Int): Int = x +fun three(): String = "" +`); + const result = extractor.extractStructure(root); + + expect(result.functions.map((f) => f.name)).toEqual([ + "one", + "two", + "three", + ]); + + tree.delete(); + parser.delete(); + }); + }); + + describe("extractStructure - classes", () => { + it("extracts a class with primary-constructor val properties + methods", () => { + const { tree, parser, root } = parse(`class Foo(val bar: Int) { + val baz: String = "hi" + fun compute(): Int = bar * 2 +} +`); + const result = extractor.extractStructure(root); + + expect(result.classes).toHaveLength(1); + expect(result.classes[0].name).toBe("Foo"); + // Both the constructor val and the body val are properties + expect(result.classes[0].properties).toEqual( + expect.arrayContaining(["bar", "baz"]), + ); + expect(result.classes[0].methods).toContain("compute"); + + tree.delete(); + parser.delete(); + }); + + it("extracts an empty class", () => { + const { tree, parser, root } = parse(`class Empty +`); + const result = extractor.extractStructure(root); + + expect(result.classes).toHaveLength(1); + expect(result.classes[0].name).toBe("Empty"); + expect(result.classes[0].methods).toEqual([]); + expect(result.classes[0].properties).toEqual([]); + + tree.delete(); + parser.delete(); + }); + + it("extracts a data class and surfaces its constructor parameters as properties", () => { + const { tree, parser, root } = parse(`data class Point(val x: Double, val y: Double) +`); + const result = extractor.extractStructure(root); + + expect(result.classes).toHaveLength(1); + expect(result.classes[0].name).toBe("Point"); + expect(result.classes[0].properties).toEqual(["x", "y"]); + + tree.delete(); + parser.delete(); + }); + + it("extracts class methods into functions[] as well as the class's methods[]", () => { + // Mirrors the Go/Swift extractor convention so the graph builder can + // create function nodes for class methods. + const { tree, parser, root } = parse(`class Foo { + fun bar(): Int = 1 +} +`); + const result = extractor.extractStructure(root); + + expect(result.functions.map((f) => f.name)).toContain("bar"); + expect(result.classes[0].methods).toContain("bar"); + + tree.delete(); + parser.delete(); + }); + }); + + describe("extractStructure - interfaces", () => { + it("extracts an interface with method requirements as a class-like entry", () => { + const { tree, parser, root } = parse(`interface Greeter { + fun greet(name: String): String + fun farewell(): String +} +`); + const result = extractor.extractStructure(root); + + expect(result.classes).toHaveLength(1); + expect(result.classes[0].name).toBe("Greeter"); + expect(result.classes[0].methods).toEqual( + expect.arrayContaining(["greet", "farewell"]), + ); + + tree.delete(); + parser.delete(); + }); + }); + + describe("extractStructure - object declarations", () => { + it("extracts a singleton `object` with methods", () => { + const { tree, parser, root } = parse(`object Logger { + fun info(msg: String) {} + fun warn(msg: String) {} +} +`); + const result = extractor.extractStructure(root); + + expect(result.classes).toHaveLength(1); + expect(result.classes[0].name).toBe("Logger"); + expect(result.classes[0].methods).toEqual( + expect.arrayContaining(["info", "warn"]), + ); + + tree.delete(); + parser.delete(); + }); + }); + + describe("extractStructure - imports", () => { + it("extracts a simple dotted import", () => { + const { tree, parser, root } = parse(`import kotlin.io.println +`); + const result = extractor.extractStructure(root); + + expect(result.imports).toHaveLength(1); + expect(result.imports[0].source).toBe("kotlin.io.println"); + // The specifier is the final dotted segment + expect(result.imports[0].specifiers).toEqual(["println"]); + + tree.delete(); + parser.delete(); + }); + + it("extracts a wildcard import", () => { + const { tree, parser, root } = parse(`import kotlinx.coroutines.* +`); + const result = extractor.extractStructure(root); + + expect(result.imports).toHaveLength(1); + expect(result.imports[0].source).toBe("kotlinx.coroutines"); + // Wildcard is preserved as the specifier so consumers can distinguish it + // from a regular dotted import of a specific symbol. + expect(result.imports[0].specifiers).toEqual(["*"]); + + tree.delete(); + parser.delete(); + }); + + it("extracts an aliased import", () => { + const { tree, parser, root } = parse(`import com.example.foo.Bar as Baz +`); + const result = extractor.extractStructure(root); + + expect(result.imports).toHaveLength(1); + expect(result.imports[0].source).toBe("com.example.foo.Bar"); + // The alias is the user-visible name in this file + expect(result.imports[0].specifiers).toEqual(["Baz"]); + + tree.delete(); + parser.delete(); + }); + + it("extracts multiple imports in declaration order", () => { + const { tree, parser, root } = parse(`package com.example.app + +import kotlinx.coroutines.flow.Flow +import kotlin.io.println +import kotlinx.coroutines.* +`); + const result = extractor.extractStructure(root); + + expect(result.imports).toHaveLength(3); + expect(result.imports[0].source).toBe("kotlinx.coroutines.flow.Flow"); + expect(result.imports[1].source).toBe("kotlin.io.println"); + expect(result.imports[2].source).toBe("kotlinx.coroutines"); + + tree.delete(); + parser.delete(); + }); + }); + + describe("extractStructure - exports / visibility", () => { + it("treats no-modifier declarations as exported (Kotlin default is public)", () => { + const { tree, parser, root } = parse(`fun greet() {} +class Greeter {} +`); + const result = extractor.extractStructure(root); + + const exportNames = result.exports.map((e) => e.name); + expect(exportNames).toEqual(expect.arrayContaining(["greet", "Greeter"])); + + tree.delete(); + parser.delete(); + }); + + it("treats public/internal/protected as exported", () => { + const { tree, parser, root } = parse(`public fun a() {} +internal class B {} +`); + const result = extractor.extractStructure(root); + + const exportNames = result.exports.map((e) => e.name); + expect(exportNames).toEqual(expect.arrayContaining(["a", "B"])); + + tree.delete(); + parser.delete(); + }); + + it("does NOT treat private declarations as exported", () => { + const { tree, parser, root } = parse(`private fun helper() {} +private class Internal {} +`); + const result = extractor.extractStructure(root); + + const exportNames = result.exports.map((e) => e.name); + expect(exportNames).not.toContain("helper"); + expect(exportNames).not.toContain("Internal"); + + tree.delete(); + parser.delete(); + }); + + it("exports an object declaration by default", () => { + const { tree, parser, root } = parse(`object Logger {} +`); + const result = extractor.extractStructure(root); + + expect(result.exports.map((e) => e.name)).toContain("Logger"); + + tree.delete(); + parser.delete(); + }); + }); + + describe("extractCallGraph", () => { + it("extracts a call from one function to another", () => { + const { tree, parser, root } = parse(`fun helper(): Int = 1 + +fun caller(): Int { + return helper() +} +`); + const entries = extractor.extractCallGraph(root); + + const helperCall = entries.find((e) => e.callee === "helper"); + expect(helperCall).toBeDefined(); + expect(helperCall!.caller).toBe("caller"); + + tree.delete(); + parser.delete(); + }); + + it("extracts method calls (x.foo()) and attributes them to the enclosing function", () => { + const { tree, parser, root } = parse(`fun run() { + val s = "hi".uppercase() +} +`); + const entries = extractor.extractCallGraph(root); + + const callees = entries.map((e) => e.callee); + expect(callees).toContain("uppercase"); + + tree.delete(); + parser.delete(); + }); + + it("returns an empty array when there are no calls", () => { + const { tree, parser, root } = parse(`fun a(): Int = 1 +`); + const entries = extractor.extractCallGraph(root); + expect(entries).toEqual([]); + + tree.delete(); + parser.delete(); + }); + }); +}); diff --git a/understand-anything-plugin/packages/core/src/plugins/extractors/index.ts b/understand-anything-plugin/packages/core/src/plugins/extractors/index.ts index f148c61..4f0b5a3 100644 --- a/understand-anything-plugin/packages/core/src/plugins/extractors/index.ts +++ b/understand-anything-plugin/packages/core/src/plugins/extractors/index.ts @@ -9,6 +9,7 @@ export { RubyExtractor } from "./ruby-extractor.js"; export { PhpExtractor } from "./php-extractor.js"; export { CppExtractor } from "./cpp-extractor.js"; export { CSharpExtractor } from "./csharp-extractor.js"; +export { KotlinExtractor } from "./kotlin-extractor.js"; import type { LanguageExtractor } from "./types.js"; import { TypeScriptExtractor } from "./typescript-extractor.js"; @@ -20,6 +21,7 @@ import { RubyExtractor } from "./ruby-extractor.js"; import { PhpExtractor } from "./php-extractor.js"; import { CppExtractor } from "./cpp-extractor.js"; import { CSharpExtractor } from "./csharp-extractor.js"; +import { KotlinExtractor } from "./kotlin-extractor.js"; export const builtinExtractors: LanguageExtractor[] = [ new TypeScriptExtractor(), @@ -31,4 +33,5 @@ export const builtinExtractors: LanguageExtractor[] = [ new PhpExtractor(), new CppExtractor(), new CSharpExtractor(), + new KotlinExtractor(), ]; diff --git a/understand-anything-plugin/packages/core/src/plugins/extractors/kotlin-extractor.ts b/understand-anything-plugin/packages/core/src/plugins/extractors/kotlin-extractor.ts new file mode 100644 index 0000000..2ee2818 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/plugins/extractors/kotlin-extractor.ts @@ -0,0 +1,425 @@ +import type { StructuralAnalysis, CallGraphEntry } from "../../types.js"; +import type { LanguageExtractor, TreeSitterNode } from "./types.js"; +import { findChild, findChildren } from "./base-extractor.js"; + +/** + * Extract the visibility keyword text (e.g., "public", "private") from a + * declaration's `modifiers` child, or return null when no modifier is present. + * + * Kotlin's default visibility is `public`, so a `null` result means the + * declaration IS exported — callers must treat absence as exported, not the + * other way around. + */ +function extractVisibility(declNode: TreeSitterNode): string | null { + const modifiers = findChild(declNode, "modifiers"); + if (!modifiers) return null; + const visibility = findChild(modifiers, "visibility_modifier"); + if (!visibility) return null; + return visibility.text; +} + +/** + * Whether a Kotlin declaration is visible to other files. + * + * Default visibility in Kotlin is `public`, so a declaration with NO + * modifier counts as exported. Only an explicit `private` opts out. + * `internal` and `protected` remain exported in the project-graph sense + * because they are still resolvable from other files (within the module + * or via inheritance respectively). + */ +function isExported(declNode: TreeSitterNode): boolean { + const visibility = extractVisibility(declNode); + return visibility === null || visibility !== "private"; +} + +/** + * Get the identifier-text name of a Kotlin declaration. Works for + * function_declaration / class_declaration / object_declaration / interface + * — all carry the name as the first `identifier` child after the keyword. + */ +function extractDeclarationName(declNode: TreeSitterNode): string | null { + for (let i = 0; i < declNode.childCount; i++) { + const child = declNode.child(i); + if (child && child.type === "identifier") return child.text; + } + return null; +} + +/** + * Extract parameter names from a `function_value_parameters` node. Each + * `parameter` child carries a leading `identifier` for the parameter name; + * the optional trailing `: ` annotation is ignored. + */ +function extractParams(declNode: TreeSitterNode): string[] { + const params: string[] = []; + const valueParams = findChild(declNode, "function_value_parameters"); + if (!valueParams) return params; + for (const param of findChildren(valueParams, "parameter")) { + // The first `identifier` inside a parameter is its name. + const id = findChild(param, "identifier"); + if (id) params.push(id.text); + } + return params; +} + +/** + * Extract the return type text from a `function_declaration` by looking for + * the `:` separator and taking the next named child, which is the type node. + * Returns undefined for `Unit`-returning functions (no annotation present). + */ +function extractReturnType(declNode: TreeSitterNode): string | undefined { + // function_value_parameters comes before the optional `: ` block; + // walk children from after the parameters to find `:` followed by a type. + let sawParams = false; + for (let i = 0; i < declNode.childCount; i++) { + const child = declNode.child(i); + if (!child) continue; + if (child.type === "function_value_parameters") { + sawParams = true; + continue; + } + if (sawParams && child.type === ":") { + // The next named sibling is the type + for (let j = i + 1; j < declNode.childCount; j++) { + const next = declNode.child(j); + if (next && next.isNamed) return next.text; + } + } + } + return undefined; +} + +/** + * Walk a `class_body` and collect functions + properties. Function entries + * are added to both the class's `methods` array and the top-level + * `functions` array (matching the GoExtractor / SwiftExtractor convention). + */ +function collectClassBody( + body: TreeSitterNode, + methods: string[], + properties: string[], + functions: StructuralAnalysis["functions"], + exports: StructuralAnalysis["exports"], +): void { + for (let i = 0; i < body.childCount; i++) { + const member = body.child(i); + if (!member) continue; + + if (member.type === "function_declaration") { + const name = extractDeclarationName(member); + if (!name) continue; + methods.push(name); + functions.push({ + name, + lineRange: [member.startPosition.row + 1, member.endPosition.row + 1], + params: extractParams(member), + returnType: extractReturnType(member), + }); + if (isExported(member)) { + exports.push({ name, lineNumber: member.startPosition.row + 1 }); + } + } else if (member.type === "property_declaration") { + const name = extractPropertyName(member); + if (name) properties.push(name); + if (name && isExported(member)) { + exports.push({ name, lineNumber: member.startPosition.row + 1 }); + } + } else if (member.type === "object_declaration") { + // Nested companion-object / object members are surfaced as a single + // synthetic property pointing at the inner object's name — enough for + // the graph builder to keep the relationship without exploding scope. + const name = extractDeclarationName(member); + if (name) properties.push(name); + } + } +} + +/** + * Extract the property name from a `property_declaration`. The name lives + * inside the `variable_declaration` child as its `identifier`. + */ +function extractPropertyName(propNode: TreeSitterNode): string | null { + const varDecl = findChild(propNode, "variable_declaration"); + if (!varDecl) return null; + const id = findChild(varDecl, "identifier"); + return id ? id.text : null; +} + +/** + * Walk a `primary_constructor`'s `class_parameters` and surface every + * `val` / `var` parameter as a class property. Plain `parameter` entries + * (no val/var keyword) are constructor-only and are NOT properties — they + * vanish after the constructor returns. + */ +function collectPrimaryConstructorProperties( + declNode: TreeSitterNode, + properties: string[], +): void { + const primary = findChild(declNode, "primary_constructor"); + if (!primary) return; + const params = findChild(primary, "class_parameters"); + if (!params) return; + for (const param of findChildren(params, "class_parameter")) { + // A class_parameter that starts with `val` or `var` is a property. + let isProperty = false; + for (let i = 0; i < param.childCount; i++) { + const child = param.child(i); + if (child && (child.type === "val" || child.type === "var")) { + isProperty = true; + break; + } + } + if (!isProperty) continue; + const id = findChild(param, "identifier"); + if (id) properties.push(id.text); + } +} + +/** + * Kotlin extractor for tree-sitter structural analysis and call graph + * extraction. Maps Kotlin's class / interface / object / data-class + * declarations to the project's shared `StructuralAnalysis.classes` array. + */ +export class KotlinExtractor implements LanguageExtractor { + readonly languageIds = ["kotlin"]; + + extractStructure(rootNode: TreeSitterNode): StructuralAnalysis { + const functions: StructuralAnalysis["functions"] = []; + const classes: StructuralAnalysis["classes"] = []; + const imports: StructuralAnalysis["imports"] = []; + const exports: StructuralAnalysis["exports"] = []; + + for (let i = 0; i < rootNode.childCount; i++) { + const node = rootNode.child(i); + if (!node) continue; + + switch (node.type) { + case "package_header": + // Package is metadata about this file, not a graph member. Skip. + break; + + case "import": + this.extractImport(node, imports); + break; + + case "function_declaration": + this.extractTopLevelFunction(node, functions, exports); + break; + + case "class_declaration": + this.extractClassDeclaration(node, classes, functions, exports); + break; + + case "object_declaration": + this.extractObjectDeclaration(node, classes, functions, exports); + break; + } + } + + return { functions, classes, imports, exports }; + } + + extractCallGraph(rootNode: TreeSitterNode): CallGraphEntry[] { + const entries: CallGraphEntry[] = []; + const functionStack: string[] = []; + + const walk = (node: TreeSitterNode) => { + let pushed = false; + + if (node.type === "function_declaration") { + const name = extractDeclarationName(node); + if (name) { + functionStack.push(name); + pushed = true; + } + } + + if (node.type === "call_expression" && functionStack.length > 0) { + const callee = this.extractCalleeName(node); + if (callee) { + entries.push({ + caller: functionStack[functionStack.length - 1], + callee, + lineNumber: node.startPosition.row + 1, + }); + } + } + + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child) walk(child); + } + + if (pushed) functionStack.pop(); + }; + + walk(rootNode); + return entries; + } + + // ---- Private helpers ---- + + private extractTopLevelFunction( + declNode: TreeSitterNode, + functions: StructuralAnalysis["functions"], + exports: StructuralAnalysis["exports"], + ): void { + const name = extractDeclarationName(declNode); + if (!name) return; + functions.push({ + name, + lineRange: [declNode.startPosition.row + 1, declNode.endPosition.row + 1], + params: extractParams(declNode), + returnType: extractReturnType(declNode), + }); + if (isExported(declNode)) { + exports.push({ name, lineNumber: declNode.startPosition.row + 1 }); + } + } + + private extractClassDeclaration( + declNode: TreeSitterNode, + classes: StructuralAnalysis["classes"], + functions: StructuralAnalysis["functions"], + exports: StructuralAnalysis["exports"], + ): void { + const name = extractDeclarationName(declNode); + if (!name) return; + + const properties: string[] = []; + const methods: string[] = []; + + // 1. Primary-constructor `val`/`var` parameters become properties. + collectPrimaryConstructorProperties(declNode, properties); + + // 2. Body members (if any). Some Kotlin declarations have no body + // (e.g. `class Empty` or `data class Point(...)` without `{}`). + const body = findChild(declNode, "class_body"); + if (body) { + collectClassBody(body, methods, properties, functions, exports); + } + + classes.push({ + name, + lineRange: [declNode.startPosition.row + 1, declNode.endPosition.row + 1], + methods, + properties, + }); + + if (isExported(declNode)) { + exports.push({ name, lineNumber: declNode.startPosition.row + 1 }); + } + } + + private extractObjectDeclaration( + declNode: TreeSitterNode, + classes: StructuralAnalysis["classes"], + functions: StructuralAnalysis["functions"], + exports: StructuralAnalysis["exports"], + ): void { + const name = extractDeclarationName(declNode); + if (!name) return; + + const properties: string[] = []; + const methods: string[] = []; + + const body = findChild(declNode, "class_body"); + if (body) { + collectClassBody(body, methods, properties, functions, exports); + } + + classes.push({ + name, + lineRange: [declNode.startPosition.row + 1, declNode.endPosition.row + 1], + methods, + properties, + }); + + if (isExported(declNode)) { + exports.push({ name, lineNumber: declNode.startPosition.row + 1 }); + } + } + + /** + * Extract a Kotlin import. + * + * The grammar gives us a single `qualified_identifier` child holding the + * dotted module path. Three trailing variants must be distinguished: + * + * - `import foo.bar.Baz` → source="foo.bar.Baz", specifier="Baz" + * - `import foo.bar.*` → source="foo.bar", specifier="*" + * - `import foo.bar.Baz as Quux` → source="foo.bar.Baz", specifier="Quux" + * + * The grammar represents the wildcard as a trailing `*` token AFTER the + * qualified_identifier (which holds the dotted prefix). The alias + * appears as `as` + a top-level `identifier` sibling. + */ + private extractImport( + declNode: TreeSitterNode, + imports: StructuralAnalysis["imports"], + ): void { + const qualified = findChild(declNode, "qualified_identifier"); + if (!qualified) return; + + const parts: string[] = []; + for (const id of findChildren(qualified, "identifier")) { + parts.push(id.text); + } + if (parts.length === 0) return; + + const source = parts.join("."); + + // Look for a sibling `*` (wildcard) or an `as` keyword + identifier + // after the qualified_identifier. + let specifier = parts[parts.length - 1]; + let sawWildcardStar = false; + let sawAs = false; + for (let i = 0; i < declNode.childCount; i++) { + const child = declNode.child(i); + if (!child) continue; + if (child.type === "*") sawWildcardStar = true; + if (child.type === "as") sawAs = true; + else if (sawAs && child.type === "identifier") { + specifier = child.text; + sawAs = false; + } + } + if (sawWildcardStar) specifier = "*"; + + imports.push({ + source, + specifiers: [specifier], + lineNumber: declNode.startPosition.row + 1, + }); + } + + /** + * Extract the callee name from a Kotlin `call_expression`. Two shapes: + * + * foo(...) → first child is `identifier "foo"` + * target.method(...) → first child is `navigation_expression` whose + * last `navigation_suffix > identifier` is the + * method name + */ + private extractCalleeName(callNode: TreeSitterNode): string | null { + const first = callNode.child(0); + if (!first) return null; + + if (first.type === "identifier") return first.text; + + if (first.type === "navigation_expression") { + // The Kotlin grammar flattens navigation: `x.foo` is + // navigation_expression { x, ".", identifier "foo" } + // The method name is the LAST `identifier` child of the navigation. + let lastIdentifier: string | null = null; + for (let i = 0; i < first.childCount; i++) { + const child = first.child(i); + if (child && child.type === "identifier") { + lastIdentifier = child.text; + } + } + return lastIdentifier; + } + return null; + } +} From aef940fcdec3499160932831376a2a7f09f6b397 Mon Sep 17 00:00:00 2001 From: Tirth Kanani Date: Sun, 31 May 2026 21:29:13 +0100 Subject: [PATCH 04/12] chore(repo): add issue/PR templates, SECURITY.md, CoC, package metadata; widen CI triggers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes a cluster of community-profile gaps (#248, #249, #251, #252) in one PR rather than four micro-PRs that all touch the same surface area. ### Templates (#251, #252) - .github/ISSUE_TEMPLATE/bug_report.yml — required fields for repro (plugin version, platform, OS, project language, file count); the four pieces of context that are missing from ~every current bug report. - .github/ISSUE_TEMPLATE/feature_request.yml — leads with the *problem* rather than the proposed solution, which keeps maintainer review focused on whether to solve, not just how. - .github/ISSUE_TEMPLATE/question.yml — separate from bug to keep the bug queue triagable. - .github/ISSUE_TEMPLATE/config.yml — disables blank issues and routes general discussion to README + Discussions. - .github/PULL_REQUEST_TEMPLATE.md — includes the version-bump checklist that CLAUDE.md says must stay in sync across 5 manifests; otherwise every contributor learns this rule by getting their PR bounced. ### Community files - CODE_OF_CONDUCT.md — short, project-specific document that names the expectations and reporting path. Not a verbatim Contributor Covenant to keep it readable. - SECURITY.md — describes the project's local-only threat model explicitly so reporters know what's in / out of scope before they spend time on a writeup. Points at GitHub private vulnerability reporting as the primary channel. ### CI (#249) - ci.yml now also runs on pushes to main, not only PRs. Without this, a direct push to main (which happens when maintainers merge a PR branch locally) doesn't trigger CI, so a regression can land green- looking and stay broken for days. - Added a concurrency group that cancels stale runs for the same ref. Saves runner minutes and keeps the per-ref status meaningful. - Used `github.ref` (a controlled value), not user-controlled input, so no script-injection surface. ### package.json (#248) - Added description, license, repository, bugs, homepage, keywords — the standard set for npm package discoverability and so GitHub's community-profile check shows the project at 100%. --- .github/ISSUE_TEMPLATE/bug_report.yml | 85 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 34 +++++++++ .github/ISSUE_TEMPLATE/question.yml | 24 ++++++ .github/PULL_REQUEST_TEMPLATE.md | 26 +++++++ .github/workflows/ci.yml | 14 ++++ CODE_OF_CONDUCT.md | 39 ++++++++++ SECURITY.md | 51 +++++++++++++ package.json | 22 ++++++ 9 files changed, 303 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/question.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 SECURITY.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..0a53fd4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,85 @@ +name: Bug report +description: Report something that isn't working +title: "bug: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to file a bug. The more concrete you can be, + the faster it gets fixed. If you can include the analyzed project's + primary language and an approximate file count, that's gold. + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: What did you do, what did you expect to happen, and what actually happened? + placeholder: | + 1. Ran `/understand --full` on a ~3,000 file Rust project + 2. Expected: dashboard opens with the graph + 3. Got: dashboard shows "Failed to load graph: schema validation error" + validations: + required: true + + - type: textarea + id: reproduce + attributes: + label: Minimal reproduction + description: Smallest set of steps (or a link to a public repo) that reproduces the issue. + validations: + required: false + + - type: input + id: version + attributes: + label: Plugin version + description: Run `/understand --version` or check `~/.claude/plugins/cache/understand-anything/understand-anything/`. + placeholder: "e.g. 2.7.4" + validations: + required: true + + - type: dropdown + id: platform + attributes: + label: Platform / client + multiple: true + options: + - Claude Code (CLI) + - Claude Code (VS Code extension) + - Claude Code (JetBrains) + - Cursor + - GitHub Copilot CLI + - opencode + - Other (please describe in "What happened?") + validations: + required: true + + - type: input + id: os + attributes: + label: OS + Node version + placeholder: "e.g. macOS 14.5 (arm64), Node v22.6.0" + validations: + required: true + + - type: input + id: project-language + attributes: + label: Primary language of the analyzed project + placeholder: "e.g. TypeScript, Python, Swift…" + + - type: input + id: file-count + attributes: + label: Approximate file count of the analyzed project + placeholder: "e.g. ~3,000" + + - type: textarea + id: logs + attributes: + label: Relevant logs + description: | + Any console output, the contents of `.understand-anything/intermediate/` + if it still exists, or screenshots of the dashboard error. + render: shell diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3da6fc9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: README & docs + url: https://github.com/Lum1104/Understand-Anything#readme + about: Most usage questions are answered in the project README. + - name: Discussions + url: https://github.com/Lum1104/Understand-Anything/discussions + about: For open-ended discussion, design proposals, or sharing how you use the tool. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..bbffd7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,34 @@ +name: Feature request +description: Suggest an idea or improvement +title: "feat: " +labels: ["enhancement"] +body: + - type: textarea + id: problem + attributes: + label: What problem are you trying to solve? + description: Describe the user pain or workflow gap. Concrete examples help more than abstract framing. + placeholder: | + When onboarding new engineers to our 8k-file Go monorepo, they spend + days finding the auth boundary. /understand finds the files but the + dashboard doesn't visually separate "trusted" from "untrusted" zones. + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution (optional) + description: If you have an idea for what the feature should look like, share it. Skip if you'd rather just describe the problem. + + - type: textarea + id: alternatives + attributes: + label: Alternatives you've considered + description: Other tools, workarounds, or approaches you've tried. + + - type: input + id: scope + attributes: + label: Which part of the project? + placeholder: "skill / dashboard / core / agents / all" diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..2c0962b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,24 @@ +name: Question / usage help +description: Ask a question about how to use the project +title: "question: " +labels: ["question"] +body: + - type: markdown + attributes: + value: | + For general usage questions. If you found a bug, please use the bug + report template instead — it asks for the information needed to + reproduce. + + - type: textarea + id: question + attributes: + label: Your question + validations: + required: true + + - type: textarea + id: tried + attributes: + label: What have you already tried? + description: Helps avoid suggesting things you've already ruled out. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..fc16fb8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,26 @@ +## Summary + + + +## Linked issue(s) + + + +## How I tested this + + + +- [ ] `pnpm lint` +- [ ] `pnpm --filter @understand-anything/core test` +- [ ] `pnpm test` +- [ ] Manual smoke test (describe above) + +## Versioning + + + +- [ ] Version bumped in all five manifests, OR +- [ ] N/A — internal/docs-only change diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69771eb..9825406 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,21 @@ name: CI on: + # Run on every PR so a contributor's first push gets feedback. pull_request: + # Also run on direct pushes to main so the "main is green" signal is real. + # Without this, main can silently break for days when someone bypasses + # review. (#249) + push: + branches: [main] + +# Cancel any in-flight CI for the same ref when a new commit is pushed — +# saves runner minutes and keeps the latest commit's status the only one +# anyone reads. `github.ref` is a controlled value (refs/heads/* or +# refs/pull/*/merge), not user-controlled input, so it's safe to interpolate. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true jobs: ci: diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..d05f492 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,39 @@ +# Code of Conduct + +We want this project to be a welcoming place for everyone who wants to +contribute, learn, or use it — regardless of experience level, background, or +identity. + +## In short + +- **Be respectful.** Treat others the way you'd want to be treated. +- **Assume good intent.** Most disagreements are misunderstandings. +- **Be constructive.** Critique ideas, not people. Suggest improvements. +- **Keep it on-topic.** This project is about understanding codebases. + +## What's not OK + +- Personal attacks, insults, or sustained disruption of discussions. +- Posting someone's private information without their explicit permission. +- Repeatedly ignoring requests from maintainers to change behavior. + +## Reporting + +If you see behavior that violates this code, please open a private email to +the maintainer listed in the repository profile, or use GitHub's +[private vulnerability / abuse reporting](https://docs.github.com/en/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam). + +Maintainers will review reports and take whatever action they think is +appropriate — typically a private warning, sometimes a temporary or permanent +ban from the project. Reports will be kept confidential. + +## Scope + +This code applies in all project spaces: issues, pull requests, discussions, +commits, and any other project-affiliated channel. + +--- + +This document is intentionally short. It's based on the spirit of the +[Contributor Covenant](https://www.contributor-covenant.org/) without +reproducing it verbatim. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..a9fe378 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,51 @@ +# Reporting security issues + +Thanks for taking the time to disclose responsibly. + +## How to report + +Please use GitHub's [private vulnerability reporting](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability) +on this repository. That keeps the report visible to the maintainer without +exposing the details publicly. + +If private reporting is unavailable for any reason, open a regular issue +titled `security: brief description` **without** any exploit details, and +the maintainer will reply with a private channel. + +## What to include + +- A description of the issue and its potential impact. +- Steps to reproduce — minimal is fine, a full PoC is not required. +- Affected versions if you've narrowed them down. +- Whether you'd like to be credited in the eventual fix. + +## What to expect + +- Initial acknowledgement within a few days. +- A fix or mitigation plan within ~30 days for confirmed issues; longer for + cases that require coordinated disclosure with upstream dependencies. +- Public credit once a fix has shipped, if you'd like. + +## Scope + +This project is a **local-only** static-analysis tool. It runs on a +developer's machine, reads the analyzed project, and writes the resulting +graph to `.understand-anything/`. It does not phone home and the dashboard's +file-content endpoint is gated behind an access token and a graph-derived +path allowlist. + +Issues we care about: + +- Code execution triggered by analyzing a hostile project (e.g. a path in a + hostile file leaking outside the analyzed directory, or untrusted JSON in + the graph being executed by the dashboard). +- The dashboard's file-content endpoint serving files outside the allowlist. +- The `/understand` skill running shell commands derived from untrusted + paths or contents. + +Issues that are **out of scope**: + +- Bugs that require a malicious local user with write access to the + analyzed project (they could just edit the source directly). +- Anything that requires the user to copy a malicious URL and paste it back + into the dashboard. diff --git a/package.json b/package.json index efae49a..20b19b4 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,28 @@ "name": "understand-anything", "private": true, "type": "module", + "description": "An open-source tool combining LLM intelligence + static analysis to produce interactive dashboards for understanding codebases.", + "license": "MIT", + "homepage": "https://github.com/Lum1104/Understand-Anything#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/Lum1104/Understand-Anything.git" + }, + "bugs": { + "url": "https://github.com/Lum1104/Understand-Anything/issues" + }, + "keywords": [ + "claude-code", + "codebase-analysis", + "knowledge-graph", + "tree-sitter", + "llm", + "static-analysis", + "developer-tools", + "code-understanding", + "code-onboarding", + "claude-plugin" + ], "main": ".opencode/plugins/understand-anything.js", "packageManager": "pnpm@10.6.2+sha512.47870716bea1572b53df34ad8647b42962bc790ce2bf4562ba0f643237d7302a3d6a8ecef9e4bdfc01d23af1969aa90485d4cebb0b9638fa5ef1daef656f6c1b", "scripts": { From a6c653e36b528acb8d2d401fc8fbca6d64fd72cc Mon Sep 17 00:00:00 2001 From: Tirth Kanani Date: Sun, 31 May 2026 22:31:23 +0100 Subject: [PATCH 05/12] =?UTF-8?q?fix(extract-import-map):=20apply=20NodeNe?= =?UTF-8?q?xt=20.js=E2=86=92.ts=20rewrite=20(#294)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the silent near-edgeless-graph regression on any modern ESM TypeScript project. Reported in #294 with full repro + root-cause analysis. ### Why this matters Under `moduleResolution: NodeNext` (or `Node16` / `Bundler` with explicit extensions — the default for new TS-ESM projects since 2023), TypeScript does NOT rewrite import specifiers during compilation: // src/index.ts — real, idiomatic NodeNext source import { x } from './config.js'; // on disk: config.ts Before this fix, `probeWithExtensions` only tried APPENDING extensions to the import specifier: './config.js' → not in fileSet './config.js.ts', './config.js.tsx', './config.js.js', ... → all miss → returns null → edge dropped at merge as dangling Net result on the reporter's repro: a knowledge graph with hundreds of file nodes and almost no `imports` edges between them — silently removing exactly the dependency structure the graph is meant to show. ### Fix New `NODENEXT_REWRITES` table maps each compiled-output extension to the TypeScript source extensions that could have produced it: .js → [.ts, .tsx, .js, .jsx] .jsx → [.tsx, .jsx] .mjs → [.mts, .mjs, .ts] .cjs → [.cts, .cjs, .ts] `probeWithExtensions` now applies the rewrite when the import already ends with one of these extensions and no such file exists on disk. The rewrite runs BEFORE the legacy append-extensions loop — otherwise `./foo.js` would generate the nonsense candidate `foo.js.ts` and the append loop would never reach the actual `foo.ts`. ### Disambiguation If both `config.ts` and `config.js` exist on disk (rare, but possible during a partial migration), `import './config.js'` still resolves to the .js — that's an exact-disk match and what NodeNext compilation actually does. The rewrite only kicks in when the .js doesn't exist. ### Tests 6 new tests in `test_extract_import_map.test.mjs`: - The main #294 case (`.js → .ts`) - `.jsx → .tsx` and `.mjs → .mts` rewrites - Disambiguation when both `.ts` and `.js` exist on disk - Pure-JS projects still work (real `.js → .js` imports) - Historical no-extension probes unaffected - Missing files still return null (rewrite can't invent targets) Total: 202 tests passing (was 196). Closes #294 --- .../test_extract_import_map.test.mjs | 144 ++++++++++++++++++ .../skills/understand/extract-import-map.mjs | 42 ++++- 2 files changed, 185 insertions(+), 1 deletion(-) diff --git a/tests/skill/understand/test_extract_import_map.test.mjs b/tests/skill/understand/test_extract_import_map.test.mjs index ee64136..e25a644 100644 --- a/tests/skill/understand/test_extract_import_map.test.mjs +++ b/tests/skill/understand/test_extract_import_map.test.mjs @@ -237,6 +237,150 @@ describe('extract-import-map.mjs — TypeScript / JavaScript resolver', () => { 'packages/foo/src/y.ts', ); }); + + // ── #294: NodeNext / ESM TypeScript `.js → .ts` rewrite ──────────────── + // + // Under `moduleResolution: NodeNext`, TypeScript does NOT rewrite import + // specifiers during compilation — what you write in the .ts source is + // emitted verbatim. Because Node's ESM loader requires explicit file + // extensions at runtime, the TS source must already spell the import with + // the `.js` extension that will only be correct AFTER compilation: + // + // import { x } from './config.js'; // on disk: config.ts + // + // Before the fix, every such import resolved to null, leaving ESM-TS + // projects with a near-edgeless knowledge graph. + + it('resolves NodeNext .js → .ts relative imports (the main #294 case)', () => { + projectRoot = setupTree({ + 'src/index.ts': `import { resolveBackend } from './llm-backend-selector.js';\nimport { loadConfig } from './config.js';\n`, + 'src/llm-backend-selector.ts': `export function resolveBackend() {}\n`, + 'src/config.ts': `export function loadConfig() {}\n`, + }); + + const result = runScript(projectRoot, { + projectRoot, + files: [ + { path: 'src/index.ts', language: 'typescript', fileCategory: 'code' }, + { path: 'src/llm-backend-selector.ts', language: 'typescript', fileCategory: 'code' }, + { path: 'src/config.ts', language: 'typescript', fileCategory: 'code' }, + ], + }); + + expect(result.status).toBe(0); + expect(result.output.importMap['src/index.ts']).toEqual([ + 'src/config.ts', + 'src/llm-backend-selector.ts', + ]); + }); + + it('resolves NodeNext .jsx → .tsx and .mjs → .mts rewrites', () => { + projectRoot = setupTree({ + 'src/index.ts': `import Comp from './Comp.jsx';\nimport { fn } from './worker.mjs';\n`, + 'src/Comp.tsx': `export default function Comp() {}\n`, + 'src/worker.mts': `export function fn() {}\n`, + }); + + const result = runScript(projectRoot, { + projectRoot, + files: [ + { path: 'src/index.ts', language: 'typescript', fileCategory: 'code' }, + { path: 'src/Comp.tsx', language: 'typescript', fileCategory: 'code' }, + { path: 'src/worker.mts', language: 'typescript', fileCategory: 'code' }, + ], + }); + + expect(result.status).toBe(0); + expect(result.output.importMap['src/index.ts']).toEqual([ + 'src/Comp.tsx', + 'src/worker.mts', + ]); + }); + + it('resolves to the .js when both .ts and .js exist on disk', () => { + // Rare but possible during a partial migration: both `config.ts` and + // `config.js` exist. An `import './config.js'` is an exact-disk match + // and should resolve to that exact file — the NodeNext rewrite only + // kicks in when the .js *doesn't* exist on disk. We assert this to + // pin the disambiguation and avoid future regressions where the rewrite + // accidentally prefers `.ts` over an existing `.js`. + projectRoot = setupTree({ + 'src/index.ts': `import { x } from './config.js';\n`, + 'src/config.ts': `export const x = 1;\n`, + 'src/config.js': `export const x = 1;\n`, + }); + + const result = runScript(projectRoot, { + projectRoot, + files: [ + { path: 'src/index.ts', language: 'typescript', fileCategory: 'code' }, + { path: 'src/config.ts', language: 'typescript', fileCategory: 'code' }, + { path: 'src/config.js', language: 'javascript', fileCategory: 'code' }, + ], + }); + + expect(result.status).toBe(0); + expect(result.output.importMap['src/index.ts']).toEqual(['src/config.js']); + }); + + it('still resolves traditional .js → .js imports unchanged', () => { + // The rewrite must not break the case where `.js` IS the real file on + // disk (pure JavaScript projects, untyped libraries). + projectRoot = setupTree({ + 'src/index.js': `import { x } from './util.js';\n`, + 'src/util.js': `export const x = 1;\n`, + }); + + const result = runScript(projectRoot, { + projectRoot, + files: [ + { path: 'src/index.js', language: 'javascript', fileCategory: 'code' }, + { path: 'src/util.js', language: 'javascript', fileCategory: 'code' }, + ], + }); + + expect(result.status).toBe(0); + expect(result.output.importMap['src/index.js']).toEqual(['src/util.js']); + }); + + it('leaves the historical "no extension" probe behaviour intact', () => { + // An import like `./utils` (no extension) must still go through the + // append-extensions loop and resolve to `./utils.ts` — the new rewrite + // path is only triggered when the import already ends with a compiled + // extension. + projectRoot = setupTree({ + 'src/index.ts': `import { foo } from './utils';\n`, + 'src/utils.ts': `export function foo() {}\n`, + }); + + const result = runScript(projectRoot, { + projectRoot, + files: [ + { path: 'src/index.ts', language: 'typescript', fileCategory: 'code' }, + { path: 'src/utils.ts', language: 'typescript', fileCategory: 'code' }, + ], + }); + + expect(result.status).toBe(0); + expect(result.output.importMap['src/index.ts']).toEqual(['src/utils.ts']); + }); + + it('returns null (no resolution) for a .js import whose .ts source is missing', () => { + // The rewrite must NOT silently invent a target when neither the .js nor + // the .ts file exists. The old behaviour would also return null for this + // case — we're verifying the rewrite path doesn't regress it. + projectRoot = setupTree({ + 'src/index.ts': `import './completely-missing.js';\n`, + }); + + const result = runScript(projectRoot, { + projectRoot, + files: [{ path: 'src/index.ts', language: 'typescript', fileCategory: 'code' }], + }); + + expect(result.status).toBe(0); + expect(result.output.importMap['src/index.ts']).toEqual([]); + }); }); describe('extract-import-map.mjs — Python resolver', () => { diff --git a/understand-anything-plugin/skills/understand/extract-import-map.mjs b/understand-anything-plugin/skills/understand/extract-import-map.mjs index 6c547d3..42dd7d4 100644 --- a/understand-anything-plugin/skills/understand/extract-import-map.mjs +++ b/understand-anything-plugin/skills/understand/extract-import-map.mjs @@ -369,15 +369,55 @@ const TS_EXT_PROBES = [ '/index.ts', '/index.tsx', '/index.js', '/index.jsx', ]; +/** + * NodeNext / Node16 / Bundler-with-explicit-extensions ESM TypeScript convention: + * TypeScript does NOT rewrite import specifiers during compilation, so source + * files import their COMPILED specifier (`./config.js`) even when only + * `./config.ts` exists on disk. We map each compiled-output extension to the + * TS source extensions that could have produced it, in priority order. + * + * Without this rewrite, ESM-TS projects (which is now the default for any new + * TS project) end up with a near-edgeless knowledge graph because every + * project-internal import fails to resolve. (#294) + */ +const NODENEXT_REWRITES = { + '.js': ['.ts', '.tsx', '.js', '.jsx'], + '.jsx': ['.tsx', '.jsx'], + '.mjs': ['.mts', '.mjs', '.ts'], + '.cjs': ['.cts', '.cjs', '.ts'], +}; + /** * Try ext probes against the file set for the given base path. Returns the * first matching project-relative path, or null. If the base path already has * a code extension AND exists in the file set, returns it directly. + * + * For NodeNext-style imports (`./foo.js` where only `./foo.ts` exists), apply + * the source-extension rewrite — see NODENEXT_REWRITES above. */ function probeWithExtensions(basePath, fileSet) { if (!basePath) return null; - // Exact match (import already had an extension) + // Exact match (import already had an extension that resolves on disk) if (fileSet.has(basePath)) return basePath; + + // NodeNext rewrite: if the basePath ends with a compiled-output extension + // but no such file exists, try the corresponding source extensions. We do + // this BEFORE the legacy "append extensions" loop because for an import + // like `./foo.js`, appending `.ts` would produce `foo.js.ts` (always wrong) + // while the correct candidate is `foo.ts`. + for (const [outExt, srcExts] of Object.entries(NODENEXT_REWRITES)) { + if (!basePath.endsWith(outExt)) continue; + const stem = basePath.slice(0, -outExt.length); + for (const srcExt of srcExts) { + const candidate = stem + srcExt; + if (fileSet.has(candidate)) return candidate; + } + // The basePath had an explicit compiled extension — don't fall through + // to the "append extensions" loop, which would produce nonsense like + // `foo.js.ts`. If NodeNext rewrite didn't find anything, return null. + return null; + } + for (const ext of TS_EXT_PROBES) { const candidate = basePath + ext; if (fileSet.has(candidate)) return candidate; From 92e76190aaa8a8ddf3ba8c9d0d2c3e7fea6998fa Mon Sep 17 00:00:00 2001 From: Bozheng Long Date: Wed, 3 Jun 2026 21:19:52 +0800 Subject: [PATCH 06/12] feat(understand): auto-detect conversation language on first run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When /understand runs with no --language flag and no stored outputLanguage, step 3.6 now infers the conversation language and — only when it is non-English — confirms once before generating, then persists the choice to config.json. English conversations keep the exact same silent `en` path; --language flag and stored config still take priority. README documents the behavior; version bumped 2.7.5 -> 2.7.6 across all five manifests (user-visible behavior change). Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude-plugin/plugin.json | 2 +- .copilot-plugin/plugin.json | 2 +- .cursor-plugin/plugin.json | 2 +- README.md | 2 ++ understand-anything-plugin/.claude-plugin/plugin.json | 2 +- understand-anything-plugin/package.json | 2 +- understand-anything-plugin/skills/understand/SKILL.md | 6 ++++-- 7 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 5c75bd7..839168b 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "understand-anything", "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", - "version": "2.7.5", + "version": "2.7.6", "author": { "name": "Lum1104" }, diff --git a/.copilot-plugin/plugin.json b/.copilot-plugin/plugin.json index b18679d..ea367dd 100644 --- a/.copilot-plugin/plugin.json +++ b/.copilot-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "understand-anything", "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", - "version": "2.7.5", + "version": "2.7.6", "author": { "name": "Lum1104" }, diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 075114d..a60b8bc 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "understand-anything", "displayName": "Understand Anything", "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", - "version": "2.7.5", + "version": "2.7.6", "author": { "name": "Lum1104" }, diff --git a/README.md b/README.md index 3bdb4db..806d4f4 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,8 @@ A multi-agent pipeline scans your project, extracts every file, function, class, # Supported languages: en (default), zh, zh-TW, ja, ko, ru ``` +On the **first run** in a project — when you don't pass `--language` and no language is stored yet — `/understand` detects the language you're conversing in. If it isn't English, it asks you to confirm (or override) before generating; English conversations are unaffected. Your choice is saved to `.understand-anything/config.json` and reused on every later run. + The `--language` parameter affects: - Node summaries and descriptions in the knowledge graph - Dashboard UI labels, buttons, and tooltips diff --git a/understand-anything-plugin/.claude-plugin/plugin.json b/understand-anything-plugin/.claude-plugin/plugin.json index 5c75bd7..839168b 100644 --- a/understand-anything-plugin/.claude-plugin/plugin.json +++ b/understand-anything-plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "understand-anything", "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", - "version": "2.7.5", + "version": "2.7.6", "author": { "name": "Lum1104" }, diff --git a/understand-anything-plugin/package.json b/understand-anything-plugin/package.json index 303ff98..85f70f0 100644 --- a/understand-anything-plugin/package.json +++ b/understand-anything-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@understand-anything/skill", - "version": "2.7.5", + "version": "2.7.6", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/understand-anything-plugin/skills/understand/SKILL.md b/understand-anything-plugin/skills/understand/SKILL.md index 610a9dd..35c0b11 100644 --- a/understand-anything-plugin/skills/understand/SKILL.md +++ b/understand-anything-plugin/skills/understand/SKILL.md @@ -140,8 +140,10 @@ Determine whether to run a full analysis or incremental update. - `chinese` → `zh`, `japanese` → `ja`, `korean` → `ko`, `english` → `en`, `spanish` → `es`, `french` → `fr`, `german` → `de`, `portuguese` → `pt`, `russian` → `ru`, `arabic` → `ar`, etc. - Locale variants: `zh-TW`, `zh-HK`, `zh-CN`, `pt-BR`, etc. are preserved as-is. - If `--language` is NOT specified: - - Check `$PROJECT_ROOT/.understand-anything/config.json` for an existing `outputLanguage` field. If present, use that. - - If no stored preference, default to `en` (English). + - **Stored preference wins.** If `$PROJECT_ROOT/.understand-anything/config.json` has an `outputLanguage` field, set `$OUTPUT_LANGUAGE` to it and skip the rest. + - **Otherwise detect (first run only).** Infer the predominant language of the user's conversation as an ISO 639-1 code (`$DETECTED_LANG`). If it is `en` or cannot be confidently determined, set `$OUTPUT_LANGUAGE=en` and proceed silently — no prompt (English users see no change). + - **If `$DETECTED_LANG` ≠ `en`, confirm once before analyzing:** tell the user you detected `` and ask whether to generate all content in it; they press Enter/"yes" to accept, or type another language code/name to override (normalize via the friendly-name map above). If running non-interactively (no reply possible), skip the wait, use `$DETECTED_LANG`, and print a one-line notice instead of blocking. + - **Persist** the resolved `$OUTPUT_LANGUAGE` (including `en`) into `config.json` so it never re-prompts for this project. - If `--language` IS specified: - Update `$PROJECT_ROOT/.understand-anything/config.json` with the new language: merge `{"outputLanguage": ""}` into existing config. - Store as `$OUTPUT_LANGUAGE` for use throughout all phases. From 55d0ab23362eafa789a6b4c56c34587444d64a98 Mon Sep 17 00:00:00 2001 From: Bozheng Long Date: Wed, 3 Jun 2026 21:19:52 +0800 Subject: [PATCH 07/12] docs: design + implementation plan for language auto-detection Co-Authored-By: Claude Opus 4.8 (1M context) --- .../2026-06-03-language-auto-detection.md | 210 ++++++++++++++++++ ...26-06-03-language-auto-detection-design.md | 147 ++++++++++++ 2 files changed, 357 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-03-language-auto-detection.md create mode 100644 docs/superpowers/specs/2026-06-03-language-auto-detection-design.md diff --git a/docs/superpowers/plans/2026-06-03-language-auto-detection.md b/docs/superpowers/plans/2026-06-03-language-auto-detection.md new file mode 100644 index 0000000..8e3e217 --- /dev/null +++ b/docs/superpowers/plans/2026-06-03-language-auto-detection.md @@ -0,0 +1,210 @@ +# Conversation-Language Auto-Detection Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** On the first `/understand` run in a project, detect the user's conversation language and confirm it before generating — without changing anything for English users. + +**Architecture:** A pure prompt-logic change. The entire language decision lives in one place — `SKILL.md` step 3.6. We expand the `if --language NOT specified` branch into a resolution chain that adds conversation-language detection + a non-English-only, first-run-only confirmation gate before the existing `en` default. The resolved value is persisted to `config.json` so the gate fires at most once per project. A README sentence documents it. + +**Tech Stack:** Markdown skill prompt (`SKILL.md`), Markdown docs (`README.md`). No code, no schema, no TypeScript. There is no automated-test hook for skill-prompt behavior, so verification is the manual scenario walkthrough in Task 3 (this matches how the existing `--language` flag is verified). + +--- + +## File Structure + +| File | Responsibility | Change | +|---|---|---| +| `understand-anything-plugin/skills/understand/SKILL.md` | The `/understand` skill prompt; step 3.6 resolves `$OUTPUT_LANGUAGE`. | Rewrite the `If --language is NOT specified` sub-block (currently lines 142–144). | +| `README.md` | User-facing docs; *Localized output* section (~line 121). | Add one paragraph describing first-run auto-detection. | + +No other files. The `outputLanguage` field already exists in `packages/core/src/types.ts:119`; the `locales/.md` injection at `SKILL.md` step 4 (line ~424) and the `$LANGUAGE_DIRECTIVE` template (lines ~148–151) are unchanged and require no edits. + +--- + +## Task 1: Expand the language-resolution branch in SKILL.md + +**Files:** +- Modify: `understand-anything-plugin/skills/understand/SKILL.md` (step 3.6, the `If --language is NOT specified` sub-block) + +- [ ] **Step 1: Confirm the current text is unchanged** + +Run: `sed -n '142,144p' understand-anything-plugin/skills/understand/SKILL.md` + +Expected output (exactly these three lines): +``` + - If `--language` is NOT specified: + - Check `$PROJECT_ROOT/.understand-anything/config.json` for an existing `outputLanguage` field. If present, use that. + - If no stored preference, default to `en` (English). +``` + +If the text differs, STOP and re-read step 3.6 to find the equivalent block before editing — line numbers may have drifted. + +- [ ] **Step 2: Replace the block** + +Use the Edit tool on `understand-anything-plugin/skills/understand/SKILL.md`. + +`old_string` (match exactly, including leading spaces): +``` + - If `--language` is NOT specified: + - Check `$PROJECT_ROOT/.understand-anything/config.json` for an existing `outputLanguage` field. If present, use that. + - If no stored preference, default to `en` (English). +``` + +`new_string` (describes the confirmation *intent* in one instruction rather than +hard-coding a literal prompt box — the skill is a prompt the model interprets): +``` + - If `--language` is NOT specified: + - **Stored preference wins.** If `$PROJECT_ROOT/.understand-anything/config.json` has an `outputLanguage` field, set `$OUTPUT_LANGUAGE` to it and skip the rest. + - **Otherwise detect (first run only).** Infer the predominant language of the user's conversation as an ISO 639-1 code (`$DETECTED_LANG`). If it is `en` or cannot be confidently determined, set `$OUTPUT_LANGUAGE=en` and proceed silently — no prompt (English users see no change). + - **If `$DETECTED_LANG` ≠ `en`, confirm once before analyzing:** tell the user you detected `` and ask whether to generate all content in it; they press Enter/"yes" to accept, or type another language code/name to override (normalize via the friendly-name map above). If running non-interactively (no reply possible), skip the wait, use `$DETECTED_LANG`, and print a one-line notice instead of blocking. + - **Persist** the resolved `$OUTPUT_LANGUAGE` (including `en`) into `config.json` so it never re-prompts for this project. +``` + +- [ ] **Step 3: Verify the edit landed and is well-formed** + +Run: `sed -n '142,170p' understand-anything-plugin/skills/understand/SKILL.md` + +Expected: the new multi-level block is present; the very next content after it is the unchanged `- If `--language` IS specified:` line. Confirm no duplicate `default to en` line remains. + +Run: `grep -c 'default to \`en\`' understand-anything-plugin/skills/understand/SKILL.md` +Expected: `0` (the old standalone default line is gone; the new wording says "set `$OUTPUT_LANGUAGE` to `en`"). + +- [ ] **Step 4: Commit** + +```bash +git add understand-anything-plugin/skills/understand/SKILL.md +git commit -m "feat(understand): detect conversation language on first run + +Expand SKILL.md step 3.6 with conversation-language detection as a +fallback before the en default, gated behind a first-run-only, +non-English-only confirmation. Resolved value is persisted to +config.json so the gate fires at most once. English users see no change. + +Co-Authored-By: Claude Opus 4.8 (1M context) " +``` + +--- + +## Task 2: Document first-run auto-detection in README + +**Files:** +- Modify: `README.md` (*Localized output* section, before the "The `--language` parameter affects:" line, ~line 130) + +- [ ] **Step 1: Confirm the anchor line exists** + +Run: `grep -n 'The `--language` parameter affects:' README.md` +Expected: one match around line 130. + +- [ ] **Step 2: Insert the auto-detection paragraph** + +Use the Edit tool on `README.md`. + +`old_string`: +``` +The `--language` parameter affects: +``` + +`new_string`: +``` +On the **first run** in a project — when you don't pass `--language` and no language is stored yet — `/understand` detects the language you're conversing in. If it isn't English, it asks you to confirm (or override) before generating; English conversations are unaffected. Your choice is saved to `.understand-anything/config.json` and reused on every later run. + +The `--language` parameter affects: +``` + +- [ ] **Step 3: Verify** + +Run: `grep -n 'first run' README.md` +Expected: one match with the new sentence, located just above "The `--language` parameter affects:". + +- [ ] **Step 4: Commit** + +```bash +git add README.md +git commit -m "docs: note first-run conversation-language auto-detection + +Co-Authored-By: Claude Opus 4.8 (1M context) " +``` + +--- + +## Task 3: Manual verification (no automated test hook exists) + +There is no unit-test harness for skill-prompt behavior. Verify by reasoning through each scenario against the edited step 3.6 text and confirming the prompt logic produces the right `$OUTPUT_LANGUAGE` and `config.json` write. Record the result of each in the PR description. + +- [ ] **Step 1: Walk the five scenarios** + +For each, trace the edited step 3.6 and confirm the expected outcome: + +| # | Situation (fresh project, no `config.json`) | Expected | +|---|---|---| +| 1 | Conversation in Chinese, run `/understand` | Gate appears → confirm → `$OUTPUT_LANGUAGE=zh`; `config.json` gets `"outputLanguage":"zh"` | +| 2 | Re-run in the same project (config now has `zh`) | No gate (stored preference wins); generates `zh` | +| 3 | Conversation in English, run `/understand` | No gate; `$OUTPUT_LANGUAGE=en`; English output (no regression) | +| 4 | `--language ja` on a fresh project | No gate (flag wins); `config.json` gets `"outputLanguage":"ja"` | +| 5 | Detected `zh`, user types `en` at the gate | `$OUTPUT_LANGUAGE=en`; `config.json` gets `"outputLanguage":"en"` | + +- [ ] **Step 2: Confirm no-regression invariant** + +Re-read the edited block and confirm: there is NO code path where an English-only conversation with no flag/config produces a prompt. (Scenario 3 must be silent.) This is the single most important property for upstream acceptance. + +- [ ] **Step 3: Optional live smoke test** + +If you want a real run: in a throwaway repo with no `.understand-anything/config.json`, converse briefly in Chinese, run `/understand`, and confirm the gate appears and `config.json` is written with `outputLanguage: "zh"` after confirming. (Skip if a full analysis run is too costly; the trace in Step 1 is sufficient for the PR.) + +--- + +## Task 4: Open the PR + +- [ ] **Step 1: Push the branch** + +```bash +git push -u origin feat/understand-language-auto-detection +``` + +- [ ] **Step 2: Create the PR** + +```bash +gh pr create --title "feat(understand): detect conversation language on first run" --body "$(cat <<'EOF' +## What + +On the first `/understand` run in a project (no `--language` flag, no stored `outputLanguage`), the skill now detects the language of the conversation and — **only when it is not English** — asks the user to confirm or override before generating. The choice is persisted to `.understand-anything/config.json` and the gate never fires again. + +## Why + +The output language defaulted silently to English. A user conversing in Chinese would run the simplest command and only discover the English output after paying the full cost of an analysis run, then had to re-run with `--language zh`. + +## Zero change for English users + +The confirmation gate fires **only** when the detected language is non-English and no language has been chosen yet. English conversations follow the exact same silent `en` path as before. `--language` flag and stored config both take priority over detection. + +## Scope + +- Only `understand-anything-plugin/skills/understand/SKILL.md` step 3.6 + a README sentence. +- No code/schema changes. Other skills and the auto-update hook are unchanged (separate known gaps). +- Non-interactive invocations fall back to the detected language with a notice instead of blocking. + +Design + plan: `docs/superpowers/specs/2026-06-03-language-auto-detection-design.md`, `docs/superpowers/plans/2026-06-03-language-auto-detection.md`. + +🤖 Generated with [Claude Code](https://claude.com/claude-code) +EOF +)" +``` + +--- + +## Self-Review + +**Spec coverage:** +- Resolution chain (param > config > detect > en) → Task 1 Step 2. ✓ +- Non-English-only, first-run-only gate → Task 1 Step 2 (gate "shown ONLY when `$DETECTED_LANG` ≠ `en`"). ✓ +- Persist resolved value incl. `en` → Task 1 Step 2 ("Persist the resolved value"). ✓ +- Edge: uncertain/mixed → `en` silent → Task 1 Step 2 first bullet. ✓ +- Edge: non-interactive fallback → Task 1 Step 2 ("Non-interactive fallback"). ✓ +- Edge: auto-update hook unaffected → no task needed (no code path touched); noted here. ✓ +- `locales/.md` already wired → unchanged, noted in File Structure. ✓ +- README sentence → Task 2. ✓ +- Manual verification scenarios (5) → Task 3. ✓ + +**Placeholder scan:** No "TBD/TODO/handle edge cases" left vague — every edge case has explicit resolved behavior. The ``/`` tokens inside the gate are intentional template placeholders the skill fills at runtime, not plan gaps. ✓ + +**Type/name consistency:** Variable names used consistently — `$OUTPUT_LANGUAGE`, `$DETECTED_LANG`, `outputLanguage` (config key), `--language` (flag). These match the existing names in SKILL.md step 3.6. ✓ diff --git a/docs/superpowers/specs/2026-06-03-language-auto-detection-design.md b/docs/superpowers/specs/2026-06-03-language-auto-detection-design.md new file mode 100644 index 0000000..c3b5278 --- /dev/null +++ b/docs/superpowers/specs/2026-06-03-language-auto-detection-design.md @@ -0,0 +1,147 @@ +# Conversation-Language Auto-Detection for `/understand` + +**Date:** 2026-06-03 +**Status:** Approved — ready for implementation plan +**Scope:** `understand-anything-plugin/skills/understand/SKILL.md` (primary), `README.md` (docs) + +## Problem + +`/understand` generates all LLM-authored content (node summaries, tags, layer +names, guided tours, language notes) in **English by default**. The output +language is controlled solely by an explicit `--language ` flag or a +previously stored `outputLanguage` value in `.understand-anything/config.json`. + +The skill performs **no detection** of the language the user is actually working +in. `SKILL.md` step 3.6 (lines 142–144) hard-defaults to `en` whenever the flag +and config are both absent: + +``` +- If `--language` is NOT specified: + - Check config.json for outputLanguage. If present, use that. + - If no stored preference, default to `en` (English). +``` + +**Observed failure:** A user conversing entirely in Chinese ran the simplest +`/understand` command and received an English knowledge graph. They only +discovered the mismatch after paying the full cost of an analysis run (time + +tokens), then had to re-run with `--language zh`. The default is silent and +undiscoverable — nothing surfaces the language decision at the point it matters. + +## Goal + +On **first analysis of a project**, infer the user's working language from the +conversation and confirm it before spending the analysis budget — without +changing anything for English-speaking users (the project's core audience) and +without breaking non-interactive invocations. + +## Non-Goals + +- No side-by-side bilingual output. Output remains single-language per graph. +- No changes to other content-generating skills (`understand-domain`, + `understand-knowledge`, `understand-explain`, etc.). They currently ignore + `outputLanguage` entirely; that is a separate, known gap left for follow-up + PRs. +- No changes to the autonomous auto-update hook path + (`hooks/auto-update-prompt.md`). It reuses the existing graph and does not + resolve a language, so detection never runs there. Noted as a known separate + gap, out of scope here. +- No code, schema, or TypeScript changes. The `outputLanguage` field already + exists in `ProjectConfig` (`packages/core/src/types.ts:119`). + +## Approach (chosen) + +Add conversation-language **detection as a fallback** in the resolution chain, +placed *before* the `en` default, gated behind a **first-run-only, non-English-only** +confirmation. This was selected over two alternatives discussed: + +- **(A) Personal workaround** (always pass `--language zh` / hand-edit config): + zero upstream value, rejected — the goal is a contributable fix. +- **(B, chosen) Detection-as-fallback in `SKILL.md`:** minimal diff, strictly + better for non-English users, invisible to English users. +- **(C) Always-prompt menu on every run:** rejected — reintroduces friction and + is the most likely to be rejected by upstream maintainers. + +The confirmation gate (rather than silent application) was chosen at the user's +direction for explicitness, but constrained so it never affects English users. + +## Detailed Design + +### Resolution chain (rewrite of `SKILL.md` step 3.6) + +Priority, highest first: + +1. **`--language ` flag present** → normalize via the existing + friendly-name map (line 140), persist to `config.json`, use. *(unchanged)* +2. **`outputLanguage` present in `config.json`** → use it. *(unchanged)* +3. **First run (no flag AND no config) → NEW: detect conversation language.** + - Infer the predominant language of the user's messages in the current + conversation → `$DETECTED_LANG` (ISO 639-1 code, e.g. `zh`, `ja`). + - **If `$DETECTED_LANG` is `en`, or cannot be confidently determined, or the + conversation is mixed/ambiguous** → set `$OUTPUT_LANGUAGE = en`, persist, + **proceed with no prompt.** (Preserves current behavior exactly for the + core audience.) + - **If `$DETECTED_LANG` ≠ `en`** → show the confirmation gate below, resolve + `$OUTPUT_LANGUAGE`, persist to `config.json`. + +In all branches the resolved value is written to `config.json` +(`{"outputLanguage": ""}` merged into existing config), so the gate fires +**at most once per project**. + +### Confirmation gate (only when `$DETECTED_LANG` ≠ `en`) + +Shown before any pipeline phase runs. `SKILL.md` describes the *intent* in one +instruction rather than hard-coding a literal prompt box — the skill is a prompt +the model interprets, so it renders the question itself at runtime. The +instruction tells the model to: + +- State the detected language and ask whether to generate all content in it. +- Accept Enter / "yes" / the detected code as confirmation → `$OUTPUT_LANGUAGE = $DETECTED_LANG`. +- Accept any other language code or friendly name as an override → normalize via + the existing friendly-name map (line ~140) and use it. This doubles as the + "chatting in Chinese but want English docs for my team" escape hatch. + +The instruction text in `SKILL.md` stays in **English** (skill prompts are +English); only the *generated content* becomes the target language. + +### Edge cases + +| Case | Behavior | +|---|---| +| Detection uncertain / mixed languages | Treat as `en`, proceed silently. Never block on a guess. | +| Non-interactive invocation (headless/CI, no user to answer) | Fall back to `$DETECTED_LANG` with a one-line notice instead of hanging on the gate; persist. Confirm is best-effort, never a hard block. | +| Autonomous auto-update hook | Unaffected — that path does not resolve language. | +| Detected language has a `locales/.md` file | Already wired at step 4 (line 424); no change. | +| Detected language has no locale file | `$LANGUAGE_DIRECTIVE` still applies (existing "skip silently" behavior). | + +## Files Touched + +- `understand-anything-plugin/skills/understand/SKILL.md` — rewrite the step 3.6 + `if --language NOT specified` branch into the 4-step resolution chain above. + **Primary change.** +- `README.md` — add a sentence under the *Localized output* section (~line 121) + noting first-run auto-detection. + +No other files. No schema, code, or test-harness files change. + +## Testing / Verification + +Prompt-logic change → no unit-test hook. Verification is manual and documented +in the PR description: + +1. Fresh project, no config, converse in Chinese, run `/understand` → gate + appears → confirm → content generated in `zh`; `config.json` contains + `outputLanguage: "zh"`. +2. Re-run in same project → no gate (config wins). +3. Fresh project, converse in English → no gate, English output (proves no + regression for the core audience). +4. Fresh project, `--language ja` → no gate, flag wins, config stores `ja`. +5. Fresh project, override at the gate (detected `zh`, type `en`) → English + output, config stores `en`. + +## Risks & Upstream Framing + +The main reviewer concern is *any* added interactivity. Mitigations are built in: +the gate is **first-run-only**, **non-English-only**, and **degrades gracefully** +when non-interactive. The PR description leads with: **"Zero behavior change for +English users; the gate only appears when the conversation is non-English and no +language has been chosen yet."** From 1f8d165f86491f5931ab27aa059066fdb0c18111 Mon Sep 17 00:00:00 2001 From: Tirth Kanani Date: Fri, 5 Jun 2026 16:19:31 +0100 Subject: [PATCH 08/12] fix(extract-import-map): preserve deterministic stderr order across concurrent loaders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the regression flagged by ZebangCheng on #346: under the parallelised `buildResolutionContext`, `loadTsConfigs` / `loadGoModules` / `loadPhpAutoloads` ran concurrently but each wrote warnings to stderr inline as it iterated read results, so a fixture with both a malformed `tsconfig.json` and a malformed `composer.json` could emit `composer, tsconfig` instead of the pre-PR `tsconfig, composer` depending on I/O timing. Each loader now buffers its warnings into a returned array and the caller drains them in canonical order (tsconfig → go → php) after `Promise.all`, restoring byte-identical stderr output. Added a regression test that fixtures both malformed configs and asserts the tsconfig warning precedes the composer warning in stderr. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../test_extract_import_map.test.mjs | 52 +++++++++++++++++++ .../skills/understand/extract-import-map.mjs | 39 +++++++++++--- 2 files changed, 83 insertions(+), 8 deletions(-) diff --git a/tests/skill/understand/test_extract_import_map.test.mjs b/tests/skill/understand/test_extract_import_map.test.mjs index ee64136..a4be65a 100644 --- a/tests/skill/understand/test_extract_import_map.test.mjs +++ b/tests/skill/understand/test_extract_import_map.test.mjs @@ -1492,3 +1492,55 @@ describe('extract-import-map.mjs — tree-sitter init graceful failure', () => { expect(result.output.stats.totalEdges).toBe(0); }); }); + +describe('extract-import-map.mjs — deterministic stderr ordering across loaders', () => { + let projectRoot; + + afterEach(() => { + if (projectRoot) { + rmSync(projectRoot, { recursive: true, force: true }); + projectRoot = null; + } + }); + + // Regression for the parallel-loader stderr-order bug surfaced in + // PR #346 review: tsconfig / go.mod / composer.json loaders now run + // concurrently, but warnings must still emit in the pre-PR canonical + // order (tsconfig → go → php). If the loaders streamed warnings + // mid-flight, I/O timing could reorder them — the assertions below + // catch that regression. + it('emits warnings in canonical order (tsconfig, go, php) regardless of I/O timing', () => { + projectRoot = setupTree({ + 'tsconfig.json': '{ "compilerOptions": { "baseUrl": ".", ', // unterminated + 'composer.json': '{ "autoload": { "psr-4": { "App\\\\": "src/" }, ', // unterminated + 'src/index.ts': `import { foo } from './foo';\n`, + 'src/foo.ts': `export const foo = 1;\n`, + 'src/Http/Controller.php': + ` Date: Fri, 5 Jun 2026 16:56:42 +0100 Subject: [PATCH 09/12] fix(skill): use mv-to-trash + delayed purge for Phase 7 cleanup (#301) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 7's `rm -rf` of the just-created `intermediate/` and `tmp/` dirs trips destructive-action gates on hardened hosts (e.g. freshness-window checks that flag deleting paths created moments earlier). Move them into a timestamped `.trash-/` instead; Phase 0 reclaims the space once the trash is older than 7 days, well past any freshness window. Behavior on normal hosts is unchanged — disk usage is identical after the next run's purge. Closes #301 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../skills/understand/SKILL.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/understand-anything-plugin/skills/understand/SKILL.md b/understand-anything-plugin/skills/understand/SKILL.md index 35c0b11..10bf9cc 100644 --- a/understand-anything-plugin/skills/understand/SKILL.md +++ b/understand-anything-plugin/skills/understand/SKILL.md @@ -129,6 +129,10 @@ Determine whether to run a full analysis or incremental update. mkdir -p $PROJECT_ROOT/.understand-anything/intermediate mkdir -p $PROJECT_ROOT/.understand-anything/tmp ``` +3.1. **Purge stale trash dirs.** Phase 7 cleanup `mv`s scratch dirs into `.trash-/` rather than `rm -rf`ing them directly (see issue #301), so that destructive-action gates on hardened hosts don't trip on just-created paths. Reclaim the space here once the trash is older than 7 days — by this point any freshness-window check has long since stopped caring about those dirs: + ```bash + find $PROJECT_ROOT/.understand-anything/ -maxdepth 1 -type d -name '.trash-*' -mtime +7 -exec rm -rf {} + 2>/dev/null || true + ``` 3.5. **Auto-update configuration:** - If `--auto-update` is in `$ARGUMENTS`: write `{"autoUpdate": true}` to `$PROJECT_ROOT/.understand-anything/config.json` - If `--no-auto-update` is in `$ARGUMENTS`: write `{"autoUpdate": false}` to `$PROJECT_ROOT/.understand-anything/config.json` @@ -772,17 +776,20 @@ Report to the user: `[Phase 7/7] Saving knowledge graph...` } ``` -4. Clean up intermediate files, **preserving `scan-result.json`** so future incremental runs can skip Phase 1 SCAN (see issue #293): +4. Clean up intermediate files, **preserving `scan-result.json`** so future incremental runs can skip Phase 1 SCAN (see issue #293). We `mv` scratch dirs into a timestamped `.trash-*` instead of `rm -rf`ing them directly — this avoids tripping destructive-action gates on hardened hosts (e.g. freshness-window checks) that flag deleting directories created moments earlier (see issue #301). The delayed-purge step in Phase 0 reclaims the space once the trash is older than 7 days. ```bash # Preserve scan-result.json — Phase 1's deterministic file inventory. # Future incremental runs (Phase 2 compute-batches.mjs --changed-files=…) # need this inventory; without it, Phase 1 must re-dispatch and pay ~157k # tokens / ~158s per incremental run. + TRASH="$PROJECT_ROOT/.understand-anything/.trash-$(date +%s)" + mkdir -p "$TRASH" INTER="$PROJECT_ROOT/.understand-anything/intermediate" if [ -d "$INTER" ]; then - find "$INTER" -mindepth 1 -maxdepth 1 -not -name 'scan-result.json' -exec rm -rf {} + + # Move every entry except scan-result.json into the trash dir. + find "$INTER" -mindepth 1 -maxdepth 1 -not -name 'scan-result.json' -exec mv {} "$TRASH/" \; 2>/dev/null || true fi - rm -rf $PROJECT_ROOT/.understand-anything/tmp + mv "$PROJECT_ROOT/.understand-anything/tmp" "$TRASH/" 2>/dev/null || true ``` 5. Report a summary to the user containing: From 8de0b3b11d395e8692fea4846d27275ad6d3ba5d Mon Sep 17 00:00:00 2001 From: chengyongru Date: Tue, 9 Jun 2026 16:16:04 +0800 Subject: [PATCH 10/12] feat(install): add Nanobot platform support --- .claude-plugin/plugin.json | 2 +- .copilot-plugin/plugin.json | 2 +- .cursor-plugin/plugin.json | 2 +- README.md | 5 +++-- READMEs/README.es-ES.md | 5 +++-- READMEs/README.ja-JP.md | 5 +++-- READMEs/README.ko-KR.md | 5 +++-- READMEs/README.ru-RU.md | 5 +++-- READMEs/README.tr-TR.md | 5 +++-- READMEs/README.zh-CN.md | 5 +++-- READMEs/README.zh-TW.md | 5 +++-- install.ps1 | 1 + install.sh | 1 + understand-anything-plugin/.claude-plugin/plugin.json | 2 +- understand-anything-plugin/package.json | 2 +- 15 files changed, 31 insertions(+), 21 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 839168b..adb068e 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "understand-anything", "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", - "version": "2.7.6", + "version": "2.7.7", "author": { "name": "Lum1104" }, diff --git a/.copilot-plugin/plugin.json b/.copilot-plugin/plugin.json index ea367dd..c5413bd 100644 --- a/.copilot-plugin/plugin.json +++ b/.copilot-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "understand-anything", "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", - "version": "2.7.6", + "version": "2.7.7", "author": { "name": "Lum1104" }, diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index a60b8bc..d1a52ea 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "understand-anything", "displayName": "Understand Anything", "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", - "version": "2.7.6", + "version": "2.7.7", "author": { "name": "Lum1104" }, diff --git a/README.md b/README.md index 806d4f4..d3c5e82 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ Understand-Anything works across multiple AI coding platforms. /plugin install understand-anything ``` -### One-line install (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Trae) +### One-line install (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Trae / Nanobot) **macOS / Linux:** ```bash @@ -202,7 +202,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins The installer clones the repo to `~/.understand-anything/repo` and creates the right symlinks for the chosen platform. Restart your CLI/IDE afterwards. -- Supported `` values: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`, `trae` +- Supported `` values: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`, `trae`, `nanobot` - Update later: `./install.sh --update` - Uninstall: `./install.sh --uninstall ` @@ -243,6 +243,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin | Cline | ✅ Supported | `install.sh cline` | | KIMI CLI | ✅ Supported | `install.sh kimi` | | Trae | ✅ Supported | `install.sh trae` | +| Nanobot | ✅ Supported | `install.sh nanobot` | --- diff --git a/READMEs/README.es-ES.md b/READMEs/README.es-ES.md index 542d83b..9017977 100644 --- a/READMEs/README.es-ES.md +++ b/READMEs/README.es-ES.md @@ -181,7 +181,7 @@ Understand-Anything funciona en múltiples plataformas de codificación con IA. /plugin install understand-anything ``` -### Instalación de una línea (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI) +### Instalación de una línea (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Nanobot) **macOS / Linux:** ```bash @@ -197,7 +197,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins El instalador clona el repositorio en `~/.understand-anything/repo` y crea los enlaces simbólicos correspondientes para la plataforma elegida. Reinicia tu CLI/IDE al terminar. -- Valores soportados de ``: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi` +- Valores soportados de ``: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`, `nanobot` - Actualizar más adelante: `./install.sh --update` - Desinstalar: `./install.sh --uninstall ` @@ -237,6 +237,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin | Hermes | ✅ Soportado | `install.sh hermes` | | Cline | ✅ Soportado | `install.sh cline` | | KIMI CLI | ✅ Soportado | `install.sh kimi` | +| Nanobot | ✅ Soportado | `install.sh nanobot` | --- diff --git a/READMEs/README.ja-JP.md b/READMEs/README.ja-JP.md index cbe8a43..5454e46 100644 --- a/READMEs/README.ja-JP.md +++ b/READMEs/README.ja-JP.md @@ -182,7 +182,7 @@ Understand-Anythingは複数のAIコーディングプラットフォームで /plugin install understand-anything ``` -### ワンラインインストール(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI) +### ワンラインインストール(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Nanobot) **macOS / Linux:** ```bash @@ -198,7 +198,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins インストーラーはリポジトリを `~/.understand-anything/repo` にクローンし、選択したプラットフォーム用のシンボリックリンクを作成します。完了後はCLI/IDEを再起動してください。 -- サポートされる `` 値:`gemini`、`codex`、`opencode`、`pi`、`openclaw`、`antigravity`、`vibe`、`vscode`、`hermes`、`cline`、`kimi` +- サポートされる `` 値:`gemini`、`codex`、`opencode`、`pi`、`openclaw`、`antigravity`、`vibe`、`vscode`、`hermes`、`cline`、`kimi`、`nanobot` - 後で更新:`./install.sh --update` - アンインストール:`./install.sh --uninstall ` @@ -238,6 +238,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin | Hermes | ✅ サポート | `install.sh hermes` | | Cline | ✅ サポート | `install.sh cline` | | KIMI CLI | ✅ サポート | `install.sh kimi` | +| Nanobot | ✅ サポート | `install.sh nanobot` | --- diff --git a/READMEs/README.ko-KR.md b/READMEs/README.ko-KR.md index 2e51742..8de315e 100644 --- a/READMEs/README.ko-KR.md +++ b/READMEs/README.ko-KR.md @@ -181,7 +181,7 @@ Understand-Anything은 다양한 AI 코딩 플랫폼에서 사용할 수 있습 /plugin install understand-anything ``` -### 한 줄 설치 (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI) +### 한 줄 설치 (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Nanobot) **macOS / Linux:** ```bash @@ -197,7 +197,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins 설치 스크립트는 저장소를 `~/.understand-anything/repo`에 클론하고 선택한 플랫폼에 맞는 심볼릭 링크를 생성합니다. 설치 후 CLI 또는 IDE를 재시작하세요. -- 지원되는 `` 값: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi` +- 지원되는 `` 값: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`, `nanobot` - 이후 업데이트: `./install.sh --update` - 제거: `./install.sh --uninstall ` @@ -237,6 +237,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin | Hermes | ✅ 지원 | `install.sh hermes` | | Cline | ✅ 지원 | `install.sh cline` | | KIMI CLI | ✅ 지원 | `install.sh kimi` | +| Nanobot | ✅ 지원 | `install.sh nanobot` | --- diff --git a/READMEs/README.ru-RU.md b/READMEs/README.ru-RU.md index 3b11cb3..8585525 100644 --- a/READMEs/README.ru-RU.md +++ b/READMEs/README.ru-RU.md @@ -182,7 +182,7 @@ Understand-Anything работает с несколькими платформ /plugin install understand-anything ``` -### Установка одной командой (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI) +### Установка одной командой (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Nanobot) **macOS / Linux:** ```bash @@ -198,7 +198,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins Установщик клонирует репозиторий в `~/.understand-anything/repo` и создаёт нужные симлинки для выбранной платформы. После установки перезапустите свой CLI/IDE. -- Поддерживаемые значения ``: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi` +- Поддерживаемые значения ``: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`, `nanobot` - Обновление: `./install.sh --update` - Удаление: `./install.sh --uninstall ` @@ -238,6 +238,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin | Hermes | ✅ Поддерживается | `install.sh hermes` | | Cline | ✅ Поддерживается | `install.sh cline` | | KIMI CLI | ✅ Поддерживается | `install.sh kimi` | +| Nanobot | ✅ Поддерживается | `install.sh nanobot` | --- diff --git a/READMEs/README.tr-TR.md b/READMEs/README.tr-TR.md index 7f3168d..380b69c 100644 --- a/READMEs/README.tr-TR.md +++ b/READMEs/README.tr-TR.md @@ -182,7 +182,7 @@ Understand-Anything birden fazla AI kodlama platformunda çalışır. /plugin install understand-anything ``` -### Tek satırlık kurulum (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI) +### Tek satırlık kurulum (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Nanobot) **macOS / Linux:** ```bash @@ -198,7 +198,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins Kurulum betiği depoyu `~/.understand-anything/repo` dizinine klonlar ve seçilen platform için uygun sembolik bağlantıları oluşturur. Sonrasında CLI/IDE'ni yeniden başlat. -- Desteklenen `` değerleri: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi` +- Desteklenen `` değerleri: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`, `nanobot` - Daha sonra güncelle: `./install.sh --update` - Kaldır: `./install.sh --uninstall ` @@ -238,6 +238,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin | Hermes | ✅ Destekleniyor | `install.sh hermes` | | Cline | ✅ Destekleniyor | `install.sh cline` | | KIMI CLI | ✅ Destekleniyor | `install.sh kimi` | +| Nanobot | ✅ Destekleniyor | `install.sh nanobot` | --- diff --git a/READMEs/README.zh-CN.md b/READMEs/README.zh-CN.md index 87fedc6..0150abb 100644 --- a/READMEs/README.zh-CN.md +++ b/READMEs/README.zh-CN.md @@ -181,7 +181,7 @@ Understand-Anything 可在多个 AI 编码平台上运行。 /plugin install understand-anything ``` -### 一行命令安装(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI) +### 一行命令安装(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Nanobot) **macOS / Linux:** ```bash @@ -197,7 +197,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins 安装脚本会将仓库克隆到 `~/.understand-anything/repo`,并为所选平台创建相应的符号链接。安装完成后请重启 CLI 或 IDE。 -- 支持的 `` 取值:`gemini`、`codex`、`opencode`、`pi`、`openclaw`、`antigravity`、`vibe`、`vscode`、`hermes`、`cline`、`kimi` +- 支持的 `` 取值:`gemini`、`codex`、`opencode`、`pi`、`openclaw`、`antigravity`、`vibe`、`vscode`、`hermes`、`cline`、`kimi`、`nanobot` - 后续更新:`./install.sh --update` - 卸载:`./install.sh --uninstall ` @@ -237,6 +237,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin | Hermes | ✅ 支持 | `install.sh hermes` | | Cline | ✅ 支持 | `install.sh cline` | | KIMI CLI | ✅ 支持 | `install.sh kimi` | +| Nanobot | ✅ 支持 | `install.sh nanobot` | --- diff --git a/READMEs/README.zh-TW.md b/READMEs/README.zh-TW.md index 0b4ef96..20eb94b 100644 --- a/READMEs/README.zh-TW.md +++ b/READMEs/README.zh-TW.md @@ -181,7 +181,7 @@ Understand-Anything 可在多個 AI 編碼平台上執行。 /plugin install understand-anything ``` -### 一行指令安裝(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI) +### 一行指令安裝(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Nanobot) **macOS / Linux:** ```bash @@ -197,7 +197,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins 安裝指令稿會將儲存庫複製到 `~/.understand-anything/repo`,並為所選平台建立相應的符號連結。安裝完成後請重新啟動 CLI 或 IDE。 -- 支援的 `` 取值:`gemini`、`codex`、`opencode`、`pi`、`openclaw`、`antigravity`、`vibe`、`vscode`、`hermes`、`cline`、`kimi` +- 支援的 `` 取值:`gemini`、`codex`、`opencode`、`pi`、`openclaw`、`antigravity`、`vibe`、`vscode`、`hermes`、`cline`、`kimi`、`nanobot` - 後續更新:`./install.sh --update` - 解除安裝:`./install.sh --uninstall ` @@ -237,6 +237,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin | Hermes | ✅ 支援 | `install.sh hermes` | | Cline | ✅ 支援 | `install.sh cline` | | KIMI CLI | ✅ 支援 | `install.sh kimi` | +| Nanobot | ✅ 支援 | `install.sh nanobot` | --- diff --git a/install.ps1 b/install.ps1 index 1a4b4df..5c9c43f 100644 --- a/install.ps1 +++ b/install.ps1 @@ -39,6 +39,7 @@ $Platforms = [ordered]@{ cline = @{ Target = (Join-Path $HOME '.cline\skills'); Style = 'folder' } kimi = @{ Target = (Join-Path $HOME '.kimi\skills'); Style = 'folder' } trae = @{ Target = (Join-Path $HOME '.trae\skills'); Style = 'per-skill' } + nanobot = @{ Target = (Join-Path $HOME '.nanobot\workspace\skills'); Style = 'per-skill' } } function Show-Usage { diff --git a/install.sh b/install.sh index 8ff4293..7d49c2c 100755 --- a/install.sh +++ b/install.sh @@ -40,6 +40,7 @@ hermes|$HOME/.hermes/skills|folder cline|$HOME/.cline/skills|folder kimi|$HOME/.kimi/skills|folder trae|$HOME/.trae/skills|per-skill +nanobot|$HOME/.nanobot/workspace/skills|per-skill EOF } diff --git a/understand-anything-plugin/.claude-plugin/plugin.json b/understand-anything-plugin/.claude-plugin/plugin.json index 839168b..adb068e 100644 --- a/understand-anything-plugin/.claude-plugin/plugin.json +++ b/understand-anything-plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "understand-anything", "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", - "version": "2.7.6", + "version": "2.7.7", "author": { "name": "Lum1104" }, diff --git a/understand-anything-plugin/package.json b/understand-anything-plugin/package.json index 85f70f0..a47eaac 100644 --- a/understand-anything-plugin/package.json +++ b/understand-anything-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@understand-anything/skill", - "version": "2.7.6", + "version": "2.7.7", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", From 7254852f4adab010c12fe9fbc16eca716763c88b Mon Sep 17 00:00:00 2001 From: chienvon Date: Tue, 9 Jun 2026 14:26:48 -0700 Subject: [PATCH 11/12] sync egonex organization metadata --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 6 +- .copilot-plugin/plugin.json | 6 +- .cursor-plugin/plugin.json | 6 +- .github/FUNDING.yml | 1 - .github/ISSUE_TEMPLATE/config.yml | 4 +- LICENSE | 1 + README.md | 41 ++- READMEs/README.es-ES.md | 34 +- READMEs/README.ja-JP.md | 34 +- READMEs/README.ko-KR.md | 34 +- READMEs/README.ru-RU.md | 34 +- READMEs/README.tr-TR.md | 34 +- READMEs/README.zh-CN.md | 34 +- READMEs/README.zh-TW.md | 34 +- .../2026-03-15-homepage-implementation.md | 10 +- ...18-multi-platform-simple-implementation.md | 12 +- .../specs/2026-03-15-homepage-design.md | 2 +- ...2026-03-18-multi-platform-simple-design.md | 2 +- ...-04-01-business-domain-knowledge-design.md | 2 +- ...tic-batching-and-output-chunking-design.md | 2 +- homepage/src/components/Footer.astro | 21 +- homepage/src/components/Hero.astro | 327 +++++------------- homepage/src/components/Install.astro | 2 +- homepage/src/components/Nav.astro | 16 +- install.ps1 | 2 +- install.sh | 6 +- package.json | 6 +- .../.claude-plugin/plugin.json | 6 +- .../packages/dashboard/src/App.tsx | 2 + .../src/components/WarningBanner.tsx | 2 +- .../src/onboard-builder.ts | 2 +- 32 files changed, 309 insertions(+), 418 deletions(-) delete mode 100644 .github/FUNDING.yml diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 39a8451..92c37f9 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -4,7 +4,7 @@ "description": "LLM-powered codebase analysis producing interactive knowledge graphs, guided tours, and deep-dive explanations" }, "owner": { - "name": "Lum1104" + "name": "Egonex" }, "plugins": [ { diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 839168b..77b67be 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -3,10 +3,10 @@ "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", "version": "2.7.6", "author": { - "name": "Lum1104" + "name": "Egonex" }, - "homepage": "https://github.com/Lum1104/Understand-Anything", - "repository": "https://github.com/Lum1104/Understand-Anything", + "homepage": "https://github.com/Egonex-AI/Understand-Anything", + "repository": "https://github.com/Egonex-AI/Understand-Anything", "license": "MIT", "keywords": [ "codebase-analysis", diff --git a/.copilot-plugin/plugin.json b/.copilot-plugin/plugin.json index ea367dd..1637426 100644 --- a/.copilot-plugin/plugin.json +++ b/.copilot-plugin/plugin.json @@ -3,10 +3,10 @@ "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", "version": "2.7.6", "author": { - "name": "Lum1104" + "name": "Egonex" }, - "homepage": "https://github.com/Lum1104/Understand-Anything", - "repository": "https://github.com/Lum1104/Understand-Anything", + "homepage": "https://github.com/Egonex-AI/Understand-Anything", + "repository": "https://github.com/Egonex-AI/Understand-Anything", "license": "MIT", "keywords": ["codebase-analysis", "knowledge-graph", "architecture", "onboarding", "dashboard"], "skills": "./understand-anything-plugin/skills/", diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index a60b8bc..42af4b2 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -4,10 +4,10 @@ "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", "version": "2.7.6", "author": { - "name": "Lum1104" + "name": "Egonex" }, - "homepage": "https://github.com/Lum1104/Understand-Anything", - "repository": "https://github.com/Lum1104/Understand-Anything", + "homepage": "https://github.com/Egonex-AI/Understand-Anything", + "repository": "https://github.com/Egonex-AI/Understand-Anything", "license": "MIT", "keywords": ["codebase-analysis", "knowledge-graph", "architecture", "onboarding", "dashboard"], "skills": "./understand-anything-plugin/skills/", diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index cb75fdf..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -patreon: Lum1104 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3da6fc9..069970c 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ blank_issues_enabled: false contact_links: - name: README & docs - url: https://github.com/Lum1104/Understand-Anything#readme + url: https://github.com/Egonex-AI/Understand-Anything#readme about: Most usage questions are answered in the project README. - name: Discussions - url: https://github.com/Lum1104/Understand-Anything/discussions + url: https://github.com/Egonex-AI/Understand-Anything/discussions about: For open-ended discussion, design proposals, or sharing how you use the tool. diff --git a/LICENSE b/LICENSE index 87c7ab2..5df102e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2026 Yuxiang Lin +Copyright (c) 2026 Infinite Universe, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 806d4f4..8c21786 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,13 @@

- Lum1104%2FUnderstand-Anything | Trendshift + Understand Anything. Understand Anyone. +
+ AI should help people, not replace them. +

+ +

+ Understand Anything | Trendshift

@@ -16,7 +22,7 @@

Quick Start - License: MIT + License: MIT Claude Code Codex Copilot @@ -27,6 +33,7 @@ Trae Homepage Live Demo + Understand Anyone

@@ -34,9 +41,9 @@

- 💬 Join the Discord community → + An open-source project from Egonex
- Ask questions, share what you've built, get help from the community. + Originally created by Lum1104.

--- @@ -106,7 +113,7 @@ Point `/understand-knowledge` at a [Karpathy-pattern LLM wiki](https://gist.gith ### 1. Install the plugin ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -182,7 +189,7 @@ Understand-Anything works across multiple AI coding platforms. ### Claude Code (Native) ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -190,14 +197,14 @@ Understand-Anything works across multiple AI coding platforms. **macOS / Linux:** ```bash -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash # or skip the prompt by passing the platform: -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex ``` **Windows (PowerShell):** ```powershell -iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex +iwr -useb https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.ps1 | iex ``` The installer clones the repo to `~/.understand-anything/repo` and creates the right symlinks for the chosen platform. Restart your CLI/IDE afterwards. @@ -210,7 +217,7 @@ The installer clones the repo to `~/.understand-anything/repo` and creates the r Cursor auto-discovers the plugin via `.cursor-plugin/plugin.json` when this repo is cloned. No manual installation needed — just clone and open in Cursor. -If auto-discovery doesn't pick it up, install it manually: open **Cursor Settings → Plugins**, paste `https://github.com/Lum1104/Understand-Anything` into the search field, and add it from there. +If auto-discovery doesn't pick it up, install it manually: open **Cursor Settings → Plugins**, paste `https://github.com/Egonex-AI/Understand-Anything` into the search field, and add it from there. ### VS Code + GitHub Copilot @@ -221,7 +228,7 @@ For personal skills (available across all projects), run the `install.sh` above ### Copilot CLI ```bash -copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin +copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin ``` ### Platform Compatibility @@ -250,7 +257,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin The graph is just JSON — **commit it once, and teammates skip the pipeline**. Good for onboarding, PR reviews, and docs-as-code. -> **Example:** [GoogleCloudPlatform/microservices-demo (fork)](https://github.com/Lum1104/microservices-demo) — Go / Java / Python / Node reference with a committed graph. +> **Example:** [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/microservices-demo) — Go / Java / Python / Node reference with a committed graph. **What to commit:** everything in `.understand-anything/` *except* `intermediate/` and `diff-overlay.json` (those are local scratch). @@ -333,11 +340,11 @@ Please open an issue first for major changes so we can discuss the approach. ## Star History - + - - - Star History Chart + + + Star History Chart @@ -346,5 +353,5 @@ Please open an issue first for major changes so we can discuss the approach.

- MIT License © Lum1104 + MIT License © Yuxiang Lin and Infinite Universe, Inc.

diff --git a/READMEs/README.es-ES.md b/READMEs/README.es-ES.md index 542d83b..e30d773 100644 --- a/READMEs/README.es-ES.md +++ b/READMEs/README.es-ES.md @@ -6,7 +6,7 @@

- Lum1104%2FUnderstand-Anything | Trendshift + Understand Anything | Trendshift

@@ -15,7 +15,7 @@

Quick Start - License: MIT + License: MIT Claude Code Codex Copilot @@ -31,9 +31,9 @@

- 💬 Únete a la comunidad de Discord → + An open-source project from Egonex
- Pregunta, comparte lo que construyes y recibe ayuda de la comunidad. + Originally created by Lum1104.

--- @@ -103,7 +103,7 @@ Apunta `/understand-knowledge` a un [wiki LLM con patrón Karpathy](https://gist ### 1. Instala el plugin ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -177,7 +177,7 @@ Understand-Anything funciona en múltiples plataformas de codificación con IA. ### Claude Code (Nativo) ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -185,14 +185,14 @@ Understand-Anything funciona en múltiples plataformas de codificación con IA. **macOS / Linux:** ```bash -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash # o pasa la plataforma directamente para saltar el prompt: -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex ``` **Windows (PowerShell):** ```powershell -iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex +iwr -useb https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.ps1 | iex ``` El instalador clona el repositorio en `~/.understand-anything/repo` y crea los enlaces simbólicos correspondientes para la plataforma elegida. Reinicia tu CLI/IDE al terminar. @@ -205,7 +205,7 @@ El instalador clona el repositorio en `~/.understand-anything/repo` y crea los e Cursor detecta automáticamente el plugin a través de `.cursor-plugin/plugin.json` cuando se clona este repositorio. No requiere instalación manual: simplemente clona y abre en Cursor. -Si la detección automática no lo reconoce, instálalo manualmente: abre **Cursor Settings → Plugins**, pega `https://github.com/Lum1104/Understand-Anything` en el campo de búsqueda y añádelo desde allí. +Si la detección automática no lo reconoce, instálalo manualmente: abre **Cursor Settings → Plugins**, pega `https://github.com/Egonex-AI/Understand-Anything` en el campo de búsqueda y añádelo desde allí. ### VS Code + GitHub Copilot @@ -216,7 +216,7 @@ Para habilidades personales (disponibles en todos los proyectos), ejecuta el `in ### Copilot CLI ```bash -copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin +copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin ``` ### Compatibilidad de Plataformas @@ -244,7 +244,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin El grafo es solo JSON — **confírmalo una vez y tus compañeros se saltan el pipeline**. Ideal para onboarding, revisiones de PR y flujos docs-as-code. -> **Ejemplo:** [GoogleCloudPlatform/microservices-demo (fork)](https://github.com/Lum1104/microservices-demo) — referencia políglota (Go / Java / Python / Node) con el grafo ya confirmado. +> **Ejemplo:** [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/microservices-demo) — referencia políglota (Go / Java / Python / Node) con el grafo ya confirmado. **Qué confirmar:** todo lo que hay en `.understand-anything/` *excepto* `intermediate/` y `diff-overlay.json` (archivos temporales locales). @@ -327,11 +327,11 @@ Para cambios importantes, abre primero un issue para que podamos discutir el enf ## Historial de Stars - + - - - Star History Chart + + + Star History Chart @@ -340,5 +340,5 @@ Para cambios importantes, abre primero un issue para que podamos discutir el enf

- Licencia MIT © Lum1104 + MIT License © Yuxiang Lin and Infinite Universe, Inc.

diff --git a/READMEs/README.ja-JP.md b/READMEs/README.ja-JP.md index cbe8a43..459ccc1 100644 --- a/READMEs/README.ja-JP.md +++ b/READMEs/README.ja-JP.md @@ -7,7 +7,7 @@

- Lum1104%2FUnderstand-Anything | Trendshift + Understand Anything | Trendshift

@@ -16,7 +16,7 @@

クイックスタート - License: MIT + License: MIT Claude Code Codex Copilot @@ -32,9 +32,9 @@

- 💬 Discord コミュニティに参加 → + An open-source project from Egonex
- 質問・作品の共有・コミュニティとの交流はこちらから。 + Originally created by Lum1104.

--- @@ -104,7 +104,7 @@ Understand Anything は [Claude Code Plugin](https://code.claude.com/docs/en/plu ### 1. プラグインをインストール ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -178,7 +178,7 @@ Understand-Anythingは複数のAIコーディングプラットフォームで ### Claude Code(ネイティブ) ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -186,14 +186,14 @@ Understand-Anythingは複数のAIコーディングプラットフォームで **macOS / Linux:** ```bash -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash # プラットフォームを直接指定して対話プロンプトをスキップすることもできます: -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex ``` **Windows(PowerShell):** ```powershell -iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex +iwr -useb https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.ps1 | iex ``` インストーラーはリポジトリを `~/.understand-anything/repo` にクローンし、選択したプラットフォーム用のシンボリックリンクを作成します。完了後はCLI/IDEを再起動してください。 @@ -206,7 +206,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins Cursorはこのリポジトリをクローンすると `.cursor-plugin/plugin.json` 経由でプラグインを自動検出します。手動インストールは不要です — クローンしてCursorで開くだけです。 -自動検出されない場合は、手動でインストールしてください:**Cursor Settings → Plugins** を開き、検索欄に `https://github.com/Lum1104/Understand-Anything` を貼り付けて追加します。 +自動検出されない場合は、手動でインストールしてください:**Cursor Settings → Plugins** を開き、検索欄に `https://github.com/Egonex-AI/Understand-Anything` を貼り付けて追加します。 ### VS Code + GitHub Copilot @@ -217,7 +217,7 @@ GitHub Copilot拡張機能(v1.108+)をインストールしたVS Codeは、` ### Copilot CLI ```bash -copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin +copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin ``` ### プラットフォーム互換性 @@ -245,7 +245,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin グラフは単なる JSON ファイルです——**一度コミットすれば、チームメンバーはパイプラインを実行せずに済みます**。オンボーディング、PR レビュー、docs-as-code ワークフローに最適です。 -> **例:** [GoogleCloudPlatform/microservices-demo(fork)](https://github.com/Lum1104/microservices-demo) —— コミット済みのグラフを含む Go / Java / Python / Node のリファレンスプロジェクト。 +> **例:** [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/microservices-demo) —— コミット済みのグラフを含む Go / Java / Python / Node のリファレンスプロジェクト。 **コミット対象:** `.understand-anything/` 内のすべてのファイル。ただし `intermediate/` と `diff-overlay.json` は除きます(これらはローカルの一時ファイルです)。 @@ -328,11 +328,11 @@ git add .gitattributes .understand-anything/ ## Star History - + - - - Star History Chart + + + Star History Chart @@ -341,5 +341,5 @@ git add .gitattributes .understand-anything/

- MIT License © Lum1104 + MIT License © Yuxiang Lin and Infinite Universe, Inc.

diff --git a/READMEs/README.ko-KR.md b/READMEs/README.ko-KR.md index 2e51742..51e3d3e 100644 --- a/READMEs/README.ko-KR.md +++ b/READMEs/README.ko-KR.md @@ -6,7 +6,7 @@

- Lum1104%2FUnderstand-Anything | Trendshift + Understand Anything | Trendshift

@@ -15,7 +15,7 @@

Quick Start - License: MIT + License: MIT Claude Code Codex Copilot @@ -31,9 +31,9 @@

- 💬 Discord 커뮤니티 참여하기 → + An open-source project from Egonex
- 질문하고, 만든 것을 공유하고, 커뮤니티의 도움을 받으세요. + Originally created by Lum1104.

--- @@ -103,7 +103,7 @@ Understand Anything은 [Claude Code Plugin](https://code.claude.com/docs/en/plug ### 1. 플러그인 설치 ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -177,7 +177,7 @@ Understand-Anything은 다양한 AI 코딩 플랫폼에서 사용할 수 있습 ### Claude Code (네이티브) ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -185,14 +185,14 @@ Understand-Anything은 다양한 AI 코딩 플랫폼에서 사용할 수 있습 **macOS / Linux:** ```bash -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash # 플랫폼 이름을 직접 전달하여 프롬프트를 건너뛸 수도 있습니다: -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex ``` **Windows (PowerShell):** ```powershell -iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex +iwr -useb https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.ps1 | iex ``` 설치 스크립트는 저장소를 `~/.understand-anything/repo`에 클론하고 선택한 플랫폼에 맞는 심볼릭 링크를 생성합니다. 설치 후 CLI 또는 IDE를 재시작하세요. @@ -205,7 +205,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins 이 저장소를 클론하면 Cursor가 `.cursor-plugin/plugin.json`을 통해 플러그인을 자동으로 인식합니다. 수동 설치가 필요 없습니다. 클론 후 Cursor에서 열기만 하면 됩니다. -자동 인식이 되지 않으면 수동으로 설치하세요: **Cursor Settings → Plugins**를 열고 검색란에 `https://github.com/Lum1104/Understand-Anything`를 붙여넣은 뒤 추가하세요. +자동 인식이 되지 않으면 수동으로 설치하세요: **Cursor Settings → Plugins**를 열고 검색란에 `https://github.com/Egonex-AI/Understand-Anything`를 붙여넣은 뒤 추가하세요. ### VS Code + GitHub Copilot @@ -216,7 +216,7 @@ GitHub Copilot(v1.108+)이 설치된 VS Code는 `.copilot-plugin/plugin.json`을 ### Copilot CLI ```bash -copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin +copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin ``` ### 플랫폼 호환성 @@ -244,7 +244,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin 그래프는 단지 JSON 파일입니다 — **한 번만 커밋하면 팀원은 파이프라인을 건너뛸 수 있습니다**. 온보딩, PR 리뷰, docs-as-code 워크플로에 적합합니다. -> **예시:** [GoogleCloudPlatform/microservices-demo (fork)](https://github.com/Lum1104/microservices-demo) — 커밋된 그래프를 포함한 Go / Java / Python / Node 레퍼런스 프로젝트. +> **예시:** [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/microservices-demo) — 커밋된 그래프를 포함한 Go / Java / Python / Node 레퍼런스 프로젝트. **커밋할 대상:** `.understand-anything/` 내부의 모든 파일. 단, `intermediate/` 와 `diff-overlay.json` 은 제외합니다 (이들은 로컬 임시 파일입니다). @@ -327,11 +327,11 @@ git add .gitattributes .understand-anything/ ## Star 히스토리 - + - - - Star History Chart + + + Star History Chart @@ -340,5 +340,5 @@ git add .gitattributes .understand-anything/

- MIT 라이선스 © Lum1104 + MIT License © Yuxiang Lin and Infinite Universe, Inc.

diff --git a/READMEs/README.ru-RU.md b/READMEs/README.ru-RU.md index 3b11cb3..b853ec7 100644 --- a/READMEs/README.ru-RU.md +++ b/READMEs/README.ru-RU.md @@ -7,7 +7,7 @@

- Lum1104%2FUnderstand-Anything | Trendshift + Understand Anything | Trendshift

@@ -16,7 +16,7 @@

Quick Start - License: MIT + License: MIT Claude Code Codex Copilot @@ -32,9 +32,9 @@

- 💬 Присоединяйтесь к сообществу в Discord → + An open-source project from Egonex
- Задавайте вопросы, делитесь тем, что вы построили, получайте помощь от сообщества. + Originally created by Lum1104.

--- @@ -104,7 +104,7 @@ Understand Anything — это [плагин для Claude Code](https://code.cl ### 1. Установите плагин ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -178,7 +178,7 @@ Understand-Anything работает с несколькими платформ ### Claude Code (нативно) ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -186,14 +186,14 @@ Understand-Anything работает с несколькими платформ **macOS / Linux:** ```bash -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash # или передайте платформу, чтобы пропустить интерактивный выбор: -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex ``` **Windows (PowerShell):** ```powershell -iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex +iwr -useb https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.ps1 | iex ``` Установщик клонирует репозиторий в `~/.understand-anything/repo` и создаёт нужные симлинки для выбранной платформы. После установки перезапустите свой CLI/IDE. @@ -206,7 +206,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins Cursor автоматически обнаруживает плагин через `.cursor-plugin/plugin.json` при клонировании этого репозитория. Ручная установка не требуется — просто склонируйте и откройте в Cursor. -Если автообнаружение не сработало, установите вручную: откройте **Cursor Settings → Plugins**, вставьте `https://github.com/Lum1104/Understand-Anything` в поле поиска и добавьте оттуда. +Если автообнаружение не сработало, установите вручную: откройте **Cursor Settings → Plugins**, вставьте `https://github.com/Egonex-AI/Understand-Anything` в поле поиска и добавьте оттуда. ### VS Code + GitHub Copilot @@ -217,7 +217,7 @@ VS Code с GitHub Copilot (v1.108+) автоматически обнаружи ### Copilot CLI ```bash -copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin +copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin ``` ### Совместимость с платформами @@ -245,7 +245,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin Граф — это просто JSON. **Зафиксируйте его один раз, и коллеги смогут пропустить весь пайплайн.** Полезно для онбординга, ревью PR и подхода docs-as-code. -> **Пример:** [GoogleCloudPlatform/microservices-demo (форк)](https://github.com/Lum1104/microservices-demo) — мультиязыковой проект (Go / Java / Python / Node) с уже зафиксированным графом. +> **Пример:** [GoogleCloudPlatform/microservices-demo (форк)](https://github.com/GoogleCloudPlatform/microservices-demo) — мультиязыковой проект (Go / Java / Python / Node) с уже зафиксированным графом. **Что коммитить:** всё содержимое `.understand-anything/`, *кроме* `intermediate/` и `diff-overlay.json` (это локальные временные файлы). @@ -328,11 +328,11 @@ git add .gitattributes .understand-anything/ ## История звёзд - + - - - Star History Chart + + + Star History Chart @@ -341,5 +341,5 @@ git add .gitattributes .understand-anything/

- Лицензия MIT © Lum1104 + MIT License © Yuxiang Lin and Infinite Universe, Inc.

diff --git a/READMEs/README.tr-TR.md b/READMEs/README.tr-TR.md index 7f3168d..5d1e7d3 100644 --- a/READMEs/README.tr-TR.md +++ b/READMEs/README.tr-TR.md @@ -7,7 +7,7 @@

- Lum1104%2FUnderstand-Anything | Trendshift + Understand Anything | Trendshift

@@ -16,7 +16,7 @@

Hızlı Başlangıç - Lisans: MIT + Lisans: MIT Claude Code Codex Copilot @@ -32,9 +32,9 @@

- 💬 Discord topluluğuna katıl → + An open-source project from Egonex
- Sorular sor, yaptıklarını paylaş, topluluktan yardım al. + Originally created by Lum1104.

--- @@ -104,7 +104,7 @@ Alan görünümüne geçin ve kodunuzun gerçek iş süreçleriyle nasıl eşle ### 1. Eklentiyi yükle ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -178,7 +178,7 @@ Understand-Anything birden fazla AI kodlama platformunda çalışır. ### Claude Code (Yerli) ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -186,14 +186,14 @@ Understand-Anything birden fazla AI kodlama platformunda çalışır. **macOS / Linux:** ```bash -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash # veya platformu doğrudan geçirerek soruyu atla: -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex ``` **Windows (PowerShell):** ```powershell -iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex +iwr -useb https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.ps1 | iex ``` Kurulum betiği depoyu `~/.understand-anything/repo` dizinine klonlar ve seçilen platform için uygun sembolik bağlantıları oluşturur. Sonrasında CLI/IDE'ni yeniden başlat. @@ -206,7 +206,7 @@ Kurulum betiği depoyu `~/.understand-anything/repo` dizinine klonlar ve seçile Bu depo klonlandığında Cursor, eklentiyi `.cursor-plugin/plugin.json` aracılığıyla otomatik olarak keşfeder. Manuel kurulum gerekmez — sadece klonla ve Cursor'da aç. -Otomatik keşif çalışmazsa manuel kur: **Cursor Settings → Plugins**'i aç, arama alanına `https://github.com/Lum1104/Understand-Anything` yapıştır ve oradan ekle. +Otomatik keşif çalışmazsa manuel kur: **Cursor Settings → Plugins**'i aç, arama alanına `https://github.com/Egonex-AI/Understand-Anything` yapıştır ve oradan ekle. ### VS Code + GitHub Copilot @@ -217,7 +217,7 @@ Tüm projelerde kullanmak için kişisel beceri olarak kurmak istersen yukarıda ### Copilot CLI ```bash -copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin +copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin ``` ### Platform Uyumluluğu @@ -245,7 +245,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin Graf yalnızca bir JSON dosyasıdır — **bir kez commit'leyin, ekip arkadaşlarınız pipeline'ı çalıştırmadan kullansın**. Yeni üye oryantasyonu, PR incelemeleri ve docs-as-code iş akışları için idealdir. -> **Örnek:** [GoogleCloudPlatform/microservices-demo (fork)](https://github.com/Lum1104/microservices-demo) — commit'lenmiş grafı içeren Go / Java / Python / Node çok dilli referans projesi. +> **Örnek:** [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/microservices-demo) — commit'lenmiş grafı içeren Go / Java / Python / Node çok dilli referans projesi. **Neyi commit'leyin:** `.understand-anything/` içindeki her şey, *ancak* `intermediate/` ve `diff-overlay.json` hariç (bunlar yerel geçici dosyalardır). @@ -328,11 +328,11 @@ Büyük değişiklikler için lütfen önce bir issue aç ki yaklaşımı tartı ## Star Geçmişi - + - - - Star Geçmişi Grafiği + + + Star Geçmişi Grafiği @@ -341,5 +341,5 @@ Büyük değişiklikler için lütfen önce bir issue aç ki yaklaşımı tartı

- MIT Lisansı © Lum1104 + MIT License © Yuxiang Lin and Infinite Universe, Inc.

diff --git a/READMEs/README.zh-CN.md b/READMEs/README.zh-CN.md index 87fedc6..b05c02b 100644 --- a/READMEs/README.zh-CN.md +++ b/READMEs/README.zh-CN.md @@ -6,7 +6,7 @@

- Lum1104%2FUnderstand-Anything | Trendshift + Understand Anything | Trendshift

@@ -15,7 +15,7 @@

Quick Start - License: MIT + License: MIT Claude Code Codex Copilot @@ -31,9 +31,9 @@

- 💬 加入 Discord 社区 → + An open-source project from Egonex
- 来提问、分享你的项目、和社区一起讨论。 + Originally created by Lum1104.

--- @@ -103,7 +103,7 @@ Understand Anything 是一个 [Claude Code Plugin](https://code.claude.com/docs/ ### 1. 安装插件 ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -177,7 +177,7 @@ Understand-Anything 可在多个 AI 编码平台上运行。 ### Claude Code(原生) ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -185,14 +185,14 @@ Understand-Anything 可在多个 AI 编码平台上运行。 **macOS / Linux:** ```bash -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash # 也可以直接传入平台名跳过交互提示: -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex ``` **Windows(PowerShell):** ```powershell -iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex +iwr -useb https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.ps1 | iex ``` 安装脚本会将仓库克隆到 `~/.understand-anything/repo`,并为所选平台创建相应的符号链接。安装完成后请重启 CLI 或 IDE。 @@ -205,7 +205,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins 克隆此仓库后,Cursor 会自动通过 `.cursor-plugin/plugin.json`文件发现插件。无需手动安装 — 只需克隆并在 Cursor 中打开即可。 -若自动发现未生效,可手动安装:打开 **Cursor Settings → Plugins**,在搜索框中粘贴 `https://github.com/Lum1104/Understand-Anything` 并添加。 +若自动发现未生效,可手动安装:打开 **Cursor Settings → Plugins**,在搜索框中粘贴 `https://github.com/Egonex-AI/Understand-Anything` 并添加。 ### VS Code + GitHub Copilot @@ -216,7 +216,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins ### Copilot CLI ```bash -copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin +copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin ``` ### 多平台兼容 @@ -244,7 +244,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin 图谱就是一份 JSON 文件——**提交一次,团队成员就可以跳过整条流水线**。适合新人上手、PR 评审和 docs-as-code 工作流。 -> **示例:** [GoogleCloudPlatform/microservices-demo(fork)](https://github.com/Lum1104/microservices-demo) —— 包含已提交图谱的 Go / Java / Python / Node 多语言参考项目。 +> **示例:** [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/microservices-demo) —— 包含已提交图谱的 Go / Java / Python / Node 多语言参考项目。 **需要提交的内容:** `.understand-anything/` 下的全部文件,*除了* `intermediate/` 和 `diff-overlay.json`(这些是本地临时文件)。 @@ -327,11 +327,11 @@ git add .gitattributes .understand-anything/ ## Star 历史记录 - + - - - Star History Chart + + + Star History Chart @@ -340,5 +340,5 @@ git add .gitattributes .understand-anything/

- MIT 许可证 © Lum1104 + MIT License © Yuxiang Lin and Infinite Universe, Inc.

diff --git a/READMEs/README.zh-TW.md b/READMEs/README.zh-TW.md index 0b4ef96..753e5ac 100644 --- a/READMEs/README.zh-TW.md +++ b/READMEs/README.zh-TW.md @@ -6,7 +6,7 @@

- Lum1104%2FUnderstand-Anything | Trendshift + Understand Anything | Trendshift

@@ -15,7 +15,7 @@

Quick Start - License: MIT + License: MIT Claude Code Codex Copilot @@ -31,9 +31,9 @@

- 💬 加入 Discord 社群 → + An open-source project from Egonex
- 來提問、分享你的專案、和社群一起討論。 + Originally created by Lum1104.

--- @@ -103,7 +103,7 @@ Understand Anything 是一個 [Claude Code Plugin](https://code.claude.com/docs/ ### 1. 安裝外掛程式 ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -177,7 +177,7 @@ Understand-Anything 可在多個 AI 編碼平台上執行。 ### Claude Code(原生) ```bash -/plugin marketplace add Lum1104/Understand-Anything +/plugin marketplace add Egonex-AI/Understand-Anything /plugin install understand-anything ``` @@ -185,14 +185,14 @@ Understand-Anything 可在多個 AI 編碼平台上執行。 **macOS / Linux:** ```bash -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash # 也可以直接傳入平台名稱跳過互動提示: -curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex +curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex ``` **Windows(PowerShell):** ```powershell -iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex +iwr -useb https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.ps1 | iex ``` 安裝指令稿會將儲存庫複製到 `~/.understand-anything/repo`,並為所選平台建立相應的符號連結。安裝完成後請重新啟動 CLI 或 IDE。 @@ -205,7 +205,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins 複製此儲存庫後,Cursor 會自動透過 `.cursor-plugin/plugin.json` 檔案發現外掛程式。無需手動安裝 — 只需複製並在 Cursor 中開啟即可。 -若自動發現未生效,可手動安裝:開啟 **Cursor Settings → Plugins**,在搜尋框中貼上 `https://github.com/Lum1104/Understand-Anything` 並新增。 +若自動發現未生效,可手動安裝:開啟 **Cursor Settings → Plugins**,在搜尋框中貼上 `https://github.com/Egonex-AI/Understand-Anything` 並新增。 ### VS Code + GitHub Copilot @@ -216,7 +216,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins ### Copilot CLI ```bash -copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin +copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin ``` ### 多平台相容性 @@ -244,7 +244,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin 圖譜就是一份 JSON 檔案——**提交一次,團隊成員就可以跳過整條流水線**。適合新人上手、PR 審查和 docs-as-code 工作流程。 -> **範例:** [GoogleCloudPlatform/microservices-demo(fork)](https://github.com/Lum1104/microservices-demo) —— 包含已提交圖譜的 Go / Java / Python / Node 多語言參考專案。 +> **範例:** [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/microservices-demo) —— 包含已提交圖譜的 Go / Java / Python / Node 多語言參考專案。 **需要提交的內容:** `.understand-anything/` 底下的全部檔案,*除了* `intermediate/` 與 `diff-overlay.json`(這些是本機暫存檔)。 @@ -327,11 +327,11 @@ git add .gitattributes .understand-anything/ ## Star 歷史記錄 - + - - - Star History Chart + + + Star History Chart @@ -340,5 +340,5 @@ git add .gitattributes .understand-anything/

- MIT 授權條款 © Lum1104 + MIT License © Yuxiang Lin and Infinite Universe, Inc.

diff --git a/docs/superpowers/plans/2026-03-15-homepage-implementation.md b/docs/superpowers/plans/2026-03-15-homepage-implementation.md index 4f82f32..7b587f8 100644 --- a/docs/superpowers/plans/2026-03-15-homepage-implementation.md +++ b/docs/superpowers/plans/2026-03-15-homepage-implementation.md @@ -274,7 +274,7 @@ Create `homepage/src/components/Nav.astro`: ```astro --- -const githubUrl = 'https://github.com/Lum1104/Understand-Anything'; +const githubUrl = 'https://github.com/Egonex-AI/Understand-Anything'; ---