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
103 changes: 103 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Project Configuration

## Behavioral Rules

- Do what has been asked; nothing more, nothing less
- NEVER create files unless absolutely necessary for the goal
- ALWAYS prefer editing an existing file to creating a new one
- NEVER proactively create documentation files unless explicitly requested
- NEVER save working files, tests, or docs to the root folder
- ALWAYS read a file before editing it
- Keep files under 500 lines
- NEVER commit secrets, credentials, or .env files

## File Organization

- Use `src/` for source code
- Use `tests/` for test files
- Use `docs/` for documentation
- Use `scripts/` for utility scripts and orchestration scripts
- Use `config/` for configuration files

## Parallelism — ALWAYS parallel by default

- EVERY task must be analyzed for parallelism BEFORE execution
- Batch ALL related file reads in ONE message
- Batch ALL file edits in ONE message
- Batch ALL independent Bash commands in ONE message
- Spawn ALL independent Agent calls in ONE message with `run_in_background: true`
- After spawning background agents, STOP and wait for results — do NOT poll
- When a task has multiple independent fix targets, spawn one Agent per target in a single message
- When reviewing results from parallel agents, read ALL results before deciding next action
- Sequential steps run only when there is a true data dependency on a prior step

## Closed-Loop Execution

When a workflow specifies a loop (repeat-until-pass), follow this protocol:

1. **Read the workflow** to identify: steps, pass condition, max iterations, and what to capture per iteration
2. **Run the check/capture step** to establish baseline metrics
3. **Analyze results** — categorize issues, group by fix type
4. **Spawn parallel fix agents** — one Agent per independent issue category, ALL in one message
5. **Wait for all agents** — review ALL results together
6. **Re-run the check** — compare metrics to previous iteration
7. **Log iteration** — append to `context/MEMORY.md`: iteration number, pass/fail counts, key fixes, regressions
8. **Decide**:
- All pass → exit loop, run final confirmation
- Regression detected → revert, log what failed, try different approach
- Issues remain and under max iterations → go to step 3
- Max iterations reached → stop, report remaining issues
9. **On exit** — write final summary to `context/MEMORY.md`

### Regression handling
- If an iteration produces MORE issues than the previous one, it is a regression
- Revert the changes from that iteration immediately
- Log what was attempted and why it regressed
- Try a different fix approach in the next iteration
- Never repeat the same fix that caused a regression

## Security

- NEVER hardcode API keys, secrets, or credentials in source files
- NEVER commit .env files or any file containing secrets
- Always validate user input at system boundaries
- Always sanitize file paths to prevent directory traversal

## Memory Protocol

- At session start, read `context/MEMORY.md` for ongoing context
- Before session ends, update `context/MEMORY.md` with progress and findings
- Log important architectural decisions in `context/DECISIONS.md`
- Check `context/CONVENTIONS.md` for project-specific patterns before writing code
- During loops, append iteration results to `context/MEMORY.md` after each iteration

## Orchestration Scripts

- Orchestration scripts live in `scripts/` and automate multi-step pipelines
- Scripts should be idempotent — safe to re-run from any iteration
- Scripts must accept `--iteration N` to resume from a specific point
- Scripts must write machine-readable output (JSON) for Claude to parse
- Scripts must exit with code 0 on success, non-zero on failure
- Use `scripts/orchestrate.sh` as the template for new orchestration scripts

## Workflows

When the task matches a common pattern, follow the corresponding workflow:

- Bug fixes: follow `workflows/bug-fix.md`
- New features: follow `workflows/new-feature.md`
- Refactoring: follow `workflows/refactor.md`
- Code reviews: follow `workflows/code-review.md`
- Closed-loop QA: follow `workflows/closed-loop.md`

## Skills

Use these agent profiles when the task calls for a specialized role:

- `/planner` — Task decomposition, parallel execution planning
- `/implementer` — Writing production code
- `/reviewer` — Code review with severity ratings
- `/debugger` — Systematic bug investigation
- `/refactorer` — Safe code restructuring
- `/architect` — System design and architecture decisions
- `/orchestrator` — Design and run closed-loop pipelines
130 changes: 130 additions & 0 deletions JavaDuckerMcpServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,58 @@ public static void main(String[] args) throws Exception {
"to monitor bulk ingestion progress.",
"{}"),
(ex, a) -> call(JavaDuckerMcpServer::stats))
.tool(
tool("javaducker_summarize",
"Get a structural summary of an indexed file: class names, method names, imports, " +
"line count. One-call overview without reading the full text.",
schema(props(
"artifact_id", str("Artifact ID to summarize")),
"artifact_id")),
(ex, a) -> call(() -> summarize((String) a.get("artifact_id"))))
.tool(
tool("javaducker_map",
"Get a project map showing directory structure, file counts, largest files, and " +
"recently indexed files. Use for codebase orientation.",
"{}"),
(ex, a) -> call(JavaDuckerMcpServer::projectMap))
.tool(
tool("javaducker_stale",
"Check which indexed files are stale (modified on disk since last indexing). " +
"Accepts file_paths (list of absolute paths) or git_diff_ref (e.g. HEAD~3) to auto-detect changed files.",
schema(props(
"file_paths", str("JSON array of absolute file paths to check (optional if git_diff_ref given)"),
"git_diff_ref", str("Git ref for diff, e.g. HEAD~3 or main (optional if file_paths given)")))),
(ex, a) -> call(() -> checkStale(
(String) a.getOrDefault("file_paths", ""),
(String) a.getOrDefault("git_diff_ref", ""))))
.tool(
tool("javaducker_dependencies",
"Get the import/dependency list for an indexed file. Shows what this file imports " +
"and which indexed artifacts those imports resolve to.",
schema(props(
"artifact_id", str("Artifact ID to get dependencies for")),
"artifact_id")),
(ex, a) -> call(() -> dependencies((String) a.get("artifact_id"))))
.tool(
tool("javaducker_dependents",
"Find which indexed files import/depend on this file. Useful for impact analysis.",
schema(props(
"artifact_id", str("Artifact ID to find dependents of")),
"artifact_id")),
(ex, a) -> call(() -> dependents((String) a.get("artifact_id"))))
.tool(
tool("javaducker_watch",
"Start or stop auto-indexing a directory. When watching, file changes are " +
"automatically detected and re-indexed. Use action=start with a directory, or action=stop.",
schema(props(
"action", str("start or stop"),
"directory", str("Absolute path to watch (required for start)"),
"extensions", str("Comma-separated extensions, e.g. .java,.xml,.md (optional)")),
"action")),
(ex, a) -> call(() -> watch(
(String) a.get("action"),
(String) a.getOrDefault("directory", ""),
(String) a.getOrDefault("extensions", ""))))
.build();
}

Expand Down Expand Up @@ -204,6 +256,84 @@ static Map<String, Object> stats() throws Exception {
return httpGet("/stats");
}

static Map<String, Object> summarize(String artifactId) throws Exception {
Map<String, Object> r = httpGet("/summary/" + artifactId);
if (r == null) throw new RuntimeException("Artifact not found or no summary available: " + artifactId);
return r;
}

static Map<String, Object> projectMap() throws Exception {
return httpGet("/map");
}

@SuppressWarnings("unchecked")
static Map<String, Object> checkStale(String filePathsJson, String gitDiffRef) throws Exception {
List<String> paths = new ArrayList<>();

// If git_diff_ref is given, run git diff to get file paths
if (gitDiffRef != null && !gitDiffRef.isBlank()) {
ProcessBuilder pb = new ProcessBuilder("git", "diff", "--name-only", gitDiffRef);
pb.directory(Path.of(PROJECT_ROOT).toFile());
pb.redirectErrorStream(true);
Process proc = pb.start();
String output = new String(proc.getInputStream().readAllBytes()).trim();
proc.waitFor();
if (!output.isEmpty()) {
Path root = Path.of(PROJECT_ROOT).toAbsolutePath();
for (String line : output.split("\n")) {
paths.add(root.resolve(line.trim()).toString());
}
}
}

// If file_paths is given, parse it
if (filePathsJson != null && !filePathsJson.isBlank()) {
try {
List<String> parsed = MAPPER.readValue(filePathsJson, List.class);
paths.addAll(parsed);
} catch (Exception e) {
// Try as comma-separated
for (String p : filePathsJson.split(",")) {
if (!p.isBlank()) paths.add(p.trim());
}
}
}

if (paths.isEmpty()) {
throw new RuntimeException("Provide file_paths or git_diff_ref");
}

return httpPost("/stale", Map.of("file_paths", paths));
}

static Map<String, Object> watch(String action, String directory, String extensions) throws Exception {
if ("stop".equalsIgnoreCase(action)) {
return httpPost("/watch/stop", Map.of());
}
if ("start".equalsIgnoreCase(action)) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("directory", directory);
if (extensions != null && !extensions.isBlank()) body.put("extensions", extensions);
return httpPost("/watch/start", body);
}
if ("status".equalsIgnoreCase(action)) {
return httpGet("/watch/status");
}
throw new RuntimeException("Unknown action: " + action + ". Use start, stop, or status.");
}

static Map<String, Object> dependencies(String artifactId) throws Exception {
Map<String, Object> r = httpGet("/dependencies/" + artifactId);
if (r == null) throw new RuntimeException("Artifact not found: " + artifactId);
return r;
}

static Map<String, Object> dependents(String artifactId) throws Exception {
Map<String, Object> r = httpGet("/dependents/" + artifactId);
if (r == null) throw new RuntimeException("Artifact not found: " + artifactId);
return r;
}

// ── HTTP helpers ──────────────────────────────────────────────────────────

static Map<String, Object> httpGet(String path) throws Exception {
Expand Down
14 changes: 14 additions & 0 deletions context/CONVENTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Project Conventions

<!-- Document project-specific patterns here so Claude follows them consistently. -->

## Naming


## Imports


## Error Handling


## Testing
8 changes: 8 additions & 0 deletions context/DECISIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Architecture Decisions

<!-- Format:
## [Date] Decision Title
**Context:** Why this decision was needed
**Decision:** What was decided
**Consequences:** Trade-offs accepted
-->
27 changes: 27 additions & 0 deletions context/MEMORY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Session Memory

## Current Focus
All 8 Claude Companion features implemented and tests passing (65/65).

## Recent Decisions
- DuckDB UPDATE with PK can fail (ART index bug) — use DELETE+INSERT pattern for artifact reindex
- ALTER TABLE in SchemaBootstrap needs separate Statement objects (error closes shared stmt in DuckDB)
- chunk_embeddings cleanup requires subquery (keyed by chunk_id, not artifact_id)

## Key Findings
- All 8 features from plans/claude-companion-features.md implemented in one session
- 7 new Java classes created, 10+ existing classes modified
- 6 new MCP tools added (summarize, map, stale, dependencies, dependents, watch)
- 2 existing MCP tools enhanced (search with line numbers, index with incremental re-indexing)

## Open Questions
- HNSW index is not auto-built on startup — needs explicit buildHnswIndex() call
- Watch mode FileWatcher uses polling WatchService which may miss rapid changes on some OS

## Session Log
- 2026-03-28: Implemented all 8 features from claude-companion-features.md plan
- Phase 1 (parallel): F1 Line Numbers, F3 File Summaries, F4 Project Map, F6 Diff-Aware Search
- Phase 2 (parallel): F2 Incremental Re-indexing, F5 Dependency Graph
- Phase 3+4 (parallel): F7 Watch Mode, F8 HNSW Index
- Fixed test compilation (3 test files), DuckDB PK constraint bug, Statement closure bug
- All 65 tests green
Loading
Loading