Skip to content

feat!: one-level analysis (clarpse 11.6.0), focus extension pass, not-loaded relationships; remove resolveContextualComponents - #80

Merged
Zir0-93 merged 6 commits into
masterfrom
feat/79-one-level-analysis
Sep 19, 2026
Merged

Zir0-93 merged 6 commits into
masterfrom
feat/79-one-level-analysis

Conversation

@Zir0-93

@Zir0-93 Zir0-93 commented Sep 19, 2026 •

Copy link
Copy Markdown
Member

Summary

Integrates clarpse 11.7.0's one-level analysis into striff-lib. With StriffConfig.setAnalysisDepth(1) and a non-empty filter, the full pipeline models the filter's files in full and the repository files they reference as boundary components, without compiling the rest of the repository. Both revisions load the same referenced files. An optional focus extension pass adds files to analyse in full before the final compile. References to repository types that weren't modelled are kept as not-loaded relationships, not lost.

resolveContextualComponents is removed, so this is 5.0.0.

Closes #79

Design

OneLevelAnalysis (package-private) runs per language:

  1. prepare() base and head in parallel (ParallelParse, now generic).
  2. extra = base.levelOneFiles() ∪ head.levelOneFiles() ∪ context files.
  3. compile(extra) on both.
  4. With a FocusExtender:
    • call it once, with both first-pass models, merged across languages;
    • extendFocus(more) on both revisions (a language the filter had no files in is prepared at this point);
    • recompute the union and compile again.

CodeDiff, the relationships and the diagram are built only from the final compile. Every PreparedAnalysis is closed in finally, and ParallelParse closes a prepared analysis whose sibling failed. Clarpse holds no daemon between calls, so two prepared analyses per language don't deadlock on NodeDaemonGate: the TypeScript/Python tests run base and head in parallel.

StriffOperation.analysisScope() reports, for the whole operation:

  • the level-one files, and those held back by the budget;
  • the context files, and those held back by the budget;
  • the files the extender added.

Decisions implemented

  1. resolveContextualComponents removed, along with resolveMissingContextualComponents and its lookup by bare file name. Version 5.0.0, with "Migrating from 4.x" in the README.
  2. Not-loaded references become NotLoadedRelations (source base component or synthetic module, target name, association, origin) on CodeDiff.notLoadedRelations(). They are never in the RelationsMap, so never drawn. Origin is FOCUS when the referencing component was analysed in full (e.g. its target's file was held back by the budget) and BOUNDARY for a boundary component's reference past level one. After an extend pass, a promoted file's references become internal (tested).
  3. Key relations and trimming. A boundary component becomes a key relations component only when the other end of the added or deleted relation is not boundary. A synthetic module of a boundary file is itself boundary. Over maxComponentsPerDiagram, boundary components are removed first (context files before the rest, in unique-name order), and changed components are never removed. Nothing changes for an ordinary analysis, which has no boundary components.
  4. The focus extender receives both first-pass models, is called once, and runs before CodeDiff exists.
  5. Context files reuse expandedFiles. In depth-1 mode they join the extra level-one set of both revisions, so they are modelled as boundary without their own level one. Javadoc updated.
  6. Separate contextBudget (default 200) alongside levelOneBudget (default 1000), both per language. When the context files exceed the budget, the ones that name the filter's files most often are kept, ties broken by path, and the rest are reported in analysisScope().contextHeldByBudget().

Decision 6 is exact. Each compile caps level one at levelOneBudget plus the context files it keeps, through clarpse 11.7.0's PreparedAnalysis.compile(extra, budget). The prepared budget stays the upper bound the Java solver's load cap is sized from. Context files never displace level-one files, and neither budget can use the other's room. Covered by OneLevelAnalysisTest.levelOneBudgetIsExactWhateverTheContextBudget, which fails without it.

Also fixed

RelationsMap.filteredRelations copied only its outer map and then removed targets from inner maps it shared with the source. Rendering a diagram filters the diff's relations down to the drawn components, so it deleted every relation to an undrawn component from CodeDiff.extractedRels(). A second render, or any later reader of the same diff, therefore saw fewer relations. Found because a one-level test lost lib.B → deep.C once deep.C wasn't drawn. Each retained source now gets its own copy of its target map, with a regression test.

Tests

  • OneLevelAnalysisTest (Java, 17 tests):
    • a reference added in head doesn't add its target, and one removed in head doesn't delete it (union);
    • a boundary component's reference past level one becomes a BOUNDARY not-loaded relation, not drawn;
    • the extender is called once with the first pass, promotes a level-one file, and makes its references internal; naming only files already analysed changes nothing;
    • a budget-held target becomes a FOCUS not-loaded relation;
    • context files are boundary without their own level one;
    • the context budget ranking, and context never displacing level one;
    • Java serialisation keeps the boundary flag and not-loaded relations;
    • diagram JSON has "boundary": true only when true;
    • boundary trimming order;
    • key relations between two boundary components;
    • ordinary analysis has no scope, boundary or not-loaded relations;
    • config validation, and the incremental constructor rejecting depth 1;
    • rendering leaves the diff's relations intact.
  • OneLevelDaemonLanguageTest (Python, end to end): union, boundary, not-loaded Helper → Base, and an extend pass promoting the helper. Cleanup: no clarpse-src-<pid>-* directory is left after a successful or a failing operation. Mutation-checked: without the finally close, both cleanup tests fail with the leaked directories listed.
  • OrdinaryAnalysisUnchangedTest: a golden file generated from the clarpse-11.6.0-only commit (1162224) and compared here, covering the change set, all relations, diagram components and PlantUML source, for unfiltered, filtered, and filtered + expandedFiles Java/Python changes. Ordinary output is identical. Relations are read by a fresh extraction, since rendering no longer mutates extractedRels() (see Also fixed).
  • ParallelParseTest: a produced result whose sibling failed is discarded (closed); nothing is discarded on success.
  • StriffOperationIntegrationTest: the former resolveContextualComponents tests now cover depth 1 (the parent shows as gray context, an external library reference doesn't crash, depth 1 without a filter is ordinary) and the ordinary filtered case.
  • The clarpse 11.7.0 bump alone passed CI with no changes to expected output (commit 1162224).

Measurements

15 files of one directory are changed synthetically (a field typed with another type of the same directory, plus a method) over the whole repository, base and head. Each run is a separate JVM with -Xmx6g; peak RSS is from /usr/bin/time, which includes the Python daemon. Single run per cell on an otherwise idle 8-core machine.

repository mode wall (s) peak RSS merged components (boundary) level-one files not-loaded relations diagram components / SVG chars
apache/kafka (Java, clients/.../producer/internals) ordinary (filter) 15.6 4.10 GB 1,176 (0) – – 15 / 107,388
depth 1 7.6 1.54 GB 5,347 (4,171) 53 231 15 / 107,388
depth 1 + extend 5 9.1 1.51 GB 5,943 (4,376) 60 245 15 / 107,388
django (Python, db/models) ordinary (filter) 9.0 466 MB 3,735 (0) – – 15 / 58,497
depth 1 8.0 519 MB 8,290 (4,555) 37 145 15 / 58,497
depth 1 + extend 5 11.1 624 MB 8,451 (4,457) 35 155 15 / 58,497
dotnet/orleans (C#, Orleans.Runtime/Catalog) ordinary (filter) 6.6 488 MB 1,437 (0) – – 7 / 28,329
depth 1 6.7 1.10 GB 4,328 (2,891) 79 261 7 / 28,329
depth 1 + extend 5 11.5 1.22 GB 4,452 (2,785) 79 266 7 / 28,329
  • Java. Depth 1 is about 2× faster and about 2.7× smaller than an ordinary filtered compile over the whole repository. The ordinary compile's solver scans directories on every lookup miss.
  • Python costs about the same either way.
  • C#. Depth 1 costs about 2× the memory of a filtered compile, because clarpse builds a repository-wide declaration index and stubs. The filtered compile, however, resolves only against the filter's own types.
  • Model size grows 2.2–4.5×.
  • Diagrams are the same in every mode here: the synthetic changes relate focus files to each other, and boundary components enter a diagram only on a changed relation or as context. So the diagram cap isn't affected by the larger model.

Breaking changes & migration

  • Removed StriffConfig.setResolveContextualComponents(boolean) and resolveContextualComponents(). Use setAnalysisDepth(1) and pass every file of both revisions. Context goes in setExpandedFiles.
  • ParallelParse (package-private) is now generic.
  • Behaviour:
    • CodeDiff.extractedRels() is no longer reduced by rendering.
    • In depth-1 mode, a consumer reading internal + external references must also read notLoadedDependencies(), or CodeDiff.notLoadedRelations().
  • New API:
    • StriffConfig: setAnalysisDepth, setLevelOneBudget, setContextBudget, setFocusExtender, oneLevel(), DEFAULT_CONTEXT_BUDGET;
    • FocusExtender, AnalysisScope, StriffOperation.analysisScope();
    • NotLoadedRelation, CodeDiff.notLoadedRelations(), ExtractedRelationships.notLoadedRelations();
    • DiagramComponent.boundary();
    • StriffDiagramModel.trimBoundaryComponents(...).

🤖 Generated with Claude Code

Zir0-93 and others added 5 commits September 19, 2026 23:33
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
filteredRelations copied only the outer map, then removed targets from the inner maps it
shared with the source. Drawing a diagram filters the diff's relations down to the drawn
components, so it removed every relation to an undrawn component from CodeDiff.extractedRels(),
and a later render or reader of the same diff saw fewer relations. Each retained source now gets
its own copy of its target map.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d diagram

References to repository types the model holds no component for (clarpse's not-loaded state)
are kept as NotLoadedRelations on the CodeDiff, with the component they start from, the relation
they would be, and whether that component was analysed in full or is a boundary component. They
are never drawn.

Boundary components become key relations components only when the other end of the changed
relation is not a boundary component, are serialised with "boundary": true, and are the first
components removed when a diagram is over maxComponentsPerDiagram, context files before the rest.
A synthetic module of a boundary file is itself boundary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ContextualComponents

StriffConfig.setAnalysisDepth(1) with a non-empty filter makes the full pipeline run clarpse's
one-level analysis: both revisions are prepared, compiled with the union of their level-one files
plus the context files (expandedFiles), and, with a FocusExtender, extended with the files it names
and compiled again. The diff is built from the final compile only; every prepared analysis is closed
on every path. levelOneBudget and contextBudget cap the two sets separately, and context files never
displace level-one files. StriffOperation.analysisScope() reports what was modelled and held back.

resolveContextualComponents and its file-name lookup are removed: one-level analysis replaces them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Zir0-93
Zir0-93 marked this pull request as ready for review September 19, 2026 20:50
Each compile caps level one at levelOneBudget plus the context files it keeps, through clarpse
11.7.0's per-compile budget, so level one never uses the context budget's room.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Zir0-93
Zir0-93 merged commit d7f5266 into master Sep 19, 2026
6 checks passed
@Zir0-93
Zir0-93 deleted the feat/79-one-level-analysis branch September 19, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integrate clarpse 11.6.0 one-level analysis: prepared base/head union, a focus-extension pass, not-loaded references, boundary components

1 participant