Skip to content

Commit 3af3046

Browse files
authored
Merge pull request #153 from clay-good/fix/java-domains-leaf-package
fix(domains): infer leaf Java/Kotlin packages instead of the reverse-DNS org root (#138)
2 parents a504e80 + fe97151 commit 3af3046

19 files changed

Lines changed: 945 additions & 111 deletions

openspec/specs/analyzer/spec.md

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Analyzer Specification
22

33
> Generated by openlore v1.0.0 on 2026-04-05 10:50
4-
> Source files: src/core/analyzer/repository-mapper.ts, src/api/types.ts, src/core/analyzer/embedding-service.ts, src/core/analyzer/subgraph-extractor.ts, src/core/analyzer/architecture-writer.ts, src/core/analyzer/dependency-graph.ts, src/core/analyzer/spec-vector-index.ts, src/core/analyzer/file-walker.ts, src/core/analyzer/import-resolver-bridge.ts, src/core/analyzer/refactor-analyzer.ts, src/core/analyzer/vector-index.ts, src/core/analyzer/import-parser.ts, src/core/analyzer/signature-extractor.ts, src/core/analyzer/artifact-generator.ts, src/core/analyzer/cpp-header-resolver.ts, src/core/analyzer/call-graph.ts, src/core/analyzer/duplicate-detector.ts, src/core/analyzer/type-inference-engine.ts, src/core/analyzer/significance-scorer.ts, src/core/analyzer/http-route-parser.ts, src/core/analyzer/ast-chunker.ts, src/core/analyzer/codebase-digest.ts, src/utils/progress.ts, src/utils/prompts.ts, src/utils/logger.ts, src/utils/shutdown.ts, src/core/analyzer/iac/project.ts, src/core/services/edge-store.ts, src/core/services/mcp-handlers/analysis.ts, openspec/specs/analyzer/spec.md, src/core/analyzer/landmark-signals.ts, src/core/services/mcp-handlers/landmarks.ts, src/core/services/mcp-handlers/reachability.ts, src/core/services/mcp-handlers/tool-contract.ts, src/cli/commands/mcp.ts, src/core/services/tool-dispatch.ts, src/core/analyzer/cfg.ts, src/core/services/mcp-watcher.ts, src/core/services/mcp-handlers/graph.ts, src/core/analyzer/cfg.test.ts, src/core/services/mcp-handlers/graph.test.ts, src/core/decisions/anchor.ts, src/core/decisions/anchor-adapter.ts, src/core/services/mcp-handlers/memory.ts, src/types/index.ts, src/constants.ts, openspec/changes/add-content-addressed-stable-symbol-ids/specs/analyzer/spec.md, src/core/scip/stable-id.test.ts, src/core/analyzer/personalized-pagerank.ts, src/core/services/mcp-handlers/orient.ts
4+
> Source files: src/core/analyzer/repository-mapper.ts, src/api/types.ts, src/core/analyzer/embedding-service.ts, src/core/analyzer/subgraph-extractor.ts, src/core/analyzer/architecture-writer.ts, src/core/analyzer/dependency-graph.ts, src/core/analyzer/spec-vector-index.ts, src/core/analyzer/file-walker.ts, src/core/analyzer/import-resolver-bridge.ts, src/core/analyzer/refactor-analyzer.ts, src/core/analyzer/vector-index.ts, src/core/analyzer/import-parser.ts, src/core/analyzer/signature-extractor.ts, src/core/analyzer/artifact-generator.ts, src/core/analyzer/cpp-header-resolver.ts, src/core/analyzer/call-graph.ts, src/core/analyzer/duplicate-detector.ts, src/core/analyzer/type-inference-engine.ts, src/core/analyzer/significance-scorer.ts, src/core/analyzer/http-route-parser.ts, src/core/analyzer/ast-chunker.ts, src/core/analyzer/codebase-digest.ts, src/utils/progress.ts, src/utils/prompts.ts, src/utils/logger.ts, src/utils/shutdown.ts, src/core/analyzer/iac/project.ts, src/core/services/edge-store.ts, src/core/services/mcp-handlers/analysis.ts, openspec/specs/analyzer/spec.md, src/core/analyzer/landmark-signals.ts, src/core/services/mcp-handlers/landmarks.ts, src/core/services/mcp-handlers/reachability.ts, src/core/services/mcp-handlers/tool-contract.ts, src/cli/commands/mcp.ts, src/core/services/tool-dispatch.ts, src/core/analyzer/cfg.ts, src/core/services/mcp-watcher.ts, src/core/services/mcp-handlers/graph.ts, src/core/analyzer/cfg.test.ts, src/core/services/mcp-handlers/graph.test.ts, src/core/decisions/anchor.ts, src/core/decisions/anchor-adapter.ts, src/core/services/mcp-handlers/memory.ts, src/types/index.ts, src/constants.ts, openspec/changes/add-content-addressed-stable-symbol-ids/specs/analyzer/spec.md, src/core/scip/stable-id.test.ts, src/core/analyzer/personalized-pagerank.ts, src/core/services/mcp-handlers/orient.ts, src/core/analyzer/domain-naming.ts, src/core/analyzer/schema-extractor.ts
55
66
## Purpose
77

@@ -5061,6 +5061,36 @@ The system SHALL surface epistemic-lease freshness as neutral factual signals (e
50615061

50625062
> Decision recorded: 8e95746d
50635063
> Date: 2026-06-16
5064+
### Requirement: UnifyDomainnameDerivationBehindASingleLeaffirstHelper
5065+
5066+
The system SHALL derive domain names using a leaf-first path walk with shared noise-directory filtering, ensuring consistent domain naming across dependency-graph clustering and repository mapping.
5067+
5068+
> Decision recorded: 201ace19
5069+
> Date: 2026-06-16
5070+
### Requirement: AddJpahibernateEntityParserToSchemaExtractor
5071+
5072+
The system SHALL extract schema models from Java files annotated with JPA @Entity or @MappedSuperclass, parsing table names, field names, types, and nullable status.
5073+
5074+
> Decision recorded: 6af4d045
5075+
> Date: 2026-06-16
5076+
### Requirement: GeneralizeFileextensionStrippingAndJavaMethodSignatureExtractionForMultilanguageSupport
5077+
5078+
The system SHALL extract Java method signatures including modifiers, generic type parameters, and throws clauses, while rejecting block-comment contents and body-level statements as false positives.
5079+
5080+
> Decision recorded: 8f94535c
5081+
> Date: 2026-06-16
5082+
### Requirement: InjectCallgraphEdgesIntoTheDependencyGraphForJvmLanguages
5083+
5084+
The system SHALL inject call-graph edges into the file-level dependency graph for JVM languages whose same-package references produce no import statements.
5085+
5086+
> Decision recorded: 67580817
5087+
> Date: 2026-06-17
5088+
### Requirement: JaxrsRouteDetectionRequiresJavaxjakartawsrsImportToAvoidFalsePositivesFromHttpClientLibraries
5089+
5090+
The system SHALL only classify a Java/Kotlin file as a JAX-RS server endpoint when it imports from the javax.ws.rs or jakarta.ws.rs package, to prevent false positives from HTTP client annotation libraries.
5091+
5092+
> Decision recorded: f9de2e30
5093+
> Date: 2026-06-17
50645094
50655095
## Technical Notes
50665096

@@ -5438,3 +5468,73 @@ Shortest-path distance ranks a candidate by its single cheapest path to the task
54385468
The epistemic-lease feature injected escalating imperative language into every MCP tool response (STOP, "Repository model: EXPIRED", "do NOT…"). This is structurally a prompt-injection pattern — it trains agents to obey authoritative imperatives in tool output, the exact behavior agents must resist — and contradicts the north-star decision (c6d1ad07: deterministic structural facts, not guessing) and the landmark-salience principle (hand the agent facts, let it rank). Wall-clock age alone escalated to CRITICAL (false positive), and the agent's own commits flipped the lease to stale via git-hash divergence even though committing is the most-informed action in a session. Fix: emit a single neutral, factual freshness note (minutes since orient, cognitive load since orient, whether the analysis index is behind HEAD) phrased as information the agent can act on, not a command. Drive severity from accumulated cognitive load, not wall clock.
54395469

54405470
**Consequences:** staleBlock/degradedSignal reworded to neutral facts (no STOP/EXPIRED/do-NOT, no system-banner box art); git-hash divergence no longer forces stale — it sets a factual index-behind-HEAD flag and at most contributes to degraded; computeStaleDepth driven by cognitive load, not wall-clock age; decay tracking, cross-module density/oscillation model, and telemetry retained; epistemic-lease gains a spec requirement (mcp-handlers) and ADR where it previously had neither.
5471+
5472+
### Unify domain-name derivation behind a single leaf-first helper
5473+
5474+
**Status:** Approved
5475+
**Date:** 2026-06-16
5476+
**ID:** 201ace19
5477+
5478+
Domain naming was split across two diverging functions: `suggestDomainName` (dependency-graph clusters) correctly walks the path leaf-first and skips build-layout/reverse-DNS noise, but `inferDomains` (repository-mapper, which drives SUMMARY's 'Detected Domains' and the generated OpenSpec spec files) walked root-first and picked the first non-skip segment. On Java/Kotlin projects this grabbed the reverse-DNS org root (e.g. `org/springframework/...`) so every business file collapsed into one nonsense `springframework` domain — the exact bug reported in #138 and still present after #139, which only fixed the cluster path. Extracting one shared `deriveDomainFromPath` helper (leaf-first walk + shared DOMAIN_NOISE_DIRS + canonical pattern map) makes both code paths agree and prevents future drift.
5479+
5480+
**Consequences:** New module src/core/analyzer/domain-naming.ts owns DOMAIN_NOISE_DIRS, the canonical domain pattern map, and deriveDomainFromPath. dependency-graph.ts and repository-mapper.ts both consume it. inferDomains now yields leaf-package business domains (vet/owner/system) instead of the org root; SUMMARY and generated specs change accordingly for Java/Kotlin/Go projects.
5481+
5482+
### Add JPA/Hibernate entity parser to schema extractor
5483+
5484+
**Status:** Approved
5485+
**Date:** 2026-06-16
5486+
**ID:** 6af4d045
5487+
5488+
Java codebases using JPA/Hibernate annotations (@Entity, @MappedSuperclass) had no schema extraction support, leaving persistent models invisible to the schema inventory. The parser reads raw Java source (not skeletonized) because the TS/Py skeletonizer would mangle annotations and field declarations.
5489+
5490+
**Consequences:** OrmType union gains 'jpa' variant; Java files with @Entity/@MappedSuperclass are parsed from raw source while all other languages continue through the skeletonizer. Future ORM dialects for Java (e.g. Micronaut Data, jOOQ codegen) would need separate parsers.
5491+
5492+
### Generalize file-extension stripping and Java method signature extraction for multi-language support
5493+
5494+
**Status:** Approved
5495+
**Date:** 2026-06-16
5496+
**ID:** 8f94535c
5497+
5498+
The analyzer hardcoded JS/TS/Python extensions in several places, causing Java/Kotlin files to leak extensions into entity names (e.g. VetControllerJava), miss handler methods with inline annotations (@ResponseBody), mis-parse license-header block comments as declarations, and drop methods with complex generic type parameters. Fixes generalize extension stripping, add block-comment tracking, widen the method regex for annotations in return-type position, and reject body-statement false positives.
5499+
5500+
**Consequences:** Extension stripping is now generic (any single dot-extension), which is broader but could theoretically strip meaningful suffixes from unconventional filenames. Java signature extraction is richer (modifiers, type params, throws clauses preserved) but the regex is more complex to maintain.
5501+
5502+
### Inject call-graph edges into the dependency graph for JVM languages
5503+
5504+
**Status:** Approved
5505+
**Date:** 2026-06-17
5506+
**ID:** 67580817
5507+
5508+
Java/Kotlin require imports only for cross-package references; same-package classes are used with no import. The dependency graph was built purely from import edges, so a Java project's file-level graph was nearly empty (spring-petclinic: 10 edges) while its call graph held 1261 — same-package relationships were invisible, hurting structural comprehension and leaving cluster views empty. Fix: (1) run injection for Java/Kotlin regardless of import-edge count, (2) seed the dedup set with existing edges so injected call edges never duplicate an import edge, (3) resolve call-graph file paths to absolute so the two id spaces align.
5509+
5510+
**Consequences:** New exported SAME_PACKAGE_IMPLICIT_LANGS set (Java, Kotlin). injectCallGraphEdges now dedupes against pre-existing edges, making it safe to run alongside import edges. The absolute-path resolution also repairs the previously-silent no-op injection for Swift/C/C++. Java/Kotlin dependency graphs are now populated (petclinic 10→70 edges, gson 318→1517) with structural clusters; injected edges carry isCallEdge:true.
5511+
5512+
### Require a ws.rs import for JAX-RS route detection
5513+
5514+
**Status:** Approved
5515+
**Date:** 2026-06-17
5516+
**ID:** 954eac79
5517+
5518+
JAX-RS route detection fired on any Java file containing both @Path and an HTTP-method annotation (@GET/@POST/...). Retrofit — an HTTP CLIENT library — uses identically-named @GET/@POST/@Path from retrofit2.http on interface methods (client request templates), so OpenLore hallucinated 28 phantom server routes for it (adversarial-audit finding). The defining signal for a real JAX-RS server resource is the javax.ws.rs / jakarta.ws.rs import, which Retrofit never has and Spring does not need. Gating JAX-RS detection on that import removes the false positives without affecting Spring (separate detection path) or genuine JAX-RS resources (which always import ws.rs).
5519+
5520+
**Consequences:** extractJavaRouteDefinitions now requires an `import javax|jakarta.ws.rs` before classifying a file as JAX-RS. Retrofit/OkHttp client interfaces yield 0 routes; petclinic Spring routes (17) and JAX-RS resources (which import ws.rs) are unaffected.
5521+
5522+
### JPA field parser handles inline annotations on the same line as the field declaration
5523+
5524+
**Status:** Approved
5525+
**Date:** 2026-06-17
5526+
**ID:** 8605684f
5527+
5528+
Common JPA patterns place annotations inline with the field (e.g. `@Id private Long id;`). The previous parser only recognized annotations on their own line, causing inline-annotated fields — including primary keys — to be silently dropped from schema extraction.
5529+
5530+
**Consequences:** The parser now iteratively strips leading annotations before testing for a field match, correctly capturing inline-annotated fields. Pure-annotation lines still accumulate in pendingAnn for multi-line annotation stacks.
5531+
5532+
### JAX-RS route detection requires javax/jakarta.ws.rs import to avoid false positives from HTTP client libraries
5533+
5534+
**Status:** Approved
5535+
**Date:** 2026-06-17
5536+
**ID:** f9de2e30
5537+
5538+
Retrofit interfaces use identically-named @GET/@POST/@Path annotations from retrofit2.http, which are client request templates, not server endpoints. Without checking the import package, the parser would emit phantom server routes for HTTP client definitions.
5539+
5540+
**Consequences:** JAX-RS routes are only detected when the file imports from javax.ws.rs or jakarta.ws.rs; projects using non-standard JAX-RS re-exports would not be recognized. Retrofit and similar HTTP client interfaces are correctly excluded.

src/core/analyzer/artifact-generator.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,63 @@ describe('AnalysisArtifactGenerator', () => {
277277
}
278278
});
279279

280+
it('does not leak source file extensions into entity names (#138)', async () => {
281+
const javaFiles: ScoredFile[] = [
282+
createScoredFile({ name: 'VetController.java', path: 'src/main/java/com/acme/vet/VetController.java', directory: 'src/main/java/com/acme/vet', score: 70 }),
283+
createScoredFile({ name: 'VetRepository.java', path: 'src/main/java/com/acme/vet/VetRepository.java', directory: 'src/main/java/com/acme/vet', score: 65 }),
284+
];
285+
const repoMap = createMockRepoMap({
286+
highValueFiles: javaFiles,
287+
allFiles: javaFiles,
288+
clusters: {
289+
byDirectory: { 'src/main/java/com/acme/vet': javaFiles },
290+
byDomain: { vet: javaFiles },
291+
byLayer: { presentation: javaFiles, business: [], data: [], infrastructure: [] },
292+
},
293+
});
294+
const depGraph = createMockDepGraph();
295+
296+
const artifacts = await generateArtifacts(repoMap, depGraph, {
297+
rootDir: tempDir,
298+
outputDir,
299+
});
300+
301+
const vetDomain = artifacts.repoStructure.domains.find(d => d.name === 'vet');
302+
expect(vetDomain).toBeDefined();
303+
expect(vetDomain!.entities).toContain('VetController');
304+
expect(vetDomain!.entities).toContain('VetRepository');
305+
for (const entity of vetDomain!.entities) {
306+
expect(entity).not.toMatch(/Java$/);
307+
}
308+
});
309+
310+
it('excludes package-info/module-info marker files from entities (#138)', async () => {
311+
const files: ScoredFile[] = [
312+
createScoredFile({ name: 'TypeToken.java', path: 'src/main/java/com/acme/reflect/TypeToken.java', directory: 'src/main/java/com/acme/reflect', score: 70 }),
313+
createScoredFile({ name: 'package-info.java', path: 'src/main/java/com/acme/reflect/package-info.java', directory: 'src/main/java/com/acme/reflect', score: 40 }),
314+
createScoredFile({ name: 'module-info.java', path: 'src/main/java/module-info.java', directory: 'src/main/java', score: 40 }),
315+
];
316+
const repoMap = createMockRepoMap({
317+
highValueFiles: files,
318+
allFiles: files,
319+
clusters: {
320+
byDirectory: { 'src/main/java/com/acme/reflect': files },
321+
byDomain: { reflect: files },
322+
byLayer: { presentation: files, business: [], data: [], infrastructure: [] },
323+
},
324+
});
325+
326+
const artifacts = await generateArtifacts(repoMap, createMockDepGraph(), {
327+
rootDir: tempDir,
328+
outputDir,
329+
});
330+
331+
const reflect = artifacts.repoStructure.domains.find(d => d.name === 'reflect');
332+
expect(reflect?.entities).toContain('TypeToken');
333+
expect(reflect?.entities).not.toContain('PackageInfo');
334+
expect(reflect?.entities).not.toContain('ModuleInfo');
335+
});
336+
280337
it('should generate entry points', async () => {
281338
const repoMap = createMockRepoMap();
282339
const depGraph = createMockDepGraph();

0 commit comments

Comments
 (0)