Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,18 @@ Code graph tools are available via MCP. The MCP server injects `instructions` at

<claude-mem-context>
### Last Session
Request: 配置提交代码时的git pre-commit hook检查和代码审核工具,避免下次github上再出现错误。 Pre-commit hook (scripts/pre-commit.sh): ┌──────────┬────…
Completed: Modified package.json, .gitignore; Modified pre-commit.sh

### File Lessons
- cli.rs: Embedding availability must be validated before semantic search to prevent runtime errors; distingu… (#5321)
Request: Simulate user-level testing of all code-graph-mcp functions and UX, fix discovered problems, evaluate programming effic…
Completed: Fixed tools.rs compilation (Phase 3 result-building); Modified pipeline.rs (default resolution logic); Created SKILL.md…
Remaining: Comprehensive UX testing not executed; Loop plugin 3-iteration execution not performed; Functional testing of all code-…
Next: 1) Execute user-level functional testing workflow via loop plugin (3× as specified); 2) Document UX findings and issues…
Lessons: Phase 3 result struct initialization in tools.rs requires explicit type handling; Multi-file code pattern searches needed to identify incomplete reference mapping implementations
Decisions: Prioritized compilation correctness (tools.rs, pipeline.rs) before comprehensive UX testing; Created SKILL.md to improve project discoverability and functionality documentation

### Key Context
- [bugfix] Error: queries.rs: Error: Usage: code-graph-mcp search <query> --jso… (#5267)
- [bugfix] Error: session-init.test.js: TAP version 13 # Subtest: syncLifecycleConfig is … (#5202)
- [bugfix] Error: user-prompt-context.js, session-init.js, mcp-launcher.js: TAP version 13… (#5200)
- [bugfix] Error: cli.rs: struct Database src/storage/db.rs:30-33 fn McpSe… (#5096)
- [bugfix] Error: cli.rs: code-graph Symbol not found: --- code-graph No r… (#5079)
- [discovery] Reviewed 2 files: treesitter.rs, relations.rs (#5740)
- [refactor] Remove unused thread import from watcher.rs (#5714)
- [bugfix] Error: tools.rs: Compiling code-graph-mcp v0.7.14 (/mnt/data_ssd/d… (#5701)
- [bugfix] Error: tools.rs: error: Your local changes to the following files … (#5697)
- [change] Add idempotent column insertion checks to schema.rs (#5696)

</claude-mem-context>
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 0 additions & 9 deletions claude-plugin/commands/impact.md

This file was deleted.

7 changes: 0 additions & 7 deletions claude-plugin/commands/rebuild.md

This file was deleted.

5 changes: 0 additions & 5 deletions claude-plugin/commands/status.md

This file was deleted.

12 changes: 0 additions & 12 deletions claude-plugin/commands/trace.md

This file was deleted.

12 changes: 0 additions & 12 deletions claude-plugin/commands/understand.md

This file was deleted.

6 changes: 5 additions & 1 deletion claude-plugin/scripts/pre-edit-guide.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,14 @@ if (!symbol || symbol.length < 3) {
if (!symbol || symbol.length < 3) process.exit(0);

// Skip common patterns that aren't real function names
if (/^(if|for|while|switch|catch|else|return|new|get|set|try)$/i.test(symbol)) {
if (isCommonKeyword(symbol)) {
process.exit(0);
}

function isCommonKeyword(s) {
return /^(if|for|while|switch|catch|else|return|new|get|set|try)$/i.test(s);
}

// --- Per-symbol cooldown: 2 minutes ---
const cooldownFile = path.join(os.tmpdir(), `.cg-impact-${symbol}`);
try {
Expand Down
161 changes: 161 additions & 0 deletions claude-plugin/scripts/pre-edit-guide.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');

// Pre-edit-guide.js is a script with side effects (reads stdin, checks db).
// We test its PATTERNS directly without requiring the module.

// --- Function signature patterns (copied from pre-edit-guide.js) ---
const fnPatterns = [
/(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/, // Rust
/(?:export\s+)?(?:async\s+)?function\s+(\w+)/, // JS/TS
/(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|_)\s*=>/, // JS arrow
/(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/, // JS method / Go func
/def\s+(\w+)/, // Python/Ruby
/func\s+(\w+)/, // Go/Swift
/(?:public|private|protected|static|override|virtual|abstract|internal)\s+\S+\s+(\w+)\s*\(/, // Java/C#/Kotlin
/(?:public\s+)?function\s+(\w+)/, // PHP
];

function extractFunctionName(code) {
for (const pat of fnPatterns) {
const m = code.match(pat);
if (m) return m[1] || m[2];
}
return null;
}

function isCommonKeyword(s) {
return /^(if|for|while|switch|catch|else|return|new|get|set|try)$/i.test(s);
}

// ── Rust ────────────────────────────────────────────────

test('fn-extract: Rust pub fn', () => {
assert.equal(extractFunctionName('pub fn parse_code(input: &str) -> Vec<Node> {'), 'parse_code');
});

test('fn-extract: Rust pub async fn', () => {
assert.equal(extractFunctionName('pub async fn handle_message(&self, msg: &str) -> Result<()> {'), 'handle_message');
});

test('fn-extract: Rust fn (no pub)', () => {
assert.equal(extractFunctionName('fn helper_func(x: i32) -> i32 {'), 'helper_func');
});

// ── JavaScript/TypeScript ───────────────────────────────

test('fn-extract: JS function', () => {
assert.equal(extractFunctionName('function handleRequest(req, res) {'), 'handleRequest');
});

test('fn-extract: JS export function', () => {
assert.equal(extractFunctionName('export function processData(input) {'), 'processData');
});

test('fn-extract: JS async function', () => {
assert.equal(extractFunctionName('async function fetchData(url) {'), 'fetchData');
});

test('fn-extract: JS export async function', () => {
assert.equal(extractFunctionName('export async function loadConfig(path) {'), 'loadConfig');
});

test('fn-extract: JS arrow function (const)', () => {
assert.equal(extractFunctionName('const handleError = (err) => {'), 'handleError');
});

test('fn-extract: JS arrow function (async)', () => {
assert.equal(extractFunctionName('const fetchUser = async (id) => {'), 'fetchUser');
});

test('fn-extract: JS method', () => {
assert.equal(extractFunctionName(' handleMessage(msg) {'), 'handleMessage');
});

// ── Python ──────────────────────────────────────────────

test('fn-extract: Python def', () => {
assert.equal(extractFunctionName('def process_data(self, items):'), 'process_data');
});

test('fn-extract: Python async def', () => {
assert.equal(extractFunctionName('async def fetch_data(url):'), 'fetch_data');
});

// ── Go ──────────────────────────────────────────────────

test('fn-extract: Go func', () => {
assert.equal(extractFunctionName('func HandleRequest(w http.ResponseWriter, r *http.Request) {'), 'HandleRequest');
});

// ── Java/C#/Kotlin ──────────────────────────────────────

test('fn-extract: Java public method', () => {
assert.equal(extractFunctionName('public void processItem(Item item) {'), 'processItem');
});

test('fn-extract: Java private method', () => {
assert.equal(extractFunctionName('private String formatOutput(Data data) {'), 'formatOutput');
});

test('fn-extract: C# static method', () => {
assert.equal(extractFunctionName('static int CalculateTotal(List<int> items) {'), 'CalculateTotal');
});

// ── PHP ─────────────────────────────────────────────────

test('fn-extract: PHP function', () => {
assert.equal(extractFunctionName('function handleUpload($file) {'), 'handleUpload');
});

test('fn-extract: PHP public function', () => {
assert.equal(extractFunctionName('public function getUser($id) {'), 'getUser');
});

// ── Ruby ────────────────────────────────────────────────

test('fn-extract: Ruby def', () => {
assert.equal(extractFunctionName('def process_request(params)'), 'process_request');
});

// ── Keyword filter ──────────────────────────────────────

test('keyword-filter: common keywords rejected', () => {
for (const kw of ['if', 'for', 'while', 'switch', 'catch', 'else', 'return', 'new', 'get', 'set', 'try']) {
assert.ok(isCommonKeyword(kw), `"${kw}" should be rejected`);
}
});

test('keyword-filter: real function names pass', () => {
for (const name of ['parse_code', 'handleMessage', 'process_data', 'fetchUser']) {
assert.ok(!isCommonKeyword(name), `"${name}" should pass`);
}
});

// ── No false positives ──────────────────────────────────

test('fn-extract: plain code body returns null', () => {
assert.equal(extractFunctionName('let x = 42;\nreturn x + 1;'), null);
});

test('fn-extract: comment returns null', () => {
assert.equal(extractFunctionName('// This is a comment about the function'), null);
});

test('fn-extract: short strings return null', () => {
assert.equal(extractFunctionName('x = 1'), null);
});

// ── Pattern consistency check ───────────────────────────
// Verify fnPatterns in this test match what's in pre-edit-guide.js

test('pattern-sync: fnPatterns count matches source', () => {
const fs = require('node:fs');
const path = require('node:path');
const source = fs.readFileSync(path.join(__dirname, 'pre-edit-guide.js'), 'utf8');
// Count regex pattern lines in the fnPatterns array (lines containing // Language comment)
const sourcePatternCount = (source.match(/\/\/\s*(Rust|JS|Python|Go|Java|C#|PHP|Ruby|Swift|Kotlin)/g) || []).length;
assert.ok(fnPatterns.length === 8, `Expected 8 patterns, got ${fnPatterns.length}`);
assert.ok(sourcePatternCount >= 7, `Source should have >= 7 language comments, found ${sourcePatternCount}`);
});
Loading
Loading