chore: import upstream snapshot with attribution
Deploy site to GitHub Pages / build (push) Failing after 1s
Deploy site to GitHub Pages / deploy (push) Has been skipped

This commit is contained in:
wehub-resource-sync
2026-07-13 12:02:56 +08:00
commit 5f98960d22
495 changed files with 149129 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Add-lang benchmark for ONE repo:
# clone -> wipe+index (with the codegraph on PATH) -> verify extraction ->
# with/without retrieval A/B (reuses scripts/agent-eval/run-all.sh).
#
# Assumes the codegraph dev build is already built + linked on PATH — the skill
# runs `npm run build && ./scripts/local-install.sh` ONCE before looping repos.
# The A/B is skipped if extraction fails its critical checks (don't burn $ on a
# broken extractor); set FORCE_AB=1 to run it anyway.
#
# Usage: bench.sh <lang> <repo-name> <repo-url> "<question>" [headless|tmux|all]
# Env: CORPUS corpus dir (default /tmp/codegraph-corpus, shared with agent-eval)
set -uo pipefail
LANG_TOKEN="${1:?usage: bench.sh <lang> <repo-name> <repo-url> \"<question>\" [mode]}"
NAME="${2:?repo-name required}"
URL="${3:?repo-url required}"
Q="${4:?question required}"
MODE="${5:-headless}"
HARNESS="$(cd "$(dirname "$0")" && pwd)"
AGENT_EVAL="$(cd "$HARNESS/../agent-eval" && pwd)"
CORPUS="${CORPUS:-/tmp/codegraph-corpus}"
REPO="$CORPUS/$NAME"
command -v codegraph >/dev/null || { echo "no codegraph on PATH (build + ./scripts/local-install.sh first)"; exit 1; }
echo "==================== add-lang bench: $NAME ($LANG_TOKEN) ===================="
echo "codegraph: $(command -v codegraph) -> $(codegraph --version 2>/dev/null || echo '?')"
# 1. Ensure the repo (shallow clone, reuse if present).
mkdir -p "$CORPUS"
if [ -d "$REPO/.git" ]; then
echo "→ reusing checkout: $REPO"
else
echo "→ cloning $URL"
git clone --depth 1 "$URL" "$REPO" || { echo "git clone failed"; exit 1; }
fi
# 2. Wipe + index with the binary under test.
echo "→ wiping .codegraph and indexing"
rm -rf "$REPO/.codegraph"
( cd "$REPO" && codegraph init -i ) || { echo "indexing failed"; exit 1; }
# 3. Verify extraction (cheap guard before the paid A/B).
echo "→ verifying extraction"
node "$HARNESS/verify-extraction.mjs" "$REPO" "$LANG_TOKEN"
VERIFY=$?
# 4. Retrieval A/B (skipped if extraction is broken, unless FORCE_AB=1).
if [ "$VERIFY" -ne 0 ] && [ "${FORCE_AB:-0}" != "1" ]; then
echo "→ SKIPPING A/B — extraction failed critical checks (set FORCE_AB=1 to override)"
else
echo "→ retrieval A/B (mode=$MODE)"
bash "$AGENT_EVAL/run-all.sh" "$REPO" "$Q" "$MODE"
fi
echo "==================== bench complete: $NAME (verify exit=$VERIFY) ===================="
# Exit reflects extraction: 0 = pass/warn, 1 = critical fail, 2 = couldn't read status.
exit "$VERIFY"
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env node
// Verify a tree-sitter grammar wasm is HEALTHY under the project's web-tree-sitter
// runtime BEFORE writing an extractor. Prints the ABI version and parses a valid
// sample many times in a multi-grammar context, to catch heap-corruption bugs
// that silently drop nodes on every parse after the first.
//
// Why this exists: the tree-sitter-wasms Lua grammar is ABI 13 and corrupts the
// shared WASM heap under web-tree-sitter 0.25 — Lua extraction degraded on every
// file after the first (nested calls/imports vanished). The fix was to vendor the
// upstream ABI-15 wasm. Run this on any new grammar first; if it FAILs, vendor a
// newer build instead of using the tree-sitter-wasms one.
//
// Usage: node scripts/add-lang/check-grammar.mjs <lang|wasm-path> <valid-sample> [iterations]
// Exit: 0 healthy, 1 corruption / parse errors, 2 could not run.
// NOTE: the sample must be SYNTACTICALLY VALID — a broken sample fails for the
// wrong reason.
import { readFileSync, existsSync } from 'node:fs';
import { createRequire } from 'node:module';
import { Parser, Language } from 'web-tree-sitter';
const require = createRequire(import.meta.url);
const fail = (code, msg) => { console.error(`[check-grammar] ${msg}`); process.exit(code); };
const [token, sample, iterArg] = process.argv.slice(2);
if (!token || !sample) fail(2, 'usage: check-grammar.mjs <lang|wasm-path> <valid-sample> [iterations]');
if (!existsSync(sample)) fail(2, `sample not found: ${sample}`);
const iters = iterArg ? parseInt(iterArg, 10) : 20;
const SPECIAL = { csharp: 'c_sharp', 'c#': 'c_sharp' };
function resolveWasm(t) {
if (t.endsWith('.wasm')) return existsSync(t) ? t : fail(2, `wasm not found: ${t}`);
const base = SPECIAL[t.toLowerCase()] ?? t.toLowerCase();
try { return require.resolve(`tree-sitter-wasms/out/tree-sitter-${base}.wasm`); } catch { /* try vendored */ }
const vendored = `src/extraction/wasm/tree-sitter-${base}.wasm`;
if (existsSync(vendored)) return vendored;
return fail(2, `no grammar for "${t}" — not in tree-sitter-wasms and not vendored`);
}
const wasmPath = resolveWasm(token);
const source = readFileSync(sample, 'utf8');
try { await Parser.init(); }
catch { await Parser.init({ locateFile: () => require.resolve('web-tree-sitter/tree-sitter.wasm') }); }
// Load a second, known-good grammar — the corruption surfaces under the
// multi-grammar runtime that real indexing uses, not a single grammar in isolation.
try { await Language.load(require.resolve('tree-sitter-wasms/out/tree-sitter-python.wasm')); } catch { /* ok */ }
let language;
try { language = await Language.load(wasmPath); }
catch (e) { fail(2, `failed to load ${wasmPath}: ${e.message}`); }
const parser = new Parser();
parser.setLanguage(language);
let ok = 0, err = 0;
for (let i = 0; i < iters; i++) {
const tree = parser.parse(source);
if (tree.rootNode.hasError) err++; else ok++;
}
console.log(`grammar: ${wasmPath.split('/').pop()}`);
console.log(` ABI version: ${language.abiVersion}`);
console.log(` parses: ${ok} clean / ${err} with errors (of ${iters})`);
if (err > 0) {
console.log(
`RESULT: FAIL — ${err}/${iters} parses produced ERROR trees on a valid sample. ` +
`This grammar corrupts under web-tree-sitter; vendor a newer (ABI 14/15) wasm ` +
`(see SKILL.md "Find a grammar"). Confirm your sample is syntactically valid first.`
);
process.exit(1);
}
console.log('RESULT: PASS — grammar parses cleanly and reuses safely.');
process.exit(0);
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env node
// Dump the tree-sitter AST for a sample file so you can write a LanguageExtractor
// mapping. Loads a grammar .wasm directly via web-tree-sitter (the same runtime
// codegraph uses) — you do NOT need to register the language first.
//
// Usage:
// node scripts/add-lang/dump-ast.mjs <lang|wasm-path> <sample-file> [--depth=N] [--full]
// Examples:
// node scripts/add-lang/dump-ast.mjs lua sample.lua
// node scripts/add-lang/dump-ast.mjs src/extraction/wasm/tree-sitter-zig.wasm a.zig --depth=4
//
// Output: an indented AST (named nodes, with field names) followed by a
// node-type FREQUENCY table. The frequency table is the payoff — it tells you
// which node types to map to functionTypes / classTypes / importTypes / etc.
import { readFileSync, existsSync } from 'node:fs';
import { createRequire } from 'node:module';
import { Parser, Language } from 'web-tree-sitter';
const require = createRequire(import.meta.url);
const fail = (msg) => { console.error(`[dump-ast] ${msg}`); process.exit(1); };
const argv = process.argv.slice(2);
const positional = argv.filter((a) => !a.startsWith('--'));
const [langOrWasm, sampleFile] = positional;
const depthFlag = argv.find((a) => a.startsWith('--depth='));
const showAll = argv.includes('--full'); // also print anonymous (token) nodes
const maxDepth = depthFlag ? parseInt(depthFlag.split('=')[1], 10) : (showAll ? Infinity : 8);
if (!langOrWasm || !sampleFile) {
fail('usage: dump-ast.mjs <lang|wasm-path> <sample-file> [--depth=N] [--full]');
}
if (!existsSync(sampleFile)) fail(`sample file not found: ${sampleFile}`);
// Language tokens whose tree-sitter-wasms filename differs from the token.
const WASM_SPECIAL = { csharp: 'c_sharp', 'c#': 'c_sharp' };
function resolveWasm(token) {
if (token.endsWith('.wasm')) {
if (!existsSync(token)) fail(`wasm not found: ${token}`);
return token;
}
const base = WASM_SPECIAL[token.toLowerCase()] ?? token.toLowerCase();
try {
return require.resolve(`tree-sitter-wasms/out/tree-sitter-${base}.wasm`);
} catch {
/* not in tree-sitter-wasms — try a vendored copy */
}
const vendored = `src/extraction/wasm/tree-sitter-${base}.wasm`;
if (existsSync(vendored)) return vendored;
fail(
`no grammar for "${token}" — not in tree-sitter-wasms and not vendored at ` +
`${vendored}. Pass an explicit .wasm path, or vendor one (see SKILL.md "Find a grammar").`
);
}
const wasmPath = resolveWasm(langOrWasm);
const source = readFileSync(sampleFile, 'utf8');
try {
await Parser.init();
} catch {
await Parser.init({ locateFile: () => require.resolve('web-tree-sitter/tree-sitter.wasm') });
}
let language;
try {
language = await Language.load(wasmPath);
} catch (e) {
fail(`failed to load grammar ${wasmPath}: ${e.message}`);
}
const parser = new Parser();
parser.setLanguage(language);
const tree = parser.parse(source);
const freq = new Map();
const snippet = (node) => {
const t = node.text.replace(/\s+/g, ' ').trim();
return t.length > 48 ? `${t.slice(0, 48)}` : t;
};
function walk(node, depth, fieldName) {
if (node.isNamed) freq.set(node.type, (freq.get(node.type) || 0) + 1);
if ((node.isNamed || showAll) && depth <= maxDepth) {
const field = fieldName ? `${fieldName}: ` : '';
const leaf = node.childCount === 0 ? ` "${snippet(node)}"` : '';
console.log(`${' '.repeat(depth)}${field}${node.type} @${node.startPosition.row + 1}:${node.startPosition.column}${leaf}`);
}
for (let i = 0; i < node.childCount; i++) {
const child = node.child(i);
if (child) walk(child, depth + 1, node.fieldNameForChild(i));
}
}
console.log(`\n# AST for ${sampleFile} (grammar: ${wasmPath.split('/').pop()})\n`);
walk(tree.rootNode, 0, null);
console.log('\n# Node-type frequency (named nodes) — map the relevant ones in your extractor:\n');
[...freq.entries()]
.sort((a, b) => b[1] - a[1])
.forEach(([type, n]) => console.log(` ${String(n).padStart(5)} ${type}`));
console.log();
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env node
// Sanity-check that codegraph extracted REAL symbols (not just file/import nodes)
// from a repo for a given language. Exits non-zero on a critical failure so it
// can drive a write-extractor -> build -> re-check loop.
//
// Usage: node scripts/add-lang/verify-extraction.mjs <repo-path> <lang>
// Reads `codegraph status <repo> --json` using whatever codegraph is on PATH,
// so it reflects the binary that built the index.
//
// Exit codes: 0 = pass or soft-warn, 1 = critical fail, 2 = could not run.
import { execFileSync } from 'node:child_process';
const [repo, lang] = process.argv.slice(2);
if (!repo || !lang) {
console.error('usage: verify-extraction.mjs <repo-path> <lang>');
process.exit(2);
}
let status;
try {
const out = execFileSync('codegraph', ['status', repo, '--json'], { encoding: 'utf8' });
status = JSON.parse(out);
} catch (e) {
console.error(`[verify] could not read codegraph status for ${repo}: ${e.message}`);
process.exit(2);
}
// Kinds that prove the extractor mapped AST node types (everything except
// 'file' and 'import', which codegraph creates structurally for any language).
const SYMBOL_KINDS = new Set([
'module', 'class', 'struct', 'interface', 'trait', 'protocol', 'function',
'method', 'property', 'field', 'variable', 'constant', 'enum', 'enum_member',
'type_alias', 'namespace', 'route', 'component',
]);
const byKind = status.nodesByKind || {};
const langs = status.languages || [];
const files = status.fileCount || 0;
const edges = status.edgeCount || 0;
const symbolKinds = Object.keys(byKind).filter((k) => SYMBOL_KINDS.has(k));
const symbolCount = symbolKinds.reduce((s, k) => s + byKind[k], 0);
const checks = [];
const add = (severity, ok, label, detail) => checks.push({ severity, ok, label, detail });
add('critical', status.initialized === true, 'index initialized', `initialized=${status.initialized}`);
add('critical', langs.includes(lang), `language "${lang}" detected`, `languages=[${langs.join(', ')}]`);
add('critical', symbolCount > 0, 'structural symbols extracted', `${symbolCount} symbols (${symbolKinds.join(', ') || 'NONE — only file/import nodes!'})`);
add('soft', symbolCount >= files, 'symbol density >= 1/file', `${symbolCount} symbols across ${files} files`);
add('soft', edges > files, 'edges resolved', `${edges} edges across ${files} files`);
console.log(`\n# Extraction check — ${repo} (lang=${lang}, backend=${status.backend})`);
console.log(` files=${files} nodes=${status.nodeCount} edges=${edges}`);
console.log(` nodesByKind: ${JSON.stringify(byKind)}\n`);
for (const c of checks) console.log(` ${c.ok ? '✓' : '✗'} ${c.label}${c.detail}`);
const critical = checks.filter((c) => !c.ok && c.severity === 'critical');
const soft = checks.filter((c) => !c.ok && c.severity === 'soft');
console.log();
if (critical.length) {
console.log(`RESULT: FAIL (${critical.length} critical) — extractor or grammar wiring is broken. Re-run dump-ast.mjs and fix the node-type mappings.`);
process.exit(1);
}
if (soft.length) {
console.log(`RESULT: WARN (${soft.length} soft) — extraction works but looks thin; inspect the counts above.`);
process.exit(0);
}
console.log('RESULT: PASS — extraction looks healthy.');
process.exit(0);